状态管理工具解决的问题:管理被多个组件所依赖或影响的状态

  • react-redux、redux:基础

    异步方案

  • redux-thunk:异步控制中间件

  • redux-promise:让 action 或 action 的 payload 支持异步
  • redux-saga:让异步行为成为架构中独立的一层(saga)

    redux-thunk

    让 dispath 可以支持函数类型,在这之前只能是纯对象(plain object) ```javascript import { createStore, applyMiddleware } from ‘redux’; import thunk from ‘redux-thunk’; import rootReducer from ‘./reducers’;

// createStore的时候传入thunk中间件 const store = createStore(rootReducer, applyMiddleware(thunk));

// 发起网络请求的方法 function fetchSecretSauce() { return fetch(‘https://www.baidu.com/s?wd=Secret%20Sauce‘); }

// 下面两个是普通的action function makeASandwich(forPerson, secretSauce) { return { type: ‘MAKE_SANDWICH’, forPerson, secretSauce, }; }

function apologize(fromPerson, toPerson, error) { return { type: ‘APOLOGIZE’, fromPerson, toPerson, error, }; }

// 这是一个异步action,先请求网络,成功就makeASandwich,失败就apologize function makeASandwichWithSecretSauce(forPerson) { return function (dispatch) { return fetchSecretSauce().then( (sauce) => dispatch(makeASandwich(forPerson, sauce)), (error) => dispatch(apologize(‘The Sandwich Shop’, forPerson, error)), ); }; }

// 最终dispatch的是异步action makeASandwichWithSecretSauce store.dispatch(makeASandwichWithSecretSauce(‘Me’));

  1. 缺点:写起来代码过于冗余
  2. <a name="R7UxF"></a>
  3. ## redux-promise
  4. action payload 属性为 Promise 对象时,触发一个此 action 的拷贝,payload promise 的执行结果
  5. - resolve:设置 action status 属性为 "success"
  6. - reject:设置 action status 属性为 "error"
  7. ```javascript
  8. //action types
  9. const GET_DATA = 'GET_DATA';
  10. //action creator
  11. const getData = function(id) {
  12. return {
  13. type: GET_DATA,
  14. payload: api.getData(id) //payload为promise对象
  15. }
  16. }
  17. //reducer
  18. function reducer(oldState, action) {
  19. switch(action.type) {
  20. case GET_DATA:
  21. if (action.status === 'success') {
  22. return successState
  23. } else {
  24. return errorState
  25. }
  26. }
  27. }

缺点:引入了新的字段来区分状态

redux-saga

1_slj1_7R18GXzNaQA6zmiiA.png

redux-saga 成为 component 及 reducer 之间沟通的桥梁

  1. import { createStore, applyMiddleware } from 'redux';
  2. import createSagaMiddleware from 'redux-saga';
  3. import reducer from '../reducer';
  4. import mySaga from '../saga';
  5. const sagaMiddleware = createSagaMiddleware();
  6. export default createStore(reducer, applyMiddleware(sagaMiddleware));
  7. sagaMiddleware.run(mySaga);
  1. import { call, put, takeEvery } from 'redux-saga/effects';
  2. const FETCH_DATA = 'FETCH_DATA';
  3. const FETCH_DATA_SUCCESS = 'FETCH_DATA_SUCCESS';
  4. function* fetchData() {
  5. const data = yield call(
  6. () => fetch('https://httpbin.org/get')
  7. .then(response => response.json()),
  8. );
  9. yield put({ type: FETCH_DATA_SUCCESS, payload: { data } });
  10. }
  11. function* mySaga() {
  12. yield takeEvery(FETCH_DATA, fetchData);
  13. }
  14. export default mySaga;
  1. export const FETCH_DATA = 'FETCH_DATA';
  2. export const FETCH_DATA_SUCCESS = 'FETCH_DATA_SUCCESS';
  3. const initState = {
  4. data: {},
  5. };
  6. const reducer = (state = initState, action) => {
  7. switch (action.type) {
  8. case FETCH_DATA_SUCCESS:
  9. return {
  10. ...state,
  11. data: action.payload.data,
  12. };
  13. default:
  14. return { ...state };
  15. }
  16. };
  17. export default reducer;
  1. import React, { useEffect } from 'react';
  2. import { connect } from 'react-redux';
  3. import { FETCH_DATA } from '../reducer';
  4. const Main = (props) => {
  5. const { data } = props;
  6. useEffect(() => {
  7. props.getData();
  8. }, []);
  9. return (
  10. <div>
  11. {JSON.stringify(data)}
  12. </div>
  13. );
  14. };
  15. const mapStateToProps = state => ({
  16. data: state.data,
  17. });
  18. const mapDispatchToProps = dispatch => ({
  19. getData: () => { dispatch({ type: FETCH_DATA }); },
  20. });
  21. export default connect(mapStateToProps, mapDispatchToProps)(Main);

性能优化

  • reselect:缓存状态,减少 redux 不必要的刷新,提高性能

    reselect

    设计初衷

    为了解决 react-redux 架构中,在把全局 store 里的 state 树的部分状态映射到对应组件的 props 中(mapStateToProps),每次组件更新的时候都需要执行计算的问题。即便组件依赖的状态没有改变。

使用

