为什么 React 会提出 hooks 这种设计
越来越轻的视图层
为什么目前大多数 gui 的视图层都是越来越轻?
mvc 架构演进 mvvm 架构后带来的, mvvm 本质上就是 m -> v binder , 主要解决的问题就是 自动 updateView
在 mvc 下 需要手动 model 加载到 view 中, 然后再 updateView, 如果希望这个过程自动就会变成 m -> vm - > v
所以在这个原因下, gui 的视图层就是越来越轻的方向发展
函数式编程思想对编程语言的入侵
第三代编程语言的发展, 现在已经走向了多范式, 也都从函数式编程思想里吸取了不少, Lambda 表达式的支持就是最典型的例子
React hooks 是 React开发组对使用函数式编程思想解决视图层问题的一个实践的产物
ps: 实践: 人们能动地改造和探索现实世界一切客观物质的社会性活动
hooks 的基础用法
怎么写好 hooks ?
首先要有个标准, 怎么定义好坏?
这个问题看起来很泛泛, 就像问什么样的代码写的好, 什么样的写的坏, 很难有统一的标准, 也会有个人偏好在里面, 所以在这部分只讨论些抽象的东西.
- 复杂度足够低, 简单
- 符合当前的限制性下的语境
复杂度足够低, 简单
简单解释下: 代码越短, 越容易被人理解, 就是复杂度足够低, 在使得代码变短的过程中, 我们用语法糖, 建立抽象, 封装过程, 的降低复杂度的编程手段, 在 hooks 下同样适用
符合当前的限制性下的语境
react 本身虽然只是个库, 但是说 react 代表的往往是 (react全家桶+react哲学) 对于一整套react范式编程.
范式编程 推到过程来自 架构整洁之道
- 结构化编程是多对程序控制权的直接转移的限制。
- 面向对象编程是对程序控制权的间接转移的限制。
- 函数式编程是对程序中赋值操作的限制。
每个范式都约束了某种编写代码的方式,没有一个编程范式是在增加新能力 。
react 范式编程也是相同, 我们在 hooks 内程序虽然在写的时候我们没有受到编译器的限制, 但应对自己有这个意识来指导自己什么对不应该的.
当然在某种场景下, 我们一定存在不去打破限制无法实现的情况, 在这种情况下, 应该把那些看着不好的东西通过封装藏在 类似 utils 这类的.
最后我们回到「怎么写好 hooks ?」
我们得到下面两个结论
- 需要能够写好程序, 当固定好输入输出, 能够设计好一个模块, 定义好一个函数, 起好一个变量名, 都是写好程序的先决条件.
- 需要对 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 要慎重, 上述这个文章是在性能方面来推断这个问题
我们换个到复杂度这个角度来看, 当一个 函数或者数据 被包裹一次之后, 这里存在两个点的复杂度的上升
- 包裹的方法 useCallback useMemo 我们需要对这个函数进行理解
- 可能存在饮用值的区别, useMemo(()=> obj, [a, b]); 如果 obj 本身是在当前 运行环境 定义的, 那么这个引用的返回就会和缓存后的不同.
性能优化不是免费的 他的成本不只是性能, 还有复杂度的上升. 除非你是指数级的计算
demo 在 diff 和 umout 是有问题的还需要更多的处理, 当前只是为了说 hooks 相关
react 奇巧淫技
让你的函数组件支持 await
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 (
);
};
};
// 我们可以异步使用组件
// 适用场景, 前置需要拉去一个或一组接口信息才显示, 这样就省掉一些模版代码
const LinkButton = asyncComponent(async props => {
console.log({ props });
await sleep()
return (
{
console.log(111)
}}
>
按钮
);
});
常规情况 lazy 是用来加载异步组件, 通过模拟 lazy 的返回的 Promise
不用 context 的全局通信
/**
* 创建跨组件跨树通讯 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 通信的解决方案, 大多数都还是需要再最外部 加一个
才能通行, 后来找到这个思路, 就可以 更自由的定义 任意两个组件的直接的 hooks 数据共享
end.