aiyoudiao aiyoudiao
  • JavaScript
  • Vue
  • React
  • 低代码
  • 线性系统
  • 暂未分类
  • LeetCode
  • 算法
  • 数据结构
  • 设计模式
  • Other
  • PMP
  • Office
  • 面试
  • Bash
  • 流年往事
  • 经验片段
  • 读书杂感
  • 归档
  • 分类
  • 标签
  • 简介
  • 收藏
  • 有趣
  • 文档

码二

扫微信二维码,认识一下码二吧😉。
  • JavaScript
  • Vue
  • React
  • 低代码
  • 线性系统
  • 暂未分类
  • LeetCode
  • 算法
  • 数据结构
  • 设计模式
  • Other
  • PMP
  • Office
  • 面试
  • Bash
  • 流年往事
  • 经验片段
  • 读书杂感
  • 归档
  • 分类
  • 标签
  • 简介
  • 收藏
  • 有趣
  • 文档
  • JavaScript

  • vue

  • react

    • 16_4

      • React16_4整理-开篇
      • React16_4整理-TodoList
      • React16_4整理-上阶
      • React16_4整理-Redux开篇
      • React16_4整理-Redux上阶
      • React16_4整理-实战
    • react心得
      • 前言
      • 虚拟dom
      • fiber比虚拟dom更快
      • 性能提升
      • fiber 怎么做到的?
      • requestIdleCallback
      • 将虚拟dom转成fiber对象
      • feiber 的流程(迷你流程)
      • React 生命周期
        • 16.0之前
        • 16.0之后
      • diff算法
        • 16.0之前
        • 16.0之后
      • React 状态管理
        • flux
        • redux
        • flux 和 redux对比
        • mbox
      • hooks
        • useState
        • useEffect
        • useLayoutEffect
        • useEffect 和 useLayoutEffect
        • useMemo
        • useCallback
        • useRef
        • useReducer
        • useContext
        • 自定义Hook
        • class和hook的对比
        • react性能优化
      • 总结
    • react设计的哲学
    • react上手知识点(上篇)
    • react上手知识点(下篇)
    • antd
  • 低代码

  • 读书会

  • 线性代数

  • docker

  • auto

  • 杂记

  • 笔记
  • react
aiyoudiao
2022-04-13

react心得

# 前言

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/ (opens new window)

# 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/ (opens new window)

# 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。

#react
上次更新时间: 10年18月2023日 01时57分53秒
React16_4整理-实战
react设计的哲学

← React16_4整理-实战 react设计的哲学 →

最近更新
01
01.数据结构导论一览.md
10-16
02
30.2023年06月04日.md
06-04
03
08.与测量相关.md
05-06
更多文章>
Theme by Vdoing | Copyright © 2017-2023 aiyoudiao 码二 备案号: 鄂ICP备2022002654号-1