# 1-React中hooks的优缺点是什么
在react中,hooks是一个非常抽象的概念,对初学者,往往不是很友好,比较一下React中hooks的优缺点
# 优点
[1]. 代码的可读性强,使用hooks之前,发布/订阅自定义事件需要挂载到componentDidMount生命周期上面,然后需要在componentWillUnmount生命周期中清除,在使用hooks之后,通过useEffect,可以把componentDidMount生命周期,componentDidUpdate生命周期,还有componentWillUnmount生命周期集中在一起,方便代码的维护
[2]. 组件的层级更浅,在使用hooks之前,通常使用高阶组件Hoc的方法来实现多个组件共用状态,增强组件的功能,这样是增加了组件渲染的开销,影响了性能,但是在Hooks中可以使用自定义hooks组件useXXx()的方法将多个组件之间共用的状态放到自定义hooks就可以轻松的做到状态的共享
[3]. 不用在考虑class类组件this的指向问题了,在hooks组件中不需要使用this.state来获取数据和方法了
[4]. 可以从组件中提取状态逻辑,使得这些逻辑可以单独测试并复用,Hook使你在无需修改组件结构的情况下复用状态逻辑,这使得在组件间共享Hook变得更便捷,也就是可以大大减少冗余的代码,尤其是针对那些需要复用逻辑的场景
[5]. 没有破坏性改动,Hook不会影响对React概念的理解,Hook为已知的React概念提供了更直接的API,props,state,context,refs以及生命周期,同时Hook提供了一种强大的方式来组合它们
[6]. 更易于测试: 由于hooks是纯JavaScript函数,因此他们易于编写单元测试并模拟
# 缺点
[1]. 一个useEffect里面不能写太多东西,把每个不同的功能分给多个useEffect来使用,分成多个模块,把每个功能块分开来写遵循了软件设计当中的单一职责模式,hooks的useEffect只包括conponentDidMount,componentDidUpdate和componentWillUnmount这三个生命周期,对于其他的class类组件的生命周期却不支持
[2]. 不要在class组件中调用hook,这样是无效的,不能完全模拟类组件的生命周期,虽然可以使用useEffect hook来模拟,但是它使用起来需要更多的思考和规划
[3]. Hooks是一种新的特性,存在一些兼容性的问题,相对类组件方式,学习曲线比较陡峭,需要一些时间来适应这种编程模式
React中hooks的优点主要是提高了代码的可读性和性能,方便代码的维护和迭代,同时也可以更好地实现状态共享,但是也需要在使用中结合具体的场景和需求来选择最合适的方式,无非就是有了hook,多了一种技术方案选择
# 2-React事件绑定原理
在React中,事件绑定采用驼峰命名方式,而不是DOM元素中的小写字母命名方式,例如:onclick要写成onClick,onchange要写成onChange等
React中绑定的事件不是原生事件,而是由原生事件合成的React事件,例如:click事件合成为onClick事件,blur,change,input,keydown,keyup等合成为onChange,React这么做的原因是为了消除不同浏览器之间的差异
React事件的工作原理主要分为以下几个步骤
[1]. 收集事件监听器:React会将事件监听器收集到一个数组中,其中包括目标元素的监听器和根元素的监听器
[2]. 获取所有事件:React会将所有事件名处理成domEventName和reactEventName,即react事件名和dom事件名的对应关系
React会遍历simpleEventPluginEvents列表,将事件名处理成domEventName和reactEventName,例如:click事件对应着onClick事件
一共有75个映射关系,registationNameDependencies则保存着react事件名和依赖事件名之间的关系,例如:onClick事件依赖于click事件
[3]. 特殊处理:对于onDoubleClick,onFocus和onBlur这三个事件,他们的reactEventName与对应的domEventName不通,因此需要特殊处理
[4]. 收集合成事件:React会将event对象处理成合成事件,为了消除不同浏览器之间的差异,React设计了normalize函数来将event对象处理成合成事件
如果normalize存在,说明propName对应的属性在合成事件中是一个函数,否则,propName对应的属性是一个原生事件
在React v17中,React不会在将事件处理添加到document上,而是将事件处理添加到渲染React树的根DOM容器中
const rootNode = document.getElementById('root');
ReactDOM.render(<App />,rootNode);
2
在React16及之前版本中,React会对大多数事件进行document.addEventListener()操作,React V17开始会通过调用rootNode.addEventListener()来代替
总之,React事件绑定的原理是通过使用合成事件来将浏览器原生事件(如click、keyup等)封装成一个跨浏览器可靠的事件池
在React组件中通过使用事件处理函数来监听这些合成事件。React使用了一些优化策略来提高事件绑定的性能和效率
比如,React会在组件卸载时自动销毁事件绑定。另外,React还支持一些高级特性,如事件代理、事件委托以及捕获和冒泡等。
可以通过event对象来获取事件的相关信息,如事件类型、触发元素、按下的键等
在使用React事件绑定时,应该避免直接操作DOM元素,而应该通过调用组件的setState方法来实现状态更新从而触发重新渲染
# 3-React中setState是同步还是异步的?
在React中,setState既可以是同步的也可以是异步的,这取决于执行时机和执行的上下文
[1]. setState的异步并不是指内部由异步代码实现的,实际上执行的过程和代码都是同步的,只是合成事件和钩子函数调用的顺序在更新之前,导致在合成事件和钩子函数中不能立即看到state的变化,而在原生事件和setTimeout中,setState是同步的
[2]. 在React17中,setState是批量执行的,因为执行前会设置executionContext,但如果在setTimeout,事件监听器等函数中,就不会设置executionContext的,这时候setState会同步执行,可以在外面包一层batchUpdates函数,手动设置下excutionContext来切换成异步批量执行
[3]. 在React的渲染流程中,setState会创建update对象挂到fiber对象上,然后耨调度performSyncWorkOnRoot重新渲染,一个主要任务的先后顺序是:render阶段render函数执行-->commit阶段真实DOM替换--->setState回调函数执行callback,因此,可以看出setState的执行顺序是在render之后,commit之前
[4]. 如果ExecutionContext为0,表示当前没有正在进行的其他任务,则setState是同步的,在React的源码中,当ExecutionContext为0时,setState是同步的
[5]. 批量更新:多个顺序的setState不是同步的一个一个执行的,而是会一个一个加入队列,然后最后一起执行,在合成事件和生命周期钩子中,setState更新队列时,存储的是合并状态,因此,前面设置的key值会被后面的覆盖,最终只会执行依次更新
综上所述,setState既可以是同步的也可以是异步的,具体取决于执行时机和执行的上下文,在React中,如果需要手动控制setState的异步执行
也就是在合成事件和生命周期函数中是异步的,在原生事件和定时器中都是同步的,setState本身不分同步或异步,而是取决于是否处于batch update中
可以使用batchUpdates函数手动设置excutionContext来切换成异步批量执行,同时,在合成事件和生命周期钩子中,setState更新队列时,会存储合并状态,因此需要注意key值的覆盖问题
← React 面经


