React 状态管理工具

Redux

1. Redux 核心

1.1 Redux 介绍

JavaScript 状态容器,提供可预测化的状态管理

1.2 Redux 核心概念及流程

Store: 存储状态的容器,JavaScript 对象
View: 视图,HTML页面
Actions: 对象,描述对状态进行怎样的操作
Reducers: 函数,操作状态并返回新的状态

1.3 Redux 使用: 计数器案例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Redux</title>
</head>
<body>
  <button id="minus">-</button>
  <span id="count">0</span>
  <button id="plus">+</button>

  <script src="./redux.min.js"></script>
  <script>

    // 3. 存储默认状态
    const initialState = {
      count: 0
    }

    // 2. 创建 reducer 函数
    function reducer(state = initialState, action) {
      switch (action.type) {
        case 'increment':
          return { count: state.count + 1 };
        case 'decrement':
          return { count: state.count - 1 };
        default:
          return state;
      }
    }

    // 1. 创建 store 对象
    const store = Redux.createStore(reducer);

    // 4. 定义 action 
    const increment = { type: 'increment' }
    const decrement = { type: 'decrement' }

    // 5. 获取按钮 给按钮添加点击事件
    document.getElementById('minus')
    .addEventListener('click', function () {
      // 6. 获取dispatch  触发 action 
      store.dispatch(decrement)
    })
    document.getElementById('plus')
    .addEventListener('click', function () {
      // 6. 获取dispatch  触发 action 
      store.dispatch(increment)
    })

    // 获取store {dispatch: ƒ, subscribe: ƒ, getState: ƒ, replaceReducer: ƒ, Symbol(observable): ƒ}
    console.log(store)
    // 获取 store 中存储的状态 state
    console.log(store.getState());

    // 订阅数据的变化
    store.subscribe(() => {
      console.log(store.getState())
      document.getElementById('count').innerHTML = store.getState().count
    });
  </script>
</body>
</html>
1.4 Redux 核心API
// 创建 store 对象
const store = Redux.createStore(reducer);
// 2. 创建 reducer 函数
function reducer(state = initialState, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      return state;
  }
}
// 获取 store 中存储的状态 state
store.getState()
// 订阅数据的变化
store.subscribe(() => {
  console.log(store.getState())
});
// 获取dispatch  触发 action 
store.dispatch(increment)

2. React + Redux

2.1 在 React 中不使用 Redux 时遇到的问题

在 React 中组件通信的数据流是单向的,顶层组件可以通过 Props 属性向下层组件传递数据,而下层组件不能向上层组件传递数据。要实现下层组件修改数据,需要上层组件传递修改数据的方法到下层组件。等项目越来越大的时候,组件间传递数据变得越来越困难。

2.2 在 React 项目中加入 Redux 的好处

使用 Redux 管理数据,由于 Store 独立于组件,使得数据管理独立于组件,解决了组件与组件之间传递数据困难的问题。

2.3 下载 Redux
npm install redux react-redux
2.4 Redux 工作流程
  1. 组件通过 dispatch 方法触发 Action
  2. Store 接受 Action 并将 Action 分发给Reducer
  3. Reducer 根据 Action 类型对状态进行更改并将更改后的状态返回给 Store
  4. 组件订阅了 Store 中的状态, Store 中的状态更新会同步到组件
2.5 Redux 使用步骤
2.5.1 创建 store
// src/store/index.js
import { createStore } from 'redux'
import reducer from './reducers/counter.reducer'
export const store = createStore(reducer)

在根组件中使用store

import React from 'react';
import ReactDOM from 'react-dom';
import Counter from './components/Counter'
import { Provider } from 'react-redux'
import {store} from './store'
/**
 * react-redux
 * Provider
 * connect
 */

ReactDOM.render(
  // 通过 provider 组件,将store 放在了全局的组件可以够得着的地方
  <Provider store={store}>
    <Counter />
  </Provider>,
  document.getElementById('root')
);
2.5.2 创建 reducer
// src/store/reducers/counter.reducer.js
import { DECREMENT, INCREMENT } from "../count/counter.const";

const initialState = {
  count: 0
}

export default function reducer (state = initialState, action) {
  switch (action.type) {
    case INCREMENT:
      return { count: state.count + 1 };
    case DECREMENT:
      return { count: state.count - 1 };
    default:
      return state;
  }
}
// src/store/count/counter.const.js
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
2.5.3 在组件中使用 connect 接受 store 里面的 state 和 dispatch

