React必知必会(一)

230 阅读9分钟

1、React 生命周期

⽬前React 16.8 +的⽣命周期分为三个阶段,分别是挂载阶段、更新阶段、卸载阶段

挂载阶段:

constructor: 构造函数,最先被执⾏,我们通常在构造函数⾥初始化state对象或者给⾃定义⽅法绑定this getDerivedStateFromProps: static getDerivedStateFromProps(nextProps, prevState) ,这是个静态⽅法,当我们接收 到新的属性想去修改我们state,可以使⽤getDerivedStateFromProps

render: render函数是纯函数,只返回需要渲染的东⻄,不应该包含其它的业务逻辑,可以返回原⽣的DOM、React 组件、Fragment、Portals、字符串和数字、Boolean和null等内容

componentDidMount: 组件装载之后调⽤,此时我们可以获取到DOM节点并操作,⽐如对canvas,svg的操作,服 务器请求,订阅都可以写在这个⾥⾯,但是记得在componentWillUnmount中取消订阅

更新阶段:

getDerivedStateFromProps: 此⽅法在更新个挂载阶段都可能会调⽤

shouldComponentUpdate: shouldComponentUpdate(nextProps, nextState) ,有两个参数nextProps和nextState,表示 新的属性和变化之后的state,返回⼀个布尔值,true表示会触发重新渲染,false表示不会触发重新渲染,默认返回 true,我们通常利⽤此⽣命周期来优化React程序性能

render: 更新阶段也会触发此⽣命周期

getSnapshotBeforeUpdate: getSnapshotBeforeUpdate(prevProps, prevState) ,这个⽅法在render之后, componentDidUpdate之前调⽤,有两个参数prevProps和prevState,表示之前的属性和之前的state,这个函数有 ⼀个返回值,会作为第三个参数传给componentDidUpdate,如果你不想要返回值,可以返回null,此⽣命周期必须 与componentDidUpdate搭配使⽤

componentDidUpdate: componentDidUpdate(prevProps, prevState, snapshot) ,该⽅法在getSnapshotBeforeUpdate ⽅法之后被调⽤,有三个参数prevProps,prevState,snapshot,表示之前的props,之前的state,和snapshot。 第三个参数是getSnapshotBeforeUpdate返回的,如果触发某些回调函数时需要⽤到 DOM 元素的状态,则将对⽐或 计算的过程迁移⾄ getSnapshotBeforeUpdate,然后在 componentDidUpdate 中统⼀触发回调或更新状态。

卸载阶段:

componentWillUnmount: 当我们的组件被卸载或者销毁了就会调⽤,我们可以在这个函数⾥去清除⼀些定时器,取 消⽹络请求,清理⽆效的DOM元素等垃圾清理⼯作

React 16之后有三个⽣命周期被废弃

componentWillMount

componentWillReceiveProps

componentWillUpdate

废除原因

1.在 Fiber 机制下,render 阶段是允许暂停、终止和重启的,重启就是“重复执行一遍整个任务”,这就导致 render 阶段的生命周期是有可能被重复执行的。这三个生命周期都处于 render 阶段,都可能重复被执行,导致bug

2.避免开发者不合理的编程习惯,可能在生命周期中滥用 setState 导致重复渲染死循环,阻止用户在其内部使用 this 

2、React的diff算法

调和就是将虚拟DOM映射到真实DOM的过程

调和 不等于 Diff ,Diff 只是调和的一部分

1.分层处理:同层级才会进行比较,跨层级的直接跳过diff,销毁旧的,重建新的

2.类型相同的节点才有diff的必要性,类型不同的直接原地替换旧的

3.key属性:key作为唯一标识,可以减少同一层级的节点的不必要比较

3、React数据流

1. 基于 props 的单向数据流:实现父-子通信、子-父通信和兄弟组件通信。

父子: this.props

子父:父传给子一个绑定了自身上下文的函数,那么子组件在调用该函数时,将数据以函数入参的形式给出去

2. 利用“发布-订阅”模式驱动数据流

3. 使用 Context API 维护全局状态: Provider 提供数据,Cosumer 不仅能够读取到 Provider 下发的数据,还能读取到这些数据后续的更新

4.Redux: Redux 是 JavaScript 状态容器,它提供可预测的状态管理。

在 Redux 的整个工作过程中,数据流是严格单向的

Redux 主要由三部分组成:

store:单一的数据源,而且是只读的;

action:对变化的描述。

reducer: 它负责对变化进行分发和处理, 最终将新的数据返回给 store。reducer 一定是一个纯函数

  1. 使用 createStore 来完成 store 对象的创建

  2. reducer 的作用是将新的 state 返回给 store: 接受action和旧的state,返回新的state。

  3. action 的作用是通知 reducer “让改变发生” :action 对象中只有 type 是必传的。type 是 action 的唯一标识,reducer 正是通过不同的 type 来识别出需要更新的不同的 state,由此才能够实现精准的“定向更新”

  4. 派发 action,靠的是 dispatch

