• 实战:自定义简单版redux, enhancer与react-redux


    base

    [redux]core

    • 解决问题:组件之间传值问题
    • 状态: state
    • 读写state: getState + dispatch
    • 发布订阅: subscribe + publish(in dispatch)

    [redux]enhancer

    • 高阶函数, 比如增强dispatch功能,或者其他曾庆
    • 例如redux-thunk 原本action只能接受plain对象,现在可以接受function

    [redux]applyMiddleware

    • 相当于 compose,参数是多个enhancer
    • 将多个enhancer按顺序执行,最后合并为1个

    [react-redux]core
    在redux基础上(getState,dispatch,subscribe)

    1. 提供Provider组件(注入store)
    2. connect高阶方法(连接全局store与普通组件 && 完成自动订阅发布)

    官方 react

    Xnip2022-06-23_13-54-17

    官方react + 官方redux: 查看redux的能力

    小结

    • redux核心功能:提供getState,dispatch,subscribe 这几个api给开发者使用

    • yarn add redux 指定4.1.2版本

    • 创建store

      • start-demo-redux-my/src/redux/store.js
      • createStore(redux),这个redux包装方法,可以提供 dispatchsubscribegetState 给组件使用
      • reducer(开发者自定义),这是用户希望如何处理state的方法,根据业务各不相同,包括state初始值,reducer方法,提供给dispatch使用
    • 组件调用

      • start-demo-redux-my/src/component/Next.jsx
      • store.getState() 获取state
      • store.dispatch() 修改state
      • store.subscribe() 重新渲染
    • 中间件

      • 可以在 createStore 传入自定义中间件
      • 中间件可以获取 dispatch getState 能力
      • 中间件在所有dispatch之前触发,因此 getState 的结果是之前的state
      • 如果是 redux-batch 批触发 dispatch,中间件对batch的action,都能捕获到 Test05Reselect3.redux-batch-enhancer.jsx
      • [todo] 能获取到 dispatch 之后的state吗?
        Xnip2022-06-23_14-26-08

    自定义redux

    功能:

    • 自定义redux

    • 实现 createStore,dispatchsubscribegetState

    • 创建简单版store:

      • start/start-demo-redux-my/src/redux/storeMy.js
      • 主体功能:state属性,dispatch方法(修改state,并执行publish方法),getState方法(读取state)
      • 发布订阅:listens属性(用户回调),subscribe方法(存储用户回调),publish(执行用户回调)
      • 注意:此时 dispatch与用户reducer [没分离]
    • 创建实用版store:

      • start/start-demo-redux-my/src/redux/storeMy.js
      • createStore,自实现,公共的,内部包含dispatch方法,subscribe方法,getState方法
      • reducer(开发者自定义),这是用户希望如何处理state的方法,根据业务各不相同,包括state初始值,reducer方法,提供给dispatch使用
      • 注意:此时 dispatch与用户reducer [有分离]
    • 组件调用:不变同上

      • start-demo-redux-my/src/component/Next.jsx
      • store.getState() 获取state
      • store.dispatch() 修改state
      • store.subscribe() 重新渲染

    简单版
    Xnip2022-06-23_14-30-58

    实用版
    Xnip2022-06-23_14-34-22

    dispatch增强

    enhancer的目标是什么?

    • dispatch 增强: 原本只接受 action 对象 参数,增强后可以接受 fucntion 参数
    • 官方redux: dispatch不能接受 function 参数,但可以通过 redux-thunk 实现
    • 自定义redux enhancer: start-demo-redux-my/src/redux/storeMyEnhancer.js
    // 原本调用dispatch,只接受 action 对象 
    const doAdd = () =>{
        store.dispatch({type: 'ADD'});
    }
    
    • 1
    • 2
    • 3
    • 4
    // 希望改写dispatch方法,使其接受 function 参数
    const doAdd = () =>{
        store.dispatch(dispatch =>{
            setTimeout(()=>{
                console.log('setTimeout');
                dispatch({type: 'ADD'});
            },1000);
        });
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    enhancer的基本原理

    • enhancer基本原理: hoc 高阶函数,对原本createStore调整
      // 原本fn, 期望返回值包含参数
      function fn(a){
        return {pow: a*a}
      }
    
      // 做法1:直接
      function proxyFn(a){
        const result = fn(a);
        return {...result, a}
      }
    
      console.log("fn(2): ",  JSON.stringify(fn(2))); // fn(2):  {"pow":4}
      console.log("proxyFn(2): ",  JSON.stringify(proxyFn(2)));  // proxyFn(2):  {"pow":4,"a":2}
    
    
      // 做法2:使用hoc
      function fn(a,enhancer){
        if(enhancer){
          return enhancer(fn)(a);
        }
        return {pow: a*a}
      }  
      function hoc(fn){
        return function(a){
          let result = fn(a);
          // return result; // 这样,与fn本身等价
          return {...result,a} // 这样,调整了fn返回值
        }
      }
    
      console.log("fn(2): ",  JSON.stringify(fn(2))); // fn(2):  {"pow":4}
      console.log("fn(2,hoc): ",  JSON.stringify(fn(2,hoc)));  // fn(2,hoc):  {"pow":4,"a":2}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    官方添加 redux-thunk 实现过程

    branch: fea-add-redux
    源码: https://github.com/reduxjs/redux-thunk/blob/v2.3.0/src/index.js

    // 添加 redux-thunk 
    // yarn add redux-thunk
    import { createStore, applyMiddleware } from 'redux'
    import thunk from 'redux-thunk'
    
    function counterReducer(state = { num: 0 }, action) {
        // ... 
    }
    
    export default createStore(counterReducer, applyMiddleware(thunk))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    // 使用 redux-thunk 
    const doAdd = () =>{
        // store.dispatch({type: 'ADD'});
    
        // 通过点击 redux-thunk 可以支持 dispatch 参数为 function 
        store.dispatch(dispatch =>{
            setTimeout(()=>{
                console.log('setTimeout');
                dispatch({type: 'ADD'});
            },1000);
        });
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    applyMiddleware的基本原理:compose

    • 实现compose的五种思路 https://segmentfault.com/a/1190000011447164
    • compose就是执行一系列的任务(函数)
    // 期望:希望先后执行add 和 multi
      function add(x){
        return x+1;
      }
    
      function multi(x){
        return x*2;
      }
    
      function compose(f,g){
        return function(x){
          // 从右往左执行
          return f(g(x));
        }
      }
    
      console.log("multi(add(1)):", multi(add(1)) );
      console.log("compose(multi,add)(1): ", compose(multi,add)(1));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    // 期望:如果compose入参个数不固定
    function add(x){
      return x+1;
    }
    
    function multi(x){
      return x*2;
    }
    
    function compose(...funs){    
      if(funs.length === 0 ){
          return args => args 
        }else if(funs.length === 1){
          return funs[0]
        }else{
          // 调用 reduce 的数组 至少有2个元素。
          // 因为如果funs个数为0,会报错,个funs数为1,不执行回调,直接返回初始值
          // prev 默认取 funs[0], cur 默认取 funs[1]
          return funs.reduce((prev,cur)=>{
            return (...args) => prev(cur(...args));
          })
        }
    }
    
    // 2*(1+(2*(1+1))) = 10
    console.log("multi(add(multi(add(1)))): ", multi(add(multi(add(1))))); // multi(add(multi(add(1)))):  10
    console.log("compose(multi,add,multi,add)(1)", compose(multi,add,multi,add)(1)); // compose(multi,add,multi,add)(1) 10
    
    // other test 
    console.log("compose()(1,2,3,4)", compose()(1,2,3,4)); // compose()(1,2,3,4) 1 
    console.log("compose(multi)(1)", compose(multi)(1)); // compose()(1,2,3,4) 2 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    添加自定义增强:smallLog

    参考 redux-thunk

    // 自定义middleware,在每次执行dispatch时触发log操作    
    function createSmallLogMiddleware(extraArgument) {
      return ({ dispatch, getState }) => next => action => {
        console.log('smallLog action:',JSON.stringify(action));
        return next(action);
      };
    }
    const smallLog = createSmallLogMiddleware();
    smallLog.withExtraArgument = createSmallLogMiddleware;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    applyMiddleware-smallLog

    官方react-redux

    react-redux核心功能:在redux基础上(getState,dispatch,subscribe)

    1. 提供Provider组件(注入store)
    2. connect高阶方法(连接全局store与普通组件 && 完成自动订阅发布)

    官方使用

    // [1] 添加依赖
    // yarn add react-redux
    
    // [2] 使用 Provider组件
    // Provider 直接与 store 关联,并将store注入全部组件
    import {Provider} from "react-redux";
        <Provider store={store}>
            {/*...*/}
        </Provider>
    
    // [3] 使用 connect 方法 
    // connect 只与 普通组件关联
    import { connect } from 'react-redux';
    
    function Top(props){
        const { num, add, sub } = props;
        // ....
    }
    
    const mapStateToProps = (state) =>{
        return {
            num: state.num
        }
    }
    const mapDispatchToProps = (dispatch) =>{
        return {
            add: () => dispatch({type: 'ADD'}),
            sub: () => dispatch({type: 'SUB'}),
        }
    }
    export default connect(mapStateToProps, mapDispatchToProps)(Top);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    自定义 react-redux

    • 实现Provider组件 + connect高阶方法

    基本结构

    // 输入: props.store 
    // 输出: 渲染子组件的新组件
    export const Provider = (props) => {
        const { store } = props; 
        // store: {dispatch, subscribe, getState}    
        return (
            <>
                {props.children}
            </>
        )
    }
    // 输入: mapStateToProps, mapDispatchToProps,  WrapComponent
    // 输出: 关联state的新组件
    export const connect = (mapStateToProps, mapDispatchToProps) => (WrapComponent) =>{
       return  () => {
           return (
               <WrapComponent></WrapComponent>
           )
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    传递store: provider -> connect

    • 通过 createContext + useContext
    • 至此 provider 功能完成
    const StoreContext = React.createContext("");
    export const Provider = (props) => {
        const { store } = props;
        return (
            <StoreContext.Provider value={store}>
                {props.children}
            </StoreContext.Provider>
        )
    }
    export const connect = (mapStateToProps, mapDispatchToProps) => (WrapComponent) => {
       return  () => {
            const store = useContext(StoreContext); // store: {dispatch, subscribe, getState}        
           return (
               <WrapComponent></WrapComponent>
           )
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    实现 mapStateToProps + mapDispatchToProps + subscribe

    • 至此 connect 功能完成
    export const connect = (mapStateToProps, mapDispatchToProps) => (WrapComponent) => {
       return  (props) => { // component own props
            const [allProps, setAllProps] = useState({});
            const store = useContext(StoreContext);  
            const { getState, dispatch, subscribe } = store;
    
            useEffect(() => {
                update(); // first render 
                subscribe(update); // when store state change, rerender
            }, []);
    
            const update = () =>{
                // mapStateToProps ok 
                const stateProps = mapStateToProps(getState());
                // mapDispatchToProps ok  
                // 此处只考虑  mapDispatchToProps 为函数类型,实际也可以是对象,这里省略处理 
                const dispatchProps = mapDispatchToProps(dispatch);
                // allProps
                const allProps = {...props, ...stateProps, ...dispatchProps};            
                setAllProps(allProps);
            }
           return (
               <WrapComponent {...allProps}></WrapComponent>
           )
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    tips

    redux版本

    redux 4.1.2 可以使用 createStore
    redux 4.2.0 configureStore 代替 createStore
    redux实际使用版本查看 node_modules/redux/package.json

  • 相关阅读:
    【AGC】开放式测试示例
    二维码生成器
    闲人闲谈PS之三十四——项目成本费用控制阈值
    机器学习网络模型绘图模板
    ruoyi登录功能源码分析
    准备我们心爱的IDEA写Jsp
    elasticsearch搜索IK分词器实现单个字搜索
    8. 控制转义指令
    脚手架安装
    吉利笔试——编程代码题
  • 原文地址:https://blog.csdn.net/qubes/article/details/125426754