更贴合hooks的状态管理--hox

前言

在以前的React Hooks:初探·实践提到过hooks更符合React编程模型,其中custom hooks更是让人眼前一亮,只能说用过的都说香,可是一直没找到贴合hooks的状态管理,虽然hooks的设计挺多来自于redux,可是redux基本和hooks没有交集,至于mobx,也推出了基于hooks的如useLocalStore、useObserve等Hooks,可是在个人使用过程当中也没有感受引入Hooks带了很大便利,个人我的见解是redux和mobx更强调的集中式管理状态,而Hooks更强调状态分享。最近找到了两个比较有意思的状态管理工具,一个是来自fackbook官方实验室(非React官方)的recoil和来自蚂蚁金服的hox,两个都接触了一下,以为recoil要理解的概念和API挺多,没有那么香,下面重点介绍一下我以为既简单又更贴合hooks的hoxreact

是什么

hox是蚂蚁金服推出的下一代react状态管理器。git

我以为官方这么自信的缘由有两点,第一点就是只有一个API——createModel,这个和文档同样简单的API确实可以吸引很多眼球,为上手提供了条件;第二点就是hox完美的拥抱了react hooks,而hooks又是react官方不断安利的,有react官方做为重要支撑,综上所述,因此单方面宣布下一代仍是能够接受的。github

为何

为何说hox更提盒hooks的状态管理呢?下面结合源码说一下个人我的见解!编程

对于使用而言,hox基本上只须要和一个API——createModel打交道,这一点仍是值得点个赞的。(固然还有withModel(在class组件使用)等API,不过感受都是买一送一的感受)。对于设计而言,hox很完美地利用了hooks自身强大地状态管理,可是hooks是针对单一组件而言,因此hox巧妙地利用了react reconciler,经过自定义渲染器把散落各个组件的状态以model为单位收集起来,有点像是把hooks的定义往外层提,或者说是额外建立一个组件树,经过自定义hooks和自定义渲染器实现了共享hooks,经过container容器在状态更新时通知给各个组件。redux

直接上源码(分三块解析):api

import { ModelHook, UseModel } from "./types";
import { Container } from "./container";
import { Executor } from "./executor";
import React, { useEffect, useRef, useState } from "react";
import { render } from "./renderer";

export function createModel<T, P>(hook: ModelHook<T, P>, hookArg?: P) {
  /**
  * 第一块:以model为一个单位实例化一个对应的容器进行数据管理
  * Container主要作了两个事情,经过ES6的Set结构存储了观察者,再暴露notify方法在通知所有观察者更新,就是
  * 下面useModel方法中useEffect里定义的subscriber函数
  */
  const container = new Container(hook);
  
  /**
  * 第二块:基于react reconciler + Executor组件自定义生成了一个渲染器,经过观察者模式在状态更新时通知所有观察者,就是去执行Set结构里存储的subscriber函数
  * react reconciler用法没什么特殊的,能够直接看文档
  * Executor组件主要执行了两个函数,一个是createModel传进来的hook,另外一个是hook返回的data做为参数去执行onUpdate回调
  */
 
  render(
    <Executor
      onUpdate={val => {
        container.data = val;
        container.notify();
      }}
      hook={() => hook(hookArg)}
    />
  );
  
  // 第三块:返回一个自定义的hooks,
  // ①经过useState将container的状态和React自身的状态管理挂钩,经过暴露depsFn函数来控制是否更新状态
  // ②经过useEffect注册/移除了观察者
  // ③经过defineProperty劫持当前自定义hooks的data属性,来暴露container的data,作到只读取当前的状态
  const useModel: UseModel<T> = depsFn => {
    const [state, setState] = useState<T | undefined>(() =>
      container ? (container.data as T) : undefined
    );
    const depsFnRef = useRef(depsFn);
    depsFnRef.current = depsFn;
    const depsRef = useRef<unknown[]>([]);
    useEffect(() => {
      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);
      return () => {
        container.subscribers.delete(subscriber);
      };
    }, [container]);
    return state!;
  };
  Object.defineProperty(useModel, "data", {
    get: function() {
      return container.data;
    }
  });
  return useModel;
}

function compare(oldDeps: unknown[], newDeps: unknown[]) {
  if (oldDeps.length !== newDeps.length) {
    return true;
  }
  for (const index in newDeps) {
    if (oldDeps[index] !== newDeps[index]) {
      return true;
    }
  }
  return false;
}
复制代码

怎么用

hox用起来仍是比较省心的,能够直接参考文档bash

说多无益,show me the code! 下面是基于redux和hox实现的todoListide

redux: 基于reudx官方例子 + React Profiler函数

hox: 基于hox + React Profiler工具

写在最后

  1. 使用hox的心智模型,应该以特定功能为一个单元去划分model

仍是我以前说的Hooks的主要成本不在于api的学习,而是在于心智模型的转变。这一点一样适用于hox,在redux和mbox咱们更关注的是stroe中状态的变化,可是在hox中咱们更应该关注每个model,说白了就是被createModel包装事后的那个custom hooks,更倾向特定的功能逻辑。像上面的todoList,咱们在写hooks应该奔着实现添加todo,能够改变todo状态,还有筛选功能的todoList。

  1. 性能对比和优化

上面的两个例子都用到了React Profiler(基于React Profiler写了一个useProfiler)来做为简单的性能比对,虽然hox提供了depsFn来优化性能,在比对了两个例子的全部操做,以为基于hox的TodoList就组件而言多了一些没必要要的渲染,例如第一次执行addTodo的操做,hox版本渲染了三个组件(AddTodo, TodoList, FilterFooter),而redux则渲染了一个组件(VisibleTodoList);第二次addTodo的操做,hox版本也渲染了两个组件(AddTodo, TodoList)。这一点我也是琢磨了许久,思来想去没想到好的优化办法,我的以为是hox的设计自己致使的问题。(也多是写法有问题,须要进一步的探索,望高人指点)。可是我以为只要划分好model,用好depsFn这个利器,hox的写法和复用性仍是能够完胜redux的,因此仍是挺香的。

  1. 关注issue

从学习看源码到写这篇文章的过程,一直关注hox的issue,感受有点不温不火,感受大多数人都处于观望状态,如今最新一条的issue是关于hox v2的,这条issue挺有看头的,因为v1版本存在挺多的弊端,v2打算配合context,这也意味着使用者须要手动写provider,可是相对其余的基于context的库,这个api也相对简单。所以结合issuse和hox的发展形势,我的以为小型项目(如活动页等用完即走的项目)能够考虑接入一下,大型项目就感受不必了,应该保持持续地关注,但愿hox能越走越远,若是能持续搭上hooks的快车,感受hox仍是挺有前途的!再退一步说,即便hox没能跻身状态管理的前列,它的设计思想和源码也是值得一读的,值得学习!

good!