likes
comments
collection
share

聊聊 React hooks 及实践

作者站长头像
站长
· 阅读数 35

为什么 React 会提出 hooks 这种设计

越来越轻的视图层

为什么目前大多数 gui 的视图层都是越来越轻?

mvc 架构演进 mvvm 架构后带来的, mvvm 本质上就是 m -> v binder , 主要解决的问题就是 自动 updateView

在 mvc 下 需要手动 model 加载到 view 中, 然后再 updateView, 如果希望这个过程自动就会变成 m -> vm - > v

所以在这个原因下, gui 的视图层就是越来越轻的方向发展

函数式编程思想对编程语言的入侵

第三代编程语言的发展, 现在已经走向了多范式, 也都从函数式编程思想里吸取了不少, Lambda 表达式的支持就是最典型的例子

React hooks 是 React开发组对使用函数式编程思想解决视图层问题的一个实践的产物

ps: 实践: 人们能动地改造和探索现实世界一切客观物质的社会性活动

hooks 的基础用法

Hook 简介

怎么写好 hooks ?

首先要有个标准, 怎么定义好坏?

这个问题看起来很泛泛, 就像问什么样的代码写的好, 什么样的写的坏, 很难有统一的标准, 也会有个人偏好在里面, 所以在这部分只讨论些抽象的东西.
  1. 复杂度足够低, 简单
  2. 符合当前的限制性下的语境
复杂度足够低, 简单

复杂度的本质

简单解释下: 代码越短, 越容易被人理解, 就是复杂度足够低, 在使得代码变短的过程中, 我们用语法糖, 建立抽象, 封装过程, 的降低复杂度的编程手段, 在 hooks 下同样适用

符合当前的限制性下的语境

react 本身虽然只是个库, 但是说 react 代表的往往是 (react全家桶+react哲学) 对于一整套react范式编程.

范式编程 推到过程来自 架构整洁之道

  • 结构化编程是多对程序控制权的直接转移的限制。
  • 面向对象编程是对程序控制权的间接转移的限制。
  • 函数式编程是对程序中赋值操作的限制。

每个范式都约束了某种编写代码的方式,没有一个编程范式是在增加新能力 。

react 范式编程也是相同, 我们在 hooks 内程序虽然在写的时候我们没有受到编译器的限制, 但应对自己有这个意识来指导自己什么对不应该的.

当然在某种场景下, 我们一定存在不去打破限制无法实现的情况, 在这种情况下, 应该把那些看着不好的东西通过封装📦藏在 类似 utils 这类的.

最后我们回到「怎么写好 hooks ?」

我们得到下面两个结论

  1. 需要能够写好程序, 当固定好输入输出, 能够设计好一个模块, 定义好一个函数, 起好一个变量名, 都是写好程序的先决条件.
  2. 需要对 react 的机制足够了解, 对函数式编程有一定了解, 对 react 哲学有自己的感悟.

react hooks 相关机制

最好的了解方式就是造一个玩具

首先先造一个玩具, 参照 react 和 preact hooks 的实现

https://github.com/nobey/noli...

https://codesandbox.io/s/noli...

造完, 我们回来再看看 hooks

Hooks 只是数组 ?

虽然我们常说 Hooks 只是数组, 但是实际上 react 的实现其实个链表, preact 的实现倒是个数组

两个指针

hooks 在原理上其实最重要的其实不是数据, 反而是两个指针, 一个是 wipnode 当前正在工作的 vnode(fibernode) , 另一个才是 wiphook 当前 hook 指向

限制带来改变

在一个 Function 组件内部 这个写的已经不是单纯的 js , 他的运行时, 以及上下文, 已经带来了改变.

就像 正常 我们定义 let a = 1; a = 2; 的编程方式变成了 const [a, setA] = useS(1); setA(2);

然后你会发现 函数式编程是对程序中赋值操作的限制 的表现在 hooks 这部分发挥出来了,

当然这是范式上的限制, 你仍然可以 hack 出去 🐶

再聊 useCallback 和 useMemo

https://jancat.github.io/post...

使用 useCallback useMemo 要慎重, 上述这个文章是在性能方面来推断这个问题

我们换个到复杂度这个角度来看, 当一个 函数或者数据 被包裹一次之后, 这里存在两个点的复杂度的上升

  1. 包裹的方法 useCallback useMemo 我们需要对这个函数进行理解
  2. 可能存在饮用值的区别, useMemo(()=> obj, [a, b]); 如果 obj 本身是在当前 运行环境 定义的, 那么这个引用的返回就会和缓存后的不同.

性能优化不是免费的 他的成本不只是性能, 还有复杂度的上升. 除非你是指数级的计算

demo 在 diff 和 umout 是有问题的还需要更多的处理, 当前只是为了说 hooks 相关

react 奇巧淫技

让你的函数组件支持 await

demo

const sleep = () => new Promise(resolve => setTimeout(resolve, 2000))

const asyncComponent = (asyncComponent, fallback = '') => {
  let Component
  return props => {
    Component = lazy(async () => {
      const component = await asyncComponent(props);
      return { default: props => cloneElement(component, props) };
    });
    return (
      <Suspense fallback={fallback}>
        <Component {...props} />
      </Suspense>
    );
  };
};

// 我们可以异步使用组件
// 适用场景, 前置需要拉去一个或一组接口信息才显示, 这样就省掉一些模版代码

const LinkButton = asyncComponent(async props => {
  console.log({ props });
  await sleep()
  return (
    <div
      onClick={() => {
        console.log(111)
      }}
    >
      按钮
    </div>
  );
});

常规情况 lazy 是用来加载异步组件, 通过模拟 lazy 的返回的 Promise

不用 context 的全局通信

demo

/**
 * 创建跨组件跨树通讯 Hooks (可以用于的跨组件使用)
 * 思路
 * 1. 通过创建一个隐藏 React Tree 来包裹 Hook
 * 2. Hook 变化触发隐藏 React Tree 渲染
 * 3. 返回的是一个被劫持的 Hook 当 隐藏 React Tree 渲染 时会更新劫持的 Hook 数据
 * @param {Function} hook 自定义的 useHooks
 */

export const createHookObserver = (hook) => {
  const div = document.createElement("div");
  const events = new Set();
  let $data;
  const update = (data) => {
    $data = data;
    events.forEach((event) => event(data));
    return null;
  };

  render(
    createElement(() => update(hook())),
    div
  );
  const useHooks = () => {
    const [val, setVal] = useState($data);
    useEffect(() => {
      events.add(setVal);
      return () => events.delete(setVal);
    }, []);
    return val;
  };

  return useHooks;
};


const useCount = createHookObserver(() => {
  const [count, setCount] = useState(0);
  return { count, setCount };
});

最开始是 hooks 刚开始流行的时候找 hooks 通信的解决方案, 大多数都还是需要再最外部 加一个 <Provider store={store}/>

才能通行, 后来找到这个思路, 就可以 更自由的定义 任意两个组件的直接的 hooks 数据共享

一堆 hooks 状态管理

end.