React 的核心流程可以分为两个部分:
reconciliation (调度算法,也可称为 render): // 调和
更新 state 与 props;
调用生命周期钩子;
生成 virtual dom;
这里应该称为 Fiber Tree 更为符合;
通过新旧 vdom 进行 diff 算法,获取 vdom change;
确定是否需要重新渲染
commit:
如需要,则操作 dom 节点更新;
constructor ====》 初始化state componentWillMount ==== 》getDerivedStateFromProps
// 当新 props 中的 data 发生变化时,同步更新到 state 上
componentDidMount ====》进行事件监听,数据的请求
componentWillUpdate === 》 getSnapshotBeforeUpdate componentDidUpdate ==== 》 当 id 发生变化时,重新获取数据 shouldComponentUpdate ====》 优化渲染的性能,return false 阻止后面的逻辑 componentWillReciveProps ==== 》少使用,使用getDerivedStateFromProps
componentWillUnmount ====》 解绑事件
在新版本中,React 官方对生命周期有了新的 变动建议:
- 使用
getDerivedStateFromProps
替换componentWillMount
; - 使用
getSnapshotBeforeUpdate
替换componentWillUpdate
; - 避免使用
componentWillReceiveProps
;
其实该变动的原因,正是由于上述提到的 Fiber。首先,从上面我们知道 React 可以分成 reconciliation 与 commit 两个阶段,对应的生命周期如下:
-
reconciliation:
componentWillMount
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
-
commit:
componentDidMount
componentDidUpdate
componentWillUnmount
在 Fiber 中,reconciliation 阶段进行了任务分割,涉及到 暂停 和 重启,因此可能会导致 reconciliation 中的生命周期函数在一次更新渲染循环中被 多次调用 的情况,产生一些意外错误。
新版的建议生命周期如下:
class Component extends React.Component { // 替换 `componentWillReceiveProps` , // 初始化和 update 时被调用 // 静态函数,无法使用 this static getDerivedStateFromProps(nextProps, prevState) {} // 判断是否需要更新组件 // 可以用于组件性能优化 shouldComponentUpdate(nextProps, nextState) {} // 组件被挂载后触发 componentDidMount() {} // 替换 componentWillUpdate // 可以在更新之前获取最新 dom 数据 getSnapshotBeforeUpdate() {} // 组件更新后调用 componentDidUpdate() {} // 组件即将销毁 componentWillUnmount() {} // 组件已销毁 componentDidUnMount() {} }
使用建议:
- 在
constructor
初始化 state; - 在
componentDidMount
中进行事件监听,并在componentWillUnmount
中解绑事件; - 在
componentDidMount
中进行数据的请求,而不是在componentWillMount
; - 需要根据 props 更新 state 时,使用
getDerivedStateFromProps(nextProps, prevState)
;- 旧 props 需要自己存储,以便比较;
-
public static getDerivedStateFromProps(nextProps, prevState) { // 当新 props 中的 data 发生变化时,同步更新到 state 上 if (nextProps.data !== prevState.data) { return { data: nextProps.data } } else { return null1 } }
- 可以在
componentDidUpdate
监听 props 或者 state 的变化,例如:
componentDidUpdate(prevProps) { // 当 id 发生变化时,重新获取数据 if (this.props.id !== prevProps.id) { this.fetchData(this.props.id); } }
- 在
componentDidUpdate
使用setState
时,必须加条件,否则将进入死循环; getSnapshotBeforeUpdate(prevProps, prevState)
可以在更新之前获取最新的渲染数据,它的调用是在 render 之后, update 之前;shouldComponentUpdate
: 默认每次调用setState
,一定会最终走到 diff 阶段,但可以通过shouldComponentUpdate
的生命钩子返回false
来直接阻止后面的逻辑执行,通常是用于做条件渲染,优化渲染的性能。
二: 虚拟DOM的理解
(1)Virtual DOM是对DOM的抽象,本质上是JavaScript对象,这个对象就是更加轻量级的对DOM的描述.
(2)两个内容的差异,进行dom的对比,事件属性方法,耗性能,dom对象转换为js对象,比较会快。有效提升性能。
(3)首先,我们都知道在前端性能优化的一个秘诀就是尽可能少地操作DOM,不仅仅是DOM相对较慢,更因为频繁变动DOM会造成浏览器的回流或者重回,这些都是性能的杀手,因此我们需要这一层抽象,在patch过程中尽可能地一次性将差异更新到DOM中,这样保证了DOM不会出现性能很差的情况.
(4)更好的跨平台,比如Node.js就没有DOM,如果想实现SSR(服务端渲染),那么一个方式就是借助Virtual DOM,因为Virtual DOM本身是JavaScript对象.
三:diff算法
(1)diff的目的就是比较新旧Virtual DOM Tree找出差异并更新.
(2)一层节点发现有问题,不再往下比,直接放弃,n的平方降低为n
- 把树形结构按照层级分解,只比较同级元素(层级比较)
- 给列表结构的每个单元添加唯一的 key 属性,方便比较(列表添加key)
- React 只会匹配相同 class 的 component(这里面的 class 指的是组件的名字)
- 选择性子树渲染。开发人员可以重写shouldComponentUpdate 提高 diff 的性能
四:key值的作用
(1)如果key值相同,dom直接复用,不用再创建dom,直接这两个比,不用循环比
(2)用新指针对应节点的key去旧数组寻找对应的节点,这里分三种情况,
当没有对应的key,那么创建新的节点,
如果有key并且是相同的节点,把新节点patch到旧节点,
如果有key但是不是相同的节点,则创建新节点
五:怎么理解HOC高阶组件
接收一些函数,返回函数,组件进行包装,返回一个新的组件,很多地方都要用,有一点区别,共用的东西写在高阶组件中,通过传递额外参数,动态改变组件不同场景下使用的差异。
六:redux中间件的原理
action--store-reducer-props action和store之间,沟通的桥梁dispatch,改装dispatch,
store.dispatch,直接把action(对象)传递给store,使用中间件,action可以是函数,可以将函数转换为对象,传递给store。
七:setState发生了什么?遇到过什么坑
调和的过程。setState一般怎么用,传递什么进去,对象还是方法。
this.setState({ name: '小李' }); this.setState(() =>({ name:'小李' })); 永远用函数调用,会避免一些坑。 this.setState({ age: this.state.age+1 }); setState异步,疯狂点击按钮,age不是每次加1,会一次加5,6个,state异步,会进行累加 <input refs="input" value={this.state.age} /> ====1 this.setState((prevState) =>{ age: ++ prevState }); this.refs.input.value ==== 1 this.setState((prevState) =>{ age: ++ prevState }, () => { this.refs.input.value // 异步执行完state之后执行 });
八:ref是一个函数,有什么好处?
方便react在销毁组件重新渲染,有效清空ref引用中的东西,防止内存泄漏
九:refs作用是什么,在什么场景下用过?
操作dom(滚动条,点击按钮,滚动条滚动到上面)
图片展示获取图片的宽和高,
十:react中this的指向问题
十一:react-router的实现原理
hash/location
十二:jsx代码的转化(babel)
createElement
十三:受控组件和非受控组件
受控:有生命周期
非受控:纯函数
十四:函数组件怎么做性能优化
十五:react-saga的设计思想
改装dispatch action-----store之间,action可以是一个函数,可以执行异步操作
十六:组件是什么?类是什么?类被编译成什么?
十七:reselect是做什么使用的?
十八:什么时候使用异步组件?
十九:xss攻击,react如何防范?
二十:react怎么提高性能优化?
shouldComponentUpdate
pureComponent
二十一:ssr
二十二:
- 为什么返回多个标签或组件必须要用一个标签或组件包裹?
- 为什么在根本没有使用
React
这个变量的情况下还要import React
?
整个UI实际上是通过层层嵌套的React.createElement
方法返回的,所以我们要在文件开头import React
,否则编译后就会发现createElement
没有定义。
React.createElement
执行的结果是一个对象,对象的属性描述了标签或组件的性状,对象再嵌套子对象。如果顶层返回多个标签,就无法表达为一个对象了