直接上核心代码

import React, { useLayoutEffect, useReducer, useCallback, useContext } from 'react';

const useForceUpdata = () => {
  const [_, forceUpdata] = useReducer(x => x + 1, 0,);
  const upDate = useCallback(() => forceUpdata(), [])
  return upDate
};
const Cunsumer = ({ store }, mapStateToProps, mapDispatchToProps, WarppedComponent, props) => {
  // stateProps其实就是以所有state为参数,mapStateProps执行的结果
  const forceUpdata = useForceUpdata()
  const stateProps = mapStateToProps(store.getStore());
  // dispatchProps需要麻烦一点因为会有两种情况
  // 第一种mapDispatchProps是函数
  // 第二种mapDispatchProps是对象
  let dispatchProps = { dispatch: store.dispacth };
  // 这里补充一下最准确的判断数据类型的方法:Object.prototype.toString.call(mapDispatchProps)
  if (typeof mapDispatchToProps === "function") {
    dispatchProps = mapDispatchToProps(store.dispach)
  }
  else if (typeof mapDispatchToProps === "object") {
    dispatchProps = bindActionCreators(mapDispatchToProps, store.dispach)
  }

  // * 重点 这里是必须要写订阅的不然我们代码跑起来不会报错但是页面也不会刷新,
  // * redux 只是一个状态存储库,不具备自动刷新页面的功能,需要我们自行编写订阅代码
  // * 这里使用useLayoutEffect而不是useEffect,是因为useLayoutEffect在dom变更后就开始同步执行,而useEffect有延迟
  useLayoutEffect(() => {
    const unsubscribe = store.subscribe(() => {
      forceUpdata()
    })
    return () => {
      unsubscribe()
    };
  }, [store])


  return <WarppedComponent  {...props} {...stateProps} {...dispatchProps} />
};
// 创建Context
const Context = React.createContext();

// 导出Provider组件
export const Provider = ({ store, children }) => {
  return <Context.Provider value={store}>{children}</Context.Provider>
};

export const connect = ({ mapStateToProps, mapDispatchToProps }) => WarppedComponent => props => {

  // 子孙组件消费父级传下来的value
  return <Context.Cunsumer>
    {(value) => Cunsumer(value, mapStateToProps, mapDispatchToProps, WarppedComponent, props)}
  </Context.Cunsumer>
}

export const bindActionCreators = (data, dispacth) => {
  let obj = {}

  for (const key in data) {
    obj[key] = dispacth((...arg) => obj[key](...arg))
  }
  return obj
};

export const useDispatch = () => {
  const store = useContext(Context)

  // 直接返回store中的dispatch即可
  return store.dispatch
}

export const useSelector = (selctor) => {
  const store = useContext(Context)
  // 这里一样是要订阅一下不然页面不会更新
  useLayoutEffect(() => {
    const unsubscribe = store.subscribe(() => {
      forceUpdata()
    })
    return () => unsubscribe()
  }, [store])
  return selctor(store.getStore())
}

以上便是几个常用API的基本实现.

重点说一下

  1. 我们这里写的useSelector不可以重复调用, 不然会产生重复订阅的效果,影响性能。
  2. 订阅时使用useLayoutEffect而不是useEffect,是因为useLayoutEffect在dom变更后就开始同步执行,而useEffect有延迟,详情请移步
03-05 15:19