reselect 提供的 createSelector 方法主要是替代 mapStateToProps 函数的,并提供了缓存和比较机制,来减少组件更新时的无谓计算,从而提高性能。

  • reselect 默认只有一份 cache,cache 命中的策略是浅比较,引用改变了会导致 cache 失效 ```javascript // Reselect selector // Takes a list of posts and post Ids, and picks out // the selected Posts import { createSelector } from ‘reselect’; import _ from ‘loadash’;

// Create select functions to pick off the pieces of state we care about // for this calculation // 采用 reselect 后,相当于记忆缓存,会缓存状态 // 如果 state.posts 和 state.selectedPostIds 发生变化,它会重新计算 state // 但是发生在其他部分的 state 变化,就不会重新计算 const postsSelector = state => state.posts; const selectedPostsSelector = state => state.selectedPostIds;

// 参数名字无所谓 const getPosts = (posts, selectedPostIds) => { const selectPosts = .filter( posts, post => .contains(selectedPostIds, post.id) );

  1. return selectPosts;

}

/**

  • 前面几个参数:state selecting functions,当 state 改变时,这些函数都会被执行
  • 前面这些参数产生的结果都会传到最后一个函数 */ const SelectedPostsSelector = createSelector([
    1. postsSelector, // pick off a piece of state
    2. selectedPostsSelector // pick off a piece of state
    ], getPosts // last argument is the function that has our select logic );

// App const mapStateToPorps = state => ({ posts: SelectedPostsSelector(state) });

export default connect(mapStateToPorps)(App);

  1. <a name="MHnh5"></a>
  2. # 优化写法
  3. - redux-actions:简化接口调用
  4. <a name="iCW8K"></a>
  5. ## redux-actions
  6. > 一个帮助 redux 应用快速创建 actions、reducers 的辅助类函数库
  7. <a name="ggtGc"></a>
  8. ### createAction
  9. ```javascript
  10. import { createAction } from 'redux-actions';
  11. const increment = createAction('increment');
  12. let incrementObj = increment();// { type:"increment"}
  13. let objincrement = increment(10);// {type:"increment",paylaod:10}
  14. const increment = createAction('increment',(t)=> t * 2);
  15. let objincrement = increment(10);// {type:"increment",paylaod:20}

原理实现:其实就是个柯里化函数

  1. function createAction(type,payloadCreator) {
  2. return (payload) => {
  3. const action = {
  4. type,
  5. };
  6. if(payload !== undefined){
  7. action.payload = payloadCreator?payloadCreator(payload):payload
  8. }
  9. return action;
  10. };
  11. }

createActions

  1. const { actionOne, actionTwo, actionThree } = createActions(
  2. {
  3. // function form; payload creator defined inline
  4. ACTION_ONE: (key, value) => ({ [key]: value }),
  5. // array form
  6. ACTION_TWO: [
  7. first => [first], // payload
  8. (first, second) => ({ second }) // meta
  9. ]
  10. // trailing action type string form; payload creator is the identity
  11. },
  12. 'ACTION_THREE'
  13. );
  14. expect(actionOne('key', 1)).to.deep.equal({
  15. type: 'ACTION_ONE',
  16. payload: { key: 1 }
  17. });
  18. expect(actionTwo('first', 'second')).to.deep.equal({
  19. type: 'ACTION_TWO',
  20. payload: ['first'],
  21. meta: { second: 'second' }
  22. });
  23. expect(actionThree(3)).to.deep.equal({
  24. type: 'ACTION_THREE',
  25. payload: 3
  26. });

handleActions

  1. const INCREMENT = "increment"
  2. const DECREMENT = "decrement"
  3. var reducer = handleActions({
  4. [INCREMENT]: (state, action) => ({
  5. counter: state.counter + action.payload
  6. }),
  7. [DECREMENT]: (state, action) => ({
  8. counter: state.counter - action.payload
  9. })
  10. },initstate)
  11. //传统的写法
  12. let reducer = (state,action)=>{
  13. switch(action.type){
  14. case "increment":return {count:state.count+1};break;
  15. case "decrement":return {count:state.count-1};break;
  16. default:return state;
  17. }
  18. }

为什么store中不相关的数据的改变会引起界面的重新渲染?

  • connect 返回一个监听了 store 改变的 HOC
  • 被 connect 的组件最终是否要 re-render,取决于被 connect 组件被传入的 props 是否改变

    • 通过 mapStateToProps、mapDispatchToProps、mergeProps 计算得到最终的 prop ```javascript if (newChildProps === lastChildProps.current) { // 直接通知下层 connect 的组件去执行订阅事件,本组件不触发 re-render if (!renderIsScheduled.current) { notifyNestedSubs() } } else { lastChildProps.current = newChildProps childPropsFromStoreUpdate.current = newChildProps renderIsScheduled.current = true

    // 通过 setState 来 re-render forceComponentUpdateDispatch({ type: ‘STORE_UPDATED’, payload: {

    1. error

    } }) } ```

    参考资料

  1. Understanding Asynchronous Redux Actions with Redux Thunk
  2. What the heck is a ‘thunk’?
  3. Redux异步解决方案之Redux-Thunk原理及源码解析
  4. Redux异步方案总结
  5. Redux异步方案选型
  6. redux-saga 实践总结
  7. 如何使用Reselect做性能优化
  8. Redux Saga | Redux 界的非同步救星 - 基本用法
  9. 关于react, redux, react-redux和reselect的一些思考
  10. reselect源码分析和最佳实践思考
  11. 使用 Redux 打造你的应用 —— redux-actions
  12. 推荐使用并手写实现redux-actions原理
  13. redux-actions 官网
  14. 深入理解 react/redux 数据流并基于其优化前端性能
  15. 浅谈 React 数据流管理
  16. Build Your Own React-Redux Using useReducer and useContext Hooks
  17. 黑帕云前端性能揭秘:React性能调优