connect方法接受两个参数,返回一个高阶组件。
connect方法的第一个参数是mapStateToProps方法,将store中的state传递到组件的props中,mapStateToProps方法的参数是state,返回值是一个对象,会传递到组件中,写法如下:

const mapStateToProps = (state) => ({
  count: state.count,
  a: 'a', // 这里怎么定义,组件中就可以或得到一个属性
})

connect方法的第二个参数是mapDispatchToProps方法,将store中的dispatch传递到组件的props中,mapDispatchToProps方法的参数是dispatch,返回值是一个对象,对象中的方法可以使用dispatch,这个对象中的方法会传递到组件中,写法如下:

const mapDispatchToProps = (dispatch) => ({
  increment () {
    dispatch({ type: 'increment'})
  },
  decrement () {
    dispatch({ type: 'decrement' })
  }
})

此外,我们还可以通过redux中的bindActionCreators来帮我们创建action函数:

import {bindActionCreators} from 'redux'

// bindActionCreators 会返回一个对象
const mapDispatchToProps = dispatch => (
  // 解构
  ...bindActionCreators({
    increment () {
      return { type: 'increment'}
    },
    decrement () {
      return { type: 'decrement'}
    }
  }, dispatch)
)

或者写成:

const mapDispatchToProps = dispatch => bindActionCreators({
    increment () {
      return { type: 'increment'}
    },
    decrement () {
      return { type: 'decrement'}
    }
  }, dispatch)

也可以将bindActionCreators的第一个参数进行抽离:

import * as counterActions from '../store/actions/counter.actions'

const mapDispatchToProps = dispatch => bindActionCreators(conterActions, dispatch)
// src/store/actions/counter.actions.js
import { DECREMENT, INCREMENT } from "../count/counter.const"

export const increment = () => ({type: INCREMENT})
export const decrement = () => ({type: DECREMENT})

connect方法接受mapStateToPropsmapDispatchToProps,返回一个高阶组件,然后传入Counter组件进行导出:

export default connect(mapStateToProps, mapDispatchToProps)(Counter)

最终组件代码如下:

// src/components/Counter.js
import React from 'react'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import * as counterActions from '../store/actions/counter.actions'

function Counter ({count, increment, decrement}) {
  return (
    <div>
      <button onClick={decrement}>-</button>
      <span>{count}</span>
      <button onClick={increment}>+</button>
    </div>
  )
}

// 1. connect 会帮助我们去订阅 store,当store中的状态发生了变化后,可以帮我们重新渲染组件
// 2. connect 方法可以让我们获取 store 中的状态,将状态通过组建的props属性映射给组件
// 3. connect 方法可以让我们获取 dispatch 方法

const mapStateToProps = (state) => ({
  count: state.count,
  a: 'a', // 这里怎么定义,组件中就可以或得到一个属性
})

const mapDispatchToProps = dispatch => bindActionCreators(counterActions, dispatch)
export default connect(mapStateToProps, mapDispatchToProps)(Counter)
2.5.4 为 action 传递参数
  1. 传递参数

    <button onClick={() => increment(5)}> + 5</button>
  2. 接受参数,传递reducer

    export const increment = payload => ({type: INCREMENT, payload})
    export const decrement = payload => ({type: DECREMENT, payload})
  3. reducer根据接受收到的数据进行处理

    export default function reducer (state = initialState, action) {
      switch (action.type) {
     case INCREMENT:
       return { count: state.count + action.payload };
     case DECREMENT:
       return { count: state.count - action.payload };
     default:
       return state;
      }
    }
2.6 redux 实现弹出框案例

store中的状态越多,reducer中的switch分支就会越多,不利于维护,需要拆分reducer

src/index.js

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { Provider } from 'react-redux'
import {store} from './store'

