依稀记得去年的时候,有面试官问我:怎么理解React Hook?当时我尚未使用React开发项目的经验,固然也没有对Hook会有什么深刻的了解,因此当时只是应付回答了一下,估计当时的面试官对于个人回答应该是挺失望的(由于此次面试完后就没有啥消息了);好吧,直到如今,我想我已经有了一个还算不错的答案,如今记录下来,方便之后跟面试官吹牛逼。面试
Hook简直就是天使与魔鬼混血儿,关于Hook的出如今某乎早就争论不休,大致是有的人是激进派,以为Hook是颠覆性的,全项目切换到Hook方式开发;有的人则是保守派,复杂的组件仍然使用传统类组件,简单的组件则使用hook开发;有的人是反对派,对于Hook彻底持否认态度。
总结本身最近的使用感觉,彻底理解为何这些人的观点有如此大的差距;首先Hook的出现有它实实在在的好处(后面再分析),可是得到这种好处也是有代价的:要开发者本身维护依赖列代表显是一种心智负担,你在处理复杂的业务的时候,还要当心翼翼检查useCallback/useEffect的依赖列表,只要写少一个依赖项你的代码就有潜在的bug,有时候以为要么干脆不用useCallback还好,大不了就性能低一点,至少不会引入潜在的bug;可是回头优化性能的时候,useCallback又是彻底绕不过去的,真让人纠结;固然有人提到使用一些检查工具能够提供检查,不用人工检查那么麻烦,可是始终以为怪怪的。另一个问题就是代码逻辑的抽取,稍加不注意,很容易把全部逻辑都塞到一个function里面,致使函数代码动辄几百上千行代码,因此要用好Hook的前提条件是开发者对组件已经有一个了然于胸的规划(对于习惯代码一把梭的人真的好艰难)。
再说说Hook的天使的一面,从声明组件方面来讲,确实简单了很多,也不用记那么多烦人的生命周期函数;而后最核心的地方是,它提供一种新的抽象方式,在传统的类组件,咱们抽取出一个新的组件目的通常都是:api
因此在第二点,通常处理一个复杂的组件会抽取出一个负责业务的Controller组件,一个负责渲染的Dumb组件;可是咱们很快就会发现这样以组件为粒度划分逻辑,粒度太大了,当咱们另一个组件想重用Controller组件的一小部分逻辑,咱们应该怎么办,如果用继承,会把一些无用的逻辑带入,并且继承也还有其余的问题;因此组合才是最优的选择,或许咱们应该再划分出一个公用的逻辑组件,让他们组件间能够自由组合,可是随着你们热火朝天的重构,愈来愈多的业务组件被创造出来,而整个组件树也会慢慢跟着膨胀,这致使每次更新的时候都要diff一棵愈来愈大的组件树,明显类组件已经到了一个瓶颈;
而这个时候Hook开始闪亮登上了舞台,那么怎么用它来破解这个问题尼。函数
(舒适提示,下面是我的理解,可能会有错误误导的地方)
先抛开Hook,再来建立一个新的名词Scope,那么Scope的定义以下:工具
没错Scope好像类的定义同样,是方法和状态的集合体,只不过它是基于Hook;为啥不干脆直接定义一个普通的类,由于Hook还提供了setState,useEffect等能力,这是普通的类作不到的,或者要花费一些心思才能作到,既然官方提供了这种方式,确定是最好的一种方式。
做为Scope,它是能够保持逻辑的独立而且粒度最小,固然还有可以直接介入到宿主组件的生命周期里面。
其实若是加上Render Function,它也应该算是一个组件了,只是它的生命周期依赖宿主。
那么为啥再也不加上Render Function,由于表现形式是多变的,可是逻辑就不必定了,因此保持逻辑抽象便可。性能
那么如今组件定义以下:测试
这里就很好理解了,如今问题的关键点就是Scope之间的关系了,若是Scope之间互相独立,没啥依赖那就完美,可是这是不可能,不少时候它们之间的关系还特别强,在不少hook示例里面它们之间的关系都是经过调用useXxx的时候把依赖当作参数传入,可是很容易致使相互依赖的状况,例如日常状况:优化
TableScope须要可以点击打开对话框,DialogScope须要confirm以后去刷新表格,很明显这二者建立的时候都要依赖对方,究竟是useDialog先仍是useTable先尼;
个人建议是不要把依赖当作参数传入,我的解法:spa
function Demo() { //组件公共状态 const [stateA, setStateA] = useState(''); const [stateB, setStateB] = useState(''); //建立各类Scope const dialog = useDialog({sateA, setStateA}); //Scope只依赖公共的状态和方法,不容许建立开始互相依赖 const table = useTable({stateB, setStateB}); //经过提供回调,处理Scope之间各类联系 dialog.onConfirm(()=> { table.reload(); }); table.onClick(()=> { dialog.open(); }); //组件生命回调 useEffect(()=> { .... }, []); return ... }
Scope之间不容许建立的时候相互依赖,它们之间的联系统一在后面一段代码中处理,这样有什么好处尼?这样的话实现的时候,能够更加专一Scope自己状态和业务,也不须要担忧建立的时候会出现相互依赖状况。最后组件代码分段也十分清晰,大致能分红5段:调试
嗯。。最终的目标就是创建一种规范,让组件的结构更加清晰,不会出现一片混乱的状况。code
前一节,更可能是从抽象出发,如何合理利用Hook去组织代码,而且创建一种规范;后面就来想一想如何解决Hook不少吐槽的问题:依赖列表。
正如以前说的,例如useCallback的使用场景,若是依赖列表少写一项,就有可能埋下隐藏的bug,并且这种bug极难会被发现,由于依赖列表有可能很长,后面的维护者或者帮你review代码的人不可能会仔细看依赖列表有没有缺漏;因此我对useCallback基本上都是抵触的,宁愿不用,也不想埋下bug,到时候调试得满头大汗,甚至我也开始怀疑是否要继续使用Hook来开发组件。
直到有一天看到Vue3的Composition API(没错Vue又又又借鉴了一次React),感受Hook赶上Mutable其实也是很美妙。那么咱们有没有办法经过依赖搜集来解决依赖列表的问题,实现更加简洁的api,若是能够咱们就能够像Vue3那样使用useCallback和useEffect:
const refA = useRef(0); const refB = useRef(0); const refC = useRef(0); useCallbck(()=> { consoel.log(refA.value + refB.vlue) }); useEffect(()=> { refC.value = refA.value - refB.value; }) ...
代码上基本消灭了依赖列表,只是在变量使用上就稍微那麻烦了一丁点,可是依赖列表不用再维护,心智负担瞬间降低了许多。
问题怎么去实现这种代码效果?
下面是一个简单的实现:
const ACTIVE_EFFECT = null; function useReactiveState(initial) { const [state, setState] = useState(initial); const ref = useRef(state); const effects = useRef(new Set()); return useMemo(()=> { const memoState = { get value() { if(ACTIVE_EFFECT) { ACTIVE_EFFECT.deps.add(memoState); effects.current.add(ACTIVE_EFFECT); } return ref.current; }, set value(newValue) { ref.current = newValue; setState(newValue); effects.current.forEach((effect)=> { effect.update(); }) }, removeEffect(effect) { effects.current.remove(effect) } } return memoState; }, []) } function useReactiveEffect(call) { const [flag, setFlag] = useState(0); const effect = useRef({ deps: new Set(), update: ()=> { setFlag((flag)=> flag + 1); } }); useEffect(()=> { const prevEffect = ACTIVE_EFFECT; effect.current.deps.forEach((dep)=> { dep.removeEffect(effect); }); effect.current.deps = []; const result = call(); ACTIVE_EFFECT = prevEffect; return reuslt; }, [flag]) }
useCallback的实现同理,虽然没有怎么深刻测试,大致功能仍是可以实现的。
好吧,对React Hook的思考暂时到此为止,等之后再有新的感悟再继续写React Hook的相关的文章吧,最后仍是那句,若有错漏,请你们可以及时指出,谢谢你们的捧场。