全局状态管理(也叫全局数据管理)的优点

  • 解耦:将所有数据相关的逻辑放入store(也就是MVC中的Model,换了个名字而已)
  • 数据读写更方便:任何组件不管在哪里,都可以直接读写数据
  • 控制力更强:组件对数据的读写只能使用store提供的API进行

React 的状态管理Redux 全局状态管理 - 图2

  • 按照 React 官方指导意见, 如果多个 Component 之间要发生交互, 那么状态(即: 数据)就维护在这些 Component 的最小公约父节点上, 也即是 <App/>
  • <TodoList/> <Todo/> 以及<AddTodoBtn/> 本身不维持任何 state, 完全由父节点 传入 props 以决定其展现, 是一个纯函数的存在形式, 即:Pure Component

Redux 状态管理

React 只负责页面渲染, 而不负责页面逻辑, 页面逻辑可以从中单独抽取出来, 变成 storeRedux 全局状态管理 - 图3

  • 状态及页面逻辑从 <App/> 里面抽取出来, 成为独立的 store, 页面逻辑就是 reducer
  • 都是 Pure Component , 通过 connect 方法可以很方便地给它俩加一层 wrapper 从而建立起与 store 的联系: 可以通过 dispatch 向 store 注入 action, 促使 store 的状态进行变化, 同时又订阅了 store 的状态变化, 一旦状态有变, 被 connect 的组件也随之刷新
  • 使用 dispatch 往 store 发送 action 的这个过程是可以被拦截的, 自然而然地就可以在这里增加各种 Middleware, 实现各种自定义功能, eg: logging

    如何使用Redux

    1. import { Provider } from 'react-redux'
    2. import { createStore } from 'redux';
    3. ReactDOM.render(
    4. //todo configureStore() 创建Redux Store对象,管理所有的Redux状态 Provider供应商
    5. <Provider store={createStore(
    6. (state, { type, payload }) => {
    7. switch (type) {
    8. case 'SWITCH_MENU':
    9. return { ...state, SWITCH_MENU: payload }
    10. default:
    11. return { ...state }
    12. }
    13. },
    14. { SWITCH_MENU: '' }
    15. )}>
    16. <Router />
    17. </Provider>,
    18. document.getElementById('root'));

    Redux源码

    ```javascript let createStore = (reducer) => { let state; //获取状态对象 //存放所有的监听函数 let listeners = []; let getState = () => state; //提供一个方法供外部调用派发action let dispath = (action) => {

    1. //调用管理员reducer得到新的state
    2. state = reducer(state, action);
    3. //执行所有的监听函数
    4. listeners.forEach((l) => l())

    } //订阅状态变化事件,当状态改变发生之后执行监听函数 let subscribe = (listener) => {

    1. listeners.push(listener);

    } dispath();

    return {

    1. getState,
    2. dispath,
    3. subscribe

    } }

let combineReducers=(renducers)=>{ //传入一个renducers管理组,返回的是一个renducer return function(state={},action={}){ let newState={}; for(var attr in renducers){ newState[attr]=renducersattr

  1. }
  2. return newState;
  3. }

} export {createStore,combineReducers};

  1. Redux store 中保存了 reducer 返回的 这个 state,这个新的 store 树就是应用的下一个 state, 所有订阅 store.subscribe(listener)的监听器都将被调用,监听器里可以调用 store.getState()获得当前 state 此时,我们就可以使用新的 state 来更新 UIsetState(newState)
  2. <a name="olzEv"></a>
  3. ### reducer为什么必须是纯函数
  4. - redux源代码中将oldStatenewStatereducer返回的结果)做比较,**如果reducer为非纯函数,返回的是原来的对象,则两者指向同一个地址,导致react认为state无变化,从而不更新页面**
  5. - 比较两个 javascript 对象中所有的属性是否完全相同, 唯一的办法就是深比较, 然而, 深比较在真实的应用中代码是非常大的, 非常耗性能的,需要比较的次数特别多,所以一个有效的解决方案就是做一个规定,当无论发生任何变化时,开发者都要返回一个新的对象,没有变化时,开发者返回就的对象,这也就是 redux 为什么要把 reducer 设计成纯函数的原因
  6. - 这样做是牺牲一点计算性能(生成新对象)来保证页面刷新,在页面更新时使用reactdiff算法来计算需要更新的组件。之所以这样设计,是为了避免在reducer中进行大量的深比较
  7. - 如官网所说,reducer 就是一个纯函数,接受旧的 state action,返回新的 state
  8. ```javascript
  9. (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 } }

  1. 2. 使用 Object.assign()返回一个新对象
  2. ```javascript
  3. import { combineReducers } from 'redux'
  4. export default (state, action) => {
  5. switch (action.type) {
  6. case 'SWITCH_MENU':
  7. return Object.assign({}, state, { SWITCH_MENU: action.payload })
  8. default:
  9. return state
  10. }
  11. }
  1. 使用 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 } }

  1. <a name="ehaD6"></a>
  2. #### reducer 不是纯函数会发生什么?
  3. 我们先来试验下,如果 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 认为没有任何改变,从而导致页面没有更新
  4. <a name="9C67j"></a>
  5. ## React + Redux + Saga
  6. ![](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 这个类库, 举个栗子
  7. - 点击创建 Todo 的按钮, 发起一个 type == addTodo 的 action
  8. - saga 拦截这个 action, 发起 http 请求, 如果请求成功, 则继续向 reducer 发一个 type == addTodoSucc 的 action, 提示创建成功, 反之则发送 type == addTodoFail 的 action 即可
  9. - 有了前面的三步铺垫, Dva 的出现也就水到渠成了, 正如 Dva 官网所言, Dva 是基于 的最佳实践沉淀, 做了 3 件很重要的事情, 大大提升了编码体验
  10. <a name="uhTCW"></a>
  11. ## [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)
  12. <a name="IMbsV"></a>
  13. ### 命名由来?
  14. D.Va拥有一部强大的机甲,它具有两台全自动的近距离聚变机炮、可以使机甲飞跃敌人或障碍物的推进器、 还有可以抵御来自正面的远程攻击的防御矩阵 —— 来自 [守望先锋](https://ow.blizzard.cn/heroes/overwatch-dva)
  15. <a name="2WsmI"></a>
  16. ### Dva是什么
  17. dva 是基于现有应用架构 (redux + react-router + redux-saga 等)的一层轻量封装,没有引入任何新概念,全部代码不到 100 行。( Inspired by elm and choo. )<br />可以说,dva是基于react+redux最佳实践上实现的封装方案,简化了redux和redux-saga使用上的诸多繁琐操作
  18. | **5个API** | app = dva(Opts) |
  19. | --- | --- |
  20. | | app.use(Hooks) |
  21. | | app.models(ModelObject) |
  22. | | app.router(Function) |
  23. | | app.start([HTMLElement]) |
  24. | | |
  25. | <br /><br />**8个概念** | State |
  26. | | Action |
  27. | | Model |
  28. | | Reducer<br />是唯一可以更新 state 的地方 |
  29. | | Effect |
  30. | | Subscription |
  31. | | Router |
  32. | | RouteComponent |
  33. <a name="JgJQ9"></a>
  34. ### Dva 数据流向
  35. ![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)
  36. 向数据的改变发生通常是通过用户交互行为或者浏览器行为(如路由跳转等)触发的,当此类行为会改变数据的时候可以通过 dispatch 发起一个 action,如果是同步行为会直接通过 Reducers 改变 State ,如果是异步行为(副作用)会先触发 Effects 然后流向 Reducers 最终改变 State,所以在 dva 中,数据流向非常清晰简明,并且思路基本跟开源社区保持一致(也是来自于开源社区)
  37. ```javascript
  38. app.model({
  39. namespace: 'count',
  40. state: {
  41. record: 0,
  42. current: 0,
  43. },
  44. reducers: {
  45. add(state) {
  46. const newCurrent = state.current + 1;
  47. return { ...state,
  48. record: newCurrent > state.record ? newCurrent : state.record,
  49. current: newCurrent,
  50. };
  51. },
  52. minus(state) {
  53. return { ...state, current: state.current - 1};
  54. },
  55. },
  56. effects: {
  57. *add(action, { call, put }) {
  58. yield call(delay, 1000);
  59. yield put({ type: 'minus' });
  60. },
  61. },
  62. subscriptions: {
  63. keyboardWatcher({ dispatch }) {
  64. key('⌘+up, ctrl+up', () => { dispatch({type:'add'}) });
  65. },
  66. },
  67. });
  1. subscriptions: {
  2. setup({ history, dispatch }): void {
  3. // Subscribe history(url) change, trigger `load` action if pathname is `/`
  4. history.listen(({ pathname, search }): void => {
  5. if (typeof window.ga !== 'undefined') {
  6. window.ga('send', 'pageview', pathname + search);
  7. }
  8. });
  9. },
  10. },