我明白了,react心得

简介: React 就干两件事,打造用户界面,响应各种事件。16.0版本提出fiber,16.8版本提出hooks。React原来是php项目,后来改了编译器,换成js来编译,然后加入虚拟dom。所以React只做两件事,一件事是渲染ui,一件事是响应事件。mvc和mvvm都是早些年angular的设计理念。react并不是什么mvvm和mvc,它只是一个很小的东西,只做那两件事儿。它只是个工具。backbone才是真正的mvc,它是模仿java的spring,挪到前端来了。

前言

React 就干两件事,打造用户界面,响应各种事件。16.0版本提出fiber,16.8版本提出hooks。

React原来是php项目,后来改了编译器,换成js来编译,然后加入虚拟dom。所以React只做两件事,一件事是渲染ui,一件事是响应事件。mvc和mvvm都是早些年angular的设计理念。react并不是什么mvvm和mvc,它只是一个很小的东西,只做那两件事儿。它只是个工具。backbone才是真正的mvc,它是模仿java的spring,挪到前端来了。

虚拟dom

json结构的描述,一个带有规则定义的js的对象(schema 数据模型)。比如你定义的菜单按钮权限的json,比如你定义低代码的json配置文件 如拖拉拽的图表生成器。

react的虚拟dom就是一个json描述的数据结构,一个能够映射出真实dom的数据schema。

fiber比虚拟dom更快

以前的虚拟dom是一颗树,操作这颗树的时间复杂度是O(n^3),而且递归一棵树是没法暂停的。这样当树结构过大,必须得遍历时,那么页面会发生严重的卡顿。
而fiber是一个链表,操作一个链表的时间复杂度是O(n),而且fiber是可以暂停的,它不容易造成卡顿。

性能提升

react提升性能,使用16.0之前的版本,使用虚拟dom时,不改变版本的情况下,要减少跨层级的移动、删除节点的业务。
因为那样会很卡,虚拟dom是一颗树,时间复杂度是O(n^3),例如排序,就可以使用第三方别的库来做,改变dom的顺序一般不需要改变dom的内容,react的强项是改变dom的内容。

fiber 怎么做到的?

利用浏览器空闲时间执行,不会长时间占用主线程。
将对比、更新dom的操作碎片化了。diff完成dom在内存中已经存在了,只是没有放在页面中,存到了fiber对象中的stateNode中去了。
碎片化的任务,可以根据需要来被暂停。

requestIdleCallback

这个是浏览器提供的api,可以利用浏览器空闲时间执行任务。fiber就是借用这个api外加它的任务调度来做的。

将虚拟dom转成fiber对象

会将虚拟dom对象构建成fiber对象,根据fiber对象渲染成真实dom。

feiber 的流程(迷你流程)

// 创建任务队列
const taskQueue = createTaskQueue()

//空闲时间执行的具体方法
const performTask = deadline => {
    //执行务workLoop方法后续补充 
    workLoop(deadline)
    //实现持续调用
    if (subTask || !taskQueue.isEmpty()) {
        requestIdleCallback(performTask)
    }
}
// 暴露的render方法
export const render = (element, dom) => {
    //1.添加任务 -> 构建fiber对象 
    taskQueue.push({
        dom,
        props: { children: element }
    })
    //2.指定浏览器空闲时间执行performTask 方法 
    requestIdleCallback(performTask)
}
// 子任务
let subTask = null
//commit操作标志
let pendingCommit = null

const workLoop = deadline => {
    //1.构建根对象 
    if (!subTask) {
        subTask = getFirstTask()
    }
    //2.通过while循环构建其余对象
    while (subTask && deadline.timeRemaining() > 1) {
        subTask = executeTask(subTask)
    }
    //3.执行commit操作。实现Dom挂载 
    if (pendingCommit) {
        commitAllwork(pendingCommit)
    }
}
const getFirstTask = () => {
    // 获取任务队列中的任务
    const task = taskQueue.pop()
    //返回Fiber对象 
    return {
        props: task.props, 
        stateNode: task.dom,
        //代表拟Dom挂载的节点 
        tag: "host_root", 
        effects: [], 
        child: null
    }
}

