1. React 中 setState 什么时候是同步的,什么时候是异步的?
在React中,如果是由React引发的事件处理(比如通过onClick引发的事件处理),调用setState不会同步更新
this.state,除此之外的setState调用会同步执行this.state。所谓“除此之外”,指的是绕过React通过addEventListener直接添加的事件处理函数,还有通过setTimeout/setInterval产生的异步调用。
原因:在React的setState函数实现中,会根据一个变量isBatchingUpdates判断是直接更新this.state还是放到队列中回头再说,而isBatchingUpdates默认是false,也就表示setState会同步更新this.state,但是,有一个函数batchedUpdates,这个函数会把isBatchingUpdates修改为true,而当React在调用事件处理函数之前就会调用这个batchedUpdates,造成的后果,就是由React控制的事件处理过程setState不会同步更新this.state。
总结:由React控制的事件处理程序,以及生命周期函数调用setState不会同步更新state 。React控制之外的事件中调用setState是同步更新的。比如原生js绑定的事件,setTimeout/setInterval等。
class Example extends React.Component {
constructor() {
super();
this.state = {
val: 0
};
}
componentDidMount() {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 1 次 log
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 2 次 log
setTimeout(() => {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 3 次 log
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 4 次 log
}, 0);
}
render() {
return null;
}
};
答案:0 0 2 3
1、第一次和第二次都是在 react 自身生命周期内,触发时 isBatchingUpdates 为 true,所以并不会直接执行更新 state,而是加入了 dirtyComponents,所以打印时获取的都是更新前的状态 0。
2、两次 setState 时,获取到 this.state.val 都是 0,所以执行时都是将 0 设置成 1,在 react 内部会被合并掉,只执行一次。设置完成后 state.val 值为 1。
3、setTimeout 中的代码,触发时 isBatchingUpdates 为 false,所以能够直接进行更新,所以连着输出 2,3。
2. useState 怎么做缓存的?
通过一个环形链表。每一个hook都会在Fiber链表上,而useState会将同一个state的更新操作都保存到一个环形链表上,如下图。
3. 常用的 react hooks 方法
useState、useEffect、useMemo、useCallback、useReducer、useLayoutEffect、useRef、useContext。
4. 讲一下 React Fiber
Fiber 可以理解为是一个执行单元,也可以理解为是一种数据结构。Fiber 可以理解为一个执行单元,每次执行完一个执行单元,React 就会检查现在还剩多少时间,如果没有时间则将控制权让出去。Fiber 还可以理解为是一种数据结构,React Fiber 就是采用链表实现的。每个 Virtual DOM 都可以表示为一个 Fiber。
总结,Fiber 是 React 实现异步更新 UI 的基础,
在 react16 引入 Fiber 架构之前,react 会采用递归对比虚拟DOM树,找出需要变动的节点,然后同步更新它们,这个过程 react 称为reconcilation(协调)。在reconcilation期间,react 会一直占用浏览器资源,会导致用户触发的事件得不到响应。
这种遍历是递归调用,执行栈会越来越深,而且不能中断,中断后就不能恢复了。递归如果非常深,就会十分卡顿。如果递归花了100ms,则这100ms浏览器是无法响应的,代码执行时间越长卡顿越明显。传统的方法存在不能中断和执行栈太深的问题。
因此,为了解决以上的痛点问题,React希望能够彻底解决主线程长时间占用问题,于是引入了 Fiber 来改变这种不可控的现状,把渲染/更新过程拆分为一个个小块的任务,通过合理的调度机制来调控时间,指定任务执行的时机,从而降低页面卡顿的概率,提升页面交互体验。通过Fiber架构,让reconcilation过程变得可被中断。适时地让出CPU执行权,可以让浏览器及时地响应用户的交互。
5. 怎么解决 useEffect 闭包的问题?
传入依赖项即可。或者将state值赋值给useRef,这样每次useRef上的值都是最新的值。
6. useReducer 比 redux 好在哪里?
react内置支持。支持依赖项不更新,不触发useReducer的回调。更加轻量,无需引用框架,无需维护一个大的store树。