react Hooks 将调度权限全权交于用户,因此,分享一下个人在处理 Hooks 调度时的经验,希望对大家有所帮组
先前情提要一下,state 是时间上的数组,且细化到了具体的每一个 state,你可以自由地组织自己的调度结构,将调度之类的逻辑同状态一起进行封装复用,很方便。我相信调度问题对于事件驱动的 angular 来说,属于呼吸一样简单,但是对于数据驱动的 React 来说,可能是较为困难的地方,就比如 "state 是时间上数组" 这个论述,别急,先脑袋里想象一根时间线,数据随着时间进行变化,这是阅读下文的前提
所有读取 state 的操作,都需要加入 useEffect,useMemo依赖数组,是的,包括你想封装读取了 state 的函数,也是必须 useMemo (简化版 useCallback) 的
这是必须付出的代价
// 出现了读取
useEffect(()=>{
console.log(state)
},[state])
// 出现了隐式读取
const handleTimeout = useCallback(()=>{
console.log(state)
},[state])
useEffect(()=>{
setTimeout(handleTimeout, 1000)
},[handleTimeout])
// createElement 或者 jsx
return <div>
{useMemo(()=><Compo data={state}/>,[state])}
</div>
useEffect 处添加,和 useMemo 处添加,大家能理解,但是 setTimeout 函数?也得用 useCallback 封装?
return 的 jsx Element 也要 useMemo?
如果你知道不 useMemo 有什么后果,请自行判断,因为它是你的逻辑,你如果不 useMemo 带参数组件,意思便是希望每次当前组件有依赖变化,都重新运行 Compo 的函数,不是没有这种需求,但是一般情况下,依赖数组中的依赖, 只能比被读取的依赖多,不能更少,更少会导致获取不到
这是你获得 hooks 完美封装性的必然代价,愿不愿意付出,你可以做选择,选择付出,可以继续往下看,选择不付出,请不要触碰任何 hooks api,因为它会破坏 class 写法的调度一致性,现如今 class 和 hooks 只能二选一,若项目存在 class 和 hooks 混用,甚至在组件树的不同层级混用,且涉及调度逻辑,那就只能说节哀
决定好了么?决定好了,前戏结束,我们开始正文
ref state 是利用 ref 将 state 存储下来,保存每次调度的最新数据,如果你不想或者无法传递依赖数组依赖,这是个很好的方式:
const [a,setA] = useState(0)
const handler = useCallback(() => {
console.log("not dep provoided:", a);
// 0
// because a is not in deps list
}, []);
useEffect(() => {
setTimeout(() => {
setA(1);
}, 500);
setTimeout(handler, 1000);
}, [handler]);
// but if a is in handlers deps list
// useEffect will be trigger twice
// how to deal with it?
const aRef = useRef(a);
useEffect(() => {
aRef.current = a;
}, [a]);
useEffect(() => {
setTimeout(() => {
console.log("ref state", aRef.current);
}, 1000);
}, []);
一旦处理异步,读取 state,很容易遇到这样的问题,即 异步 useCallback 中存在依赖 a,而一点在 useEffect 中调用 callback,callback 传入依赖数组,会导致死循环
我们希望 handler 不变,而传入 a 的做法,使得 handler 改变了,因此,利用 ref state 转存 a 的最新值,实现无依赖
但请不要滥用 ref state, ref state 只需要用在事件处理 (合成事件除外),即 React 捕获不到的调度(实际上被逼无奈使用 ref state 的本质,就是因为 React 不知道事件触发,你的将事件调度和 React 调度联系起来)
useState,useMemo,都是默认自带去重逻辑,即:
const [a, setA] = useState(0);
useEffect(() => {
console.log("a effected:", a);
}, [a]);
useEffect(() => {
setTimeout(() => {
setA(1);
}, 100);
setTimeout(() => {
setA(1);
}, 200);
}, []);
a effected 只会打印两次,后面两次 setA(1) 因为值没有改变,所以无法触发
这个去重逻辑你不一定需要,比如在 大对象处理或者分发事件 的时候
你可以采用函数返回值的做法,保证每次都是全新的函数,即可实现阻止去重
const [action, setAction] = useState(()=>()=>'payload')
useEffect(()=>{
const payload = action()
},[action])
const [bigObj,setBigObj] = useState(()=>()=>{/* large data */})
const changeBigObj = useCallback((value:any)=>{
setBigObj(res=>{
const current = res()
current.value.value.value.value = value
// immutable 喔~
return ()=> current
})
})
浏览器中的调度模式,主要分为 同步调度 , 延迟(微任务)调度 , 事件循环调度 , raf 调度 ,还有个计时器调度,不过它衍生自事件循环调度
在 React 18+ 中,默认为 batchUpdate (同事件循环统一延迟调度),即事件循环内的所有变更,统一在事件循环结束之前调度:
const [a, setA] = useState(0);
const [b, setB] = useState(0);
useEffect(() => {
console.log(a, b);
}, [a, b]);
useEffect(() => {
setTimeout(() => {
setTimeout(() => {
console.log("next ev");
}, 0);
Promise.resolve().then(() => console.log("start batchUpdate"));
setA(1);
setB(1);
Promise.resolve().then(() => console.log("end batchUpdate"));
// 0 0
// start batchUpdate
// end batchUpdate
// 1 1
// next ev
// in current ev, after all promise in useEffect
}, 1000);
}, []);
同步调度为 flushSync :
useEffect(() => {
setTimeout(() => {
Promise.resolve().then(() => console.log("end flush sync"));
flushSync(() => {
setA(2);
});
console.log("a changed");
flushSync(() => {
setB(2);
});
console.log("b changed");
}, 2000);
// 2 1
// a changed
// 2 2
// b changed
// end flush sync
// synchronized
}, []);
Raf调度为 startTransition (需配合 isPending 使用):
useEffect(() => {
setTimeout(() => {
Promise.resolve().then(() => console.log("next micro"));
setTimeout(() => {
console.log("next ev");
}, 0);
startTransition(() => {
setA(3);
});
startTransition(() => {
setB(3);
});
}, 3000);
// next micro
// 3 3
// next ev
// requestAnimationFrame scheduler
// run with animation frame when spare
}, []);
事件循环调度不用多说,setTimeout,setInterval
你需要综合多个调度的特性,安排状态变化的先后顺序,实现更多复杂逻辑,在时间上组织代码
使用 useRef,通过 ref 在不同调度系统中传递消息,可以实现调度探针,对调度进行精确判定:
const [a, setA] = useState(0);
const firstEvEnded = useRef(false);
const secondEvStarted = useRef(false);
const compoDestroy = useRef(false);
const preRef = useRef<number | undefined>();
useEffect(() => {
// only batchUpdates work
Promise.resolve().then(() => (firstEvEnded.current = true));
setTimeout(() => {
secondEvStarted.current = true;
}, 0);
return () => {
// only use in return cb of effect cb
compoDestroy.current = true;
};
}, []);
// get preA
useEffect(() => {
Promise.resolve().then(() => {
preRef.current = a;
});
}, [a]);
这里实现了 探针 ,探查当前 effect 是否在 第一次 ev 结束后,是否在第二次 ev 开始前,记录了它的前值
同样,你也可以利用 useMemo,将 ref 绑定在 特定调度源 上:
// useMemo on ref
// eslint-disable-next-line
const preA = useMemo(() => preRef.current, [a]);
useEffect(() => {
console.log("pre a:", preA);
}, [preA]);
这样,preA 也可以对调度进行驱动了
最后再补充一个 domRef 获取的问题:
const ref = useRef<htmlElement|null>()
// ref 总有依赖
{a?<div ref={ref}/>:null}
useLayoutEffect(()=>{
// a 变化后,在渲染回调拿到结果
console.log(ref.current)
},[a])
<div ref/>
useLayoutEffect(()=>{
// 所有 use(Layout)Effect 的依赖为空,都意味着隐式依赖组件的显示依赖
// 在组件之外
// {data? <Compo/>: null}
// 这种思维对于直接编写跨组件逻辑非常重要
// 逻辑不能局限在组建以内,需求不是按照组件给的
console.log(ref.current)
},[])
以上代码接在顶部 sandbox 链接,再来一个通过调度解决 io 死锁问题的例子:
当你能控制调度的时候,逻辑才可能被你完全封装,视图才能实现对你被动的响应
注意, hooks 不再计较生命周期 ,没有生命周期,你的所有事件,渲染发生,总是因为数据的改变,你不需要去响应视图,而是 视图来响应你
原文 https://zhuanlan.zhihu.com/p/398036702
近日,据 MIT Technology Review 报道,一位名为“Repairnator”的机器人在 GitHub 上“卧底”数月,查找错误并编写和提交修复补丁,结果有多个补丁成功通过并被采纳,这位 Repairnator 到底是如何拯救程序员于水火的呢?
你还在为该使用无状态组件(Function)还是有状态组件(Class)而烦恼吗?你还在为搞不清使用哪个生命周期钩子函数而日夜难眠吗?你在还在为组件中的this指向而晕头转向吗?这样看来,说React Hooks是今年最劲爆的新特性真的毫不夸张。
我们将userReducer函数返回的原始dispath命名为origin_dispatch,自定义dispatch函数,当action为函数的时候,我们执行action函数,并将origin_dispatch当作参数传进去;action不是函数,直接调用origin_dispatch,不做处理
使用useEffect 就像瑞士军刀。它可以用于很多事情,从设置订阅到创建和清理计时器,再到更改ref的值。与 componentDidMount、componentDidUpdate 不同的是,在浏览器完成布局与绘制之后,传给 useEffect 的函数会延迟调用。
从 React Hooks 正式发布到现在,我一直在项目使用它。但是,在使用 Hooks 的过程中,我也进入了一些误区,导致写出来的代码隐藏 bug 并且难以维护。这篇文章中,我会具体分析这些问题,并总结一些好的实践,以供大家参考
Hooks 的 API 可以参照 React 官网。本文主要是结合 Demo 详细讲解如何用 Hooks 来实现 React Class Component 写法,让大家更深的理解 Hooks 的机制并且更快的入门。 注意:Rax 的写法和 React 是一致的
以下是上一代标准写法类组件的缺点,也正是hook要解决的问题,型组件很难拆分和重构,也很难测试。业务逻辑分散在组件的各个方法之中,导致重复逻辑或关联逻辑。
Hooks出来已经有段时间了,相信大家都用过段时间了,有没有小伙伴们遇到坑呢,我这边就有个 setInterval 的坑,和小伙伴们分享下解决方案。写个 count 每秒自增的定时器,如下写法结果,界面上 count 为 1 ?
对于 React 16.7 中新的 hooks 系统在社区中引起的骚动,我们都有所耳闻了。人们纷纷动手尝试,并为之兴奋不已。一想到 hooks 时它们似乎是某种魔法,React 以某种甚至不用暴露其实例
9月份开始,使用了React16.8的新特性React Hooks对项目进行了重构,果然,感觉没有被辜负,就像阮一峰老师所说的一样,这个 API 是 React 的未来。
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!