React 生命周期

react只是删除了那几个生命周期函数,并没有删除那几个生命周期,生命周期函数在16.0之后发生了变化。生命周期的变化是加入fiber之后,才发生了那些变化。

16.0之前

初始化=》挂载props=》初始化state=》render dom=》完成
获取数据完毕=》更新state=》diff=》render dom =》完成

用的比较多的是componentDidMount。
shouldComponentUpdate 是手动干预提升react性能的唯一手段。
如果你能手动控制每一个组件的shouldComponentUpdate,就算你不用fiber,就算虚拟dom是树,你也未必慢。
所以这也体现了有经验的人写react和没经验的人写react是不一样的。

16.0之后

之前是树,循环加递归的去判断它是否发生变化。现在是链表,循环加打打标记的来判断它是否发生变化。

fiber的diff算法是在render中执行的,为了保证应对fiber的链式渲染,所以render之前的那些方法。
比如 componentWillMount、componentWillReceiveProps、componentWillUpdate,它们就不需要了,因为它可能会在render之前改变state属性,会造成优先级不正确的问题。
于是react觉得这些方法会存在风险,所以把它们直接干掉了。不过shouldComponentUpdate保留下来了,它不会改变state。

getDerivedStateFromProps 允许你在render之前改一下state,返回null就不更新state。
getSnapshotBeforeUpdate 可以获得上一次真实渲染的快照。

diff算法

16.0之前

只要类型和key中任何一个发生变化,就算你节点变化。
树的节点遍历,只需要遍历一遍就行了,但是把树这个结构的遍历应用到diff算法中就是O(n^3),因为它进行三层遍历,分别时tree diff、component diff、element diff。

针对树结构(tree diff):只要存在UI层的DOM节点跨层级的操作,直接干掉,重新创建。
针对组件结构(component diff):拥有相同嘞的两个组件生成相似的树形结构,拥有不同类的两个组件会生成不同的树形结构。
针对元素结构(element diff):对于同一层级的一组节点,使用具有唯一性的id区分(key属性来区分)。

16.0之后

fiber的diff是可中断的,其实就是循环三遍,时间复杂度其实是O(3n)去除系数就是O(n)。

先通过state计算出新的fiber节点。
对比节点的tag和key确定节点操作(修改,删除,新增,移动)。移动是最复杂的。
用effectTag标记出fiber对象,第一次循环结束之后,它就能知道所有节点都干了啥。
收集所有标记的fiber对象,形成effctList。没有打上标记的,就表示它没变,就不用进行接下来的循环操作了。
然后在commit阶段一次性处理完,处理完所有变化的节点。

单节点

新旧相同 不变
新旧不同 更新
新有旧无 新增

多节点

新旧相同 不变
新有旧无 新增
新无旧有 删除
新旧顺序不同 位置移动或者互换
新旧顺序不同并且旧有新无 先删除再位置移动或者互换

React 状态管理

React 默认没带数据流(状态管理),那些实现了数据流的库不光可以在react中使用,也可以在vue中使用。
每一个好的东西都有它自己适合的范围。

flux

UI产生动作消息,将动作传递给分发器。(这个动作就是 比如 点击一个按钮或者ajax返回数据)
分发器广播给所有store。(以广播的方式告诉所有store。)
订阅的store做出反应,传递新的state给UI。


在react中flux叫rflux,flux中有多个store。

redux

redux与flux不同,它只有一个单一的store。整个应用的state存储在一个单一的store树中。

state状态为只读,你不能直接修改state,应该要通过action触发state修改。

