# hello
传统的状态管理
在jquery时代,往往把全局变量定义在代码的最前面,使每一个页面都能使用他
<body>
<div>
<button id="add">+ 1</button>
<span id="number"></span>
<button id="minus">- 1</button>
</div>
<script src='https://cdn.bootcss.com/jquery/3.4.1/jquery.js'></script>
<script>
$(function () {
// 变量 注释1
const myState = { number: 1 }
// 增加
$("#add").click(() => {
myState.number++; // 注释2
render();
})
// 减少
$("#minus").click(() => {
myState.number--; // 注释2
render();
})
// 渲染DOM
const render = () => $("#number").html(myState.number);
render();
});
</script>
</body>
在 React 中,我们把变量(注释1)称作状态(state)
而通过 React 基础的学习我们发现, React 的状态都是在组件内部(局部),当组件嵌套复杂时,很难管理状态,也就是说,很难实现全局变量
这个时候,市场上出现了很多优秀的解决方案,比如Flux,Redux,React-Redux,useContext+useReduce,dva等
Redux 是基于 Flux 的改良版,不仅仅服务于React,任何前端框架都可以使用
Redux 思路
把状态(上述代码注释1),操作(上述代码注释2)融合成一个容器,通过视图分发指令去更新状态,并通过订阅更新视图
状态:State
操作:Reduce
容器:Store
分发:dispatch
指令:action
订阅:subscribe
hello Redux
Redux 通过 createStore 创建一个容器,createStore 需要参数 Reduce , Reduce 有两个参数,状态和指令
Reduce 是一个纯函数,只能回返新状态,不更新原状态
dispatch 分发的必须是一个对象
分发后,会调用 Reduce,而且会把 dispatch 的参数会作为 Reduce 的第二个参数
<body>
<div>
<button id="add">+ 1</button>
<span id="number"></span>
<button id="minus">- 1</button>
</div>
<script src='https://cdn.bootcss.com/jquery/3.4.1/jquery.js'></script>
<script src="./redux.js"></script>
<script>
$(function () {
// 状态
const myState = { number: 1 }
// 操作
const Reduce = (state = myState, action) => {
if (action.type == "增加") return { ...state, number: state.number + 1 }
else if (action.type == "减少") return { ...state, number: state.number - 1 }
else return state;
}
// 容器
const Store = Redux.createStore(Reduce);
// 订阅
Store.subscribe(() => render());
// 增加
$("#add").click(() => Store.dispatch({ type: "增加" }));
// 减少
$("#minus").click(() => Store.dispatch({ type: "减少" }));
// 渲染DOM
const render = () => $("#number").html(Store.getState().number);
render();
});
</script>
</body>
指令重写
指令可以带一个参数,约定俗称payload,而且type我们把它改成英文名显得更专业些
// 操作
const Reduce = (state = myState, action) => {
if (action.type == "add") return { ...state, number: action.payload }
else if (action.type == "minus") return { ...state, number: action.payload }
else return state;
}
// 增加
$("#add").click(() => Store.dispatch({ type: "add", payload: Store.getState().number + 1 }));
// 减少
$("#minus").click(() => Store.dispatch({ type: "minus", payload: Store.getState().number - 1 }));
可以所有的指令用一个变量存储(大写表示常量),那么再使用的过程中,不容易出错,还可以复用
// 所有指令
const Actions = {
ADD: "add",
MINUS: "minus"
}
// 操作
const Reduce = (state = myState, action) => {
if (action.type == Actions.ADD) return { ...state, number: action.payload }
else if (action.type == Actions.MINUS) return { ...state, number: action.payload }
else return state;
}
// 增加
$("#add").click(() => Store.dispatch({ type: Actions.ADD, payload: Store.getState().number + 1 }));
// 减少
$("#minus").click(() => Store.dispatch({ type: Actions.MINUS, payload: Store.getState().number - 1 }));
为了更加贴合管网(深层装X),我们可以在 dispatch 一个由 createAction 生成的指令
// 所有指令
const Actions = {
ADD: "add",
MINUS: "minus"
}
// 生成指令
const createAction = {
ADD(payload) {
return { type: Actions.ADD, payload }
},
MINUS(payload) {
return { type: Actions.MINUS, payload }
}
}
// 操作
const Reduce = (state = myState, action) => {
if (action.type == Actions.ADD) return { ...state, number: action.payload }
else if (action.type == Actions.MINUS) return { ...state, number: action.payload }
else return state;
}
// 增加
$("#add").click(() => Store.dispatch(createAction.ADD(Store.getState().number + 1)));
// 减少
$("#minus").click(() => Store.dispatch(createAction.ADD(Store.getState().number - 1)));
再把 Reduce 的if判断改为更为高大上的 switch
OK ,全部改完后的代码如下
<body>
<div>
<button id="add">+ 1</button>
<span id="number"></span>
<button id="minus">- 1</button>
</div>
<script src='https://cdn.bootcss.com/jquery/3.4.1/jquery.js'></script>
<script src="./redux.js"></script>
<script>
$(function () {
// 状态
const myState = { number: 1 }
// 所有指令
const Actions = {
ADD: "add",
MINUS: "minus"
}
// 生成指令
const createAction = {
ADD(payload) {
return { type: Actions.ADD, payload }
},
MINUS(payload) {
return { type: Actions.MINUS, payload }
}
}
// 操作
const Reduce = (state = myState, action) => {
switch (action.type) {
case Actions.ADD:
return { ...state, number: action.payload };
case Actions.MINUS:
return { ...state, number: action.payload };
default:
return state;
}
}
// 容器
const Store = Redux.createStore(Reduce);
// 订阅
Store.subscribe(() => render());
// 增加
$("#add").click(() => Store.dispatch(createAction.ADD(Store.getState().number + 1)));
// 减少
$("#minus").click(() => Store.dispatch(createAction.ADD(Store.getState().number - 1)));
// 渲染DOM
const render = () => $("#number").html(Store.getState().number);
render();
});
</script>
</body>
再读 Redux
Redux 是 JavaScript 状态容器,提供可预测化的状态管理
我们把这句话分解一下:
状态(State)
容器(Store)
可预测(Action)
状态管理(Reduce)
# React 配合 Redux
安装Redux
yarn add Redux
容器
import { createStore } from "redux"
// 状态
const myState = { number: 1 }
// 所有指令
const Actions = {
ADD: "add",
MINUS: "minus"
}
// 生成指令
const createAction = {
ADD(payload) {
return { type: Actions.ADD, payload }
},
MINUS(payload) {
return { type: Actions.MINUS, payload }
}
}
// 操作
const Reduce = (state = myState, action) => {
switch (action.type) {
case Actions.ADD:
return { ...state, number: action.payload };
case Actions.MINUS:
return { ...state, number: action.payload };
default:
return state;
}
}
// 容器
const Store = createStore(Reduce);
export {
createAction, Store
}
App组件
import React, { useState } from 'react';
import { Store, createAction } from "./Store/index"
function App() {
const [number, setNumber] = useState(Store.getState().number);
// 订阅状态更新
Store.subscribe(() => setNumber(Store.getState().number));
const changeNumber = (props) => {
// 分发指令
if (props == `add`) Store.dispatch(createAction.ADD(number + 1));
else if (props == `minus`) Store.dispatch(createAction.MINUS(number - 1));
}
return (
<div>
<button onClick={() => changeNumber(`add`)}>+ 1</button>
<span>{number}</span>
<button onClick={() => changeNumber(`minus`)}>- 1</button>
</div>
)
}
export default App;
# 图解 React + Redux
图一
React 某个组件分发(Dispatch)一个指令更新Store
容器订阅(Subscribe)状态(State)更新后,重新渲染组件
图二
视图(View)把指令(Actions)分发(Dispatch)给容器(Store)
容器根据分发的指令可预测的管理(Redux)状态(State)
容器侦听(Subscibe)了状态(State)的变化,更新了视图(View)
# 取消订阅
每次在组件销毁的时候,必须取消订阅避免内存泄露
其实在组件销毁时,不仅仅要取消订阅,还要清空定时器,DOM侦听等副作用
import React, { useState, useEffect } from 'react';
import { Store, createAction } from "./Store/index"
function App() {
const [number, setNumber] = useState(Store.getState().number);
// 订阅状态更新
const sub = Store.subscribe(() => setNumber(Store.getState().number));
useEffect(() => {
return () => {
sub();
}
});
const changeNumber = (props) => {
// 分发指令
if (props == `add`) Store.dispatch(createAction.ADD(number + 1));
else if (props == `minus`) Store.dispatch(createAction.MINUS(number - 1));
}
return (
<div>
<button onClick={() => changeNumber(`add`)}>+ 1</button>
<span>{number}</span>
<button onClick={() => changeNumber(`minus`)}>- 1</button>
</div>
)
}
export default App;
# reduce
介绍
当页面非常庞大的时候,如果状态管理都写在一起非常不好维护,redux 有一个 combineReducers 可以把状态管理拆成小块,从而使开发维护更加方便
快速 demo
Store.js
import { createStore, combineReducers } from "redux"
// 状态
const NumberState = { number: 1 }
const UserState = [{ name: "张三", age: 12 }]
// 所有指令
const NumberActions = {
ADD: "add",
MINUS: "minus"
}
const UserActions = {
INIT: "init",
ADD: "add",
UPDATE: "update"
}
// 生成指令
const createNumberAction = {
ADD(payload) {
return { type: NumberActions.ADD, payload }
},
MINUS(payload) {
return { type: NumberActions.MINUS, payload }
}
}
const createUserAction = {
INIT(payload) {
return { type: UserActions.INIT, payload }
},
ADD(payload) {
return { type: UserActions.ADD, payload }
},
UPDATE(payload) {
return { type: UserActions.UPDATE, payload }
}
}
// 操作
const NumberReduce = (state = NumberState, action) => {
switch (action.type) {
case NumberActions.ADD:
return { ...state, number: action.payload };
case NumberActions.MINUS:
return { ...state, number: action.payload };
default:
return state;
}
}
const UserReduce = (state = UserState, action) => {
switch (action.type) {
case NumberActions.ADD:
return { ...state, number: action.payload };
case NumberActions.MINUS:
return { ...state, number: action.payload };
default:
return state;
}
}
const reduce = combineReducers({
Num: NumberReduce,
User: UserReduce
});
// 容器
const Store = createStore(reduce);
export {
createNumberAction, Store
}
action , createAction, Reduce 都要写两份,最后合成一份Store,并在 combineReducers 时,给不同的 redux 加上名称,方便 Store 调用
APP.js
import React, { useState, useEffect } from 'react';
import { Store, createNumberAction } from "./Store/index"
function App() {
const [number, setNumber] = useState(Store.getState().Num.number);
// 订阅状态更新
const sub = Store.subscribe(() => setNumber(Store.getState().Num.number));
useEffect(() => {
return () => {
sub();
}
});
const changeNumber = (props) => {
// 分发指令
if (props == `add`) Store.dispatch(createNumberAction.ADD(number + 1));
else if (props == `minus`) Store.dispatch(createNumberAction.MINUS(number - 1));
}
return (
<div>
<button onClick={() => changeNumber(`add`)}>+ 1</button>
<span>{number}</span>
<button onClick={() => changeNumber(`minus`)}>- 1</button>
</div>
)
}
export default App;
调用的时候,需要用 Store 调用 combineReducers 合并时取的别名
Redux 文件拆分
可以把不同的Redux拆分文件单独提出
├── Store # 状态管理容器
│ ├── numberReduce.js # 数字reduce
│ ├── userReduce.js # 用户reduce
│ └── index.js
├── App.js # 根组件
./Store/numberReduce.js
// 状态
const NumberState = { number: 1 }
// 所有指令
const NumberActions = {
ADD: "add",
MINUS: "minus"
}
// 生成指令
const createNumberAction = {
ADD(payload) {
return { type: NumberActions.ADD, payload }
},
MINUS(payload) {
return { type: NumberActions.MINUS, payload }
}
}
// 操作
const NumberReduce = (state = NumberState, action) => {
switch (action.type) {
case NumberActions.ADD:
return { ...state, number: action.payload };
case NumberActions.MINUS:
return { ...state, number: action.payload };
default:
return state;
}
}
export { createNumberAction, NumberReduce }
export default NumberReduce;
./Store/userReduce.js
// 状态
const UserState = [{ name: "张三", age: 12 }]
// 所有指令
const UserActions = {
INIT: "init",
ADD: "add",
UPDATE: "update"
}
// 生成指令
const createUserAction = {
INIT(payload) {
return { type: UserActions.INIT, payload }
},
ADD(payload) {
return { type: UserActions.ADD, payload }
},
UPDATE(payload) {
return { type: UserActions.UPDATE, payload }
}
}
// 操作
const UserReduce = (state = UserState, action) => {
return UserState;
}
export { createUserAction, UserReduce }
export default UserReduce;
./Store/index.js
import { createStore, combineReducers } from "redux"
import NumberReduce from "./numberReduce"
import UserReduce from "./userReduce"
// 合并
const reduce = combineReducers({
Num: NumberReduce,
User: UserReduce
});
// 容器
export default createStore(reduce);
./App.js
import React, { useState, useEffect } from 'react';
import Store from "./Store/index"
import { createNumberAction } from "./Store/numberReduce"
function App() {
const [number, setNumber] = useState(Store.getState().Num.number);
// 订阅状态更新
const sub = Store.subscribe(() => setNumber(Store.getState().Num.number));
useEffect(() => {
return () => {
sub();
}
});
const changeNumber = (props) => {
// 分发指令
if (props == `add`) Store.dispatch(createNumberAction.ADD(number + 1));
else if (props == `minus`) Store.dispatch(createNumberAction.MINUS(number - 1));
}
return (
<div>
<button onClick={() => changeNumber(`add`)}>+ 1</button>
<span>{number}</span>
<button onClick={() => changeNumber(`minus`)}>- 1</button>
</div>
)
}
export default App;
这是以功能模块拆分,还可以以 Store 拆分
├── Store # 状态管理容器
│ ├── action
│ │ ├── numberAction
│ │ ├── userAction
│ ├── reduce
│ │ ├── numberReduce
│ │ ├── userReduce
│ ├── state
│ │ ├── numberState
│ │ ├── userState
│ └── index.js
├── App.js # 根组件
# 配合请求更新状态
用 json-server 模拟数据
安装
$ npm install -g json-server
数据模拟
{
"User": [
{
"id": "001",
"name": "Sherry",
"age": 24
},
{
"id": "002",
"name": "Addy",
"age": 24
},
{
"id": "003",
"name": "Jack",
"age": 24
},
{
"id": "004",
"name": "Rebeca",
"age": 24
}
]
}
启动服务
$ json-server --watch --port 3001 db.json
请求数据更新状态
为了方便测试,把 numberReducer 的功能去掉
App.js
import React, { useState, useEffect } from 'react';
import Store from "./Store/index"
import axios from "axios"
import { createUserAction } from "./Store/userReduce"
function App() {
const [user, setUser] = useState(Store.getState().User);
const sub = Store.subscribe(() => setUser(Store.getState().User));
useEffect(() => {
axios.get(`http://localhost:3001/User`).then(res => {
Store.dispatch(createUserAction.INIT(res.data));
})
return () => {
sub();
}
}, []);
return (
<>
{user.map(item => <p key={item.id}>{item.name} --- {item.age}</p>)}
</>
)
}
export default App;
userReduce 简化成一个指令
// 所有指令
const UserActions = {
INIT: "init"
}
// 生成指令
const createUserAction = {
INIT(payload) {
return { type: UserActions.INIT, payload }
}
}
// 操作
const UserReduce = (state = [], action) => {
if (action.type == UserActions.INIT) {
return action.payload;
}
return state;
}
export { createUserAction, UserReduce }
export default UserReduce;
此时就能配合请求更新状态了。如果需要指令异步执行,可以使用redux中间件
中间件
redux 中间件是在 action 和 reduce环节中,添加的功能
使用例子
import { createStore, combineReducers, applyMiddleware } from "redux"
import NumberReduce from "./numberReduce"
import UserReduce from "./userReduce"
import thunk from "redux-thunk"
// 合并
const reduce = combineReducers({
Num: NumberReduce,
User: UserReduce
});
// 容器
export default createStore(reduce, applyMiddleware(thunk));
# redux-thunk
介绍
redux-thunk 属于 redux 的中间件,作用是可以让 dispatch 返回的是一个函数,那么就可以把异步的写法搬到 action 中
App.js
import React, { useState, useEffect } from 'react';
import Store from "./Store/index"
import axios from "axios"
import { createUserAction } from "./Store/userReduce"
function App() {
const [user, setUser] = useState(Store.getState().User);
const sub = Store.subscribe(() => setUser(Store.getState().User));
useEffect(() => {
Store.dispatch(createUserAction.ASYNCINIT());
return () => {
sub();
}
}, []);
return (
<>
{user.map(item => <p key={item.id}>{item.name} --- {item.age}</p>)}
</>
)
}
export default App;
Reduce.js
import axios from "axios"
// 所有指令
const UserActions = {
ASYNCINIT: "init"
}
// 生成指令
const createUserAction = {
ASYNCINIT() {
return (dispatch, getState) => {
axios.get(`http://localhost:3001/User`).then(res => {
dispatch({ type: UserActions.ASYNCINIT, payload: res.data });
})
}
}
}
// 操作
const UserReduce = (state = [], action) => {
if (action.type == UserActions.ASYNCINIT) {
return action.payload;
}
return state;
}
export { createUserAction, UserReduce }
export default UserReduce;
index.js
import { createStore, combineReducers, applyMiddleware } from "redux"
import NumberReduce from "./numberReduce"
import UserReduce from "./userReduce"
import thunk from "redux-thunk"
// 合并
const reduce = combineReducers({
Num: NumberReduce,
User: UserReduce
});
// 容器
export default createStore(reduce, applyMiddleware(thunk));
# redux-saga
介绍
redux-saga 和 redux-thunk 一样,都是 redux 中,名气非常高的中间件
dva 还是基于 redux-saga 的一个数据流方案
思路
容器分发一个指令,用 redux-saga 去侦听(其实reduce也侦听到了,只不过不处理事件),侦听到后,做一系列的异步或同步操作,再用put发送一个指令,再由reduce侦听并更新状态
注意:redux-saga 发送的指令不能和侦听的指令相同,否则会一直执行
App.js
import React, { useState, useEffect } from 'react';
import Store from "./Store/index"
import { createUserAction } from "./Store/userReduce"
function App() {
const [user, setUser] = useState(Store.getState().User);
const sub = Store.subscribe(() => setUser(Store.getState().User));
useEffect(() => {
Store.dispatch(createUserAction.ASYNCINIT);
return () => {
sub();
}
}, []);
return (
<>
{user.map(item => <p key={item.id}>{item.name} --- {item.age}</p>)}
</>
)
}
export default App;
userReduce
// 所有指令
const UserActions = {
ASYNCINIT: "asyncinit",
INIT: "init"
}
// 生成指令
const createUserAction = {
ASYNCINIT: {
type: UserActions.ASYNCINIT
},
INIT(payload) {
return {
type: UserActions.INIT,
payload: payload
}
}
}
// 操作
const UserReduce = (state = [], action) => {
console.log(action);
if (action.type == UserActions.INIT) {
return action.payload || state;
}
return state;
}
export { createUserAction, UserReduce, UserActions }
export default UserReduce;
Store
import { createStore, combineReducers, applyMiddleware } from "redux"
import NumberReduce from "./numberReduce"
import UserReduce from "./userReduce"
import reduxSaga from "redux-saga"
import sagas from "./sagas"
const sage = reduxSaga();
// 合并
const reduce = combineReducers({
Num: NumberReduce,
User: UserReduce
});
// 容器
export default createStore(reduce, applyMiddleware(sage));
sage.run(sagas);
sagas
import { takeEvery, put } from "redux-saga/effects"
import { UserActions, createUserAction } from "./userReduce"
import axios from "axios"
function* mySaga() {
yield takeEvery(UserActions.ASYNCINIT, getList);
}
function* getList() {
const res = yield axios.get("http://localhost:3001/User");
yield put(createUserAction.INIT(res.data));
}
export default mySaga;
# redux-devtools-extension 调试工具
chrome 插件
地址: https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd
配置
$ npm install --save-dev redux-devtools-extension
配置
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
const store = createStore(reducer, composeWithDevTools(
applyMiddleware(...middleware),
// other store enhancers if any
));
上一篇:React Hook篇