全局状态管理(也叫全局数据管理)的优点
- 解耦:将所有数据相关的逻辑放入store(也就是MVC中的Model,换了个名字而已)
- 数据读写更方便:任何组件不管在哪里,都可以直接读写数据
- 控制力更强:组件对数据的读写只能使用store提供的API进行
React 的状态管理
- 按照 React 官方指导意见, 如果多个 Component 之间要发生交互, 那么状态(即: 数据)就维护在这些 Component 的最小公约父节点上, 也即是 <App/>
- <TodoList/> <Todo/> 以及<AddTodoBtn/> 本身不维持任何 state, 完全由父节点
传入 props 以决定其展现, 是一个纯函数的存在形式, 即:Pure Component
Redux 状态管理
React 只负责页面渲染, 而不负责页面逻辑, 页面逻辑可以从中单独抽取出来, 变成 store
- 状态及页面逻辑从 <App/> 里面抽取出来, 成为独立的 store, 页面逻辑就是 reducer
及 都是 Pure Component , 通过 connect 方法可以很方便地给它俩加一层 wrapper 从而建立起与 store 的联系: 可以通过 dispatch 向 store 注入 action, 促使 store 的状态进行变化, 同时又订阅了 store 的状态变化, 一旦状态有变, 被 connect 的组件也随之刷新 使用 dispatch 往 store 发送 action 的这个过程是可以被拦截的, 自然而然地就可以在这里增加各种 Middleware, 实现各种自定义功能, eg: logging
如何使用Redux
import { Provider } from 'react-redux'
import { createStore } from 'redux';
ReactDOM.render(
//todo configureStore() 创建Redux Store对象,管理所有的Redux状态 Provider供应商
<Provider store={createStore(
(state, { type, payload }) => {
switch (type) {
case 'SWITCH_MENU':
return { ...state, SWITCH_MENU: payload }
default:
return { ...state }
}
},
{ SWITCH_MENU: '' }
)}>
<Router />
</Provider>,
document.getElementById('root'));
Redux源码
```javascript let createStore = (reducer) => { let state; //获取状态对象 //存放所有的监听函数 let listeners = []; let getState = () => state; //提供一个方法供外部调用派发action let dispath = (action) => {
//调用管理员reducer得到新的state
state = reducer(state, action);
//执行所有的监听函数
listeners.forEach((l) => l())
} //订阅状态变化事件,当状态改变发生之后执行监听函数 let subscribe = (listener) => {
listeners.push(listener);
} dispath();
return {
getState,
dispath,
subscribe
} }
let combineReducers=(renducers)=>{ //传入一个renducers管理组,返回的是一个renducer return function(state={},action={}){ let newState={}; for(var attr in renducers){ newState[attr]=renducersattr
}
return newState;
}
} export {createStore,combineReducers};
在 Redux store 中保存了 reducer 返回的 这个 state,这个新的 store 树就是应用的下一个 state, 所有订阅 store.subscribe(listener)的监听器都将被调用,监听器里可以调用 store.getState()获得当前 state 此时,我们就可以使用新的 state 来更新 UIsetState(newState)
<a name="olzEv"></a>
### reducer为什么必须是纯函数
- redux源代码中将oldState和newState(reducer返回的结果)做比较,**如果reducer为非纯函数,返回的是原来的对象,则两者指向同一个地址,导致react认为state无变化,从而不更新页面**
- 比较两个 javascript 对象中所有的属性是否完全相同, 唯一的办法就是深比较, 然而, 深比较在真实的应用中代码是非常大的, 非常耗性能的,需要比较的次数特别多,所以一个有效的解决方案就是做一个规定,当无论发生任何变化时,开发者都要返回一个新的对象,没有变化时,开发者返回就的对象,这也就是 redux 为什么要把 reducer 设计成纯函数的原因
- 这样做是牺牲一点计算性能(生成新对象)来保证页面刷新,在页面更新时使用react的diff算法来计算需要更新的组件。之所以这样设计,是为了避免在reducer中进行大量的深比较
- 如官网所说,reducer 就是一个纯函数,接受旧的 state 和 action,返回新的 state
```javascript
(previousState, action) => newState
什么是纯函数?
- 相同的输入永远返回相同的输出
- 不修改函数的输入值
- 不依赖外部环境状态
- 无任何副作用
- 比如: (a, b) => a + b
reducer 纯函数写法
我们首先来看下正常案例,我们一般会有如下几种写法返回新state
1. 直接返回一个新对象 ```javascript import {combineReducers} from ‘redux’
export default (state, action) => { switch (action.type) { case ‘SWITCH_MENU’: return {…state, SWITCH_MENU: action.payload} default: return state } }
2. 使用 Object.assign()返回一个新对象
```javascript
import { combineReducers } from 'redux'
export default (state, action) => {
switch (action.type) {
case 'SWITCH_MENU':
return Object.assign({}, state, { SWITCH_MENU: action.payload })
default:
return state
}
}
- 使用 Immutable.js 返回一个新对象 ```javascript import { combineReducers } from ‘redux’
export default (state, action) => { switch (action.type) { case ‘SWITCH_MENU’: return state.updateIn([‘complated’],()=>!state.gerIn(‘complated’)) default: return state } }
<a name="ehaD6"></a>
#### reducer 不是纯函数会发生什么?
我们先来试验下,如果 reducer 不是纯函数会发生什么? 我们将上面代码reducer改造一下,直接修改 state,而不是返回新的 state<br />![](https://cdn.nlark.com/yuque/0/2020/webp/604921/1598698484525-c4a3752e-86b2-4510-b86f-0b009f54c898.webp?x-oss-process=image%2Fresize%2Cw_590#from=url&id=s7lfP&margin=%5Bobject%20Object%5D&originHeight=207&originWidth=590&originalType=binary&ratio=1&status=done&style=none)<br />改变代码后,我们发现当我们触发了 action 以后,页面没有发生任何变化,这是为什么呢?<br />我们来看下 redux 源码:<br />![](https://cdn.nlark.com/yuque/0/2020/webp/604921/1598698484534-df520038-c8bd-411c-a435-6403e8e22ae3.webp?x-oss-process=image%2Fresize%2Cw_752#from=url&id=uHjrk&margin=%5Bobject%20Object%5D&originHeight=458&originWidth=752&originalType=binary&ratio=1&status=done&style=none)<br />通过源代码,我们发现, var nextStateForKey = reducer(previousStateForKey, action) , nextStateForKey就是通过 reducer 执行后返回的结果(state),然后通过 hasChanged = hasChanged || nextStateForKey !== previousStateForKey 来比较新旧两个对象是否一致,此比较法,比较的是两个对象的存储位置,也就是浅比较法,所以,当我们 reducer 直接返回旧的 state 对象时,Redux 认为没有任何改变,从而导致页面没有更新
<a name="9C67j"></a>
## React + Redux + Saga
![](https://cdn.nlark.com/yuque/0/2020/png/604921/1598690193442-34928235-25ec-4445-b005-d55a4c8446da.png?x-oss-process=image%2Fresize%2Cw_752#from=url&id=C6zNS&margin=%5Bobject%20Object%5D&originHeight=507&originWidth=752&originalType=binary&ratio=1&status=done&style=none)<br />上面说了, 可以使用** Middleware 拦截 action**, 这样一来异步的网络操作也就很方便了, 做成一个 Middleware 就行了, 这里使用 redux-saga 这个类库, 举个栗子
- 点击创建 Todo 的按钮, 发起一个 type == addTodo 的 action
- saga 拦截这个 action, 发起 http 请求, 如果请求成功, 则继续向 reducer 发一个 type == addTodoSucc 的 action, 提示创建成功, 反之则发送 type == addTodoFail 的 action 即可
- 有了前面的三步铺垫, Dva 的出现也就水到渠成了, 正如 Dva 官网所言, Dva 是基于 的最佳实践沉淀, 做了 3 件很重要的事情, 大大提升了编码体验
<a name="uhTCW"></a>
## [Dva](https://dvajs.com/guide/introduce-class.html#dva-%E5%BA%94%E7%94%A8%E7%9A%84%E6%9C%80%E7%AE%80%E7%BB%93%E6%9E%84)
<a name="IMbsV"></a>
### 命名由来?
D.Va拥有一部强大的机甲,它具有两台全自动的近距离聚变机炮、可以使机甲飞跃敌人或障碍物的推进器、 还有可以抵御来自正面的远程攻击的防御矩阵 —— 来自 [守望先锋](https://ow.blizzard.cn/heroes/overwatch-dva)
<a name="2WsmI"></a>
### Dva是什么
dva 是基于现有应用架构 (redux + react-router + redux-saga 等)的一层轻量封装,没有引入任何新概念,全部代码不到 100 行。( Inspired by elm and choo. )<br />可以说,dva是基于react+redux最佳实践上实现的封装方案,简化了redux和redux-saga使用上的诸多繁琐操作
| **5个API** | app = dva(Opts) |
| --- | --- |
| | app.use(Hooks) |
| | app.models(ModelObject) |
| | app.router(Function) |
| | app.start([HTMLElement]) |
| | |
| <br /><br />**8个概念** | State |
| | Action |
| | Model |
| | Reducer<br />是唯一可以更新 state 的地方 |
| | Effect |
| | Subscription |
| | Router |
| | RouteComponent |
<a name="JgJQ9"></a>
### Dva 数据流向
![Dva 数据流向.png](https://cdn.nlark.com/yuque/0/2020/png/604921/1598690578573-f95079ff-1304-41ec-a13a-ab79641f58b4.png#height=315&id=C5OBC&name=Dva%20%E6%95%B0%E6%8D%AE%E6%B5%81%E5%90%91.png&originHeight=315&originWidth=1000&originalType=binary&ratio=1&size=124663&status=done&style=none&width=1000)
向数据的改变发生通常是通过用户交互行为或者浏览器行为(如路由跳转等)触发的,当此类行为会改变数据的时候可以通过 dispatch 发起一个 action,如果是同步行为会直接通过 Reducers 改变 State ,如果是异步行为(副作用)会先触发 Effects 然后流向 Reducers 最终改变 State,所以在 dva 中,数据流向非常清晰简明,并且思路基本跟开源社区保持一致(也是来自于开源社区)
```javascript
app.model({
namespace: 'count',
state: {
record: 0,
current: 0,
},
reducers: {
add(state) {
const newCurrent = state.current + 1;
return { ...state,
record: newCurrent > state.record ? newCurrent : state.record,
current: newCurrent,
};
},
minus(state) {
return { ...state, current: state.current - 1};
},
},
effects: {
*add(action, { call, put }) {
yield call(delay, 1000);
yield put({ type: 'minus' });
},
},
subscriptions: {
keyboardWatcher({ dispatch }) {
key('⌘+up, ctrl+up', () => { dispatch({type:'add'}) });
},
},
});
subscriptions: {
setup({ history, dispatch }): void {
// Subscribe history(url) change, trigger `load` action if pathname is `/`
history.listen(({ pathname, search }): void => {
if (typeof window.ga !== 'undefined') {
window.ga('send', 'pageview', pathname + search);
}
});
},
},