得使用纯函数进行状态修改,需要你写reducers纯函数来进行处理,reducer通过当前状态树和action来进行计算,返回一个新的state。(这个reducer可以当它是一个merge函数,merge完了之后返回一个新的state来更新掉那个旧的。)

不过如果一个应用的state都存到一个store中,那么效率就会很低,于是它提出了一个分支的概念,不同的state放到不同的分支上。

redux:https://react-redux.js.org/

flux 和 redux对比

flux和redux都可以作为react的状态管理,flux是多store,redux是单store。
flux有调度器来进行事件处理,redux依赖reducer来实现事件处理。
flux的state可以直接修改,redux 的state不能直接修改,要通过reducer进行state的merge方式来产生一个新的state。

mbox

mbox是一个状态管理机制,是全自动的,action一改变state,views上就直接发生变化,不需要你像redux一样去写reducer函数了。
定义状态并使其可观察,使用修饰符@observer来使其可观察。
创建视图以响应状态变化。
更改状态(自动响应UI变化)。

mobx:https://cn.mobx.js.org/

hooks

组件之间复用状态逻辑很难,得使用redux或mox来实现状态逻辑的复用。
复杂组件变的难以理解
难以理解的class

hooks的使用降低了理解成本,也让react组件更像是函数。
它没有什么魔法,就是数组,还是两个数组,一个存所有state,一个存所有的state的setter。

useState

用两个数组实现的,一个存所有的state,另一个存state的setter。
所以如果是在循环和判断的逻辑下用useState,会导致state和setter错乱,最好在函数顶部使用。

useEffect

治病的过程中产生了额外的影响。
useEffect 就是在你初始化之后,再次render之前,产生的额外影响。比如 数组的slice 和 splice函数。

useEffect 函数就是hooks中副作用函数,第一个参数就是callback函数,第二个参数是一个数组,就是你要监听的变量。如果你放一个空数组,它会起到一个componentDidMount的作用,没有监听任何变量,那么就不存在任何变化,那么就只会执行一次。
如果你想销毁state,可以在useEffect函数return的第一个函数。

useLayoutEffect

useEffect是在浏览器渲染后执行,useLayoutEffect是在浏览器渲染前执行。
官方不建议你是用它,但是官方还是出了这个hooks。
useLayoutEffect 主要在useEffect函数结果不满意的时候才被用到,一般是当处理dom改变带的副作用,这个hook执行时,浏览器并未对dom进行渲染,相对于useEffect执行要早。

useEffect 和 useLayoutEffect

useEffect 和 useLayoutEffect在mount和update阶段执行方法一样,传参不一样。
useEffect异步执行,而useLayoutEffect同步执行。
Effect 用 HookPassive 标记useEffect,用HookLayout标记useLayoutEffect。

useMemo

老的是memo,新的hooks中是useMemo。传递⼀个函数和依赖项,当依赖项发⽣变
化,执⾏函数,该函数需要返回⼀个值

类似于vue中的computed 计算属性,但是写法不同。

useCallback

返回一个函数,当监听的数据发生变化,才会执行回调函数。

useRef

相当于一个id的作用,id能够找真实dom,这个可以用来找虚拟dom,可以拿到fiber对象。

useReducer

redux、mbox是完整独立的状态管理方案。未必一定是为react打造的,它们只是和react进行了结合。react本身关注的是渲染UI和响应各种事件,为了提高效率,所以官方才出了这个hook。

聚合参数,以达到开发效率的提升以及代码的简洁。
useReducer在re-render时候不会改变存储位置,state作为props传给子component不会产生diff的效率问题(useMemo优化)

useReducer也许是替代useState,由于数据并未能共享,所以 并不能替代redux、mbox。

useContext

useContext能够产生一个Provider,能够使得你的Provider标签下面所有的组件共享一组数据。类似于一个局部的window哟。

自定义Hook

