背景

React Hooks 正式公布曾经快 2 年了,它的个性让前端打工人又多了一种搬砖姿态,特地是它的函数式编程格调、申明式副作用解决、逻辑层抽取能力,以及灵便的自定义 Hooks 能力,用起来特地香!那该用哪一款状态管理器和它搭配呢,我发现蚂蚁正好开源了一款:hox,齐全是按 hooks 形式实现的状态管理器,于是拉下源码小剖析一把,往下看。

代码构造

看到代码,发现写得相当简洁,外围代码 5 个源文件,次要逻辑一共就50多行。

来看看构造和模块性能:

  • container:共享状态的数据容器,实现形式是音讯订阅和告诉;
  • create-model:外围模块,创立 model 的全副逻辑都在这里了,实现形式是工厂模式;
  • executor:model hooks 执行器,用于更新全局状态,留神:它设计成一个组件,在 create-model 时渲染,这样生命周期就能够由 react 治理;
  • renderer:渲染器,因为下面的 executor 是一个组件,须要在 create-model 时渲染,这里独自抽出来,为了能够跨平台应用(RN、小程序,SSR等)。留神:这里用的是 react-reconciler,这是 react 的虚构 dom 引擎,react 代码由它来创立和更新虚构 dom 树,所以,executor 最终并没有渲染进去,只是一个独立的虚构 dom 节点,为了跨平台,不过也减少了包的大小
  • with-model:这是兼容类组件用的,应用了高阶组件模式,代码比较简单,大部分是 ts 包装类型申明;

援用一个网络流程图:

外围源码解读

咱们就看三个文件:container、executor和create-model。

container

这个类有两个属性和一个办法,一个简略的音讯订阅/告诉实现。

export class Container<T = unknown> {  constructor(public hook: ModelHook<T>) {}  // 音讯订阅者表  subscribers = new Set<Subscriber<T>>();  data!: T;  // 音讯告诉  notify() {    for (const subscriber of this.subscribers) {      subscriber(this.data);    }  }}

executor

这里的次要流程就是:Executor 在 render 时,就会调用 hook 函数,而后调用更新回调响应。目标就是在 model 状态扭转后,触发更新回调,具体细节往下看 create-model。

export function Executor<T>(props: {  hook: () => ReturnType<ModelHook<T>>;  onUpdate: (data: T) => void;}) {  // 执行 hooks  const data = props.hook();  // 触发更新回调  props.onUpdate(data);  return null as ReactElement;}

create-model

压轴来了,我下面说的 50 行外围代码就是它了!用于创立 model,useModel 局部也在外面。

它是个高阶函数,它的目标是创立并返回一个 hooks,把 container、executor 和 model 连接起来,成为全局状态。

它有两个参数:

  • hook:它是你定义的 model,比方:用户信息。

它也是一个自定义 Hooks,所以要用 Executor 去包装。

它外部个别申明状态和操作,援用一个官网示例:

function useCounter() {  const [count, setCount] = useState(0);  const decrement = () => setCount(count - 1);  const increment = () => setCount(count + 1);  return {    count,    decrement,    increment  };}
  • 第二个参数,hookArg:你想传给 model 的参数值,比方 useCounter 有默认值,咱们就能够用这种形式传递。

看正文。

// 它会返回一个自定义 Hooks:useModelexport function createModel<T, P>(hook: ModelHook<T, P>, hookArg?: P) {  // 实例化了一个数据容器,这个就是用来寄存全局状态用的,并且它是设计成音讯订阅/告诉模式  // 通过闭包来长久化  const container = new Container(hook);    // 渲染 model hooks 执行器,用于更新全局状态,它是一个独立虚构 dom  render(    <Executor      onUpdate={val => {        container.data = val;        container.notify();      }}      hook={() => hook(hookArg)}    />  );    // useModel,它是自定义 Hooks  // 能够把它看作相似于 useState 的货色  // 咱们下面能够看到 Model 自身也是一个 Hooks,为什么 Model 还要再包装一层,这里就是为了把 container、executor 和 model 连接起来,成为全局状态  const useModel: UseModel<T> = depsFn => {    // 这里的 state 就是使用者所用的状态,类型是:model hooks 的返回值    const [state, setState] = useState<T | undefined>(() =>      container ? (container.data as T) : undefined    );        // 这里如果有申明 deps 函数,就用 deps 函数的后果来决定是否该更新状态    // deps 用 ref 存储    const depsFnRef = useRef(depsFn);    depsFnRef.current = depsFn;    const depsRef = useRef<unknown[]>([]);    useAction(() => {      if (!container) return;            // 这里就是订阅      function subscriber(val: T) {        if (!depsFnRef.current) {          setState(val);        } else {          const oldDeps = depsRef.current;          const newDeps = depsFnRef.current(val);          if (compare(oldDeps, newDeps)) {            setState(val);          }          depsRef.current = newDeps;        }      }            // 增加订阅      container.subscribers.add(subscriber);            // container 变动时,销毁旧的订阅      return () => {        container.subscribers.delete(subscriber);      };    }, [container]);        // 返回的就是 model hooks 的返回值    return state!;  };    // 应用劫持,挂载 data 到 model 上  Object.defineProperty(useModel, "data", {    get: function() {      return container.data;    }  });  return useModel;}