19、React的事件代理机制
React并不会把所有的处理函数直接绑定在真实DOM节点上,而是把所有的事件绑定到结构的最外层,使用一个统一的事件监听器,这个事件监听器上维持了一个映射来保存所有组件内部的事件监听和处理函数。
当组件挂载或卸载时,只是在这个统一的事件监听器上插入或删除一些对象。
当事件发生时,首先被这个统一的事件监听器处理,然后在映射里找到真正的事件处理函数并调用。
优点:解决了兼容性的问题,简化了事件处理和回收机制(不需要手动的解绑事件,React已经在内部处理了)
缺点:有些事件React并没有实现,比如window的resize事件。
20、 常见的Hook
- 状态钩子(useState):用于定义组件的State,类似类定义中的this.state的功能。
- 生命周期钩子(useEffect):类定义中有许多生命周期函数,而在React Hooks中也提供了一个相应的函数(useEffect),这里可以看做componentDidMount、componentDidUpdate和componentWillUnmount的结合
- useContext:获取context对象
- useCallback:缓存回调函数,避免传入的回调每次都是新的函数实例,而导致依赖组件重新渲染,具有性能优化的效果
- useMemo:用于缓存传入的props,避免依赖的组件每次都重新渲染
- useRef:获取组件的真实节点
21、 react-router里的Link标签和 a 标签
两个标签对比,Link避免了不必要的重新渲染。
react-router是伴随着react框架出现的路由系统,他是公认的一种优秀的路由解决方案,在使用react-router的时候,我们经常使用其自带的路径跳转组件Link从而实现跳转。
react-router接管了其默认的链接跳转行为,区别传统的页面跳转,Link的“跳转”行为只会触发相匹配的对应的页面内容更新,而不会刷新整个页面。
Link跳转做了三件事情:
- 有onclick就执行onclick
- click的时候组织a标签默认事件
- 根据跳转href,用history跳转,此时只是链接变了,并没有刷新页面
a标签就是普通的超链接了,用于从当前页面跳转到href指向的另一个页面。
22、React Hooks的优势
hooks是react 16.8引入的特性,其允许你在不写class的情况下操作state和react 的其他特性。
React Hooks要解决的问题是状态共享,是继reader-props和higher-order components 之后的第三种状态共享方案,不会产生JSX嵌套地狱问题。这个状态指的是状态逻辑,所有被称为状态逻辑复用,因为只共享数据处理逻辑,不会共享数据本身。
23、 React中的diff算法
diff算法主要基于三个规律:
- DOM节点的夸层级移动的操作特别少,可以忽略不计
- 拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构
- 对于同一层级的一组子节点,可以通过唯一的id进行区分
tree diff
因为上面的三个策略中的第一点,DOM节点的跨级操作比较少,那么diff算法只会对相同层级的DOM节点进行比较。如果发现节点不存在,那么会将节点以及子节点完全删除,不会再继续比较。如果出现了DOM节点的跨层级的移动操作,那么会删除及诶单以及其所有的子节点,然后在移动后的位置重新创建。
component diff
如果是同一类型的组件,那么会继承对比VM数。如果不是同一类型的组件,那么会将其和其子节点完全替换,不会再进行比对。同一类型的组件,有可能VM没有任何的变化,如果可以确定的知道这点,那么就可以节省大量的diff时间,所以用户可以设置shouldComponentUpdate()来判断是否需要进行diff算法。
element diff
当节点处于同一层级时,有三种操作:INSERT_MAKEUP插入、MOVE_EXISTING移动、REMOVE_NODE删除。
这里React有个优化策略,对于同一层级的同组子节点,添加唯一的key进行区分。这样的话,就可以判断出来是否是移动节点。通过key发现新旧集合中的节点都是相同的节点,就只需要进行移动操作就可以了。
24、React中,能否直接将props的值复制给state?
应该避免这种写法:
constructor(props) {
super(props);
// 不要这样做
this.state = { color: props.color };
}
因为这样做毫无必要(可以直接使用this.props.color),同时还产生了bug(更新prop中的color时,并不会影响state)。只有你在刻意忽略props更新的情况下使用。
此时,应将props重新命名为initialColor或defaultColor。必要时,可以修改它的key,以强制重置其内部state。
25、React Hooks当中的userEffect是如何区分生命周期钩子的
useEffect可以看成是componentDidMount,componentDidUpdate和componentWillUnmount三者的结合。
useEffect(callback,[source])接收两个参数,调用方式如下:
useEffect(() => {
console.log('mounted');
return () => {
console.log('willUnmount');
}
}, [source]);
生命周期函数的调用主要是通过第二个参数[source]来进行控制,有如下集中情况:
- [source]参数不传时,则每次都会优先调用上次保存的函数中返回的那个函数,然后再调用外部那个函数。
- [source]差数传[ ]时,则外部的函数只会在初始化时调用一次,返回的那个函数也只会最终在组件卸载时调用一次。
- [source]参数有值时,则只会监听到数组中的值发生变化后才优先调用返回的那个函数,再调用外部的函数。
26、 为什么不能用数组下标来作为react组件中的key?
react使用diff算法,使用key来做同级对比。如果使用数组下标作为key,有一下情况:
- 在数组头部或者中部插入或删除元素:所有key对应的节点的值发生更改,进行重新渲染。造成性能损耗。
- 使用数组唯一值来作为key:不管在何处插入或删除节点,其他key对应的节点的值未发生更改,只需插入或删除操作的数组节点。
27、React Fiber是什么?
Fiber背景
JavaScript引擎和页面渲染引擎两个线程是互斥的,当其中一个线程执行时,另一个线程只能挂起等待。在这种机制下,如果JavaScript线程长时间的占用了主线程,那么渲染层面的更新就不得不长时间的等待,界面长时间不更新,会导致页面响应度变差,用户可能会感觉到卡顿。
而这正是React 15 的Stack Reconciler 所面临的问题,即是JavaScript对主线程的超时占用问题。Stack Reconciler是一个同步的递归过程,使用的是JavaScript引擎自身的函数调用栈,它会一直执行到栈空为止,所以当React在渲染组件时,从开始到渲染完成整个过程是一气呵成的。如果渲染的组件比较庞大,js执行会占据主线程较长时间,会导致页面响应度变差。
而且所有的任务都是按照先后顺序,没有区分优先级,这样就会导致优先级比较高的任务无法被优先执行。
Fiber是什么
Fiber的中文翻译叫纤程,与进程、线程同为程序执行过程。Fiber就是比线程还要纤细的一个过程。纤程意在对渲染过程实现进行更加精细的控制。
从架构角度来看,Fiber是对React核心算法(即调和过程)的重写。
从编码角度来看,Fiber是对React 内部所定义的一种数据结构,他是Fiber 树结构的节点单位,也就是React 16 新架构下的虚拟DOM。