自定义的hook其实并不是hook,只是一个自定义的函数,只是它用了use标识开头,看起来像是符合了Hook的规则。
useXXX 和vue3的composition api很像。

class和hook的对比

官方不考虑废除class。class只是一种编程方式,在16.0之后依然是使用fiber和新的diff算法,效率上并不比class差,提升效率使用的是fiber。

hooks延申的写法太多,也许页面中一堆useState,可能显得比较乱,有时很难搞清楚这些state之间的关联,当然你可以尝试根据相应的业务逻辑做一下聚合。容易发生内存泄露。
class语法虽然难以理解,都是代码逻辑比较清晰,它面向对象嘛。

hook往往作为那种单节点组件,比如几十个表单域。最后使用class来写一个整个表单的数据流的入口。

useEffect是浅监听。

react性能优化

  1. 避免跨层级的移动
  2. sholudComponentUpdate
  3. Memo/useMemo
  4. useReducer

总结

看changelog的方法:看当中长的、大版本的,看你能看懂的,看不懂的就过。你能看懂,你还想了解的更深,点击#中的issue,然后你还可以通过有道的划词翻译,查看具体的译文。关键节点一定要记到时间点,至少要记到月份。

react 的fiber 让 react得到了一个质的提升,但react的生命周期并没有很大的变化,废弃掉了一些生命周期钩子,这些生命周期应该还有,只是不暴露到外面,防止你影响fiber的正常运行。

看源码:github桌面版,能够看到那些changelog的具体变化。具体的代码用vscode来看。
看源码不要广泛的去看,文件太多不那么容易看,看源码要学会放下,要学会存疑,看不懂就假想它是一个符号,你不用关系这个符号是干什么的,只需要知道这个符号是干什么的。
先看你认识的,你感兴趣的,不认识的先别管。官方有一些demo的,比如你要学什么,你通过看那些大牛写的demo,就能够学会怎么用以及部分的原理。
一个不恰当的例子吧,它和看毛片类似,先看你感兴趣的地方,然后再看看其它地方有没有漏掉的,你不可能每一部都从头看到尾。
先看react-dom,react-dom中没有fiber相关的内容,fiber相关的在react-reconciler中。

react的diff算法在fiber之后也有了很大的变化,之前三层遍历,分别时tree diff、component diff、element diff。
而现在fiber的diff是可中断的,其实就是循环三遍,时间复杂度其实是O(3n)去除系数就是O(n)。

react的状态管理有很多 flux、redux、mbox等等,没有谁好谁坏,只是每一个好的东西都有它自己适合的范围。

react的hooks降低了理解成本,也让react组件更像是函数。它没有什么魔法,就是数组,还是两个数组,一个存所有state,一个存所有的state的setter。用到了闭包,容易发生内存泄露。
class语法虽然难以理解,都是代码逻辑比较清晰,毕竟是面向对象,官方并没有废除class。
hooks往往作为那种单节点组件,比如几十个表单域。最后使用class来写一个整个表单的数据流的入口。

就算你使用了老的vdom,最终也会被转成fiber dom的,fiber dom就是react新一代的vdom。

目录
相关文章
|
3月前
|
前端开发 JavaScript 算法
如何学习react原理
【8月更文挑战第9天】 如何学习react原理
45 6
|
6月前
|
XML 资源调度 前端开发
React基础知识点
React基础知识点
76 0
|
6月前
|
缓存 监控 前端开发
这个知识点,是React的命脉
这个知识点,是React的命脉
|
6月前
|
XML 存储 前端开发
react部分知识点总结
react部分知识点总结
58 0
|
设计模式 存储 缓存
|
XML 前端开发 JavaScript
react常用知识点1
react常用知识点1
48 0
|
前端开发
react常用知识点2
react常用知识点2
55 0
|
缓存 前端开发 JavaScript
学React,一篇就够了!
学React,一篇就够了!
125 0
|
前端开发 JavaScript API
下一篇
无影云桌面