7、 为什么不能直接使用this.state改变数据?
React中不能直接修改state,因为并不会重新出发render。需要使用setState()方法。状态改变时,组件通过重新渲染做出响应。
setState通过一个队列机制来实现state更新。当执行setState时,会将需要更新的state合并后放入状态队列,而不会立刻更新this.state。队列机制可以高效的批量更新state,如果不通过setState而直接修改this.state,那么该state将不会被放入状态队列中,当下次调用setState并对状态队列进行合并时,将会忽略之前直接被修改的state,而造成无法预知的错误。
8、 React中如果绑定事件使用匿名函数有什么影响?
class Demo{
render(){
return(<button onClick={(e)=>{console.log("点击按钮")}}>
按钮
</button>)
}
}
这样的写法,因为使用的是匿名函数,所以组件每次都会被认为是一个新的props,不会使用缓存优化,在性能上会有一定的损耗。
9 、React的事件代理机制和原生事件绑定混用会有什么问题?
在我们平常的开发中应该尽可能的避免React的事件代理机制和原生事件绑定混用。
React 的合成事件层,并没有事件绑定到DOM元素上,所以使用e.stopRpopagation()来阻止原生DOM的冒泡的行为是不行的。阻止React事件冒泡的行为只能用于React合成事件系统,但是在原生事件中的组织冒泡行为,却可以阻止React合并事件的传播。
10、React的事件代理机制和原生事件绑定有什么区别?
- 事件传播与阻止事件的传播:React的合成事件并没有实际事件捕获,只支持了事件冒泡。阻止事件传播React做了兼容性处理,只需要e.preventDefault()即可,原生存在兼容性问题。
- 事件类型:React是原生事件类型的一个子集(React只是实现了DOM level3的事件接口,有些事件React并没有实现,比如window的resize事件。)阻止React事件冒泡的行为只能用于React合并事件系统,但是在原生事件中的组织冒泡行为,却可以阻止React合成事件的传播。
- 事件的绑定方式:原生事件系统中支持多种不同的绑定事件的方式,React中只有一种。
- 事件对象:原生中存在IE的兼容性问题,React做了兼容处理。
11、React中为什么要给组件设置key?
在开发过程中,我们需要保证某个元素的key在其同级元素中具有唯一性。
在React Diff算法中React会借助元素的Key值来判断该元素是新创建的还是被移动而来的元素,从而减少不必要的元素重新渲染。此外,React还需要借助Key值来判断元素与本都状态的关联关系。
12、setState之后发生了什么?
简述:React利用状态队列机制实现了setState的“异步”更新,避免频繁的重复更新state。首先将新的state合并到状态更新队列分钟,然后根据更新队列和shouldComponentUpdate的状态来判断是否需要更新组件。
深层:
-
enqueueSetState将state放入队列分钟,并调用enqueueUpdate处理要更新的Component。
-
如果组件当前正处理update事务中,则先将Component存入dirtyComponent中。否则调用batchedUpdates处理。
-
batchedUpdates发起一次transaction.perform()事务
-
开始执行事务初始化,运行,结束三个阶段
- 初始化:事务初始化阶段没有注册方法,故无方法要执行
- 运行:执行setState时传入的callback方法
- 结束:更新isBachingUpdates为false,并执行FLUSH_BATCHED_UPDATES这个wrapper中的close方法,FLUSH_BATCHED_UPDATES在close阶段,会循环遍历所有的 dirtyComponents,调用updateComponent 刷新组件,并执行它的 pendingCallbacks, 也就是 setState 中设置的 callback。
13、在shouldComponentUpdate或componentWillUpdate中使用setState会发生什么?
当调用setState时,会将新的state合并到状态更新队列中,并对paratialState以及pendingStateQueue更新队列进行合并操作。最终通过enquueUpdate执行state更新。如果在 shouldComponentUpdate 或 componentWillUpdate 中使用 setState,会使得 state 队列( pendingStateQueue)不为 null,从而调用 updateComponent 方法,updateComponent 中会继续调用 shouldComponentUpdate 和 componentWillUpdate,因此造成死循环。
14、React中的ref有什么用?
使用refs获取组件被调用时会新建一个该组建的实例。refs会指向这个实例,可以是一个回调函数,回调函数会在组件被挂载之后立即执行。
如果吧refs放到原生DOM组件的input中,我们就可以通过refs得到DOM节点;如果把refs放到React组件中,那得到的是组件的实例,因此就可以调用实例的方法(如果想访问该组件的真实DOM,那么可以用React.findDomNode来找到DOM节点,但不建议使用次方法。)
refs无法用于无状态组件,无状态组件挂载时只是方法调用,没有新建实例。在v16.8之后,可以使用hooks中的useRef。
15、React 的虚拟dom是怎么实现的?
React是把真实DOM树转化为JS对象树,也就是Vitual DOM。每次数据更新后,重新计算VM。并和上一次生成的VM树进行对比,对发生变化的部分进行批量更新。除了性能之外VM的实现最大的好处在于和其他平台的集成。例如:
<button class="btn">
<span>this is button</span>
</button>
在转化为VM之后就变成:
{
type:'button',
props:{
className:"btn",
props:{
type:"text",
children:"this is button"
}
}
}
16、为什么React的VM可以提高性能?
因为VM并不是真实的操作DOM,通过diff算法可以避免一些不必要的DOM操作,从而提高了性能。
17、React中的VM一定会提高性能吗?
不一定。因为VM只是通过diff算法避免了一些不需要变更的DOM操作,最终还是要操作DOM的,并且diff 的过程也是有成本的。
对于某些场景,比如都是需要变更DOM的操作,因为VM还会有额外的diff算法的成本在里面,所以VM的方式并不会提高性能,甚至比原生DOM要慢。
框架的意义在于为你掩盖底层的DOM操作,让你用更声明式的方式来描述你的目的,从而让你的代码更容易维护。
没有任何框架可以比纯手动的优化DOM操作更快,因为框架的DOM操作层需要应对任何上层API可能产生的操作,它的实现必须是普适的。
18、 React Kooks带来了什么便利?
在没有hooks之前,我们使用函数定义的组件中,不能使用React的state、各种生命周期钩子类组件的特性。在React16.8之后,推出了新功能:Hooks,通过hooks我们可以在函数定义的组件中使用类组件的特性。
好处:
-
跨组件复用:其实render、props/HOC也是为了复用,相比于它们,Hooks作为官方 底层的API,最为轻量,而且改造成本小,不会影响原来的组件层次结构和传说中的嵌套地狱
-
相比而言,类组件的实现更为复杂:
- 不同的生命周期会使逻辑变得分散且混乱,不易维护和管理
- 时刻需要关注this的指向问题
- 代码复用代价高,高阶组件的使用经常会使整个组件树变得臃肿
-
状态与UI隔离:正是由于Hooks的特性,状态逻辑会变成更小的粒度,并且极容易被抽象成一个自定义的Hooks,组件中的状态和UI变得更为清晰和隔离
注意
- 避免在循环、条件判断、嵌套函数 中调用hooks,保证调用顺序的稳定
- 不能在useEffect中使用useState,React会报错提示
- 类组件不会替换或废弃,不需要强制改造类组件,两种方式能并存。