4、setState到底是异步还是同步?

  1. setState 只在合成事件和钩⼦函数中是“异步”的,在原⽣事件和 setTimeout 中都是同步的

  2. setState 的“异步”并不是说内部由异步代码实现,其实本身执⾏的过程和代码都是同步的,只是合成事件和钩⼦ 函数的调⽤顺序在更新之前,导致在合成事件和钩⼦函数中没法⽴⻢拿到更新后的值,形成了所谓的“异步”,当然 可以通过第⼆个参数 setState(partialState, callback) 中的 callback 拿到更新后的结果。

  3. setState 的批量更新优化也是建⽴在“异步”(合成事件、钩⼦函数)之上的,在原⽣事件和setTimeout 中不会 批量更新,在“异步”中如果对同⼀个值进⾏多次 setState , setState 的批量更新策略会对其进⾏覆盖,取最后⼀ 次的执⾏,如果是同时 setState 多个不同的值,在更新时会对其进⾏合并批量更新。

5、React的Hooks

Hooks 的本质:一套能够使函数组件更强大、更灵活的“钩子”

1.告别难以理解的 Class: this 和生命周期。class中的 this 可能不符合预期,使用 bind或箭头函数,函数组件不用关心 this 。生命周期的问题有学习成本和不合理的逻辑规划方式。

2.解决业务逻辑难以拆分的问题: 逻辑曾经一度与生命周期耦合在一起,一个生命周期可能不止做一件事情,会给阅读和维护者带来很多麻烦。按照逻辑上的关联拆分进不同的函数组件里:专门管理订阅的函数组件、专门处理 DOM 的函数组件、专门获取数据的函数组件

3.使状态逻辑复用变得简单可行: 过去复用状态逻辑,靠的是 HOC 和 Render Props ,破坏组件结构,造成“嵌套地狱”。自定义 Hook,达到既不破坏组件结构、又能够实现逻辑复用的效果

4.函数组件从设计思想上来看,更加契合 React 的理念: 类组件是面向对象思想,函数组件是函数式编程思想,更加契合 React 框架的设计理念 UI=f(data)。 函数组件会捕获 render 内部的状态,将 数据和渲染真正绑定到了一起

缺点:

1.Hooks 暂时还不能完全地为函数组件补齐类组件的能力:比如 getSnapshotBeforeUpdate、componentDidCatch 这些生命周期

2.“轻量”几乎是函数组件的基因,这可能会使它不能够很好地消化“复杂” :类组件中一些方法非常繁多的实例,如果用函数组件来解决相同的问题,业务逻辑的拆分和组织会是一个很大的挑战

3.Hooks 在使用层面有着严格的规则约束: 只在 函数中调用 Hook;不能在循环、条件或嵌套函数中调用 Hook

Hooks 的本质是链表,渲染是通过“依次遍历”来定位每个 hooks 内容的。如果前后两次读到的链表在顺序上出现差异,那么渲染的结果自然是不可控的。

6、React的Fiber

JavaScript 是单线程的,浏览器是多线程的

JavaScript 线程和渲染线程是互斥的:当其中一个线程执行时,另一个线程只能挂起等待。

若 JavaScript 线程长时间地占用了主线程,那么渲染层面的更新就不得不长时间地等待,界面长时间不更新,带给用户的体验就是所谓的“卡顿”

Stack Reconciler 是一个同步的递归过程,不可以被打断

当处理结构相对复杂、体量相对庞大的虚拟 DOM 树时,Stack Reconciler 需要的调和时间会很长,这就意味着 JavaScript 线程将长时间地霸占主线程,进而导致渲染卡顿/卡死、交互长时间无响应等问题。

Fiber 架构实现了“增量渲染”。所谓“增量渲染”,就是把一个渲染任务分解为多个渲染任务,而后将其分散到多个帧里面。不过严格来说,增量渲染其实也只是一种手段,实现增量渲染的目的,是为了实现任务的可中断、可恢复,并给不同的任务赋予不同的优先级,最终达成更加顺滑的用户体验。

Fiber 架构核心:“可中断”“可恢复”与“优先级”

React 16 中,为了实现“可中断”和“优先级”,多出来一层架构,Scheduler(调度器),作用是调度更新的优先级。

新老两种架构对 React 生命周期的影响主要在 render 这个阶段,这个影响是通过增加 Scheduler 层和改写 Reconciler 层来实现的。

在 render 阶段,一个庞大的更新任务被分解为了一个个的工作单元,这些工作单元有着不同的优先级,React 可以根据优先级的高低去实现工作单元的打断和恢复。由于 render 阶段的操作对用户来说其实是“不可见”的,所以就算打断再重启,对用户来说也是 0 感知。

参考:

  1. 深入浅出搞定React
  2. react中文网