ReactDOM.render(
  // 通过 provider 组件,将store 放在了全局的组件可以够得着的地方
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

src/store/index.js

// src/store/index.js
import { createStore } from 'redux'
import reducer from './reducers/counter.reducer'

export const store = createStore(reducer)

src/store/reducers/counter.reducer.js

// src/store/reducers/counter.reducer.js
import { DECREMENT, INCREMENT } from "../const/counter.const";
import { HIDEMODAL, SHOWMODAL } from "../const/modal.const";

const initialState = {
  count: 0,
  show: false
}

export default function reducer (state = initialState, action) {
  switch (action.type) {
    case INCREMENT:
      return { ...state, count: state.count + action.payload };
    case DECREMENT:
      return { ...state, count: state.count - action.payload };
    case SHOWMODAL:
      return { ...state, show: true };
    case HIDEMODAL:
      return { ...state, show: false };
    default:
      return state;
  }
}

src/App.js

// src/App.js
import Modal from './components/Modal'
import Counter from './components/Counter'

function App() {
  return (
    <div className="App">
      <Counter />
      <Modal />
    </div>
  );
}

export default App;

src/components/Modal.js

// src/components/Modal.js
import React from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import * as modalActions from '../store/actions/modal.actions'

function Modal ({ showStatus, show, hide }) {
  const styles = {
    display: showStatus ? 'block': 'none',
    width: 200,
    height: 200,
    position: 'absolute',
    top: 0,
    right: 0,
    bottom: 0,
    left: 0,
    margin: 'auto',
    backgroundColor: 'skyblue'
  }
  return (
    <div>
      <button onClick={show}>显示</button>
      <button onClick={hide}>隐藏</button>
      <div style={styles}></div>
    </div>
  )
}

const mapStateToProps = state => ({
  showStatus: state.show
})
const mapDispatchToProps = dispatch => bindActionCreators(modalActions, dispatch)

export default connect(mapStateToProps, mapDispatchToProps)(Modal)

src/store/actions/modal.action.js

// src/store/actions/modal.action.js
import { HIDEMODAL, SHOWMODAL } from "../const/modal.const"

export const show = () => ({ type: SHOWMODAL })
export const hide = () => ({ type: HIDEMODAL })

src/store/const/modal.const.js

// src/store/const/modal.const.js
export const SHOWMODAL = 'showModal'
export const HIDEMODAL = 'hideModal'
2.7 拆分reducer

使用reducer提供的工具combineReducers合并每一个小的reducer

src/store/reducers/root.reducer.js

// src/store/reducers/root.reducer.js
import {combineReducers} from 'redux'
import CounterReducer from './counter.reducer'
import ModalReducer from './modal.reducer'

// { counter: { count: 0 }, modal: { show: false } }
export default combineReducers({
  counter: CounterReducer,
  modal: ModalReducer
})

src/store/reducers/counter.reducer.js

// src/store/reducers/counter.reducer.js
import { DECREMENT, INCREMENT } from "../const/counter.const";

const initialState = {
  count: 0,
}

export default function counterReducer (state = initialState, action) {
  switch (action.type) {
    case INCREMENT:
      return { ...state, count: state.count + action.payload };
    case DECREMENT:
      return { ...state, count: state.count - action.payload };
    default:
      return state;
  }
}

src/store/reducers/modal.reducer.js

// src/store/reducers/modal.reducer.js
import { HIDEMODAL, SHOWMODAL } from "../const/modal.const";
const initialState = {
  show: false
}

export default function modalReducer (state = initialState, action) {
  switch (action.type) {
    case SHOWMODAL:
      return { ...state, show: true };
    case HIDEMODAL:
      return { ...state, show: false };
    default:
      return state;
  }
}

创建store时传入的reducer则来自于我们刚才定义的root.reducer.js

import { createStore } from 'redux'
import RootReducer from './reducers/root.reducer'

export const store = createStore(RootReducer)

在每个组件中的mapStateToProps中也要发生相应的改变(state.counterstate.modal):

const mapStateToProps = (state) => ({
  count: state.counter.count,
})
const mapStateToProps = state => ({
  showStatus: state.modal.show
})

3. Redux 中间件

3.1 什么是中间件

中间价允许我们扩展和增强 redux 应用程序

3.2 开发 Redux 中间件

开发中间件的模板

export default store => next => action => {  }
3.3 注册中间件

中间件在开发完成以后只有被注册才能在 Redux 的工作流程中生效

src/store/index.js

// src/store/index.js
import { createStore, applyMiddleware } from 'redux'
import logger from './middlewares/logger'

createStore(reducer, applyMiddleware(
  logger
))

src/store/middleware/logger.js

const logger = store => next => action => {
  console.log(store)
  console.log(action)
  next(action) // 千万别忘了调用 next(action)
}
export default logger

如果注册多个中间件,中间件的执行顺序就是注册顺序,如:

createStore(reducer, applyMiddleware(
  logger,
  test
))

那么执行顺序就是先执行logger中间件,再执行test中间件。
如果中间件中的结尾不调用next(action),则整个流程就会卡在此处不会再往后执行了

3.4 Redux 中间件开发实例 thunk (异步中间件)

当前这个中间件函数不关心你想执行什么样的异步操作,只关心你执行的是不是异步操作,
如果你执行的是异步操作,你在触发 action 的时候,给我传递一个函数,如果执行的是同步操作,就传递一个 action 对象,
异步操作代码要写在你传进来的函数中
当这个中间件函数,在调用你传进来的函数时要将 dispatch 方法传递过去

src/store/middleware/thunk.js

// src/store/middleware/thunk.js
import { DECREMENT, INCREMENT } from "../const/counter.const";

const thunk = ({dispatch}) => next => action => {
  if (typeof action === 'function') {
    return action(dispatch) // action 方法内部会发起新的 dispatch 
  }
  next(action)
}
export default thunk

在action文件中定义异步函数action:

src/store/actions/modal.actions.js

// src/store/actions/modal.actions.js
import { HIDEMODAL, SHOWMODAL } from "../const/modal.const"

export const show = () => ({ type: SHOWMODAL })
export const hide = () => ({ type: HIDEMODAL })

export const show_async = () => dispatch => {
  setTimeout(() => {
    dispatch(show())
  }, 2000);
}

原本使用show的地方,现在改用show_async,实现了异步的功能

4. Redux 常用中间件

4.1 redux-thunk

4.1.1 redux-thunk 下载

npm install redux-thunk

4.1.2 引入 redux-thunk

import thunk from 'redux-thunk';

4.1.3 注册 redux-thunk

import { applyMiddleware } from 'redux'
createStore(rootReducer, applyMiddleware(thunk));

4.1.4 使用 redux-thunk 中间件

const loadPosts = () => async dispatch => {
  const posts = await axios.get('/api/posts').then(response => response.data);
  dispatch({type: LOADPOSTSSUCCE, payload: posts});
}
4.2 redux-saga
4.2.1 redux-saga 解决的问题

redux-saga可以将异步操作从Action Creator文件中抽离出来,放在一个单独的文件中。

4.2.2 下载redux-saga
npm install redux-saga
4.2.3 创建 redux-saga 中间件

src/store/index.js

// src/store/index.js
import createSagaMiddleware from 'redux-saga';
const sagaMiddleware = createSagaMiddleware();
4.2.4 注册 sagaMiddleware

src/store/index.js

// src/store/index.js
createStore(reducer, applyMiddleware(sagaMiddleware))
4.2.5 使用 saga 接受 action 异步执行操作

src/store/sagas/counter.saga.js

// src/store/sagas/counter.saga.js
import { takeEvery, put, delay } from 'redux-saga/effects'
import { increment } from '../actions/counter.actions'
import { INCREMENT_ASYNC } from '../const/counter.const'

// takeEvery 接收 action 
// put 触发 action 

function * increment_async_fn (action) {
  yield delay(2000) // 此处会暂停2秒钟
  yield put(increment(action.payload))
}

export default function * counterSaga () {
  // 接收 action
  yield takeEvery(INCREMENT_ASYNC, increment_async_fn) // 第二个函数形参会接受一个 action 函数
}

src/store/actions/counter.actions.js

// src/store/actions/counter.actions.js

// 给 saga 使用
export const increment_async = (payload) => ({ type: INCREMENT_ASYNC, payload });

src/store/const/counter.const.js

// src/store/const/counter.const.js
export const INCREMENT_ASYNC = 'increment_async'

src/components/Counter.js

      <button onClick={() => increment_async(20)}>+</button>
4.2.6 启动 saga

src/store/index.js

// src/store/index.js
import counterSaga from './sagas/counter.saga'

sagaMiddleware.run(counterSaga);
4.2.7 合并 saga

src/store/saga/root.saga.js

// src/store/saga/root.saga.js
import { all } from 'redux-saga/effects'
import counterSaga from './counter.saga'
import modalSaga from './modal.saga'

export default function * rootSaga () {
  yield all([
    counterSaga(),
    modalSaga()
  ])
}

modal.saga.js 没变,modal.saga.js 如下

src/store/saga/modal.saga.js

// src/store/saga/modal.saga.js
import { takeEvery, put, delay } from 'redux-saga/effects'
import { show } from '../actions/modal.actions'
import { SHOWMODAL_ASYNC } from '../const/modal.const'

// takeEvery 接收 action 
// put 触发 action 

function * showModal_async_fn () {
  yield delay(2000)
  yield put(show())
}

export default function * modalSaga () {
  // 接收 action
  yield takeEvery(SHOWMODAL_ASYNC, showModal_async_fn)
}

store 入口文件中的 saga 中间件启动 root.saga

src/store/index.js

// src/store/index.js
import rootSaga from './sagas/root.saga'

sagaMiddleware.run(rootSaga)
4.3 redux-actions
4.3.1 redux-actions 解决的问题

redux 流程中大量的样板代码读写很痛苦,使用 redux-action 可以简化 Action 和 Reducer 的处理

4.3.2 redux-action 下载
npm install redux-actions
4.3.3 创建 Action
import { createAction } from 'redux-actions'

const increment_action = createAction('increment');
const decrement_action = createAction('decrement');
4.3.4 创建 Reducer

src/store/actions/counter.actions.js

src/store/actions/counter.actions.js
// 使用 redux-actions 
import { createAction } from 'redux-actions'

export const increment = createAction('increment')
export const decrement = createAction('decrement')

src/store/reducers/counter.reducer.js

// src/store/reducers/counter.reducer.js
import { handleActions as createReducer } from 'redux-actions'
import { increment, decrement } from '../actions/counter.actions'
const initialState = {
  count: 0,
}
const handleIncrement = (state, action) => ({
  count: state.count + action.payload
})

const handleDecrement = (state, action) => ({
  count: state.count - action.payload
})

export default createReducer({
  [increment]: handleIncrement,
  [decrement]: handleDecrement,
}, initialState)

组件使用:
src/components/Counter.js

// src/components/Counter.js
function Counter ({count, increment, decrement}) {
  return (
    <div>
      <button onClick={() => decrement(1)}>-</button>
      <span>{count}</span>
      <button onClick={() => increment(1)}>+</button>
    </div>
  )
}

redux-actions 也可以结合在 redux-saga 中使用

MobX

1. Mobx 简介

1.1 Mobx 介绍

简单,可扩展的状态管理库

Mobx 是由 Mendix(代码开发平台), Coinbase(比特币公司), Facebook 开源和众多个人赞助商所赞助的
React 和 Mobx 是一对强力组合, React 负责渲染应用状态, Mobx 负责管理应用状态供 React 使用

1.2 MobX 浏览器支持

MobX 5 版本运行在任何支持 ES6 Proxy的浏览器,不支持 IE11, Node.js 6
MobX 4 可以运行在任何支持 ES5 的浏览器上
MobX 4 和 5 的 API 是相同的

2. 开发前的准备

2.1 启用装饰器语法支持(方式一)
  1. 弹射项目底层配置: npm run eject
  2. 下载装饰器语法 babel 插件: npm install @babel/plugin-proposal-decorators
  3. 在 package.json 文件中加入配置
"babel": {
  "plugins": [
    [
      "@babel/plugin-proposal-decorators",
      {
        "legacy": true
      }
    ]
  ]
}

启用装饰器语法支持(方式二)

  1. npm install react-app-rewired customize-cra @babel/plugin-proposal-decorators
  2. 在项目根目录下创建 config-overrides.js 并加入配置

    const { override, addDecoratorsLegacy } = require("customize-cra");
    module.exports = override(addDecoratorsLegacy());
  3. package.json

    "scripts": {
      "start": "react-app-rewired start",
      "build": "react-app-rewired build",
      "test": "react-app-rewired test"
    }
    2.2 解决 vscode 编辑器关于装饰器语法的警告

    在 vscode 按 command + 逗号,然后在输入框中输入javascript.implicitProjectConfig.experimentalDecorators
    修改配置: "javascript.implicitProjectConfig.experimentalDecorators": true

3. Mobx + React

3.1 下载 Mobx
npm install mobx mobx-react
3.2 Mobx 工作流程

Action -> state -> Views

5. Mobx 数据监测

5.1 computed 计算值

什么时候使用计算值
将复杂的业务逻辑从模板中进行抽离

5.2 autorun 方法

当监听的状态发生变化时,你想根据状态产生“效果”,请使用 autorun.
autorun 会在初始化的时候执行一次,会在每次状态发生变化时执行。


zhuanglog
162 声望9 粉丝

« 上一篇
React源码解读