React常见面试题——原理篇
程序员文章站
2022-06-13 10:37:19
...
函数式编程
- 函数式编程是一种编程范式, 对过程进行抽象,将数据以输入输出流的方式封装进过程内部, 从而也降低系统的耦合度。
-
纯函数:即相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用, 副作用包括但不限于:
- 打印/log
- 发送一个http请求
- DOM查询
- 可变数据
- 简单一句话, 即只要是与函数外部环境发生交互的都是副作用。
- 像js中的slice是纯函数,splice则不是纯函数
vdom和diff
- 对于 vdom 和 diff,h 函数、vdom 数据结构、patch 函数。只比较同一层级,不跨级比较。tag 不相同,则直接删掉重建,不再深度比较。tag 和 key,两者都相同,则认为是相同节点,不再深度比较
简述diff算法过程.
jsx本质
- JSX的本质即
React.createElement
函数,React.createElement 即 h 函数,返回vnode - React.createElement的第一个参数是渲染的标签,第二个参数为标签的属性,之后的为子元素
- 第一个参数,可能是组件,也可能是一个html tag(组件和tag区分是大小写,组件名首字母必须要大写)
- 函数返回值是一个
vnode
-
vnode
在通过patch
函数进行渲染
// 如下是一个循环的编译
const listElem = <ul>
{this.state.list.map((item, inedx) => {
return (<li key={item.id}>
index: {index}; title:{item.titlle}
</li>)
})}
</ul>
// 编译结果如下:
var listElem = /*#__PURE__*/React.createElement("ul", null, (void 0).state.list.map(function (item, inedx) {
return /*#__PURE__*/React.createElement("li", {
key: item.id
}, "index: ", index, "; title:", item.titlle);
}));
合成事件
- 所有的事件都挂在到
document
上 - event不是原生的,是
SyntheticEvent
合成事件对象 - 和Vue事件不同,和DOM事件也不同
- DOM事件会冒泡到document上,document会生成一个统一的React event,在合成事件层会派发事件(通过target可以获取到哪个元素触发的事件,找到这个元素所在组件这个)
-
为什么要合成事件机制
?- 更好的兼容性和跨平台
- 挂在到document,减少内存消耗,避免频繁解绑
- 方便事件的统一管理(如事物机制)
setState和batchUpdate
-
setState
有时异步(普通使用),有时同步(setTimeout, 自己定义的DOM事件) -
有时合并(对象形式), 有时不合并(函数形式)
-
调用了setState之后,newState会被存入pending队列
-
是否处于batch update机制中
-
处于batch update机制
中, 保存组件与dirtyComponent(state被更新的component)中(走异步操作) -
不处于batch update机制
中,遍历所有的dirtyComponent,调用updateCompoennt,更新pending state or props(走同步操作)
-
-
如何判断是否处于batch update中,是需要根据
isBatchingUpdates
进行判断 -
因此:setState无所谓同步和异步,需要看是否命中batchUpdate机制,是否命中batchUpdate机制的判断就是
isBatchingUpdate
increase(){
// 开始:处于batch update
// isBatchingUpdate = true
this.setState({
count: this.state.count+1
})
// 结束
// // isBatchingUpdate = false
}
// 在increase1中,当执行到setState时,isBatchingUpdate 已经为false
increase1(){
// 开始:处于batch update
// isBatchingUpdate = true
setTimeout(() => {
this.setState({
count: this.state.count+1
})
}, 0)
// 结束
// // isBatchingUpdate = false
}
componentDidMount(){
// 开始:处于batch update
// isBatchingUpdate = true
document.body.addEventListener('click', () => {
this.setState({
count: this.state.count+1
})
console.log('count in body event', this.state.count);
})
// 结束
// // isBatchingUpdate = false
}
- 那些能命中batchUpdate机制?
- 生命周期(和它调用的函数)
- React中注册的事件(和它调用的函数)
- React可以 “管理” 的入口
- 哪些不能够命中 batchUpdate 机制
- setTimeout、setInterval 等和它调用的函数
- 自定义的 DOM 事件和它调用的函数
- React 管不到的入口
trantransaction事务机制
- 对于
transition
事务机制,开始处于batchUpdate
,isBatchUpdates = true
,其它任何操作,结束,isBatchUpdates = false
increase(){
// 开始:处于batch update
// isBatchingUpdate = true
// 其它任何操作
// 结束
// // isBatchingUpdate = false
}
组件渲染和更新过程
-
渲染过程
- 有了
props
和state
-
render()
生成vnode
patch(elem, vnode)
- 有了
-
更新过程
-
setState
()—>dirtyComponents
(可能有子组件) - 执行
render
函数,根据新的state
和props
生成newVnode
patch(vnode, newVnode)
-
-
patch 更新的两个阶段
-
reconciliation
阶段——执行diff算法,纯JS计算 -
commit
阶段——将diff结果渲染DOM
-
-
可能出现的性能问题(如果不将patch更新分为两个阶段的话可能出现的心梗问题)
- JS 是单线程的,且和 DOM 渲染共用一个线程
- 当组件足够复杂的时候,组件更新时计算和渲染都压力大
- 同时再有 DOM 操作需求,如动画、鼠标拖拽等,将卡顿
-
fiber
- 将
reconciliation
阶段进行任务拆分(但是 commit 无法拆分) - DOM 需要渲染时暂停更新,空闲时修复
- 如何知道DOM需要渲染呢?是需要借助于
window.requestIdleCallback
- 将