一、React基础使用
1. React中数据通信有哪些方式
-
简单层级传递,如父子,可以使用props。
<Child title="数据"/> -
祖先与子孙,跨层级数据传递或者兄弟组件通信:Context、第三方状态管理库如redux、mobx等。
2. 如何使用跨层级数据传递
- 创建Context对象
const TheContext = React.createContext();
- 使用Context Provider传递value
<TheContext.Provider>
<Child/>
</TheContext.Provider>
- Provider的子孙组件消费value:contextType(只能用在类组件中且只能订阅单一context来源)、useContext(hook函数,只能用在函数组件或者自定义hook中)、Consumer。
//contextType
class Child extends Component {
contextType = TheContext;
componentDidMount() {
console.log(this.context); //sy-log
}
render() {
return <div>呵呵呵</div>;
}
}
//useContext
function Child(props) {
const context = useContext(TheContext);
console.log(context); //sy-log
return <div>哈哈哈</div>;
}
//Consumer 函数类组件都可
function Child(props) {
return (
<div>
<TheContext.Consumer>
{(context) => {
console.log(context); //sy-log
return <div>嗯嗯嗯</div>;
}}
</TheContext.Consumer>
</div>
);
}
3. 类组件中的setState如何使用
setState(updater, [callback]);
update:object | function。如果是object,则会与类组件中上次的state对象合并,如果是函数,(prevState, nextProps)=>{ return newState };
callback:可选,回调函数。
类组件中的setState是批量更新,即我们常说的异步。但是在原生事件和setTimeout中是同步的。
二、React原理
1. key值有什么用?
key标记了节点在当前层级下的唯一性,和组件类型一起用于diff时候判断节点是否可以复用、是否要删除、是否要新增。
2. setState是同步还是异步
有时表现出异步,有时表现出同步。
- setState在合成事件和钩子函数中是异步的,在原生事件和 setTimeout 中是同步的。
- 这里的异步并不是说setState内部由异步代码实现,而是指setState的更新是批量更新,也就是说合成事件和钩子函数是在state更新之前就调用了,导致无法立即取到state更新之后的值,当然如果你需要再state更新之后做某些操作,则可以使用setState(partialState, callback)的callback参数。
- setState在原生事件中是同步的,主要是因为React源码无法确定原生事件是什么时候注册的,不像合成事件,组件渲染的时候就可以通过合成事件的映射表得知。
- 而如果setState定义在setTimeout中,由于setTimeout本身就是异步,就算是合成事件,等setTimeout里的setState要执行的时候,批量更新的标记也已经失效了,因此表现出来的依然是同步。
3. 什么fiber
fiber在React源码中表现为一个节点对象,仍然是虚拟dom,表现为单链表结构。其中child属性是第一个子fiber节点,sibling是下一个兄弟节点。
- 在React16之前,React组件的子组件是一个children数组,而这些子组件的递归渲染只和children数组中的下标位置相关。
- 对于大型项目来说,组件树会非常大,那么递归遍历也就会非常耗时耗力,这个时候一些较高优先级的任务,如动画任务、和用户交互的任务(如input的onChange)等就应该被早点执行,不然用户可能就会看到卡顿的现象,这对用户体验是极其不利的。
- 为了要解决上面的问题,可以给不同的任务添加优先级。要做到这一点,首先节点的颗粒度也要够小,这就是后来出现的fiber节点。
- 再者,如果一个任务a正在执行,但是这个时候来了更高优先级的任务b,那就要中断a,去执行b。也就是说,任务是需要可中断的。并且,被迫中断的a并不是要被抛弃掉,可能执行完b以后还是要再执行a的,那这个时候就需要a和b之间有个指针关系,不然执行完b了,怎么能找到a呢?所以fiber是个单链表结构。
4. 说说react中的diff
算法复杂度:O(n)
React中这个diff算法复杂度的前提是:深度优先遍历、同级比较、只有key值和类型完全相同的组件才可以复用。
接下来以下图为例,来说一下React diff的流程:
假设上面的树为老的虚拟dom节点,下面的树为新的虚拟dom节点,圆形和多边形节点标识不同的组件类型,大写字母标识节点的key。
- 按照深度优先遍历,先A再B,然后D,但是两个新老树里D组件类型不同,老E和新D的key不同,都无法复用,因此新增新D。
- 新的D节点没有子节点,因此下一步是G节点。虽然老树里有G节点,但是和新树里的G节点不是同一层级,因此无法复用。因此新G需要新增。
- 这样新树B节点下的DG都需要新增,老树下的DE都需要被删除。
- 新树的G节点没有子节点和兄弟节点,然后通过G的爸爸B节点找到叔叔C节点,新老树下都有C节点,并且同一层级下,可以复用,虽然相对位置不同,但是没关系可以移动位置。
- 老C下有G,新C的没有,则老树的C需要被删除。
- 新C没有子节点,下一步是兄弟H节点,这个时候新老H的key和组件类型都相同,还是同一层级下,因此复用就可以了。
- OVER。
三、状态管理库
1. 为什么要使用状态管理库
目前任何一个状态管理库都不是强制使用的,也有很多精小的项目不使用第三方状态管理库,而只是使用React自身的state、useContext等API就可以达到目的。当然,对于大型项目,还是建议使用一个状态管理库,毕竟项目越大,需要管理、共享的状态越多,这个时候为了避免data层与view层变成一锅粥,还是使用个状态管理库吧。
2. React目前常见的状态管理库有哪些,比较一下
redux、mobx、recoil(实验阶段)。
- redux是集中式管理state,而recoil和mobx都是分散式。
- recoil中状态的读写都是Hooks函数,目前没有提供类组件的使用方式。
- recoil是Facebook开发的,可以使用React内部的调度机制,这是redux和mobx不支持的。
- recoil目前还是实验阶段,想要应用到的自己的项目中,等待正式版发了再说吧。
3. redux如何实现异步
redux本身是不支持异步的,可以使用中间件,redux-thunk、redux-saga。
四、路由管理库
1. Route渲染组件有哪些方式?
Route有三种互斥的组件渲染方式,按照优先级高低分别是children、component、render。
2. Route渲染组件的三种方式,有什么不同?
children是不管是否匹配都会渲染,而component和render都是匹配了才会渲染。另外,children和render的参数是函数,而component的参数是React组件。
当你用component的时候,Route会用你指定的组件和React.createElement创建一个新的[React element]。这意味着当你提供的是一个内联函数的时候,每次render都会创建一个新的组件。这会导致不再更新已经现有组件,而是直接卸载然后再去挂载一个新的组件。因此,当用到内联函数的内联渲染时,请使用render或者children。
react-router中Route的render源码如下: