React (hooks)的写法与 React 实现
Hook
useState
如果我们需要创建一个响应式变量,需要将初始值传入该函数,它的返回值分别是该变量以及修改该变量的函数, (这里我们通过结构的方法来获取变量),若要改变该变量的数值,需要通过setCount来改变,这样才能实现响应式变量
useEffect
所有有副作用的操作都需要使用useEffect进行包裹,useEffect执行的时机是组件挂载(onMount)时,会执行一次
比如这里的修改document的标题就是一个有副作用的操作,故使用useEffect进行调用
有无副作用 -> 比如说一个组件内部单纯只是执行了一个语句,比如a = b + c;不会对组件外部造成影响,即无副作用。如果对组件外部造成影响,即有副作用,比如说,它发起了一个网络请求,与外部系统做了交互;又比如说,它给本地的Session或localStorage存了个字段,这也是改变了外部系统,这并不是个纯函数。(什么是纯函数? 纯函数就是1+2=3 [满足两个条件:返回结果只依赖于它的参数;并且在执行过程里面没有副作用])
什么是hook?
你可以挂到React生命周期上去执行的一个函数(上文举例的useState, useEffect都是hook, 比如说useState时挂载到了onUpdate上,useEffect是挂载到了onMount上) (其它的hook基本上都是useState和useEffect的封装)
Hook的使用法则:不要在循环,条件或嵌套函数中调用Hook
React的实现
遇到的问题
- JSX不符合JS标准语法
这里使用转译(Transpel 注:与compel(编译)不同) (将一种语言转化为另一种语言)
上图将左侧Markup高级语言片段 转化为 符合React语法的代码片段
- 返回的JSX发生改变时,如何更新DOM
Dom操作是比较耗费性能的,特别是将整个页面的Dom删除,重新挂载上新的Dom,是非常耗费性能的,且用户体验很差的一个操作
那我们可以对老的Dom结构和新的Dom结构算一个Diff,然后把不同的那部分替换上去,性能开销和用户体验都会好上不少
但我们需要注意Diff算法不能太耗时,我们既希望计算出的Diff尽可能小,又计算Diff的时间尽可能短,我们需要在这两者之间取一个平衡
- 虚拟Dom
因为Dom实际上由浏览器维护的,我们操作Dom需要通过浏览器提供的Dom API 进行操作,而为了实现一些操作(比如为了计算Diff),故我们需要在JavaSript中维护一份Dom的备份(这么说可能不太合适,但你懂我意思就好),该数据结构我们称之为虚拟Dom
背景知识补充
* 声明式编程:告诉程序**做什么**
* 指令式编程:比如C,手动一步一步的告诉程序应该**怎么做**
* 响应式编程:声明式编程的一个类别,不仅可以直接声明UI,又可以是响应的,比如说某个状态改变了,它会自动更新依赖它的状态,并更新UI,是一个自己响应自己的过程(也是React的一个重大意义,从JS原生的指令式编程变成这样的一种声明式编程)
3. State/Props 更新时,要重新触发render函数
- 问题:既然目前所有主流的前端框架都是声明式的,那为什么我们不将这个能力集成到浏览器中,由浏览器来实现呢?(这样我们就不需要去Import一个React包或Vue包了)
不能,这是因为浏览器作为一个应用平台,它是不能给你提供一个更高层的东西,否则就把自由度变低了,它所要做的就是提供一个底层,然后你在上层可以按照自己的需求做一个封装(或者用各种各样的框架去实现)
🔙现在回到对Virtual Dom的讨论
Diffing是一个递归的过程,因为父组件可以嵌套子组件,是一个树的结构 => 当父组件的状态发生改变时,(所有的子组件)它的子组件,子组件的子组件...会递归地发生重新地render(指的是函数[render函数]执行,不是Dom更新),这也通常是导致React性能问题的原因(比如说一个在树中位置很高的子组件的状态发生了改变,它的子树,所有的render函数都重新执行了一遍),然后得到新的virtual Dom然后与旧的Virtual Dom进行一个对比(Diff),然后就知道怎么更新Dom了 => 最后就是Re-render Virtual Dom 和Dom change了(这就是一个React动态渲染的一个过程)