Vue 与 React 的区别
-
Vue 使用模版语法开发,框架本身封装了很多 api(v-if v-show)等可以很方便的去实现数据渲染、数据与试图层是双向绑定(object.defineProperty、getter,setter,发布订阅),不需要特别的优化手段也能达到良好的性能;
-
React 使用 JSX 语法,框架本身没有 vue 的那些语法糖,数据视图渲染需要自己去完成、React 是单向数据流,强调数据的安全与不可变。只能通过 setState 去改变数据,如果没有使用优化手段可能会导致一些性能上的浪费
React render 执行两次
主要是因为严格模式(StrictMode),在开发环境中通过调用两次的方式将一些隐藏比较深的副作用放大,让开发者更好的发现
class 组件与 function 组件的本质区别
类组件底层只需要实例化一次,实例中保存了组件的 state 等状态,每次更新只需要调用 render 方法以及对应的生命周期;函数组件中,每一次更新都是一次新的函数执行,一次函数组件更新,里面的变量都会重新声明
state 到底是同步还是异步
关键点:batchUpdate(批量更新),批量更新被打破的条件(Promise/setTimeOut)
触发 setState 流程:
- 首先判断更新优先级(expirationTime/lane)
- 从 fiber 根部开始向下调和子节点,调和阶段对比发生更新的地方,找到发生更新的组件,合并 state,触发 render,获得新的视图,完成 render
- commit 阶段,替换真实 dom,完成更新,如果 setState 中有 callback,执行 callback,完成一次 setState 全过程
原理:类组件初始化时绑定了负责更新的 updater 对象,调用 setState 的时候实际上是调用 Updater 对象上的 enqueueSetState 方法
// 创建update,放入当前fiber对象的待更新队列中,开启调度
enqueueSetState(){
/* 每一次调用`setState`,react 都会创建一个 update 里面保存了 */
const update = createUpdate(expirationTime, suspenseConfig);
/* callback 可以理解为 setState 回调函数,第二个参数 */
callback && (update.callback = callback)
/* enqueueUpdate 把当前的update 传入当前fiber,待更新队列中 */
enqueueUpdate(fiber, update);
/* 开始调度更新 */
scheduleUpdateOnFiber(fiber, expirationTime);
}
// 通过isBatchingEventUpdates = true 开启事件批量更新,事件结束再通过isBatchingEventUpdates = false 关闭,然后在scheduleUpdateOnFiber中根据开关来确定是否进行批量更新
function batchedEventUpdates(fn,a){
/* 开启批量更新 */
isBatchingEventUpdates = true;
try {
/* 这里执行了的事件处理函数, 比如在一次点击事件中触发setState,那么它将在这个函数内执行 */
return batchedEventUpdatesImpl(fn, a, b);
} finally {
/* try 里面 return 不会影响 finally 执行 */
/* 完成一次事件,批量更新 */
isBatchingEventUpdates = false;
}
}
在异步环境下继续开启批量更新模式(unstable_batchedUpdates)
setTimeout(()=>{
unstable_batchedUpdates(()=>{
this.setState({ number:this.state.number + 1 })
console.log(this.state.number)
this.setState({ number:this.state.number + 1})
console.log(this.state.number)
this.setState({ number:this.state.number + 1 })
console.log(this.state.number)
})
})
//log 0,0,0,callback1,1,callback2,1,callback3,1
React 提供 flushSync 可以将更新任务放在一个较高的优先级中
handerClick=()=>{
setTimeout(()=>{
this.setState({ number: 1 })
})
this.setState({ number: 2 })
ReactDOM.flushSync(()=>{
this.setState({ number: 3 })
})
this.setState({ number: 4 })
}
render(){
console.log(this.state.number)
return ...
}
// log 3 4 1
函数组件中的批量更新
export default function Index(props){
const [ number , setNumber ] = React.useState(0)
/* 监听 number 变化 */
React.useEffect(()=>{
console.log('此时的number是: ' + number )
},[ number ])
const handerClick = ()=>{
/** 高优先级更新 **/
ReactDOM.flushSync(()=>{
setNumber(2)
})
/* 批量更新 */
setNumber(1)
/* 滞后更新 ,批量更新规则被打破 */
setTimeout(()=>{
setNumber(3)
})
}
console.log(number)
return <div>
<span> { number }</span>
<button onClick={ handerClick } >number++</button>
</div>
}
/*log
2
此时number是:2
1
此时number是:1
3
此时number是:3
*/
注意事项 useState 中,当调用 setState 的时候,在本次函数执行上下文中是获取不到最新的 state 的值
类组件的 setState 和函数组件中的 useState 异同点
相同点:setState 和 useState 底层都调用 scheduleUpdateOnFiber 方法,而且事件驱动的情况下都有批量更新规则
不同点
-
在不是 pureComponent 组件的模式下,类组件 setState 不会浅比较两次 state 的值,直接调用 setState,函数组件 useState 中的 setState 会默认比较两次 state 是否相同,再决定是否更新
-
类组件 setState 有专门的监听 state 变化的 callback,可以获取最新的 state,函数组件中只能通过 useEffect 来执行 state 变化引起的副作用
-
类组件的 setState 底层逻辑是和老的 state 进行合并处理,函数组件的 useState 是重新赋值
生命周期
QA
Q:useEffect 和 useLayoutEffect 的区别
A:useLayoutEffect 是同步执行,useEffect 为异步; useLayoutEffect 的执行时机是在 DOM 更新之后,浏览器绘制之前。useEffect 是在浏览器绘制之后,一句话概括就是修改 DOM,改变布局用 useLayoutEffect,其他情况用 useEffect
Q:useEffect 和 componentDidMount/componentDidUpdate 执行时机有什么区别?
A:useEffect 对 React 执行栈来看是异步执行的,而 componentDidMount/componentDidUpdate 是同步执行的,useEffect 不会阻塞浏览器渲染绘制,时机上 componentDidMount/componentDidUpdate 和 useLayoutEffect 更类似