• react hooks 全面转换攻略(三) 全局存储解决方案


    针对 react hooks 的新版本解决方案

    一.redux维持原方案

    若想要无缝使用原来的 redux,和其配套的中间件 promise,thunk,saga 等等的话
    可以使用 redux-react-hook

    github 链接 redux-react-hook

    一个简单的使用例子:

    import {useDispatch, useMappedState} from 'redux-react-hook';
    
    export function DeleteButton({index}) {
      // 类似于以前 react-redux 中的 connect 函数
      const mapState = useCallback(
        state => ({
          canDelete: state.todos[index].canDelete,
          name: state.todos[index].name,
        }),
        [index],
      );
    
      // 获取 redux 的数据
      const {canDelete, name} = useMappedState(mapState);
    
      // 获取 dispatch 
      const dispatch = useDispatch();
      
      
      // button click handle
      const deleteTodo = useCallback(
        () =>
          dispatch({
            type: 'delete todo',
            index,
          }),
        [index],
      );
    
      return (
        <button disabled={!canDelete} onClick={deleteTodo}>
          Delete {name}
        </button>
      );
    }
    

    使用方法和以前一致

    二.使用 useReducer 与 context

    在 index 或 app 中提供全局的 redux 与 dispatch

    function isPromise(obj) {
      return (
        !!obj &&
        (typeof obj === "object" || typeof obj === "function") &&
        typeof obj.then === "function"
      );
    }
    
    function wrapperDispatch(dispatch) {
      // 功能和 redux-promise 相同
      return function (action) {
        isPromise(action.payload) ?
          action.payload.then(v => {
            dispatch({type: action.type, payload: v})
          }).catch((error) => {
            dispatch(Object.assign({}, action, {
              payload: error,
              error: true
            }));
            return Promise.reject(error);
          })
          :
          dispatch(action);
      };
    }
    
    
    function Wrap(props) {
      // 确保在 dispatch 后不会刷新APP组件
      const [state, dispatch] = useReducer(reducers, ReducersValue);
      console.log('render wrap')
      return (<MainContext.Provider value={{state: state, dispatch: wrapperDispatch(dispatch)}}>{props.children}</MainContext.Provider>)
    }
    
    function App() {
      console.log('render  App')
      return <Wrap>
        <Router>
          <Switch>
            <Route path="/login" component={Login} exact/>
            <Route path="/" component={MainIndex}/>
          </Switch>
        </Router>
      </Wrap>
    }
    

    具体使用:

    function useDispatch() {
      // 获取 dispatch
      const store = useContext(MainContext);
      return store.dispatch;
    }
    
    function useStoreState(mapState) {
      //存储 state 且判断是否需要 render
      const {state:store} = useContext(MainContext);
    
      const mapStateFn = () => mapState(store);
    
      const [mappedState, setMappedState] = useState(() => mapStateFn());
    
      const lastRenderedMappedState = useRef();
      // Set the last mapped state after rendering.
      useEffect(() => {
        lastRenderedMappedState.current = mappedState;
      });
    
      useEffect(
        () => {
         console.log('useEffect ')
          const checkForUpdates = () => {
            const newMappedState = mapStateFn();
            if (!_.isEqual(newMappedState, lastRenderedMappedState.current)) {
              setMappedState(newMappedState);
            }
          };
    
          checkForUpdates();
        },
        [store, mapState],
      );
      return mappedState
    }
    
    // 组件内使用
    const ResourceReducer = useStoreState(state => state.ResourceReducer)
    const dispatch = useDispatch()
    

    他的功能已经足够了,在使用的地方使用函数即可,很方便
    但是也有一些不足的地方是在根源上的,即 context,
    在同一个页面中 如果有多个使用 context 的地方
    那么如果一旦dispatch ,其他的所有地方也会触发render 造成资源的浪费,小项目还好,大项目仍旧不可

    (除非 react 的 context 函数添加 deps)

    三.自定义解决方案

    原理就是存储一个全局变量 ,通过 import 引入;
    我自己写了一个例子:https://github.com/Grewer/react-hooks-store
    想要基础的实现只需要 30+ 行的代码即可

    class Modal {
      private value: any;
      private prevValue: any;
      private reducers: (state, action) => {};
      private queue: any = [];
      private dispatch: (action) => void;
    
      constructor(reducers) {
        this.reducers = combineReducers(reducers)
        // combineReducers 来自于 reudx ,可以引入也可以自己写一个(后续我会写一个库,会包含此函数)
        this.value = this.reducers({}, {})
        this.dispatch = action => {
          this.prevValue = this.value;
          this.value = this.reducers(this.value, action)
          this.onDataChange()
        }
      }
    
      useModal = (deps?: string[]) => {
        const [, setState] = useState(this.value);
        useEffect(() => {
          const index = this.queue.push({setState, deps}); // 订阅
          return () => { // 组件销毁时取消
            this.queue.splice(index - 1, 1);
          };
        }, []);
        return [this.value, this.dispatch]
      }
    
      onDataChange = () => {
        this.queue.forEach((queue) => {
          const isRender = queue.deps ? queue.deps.some(dep => this.prevValue[dep] !== this.value[dep]) : true
          isRender && queue.setState(this.value)
        });
      }
    }
    

    // 初始化 reducers

    const modal = new Modal({
      countReducer: function (state = 0, action) {
        console.log('count Reducer', state, action)
        switch (action.type) {
          case "ADD":
            console.log('trigger')
            return state + action.payload || 1
          default:
            return state
        }
      },
      listReducer: function (state = [] as any, action) {
        console.log('list Reducer', state, action)
        switch (action.type) {
          case "ADD_LIST":
            console.log('trigger')
            state.push(action.payload)
            return [...state]
          default:
            return state
        }
      },
      personReducer: function (state = {name: 'lll', age: 18} as any, action) {
        console.log('person Reducer', state, action)
        switch (action.type) {
          case "CHANGE_NAME":
            return Object.assign({}, state, {name: action.payload})
          default:
            return state
        }
      }
    })
    // 导出 useModal
    export const useModal = modal.useModal
    

    简单的使用:

    function Count(props) {
      const [state, dispatch] = useModal(['countReducer'])
      // 非 countReducer 的更新 不会触发此函数 render
      console.warn('render Count', state, dispatch)
    
      return <div>
        <button onClick={() => dispatch({type: "ADD", payload: 2})}>+</button>
      </div>
    }
    

    当然你也可以自己写一个,自己想要的方案

    总结

    hooks 的存储方案基本就这 3 类,可以用现成的,也可以使用自己写的方案

  • 相关阅读:
    cmd 新建空文件
    查看Linux版本
    centos7时间调整
    正确卸载vs2015及以前版本方式
    vs2017,vs2019 无法连接到Web服务器“IIS Express”
    .netcore开发环境和服务器注意事项
    .netcore 网站启动后 502.5
    CentOS7开机报错piix4_smbus ****host smbus controller not enabled
    centos7 升级系统后,启动界面出现多个选项
    .gitkeep文件
  • 原文地址:https://www.cnblogs.com/Grewer/p/10980884.html
Copyright © 2020-2023  润新知