- React 生命周期函数
- setState是同步的还是异步的?setState使用需要注意哪些点? setState 之后发生了什么?为什么多次setState只一次生效?
- React如何实现自己的事件机制?为何React事件要自己绑定this?原生事件和React事件的区别?React的合成事件是什么?React和原生事件的执行顺序是什么?可以混用吗?
- 虚拟Dom是什么?
- 类组件(Class component)和函数式组件(Functional component)之间有何不同
- React如何实现自己的事件机制?为何
React
事件要自己绑定this
? - HOC 为什么会出现? 实现原理? 实现方式? 使用场景?
- 对 react 虚拟dom理解
- 谈谈你对redux的理解?说说Redux源码
- 谈谈你对fiber架构的理解?为何 react 有时间切片而 vue 不需要
- React 渲染流程, react 执行过程吗
- 谈下对react hook的理解
- React 闭包陷阱有什么好的解决办法吗?(说实话,简历上写了,实际上理解不深,只说了一种) useReducer 可以解决你知道吗? (当时真的不清楚,主要忘了闭包陷阱的表单场景,
- React新特性? React.memo, 三个场景,中间一个场景
- redux-saga原理
![b6bf9d8cbd6bdd3e6d49accfb7f044ca.png](https://i-blog.csdnimg.cn/blog_migrate/a22b85d568f2a83bde61b410ec095f72.jpeg)
1. React 生命周期函数?
- componentWillMount: 在渲染之前执行,用于根组件中的应用程序级别配置。
- componentDidMount: 仅在客户端的第一次渲染之后执行。 这是AJAX请求和DOM或状态更新应该发生的地方。此方法也用于与其他JavaScript框架以及任何延迟执行的函数(如
setTimeout
或setInterval
)进行集成,在这里使用它来更新状态,以便我们可以触发其他生命周期方法。 - componentWillReceiveProps: 只要在另一个渲染被调用之前更新
props
就被调用。 当我们更新状态时,从setNewNumber
触发它。 - shouldComponentUpdate: 确定是否将更新组件。默认情况下,它返回true。如果您确定组件在状态或道具更新后不需要渲染,则可以返回false值。这是提高性能的好地方,因为如果组件收到新的道具,它可以防止重新渲染。
- componentWillUpdate: 在由shouldComponentUpdate确认返回正值的优点和状态更改时,在重新渲染组件之前执行。
- componentDidUpdate: 通常用于更新DOM以响应属性或状态更改。
- componentWillUnmount: 它将用于取消任何传出的网络请求,或删除与该组件关联的所有事件侦听器。
![cb44d369e16b013750364b2937ee11e0.png](https://i-blog.csdnimg.cn/blog_migrate/6a6c5fc98e3d15075ad5900d154ffff8.jpeg)
初始化阶段(不用)
- constructor 构造函数
- getDefaultProps props默认值
- getInitialState state默认值
挂载阶段
- static getDerivedStateFromProps(props,state)
- render
- componentDidMount
getDerivedStateFromProps:组件每次被 rerender的时候,包括在组件构建之后(虚拟 dom之后,实际 dom挂载之前),每次获取新的 props或 state之后;每次接收新的props之后都会返回一个对象作为新的 state,返回null则说明不需要更新 state;配合 componentDidUpdate,可以覆盖 componentWillReceiveProps的所有用。props和state
1 getDerivedStateFromProps + componentDidUpdate = componentWillReceiveProps
getDerivedStateFromProps: 禁止访问this.props,强制指定props只能和state进行比对,可以说是为setState而创建的。
componentDidUpdate: 需要this.props做的事则通过componentDidUpdate来做
2 触发时机: componentWillReceiveProps 初始化不执行,state改变不执行,父组件Props改变时候触发,初始化不触发 getDerivedStateFromProps(props,state) 初始化,子组件本组件state改变,props改变时候都会触发或无关本组件状态的父组件state变化。
3 componentWillReceiveProps问题:componentWillReceiveProps中使用setState会造成死循环
static getDerivedStateFromProps(props,state){
if(props.value !== state.value){
return {
value:props.value
}
}
return null
}
componentWillReceiveProps = ({ numbers, onChange }) => {
const {
state: { selectedNumber },
} = this;
const firstNumber = numbers[0];
this.setState({
selectedNumber: firstNumber,
});
onChange(firstNumber);
};
https://www.jianshu.com/p/f2d8adefc421
https://juejin.im/post/5d2ac3be6fb9a07efd473a94
更新阶段
getDerivedStateFromProps替代componentWillReceiveProps的原因?
https://zhuanlan.zhihu.com/p/38030418https://blog.csdn.net/weixin_34233856/article/details/91423552https://www.zhihu.com/question/278328905
1 getDerivedStateFromProps是一个静态函数,所以函数体内不能访问this,我们可以做一些副作用的东西,强制用户将请求之类放在搬到componentDidMount或者componentDidUpdate里面去。
2 componentWillMount 开发者错误书写了请求接口函数(不管多块也赶不上首次render),开启异步渲染后,componentWillMount会调用多次。它的作用分配在componentDidMount中。
3 如果外部组件多次频繁更新传入多次不同的 props,如果在componentWillReceiveProps或多次调用ajax,在componentDidUpdate 只有该组件更新后会触发一个自己单次更新调用一次ajax,减少了不必要请求。
所以说最合适ajax请求是componentDidMount或者componentDidUpdate,getDerivedStateFromProps强制人们
用一个静态函数getDerivedStateFromProps来取代被deprecate的几个生命周期函数,就是强制开发者在render之前只做无副作用的操作,而且能做的操作局限在根据props和state决定新的state,而已。
这是进一步施加约束,防止开发者乱来,我说过,施加约束的哲学指导思想,是我最爱React的原因。
4 一个新的声明周期函数getSnapshotBeforeUpdate,添加的原因是componentWillUpdate
的触发时机(它在异步渲染被取缔,但此处我们假想它仍然存在)与 componentDidUpdate
的触发时机间隔较大,因为异步渲染随时可能暂缓这一组件的更新。这样一来,之前的做法将变得不够稳定,因为这间隔久到 DOM 可能因为用户行为发生了变化。
为此,React 提供了 getSnapshotBeforeUpdate
。它的触发时机是 React 进行修改前(通常是更新 DOM)的“瞬间” ,这样一来在此获取到的 DOM 信息甚至比 componentWillUpdate
更加可靠。此外,它的返回值会作为第三个参数传入 componentDidUpdate
,这样做的好处很明显——开发者可以不必将为了协调渲染前后状态之用而产生的数据保存在组件实例上,用完即可销毁。
- static getDerivedStateFromProps(props,state):static getDerivedStateFromProps(nextProps, prevState),这是个静态方法,当我们接收到新的属性想去修改我们state,可以使用getDerivedStateFromProps
- shouldComponentUpdate: shouldComponentUpdate(nextProps, nextState),有两个参数nextProps和nextState,表示新的属性和变化之后的state,返回一个布尔值,true表示会触发重新渲染,false表示不会触发重新渲染,默认返回true,我们通常利用此生命周期来优化React程序性能
- render
- getSnapshotBeforeUpdate: getSnapshotBeforeUpdate(prevProps, prevState),这个方法在render之后,componentDidUpdate之前调用,有两个参数prevProps和prevState,表示之前的属性和之前的state,这个函数有一个返回值,会作为第三个参数传给componentDidUpdate,如果你不想要返回值,可以返回null,此生命周期必须与componentDidUpdate搭配使用
getSnapshotBeforeUpdate替代了ComponentWillUpdate
ComponentWillUpdate:一般在didMout之前获取dom状态,并在 componentDidUpdate 中进行相应的处理。
替换原因:在 React 开启异步渲染模式后,在 render 阶段读取到的 DOM 元素状态并不总是和 commit 阶段相同,这就导致在
componentDidUpdate 中使用 componentWillUpdate 中读取到的 DOM 元素状态是不安全的,因为这时的值很有可能已经失效了。
getSnapshotBeforeUpdate 会在最终的 render之后,渲染前被调用,也就是说在 getSnapshotBeforeUpdate 中读取到的 DOM 元素状态是可以保证与 componentDidUpdate 中一致的。可以在getSnapshotBeforeUpdate进行大量计算后,在componentDidUpdate一次更新
- componentDidUpdate: componentDidUpdate(prevProps, prevState, snapshot),该方法在getSnapshotBeforeUpdate方法之后被调用,有三个参数prevProps,prevState,snapshot,表示之前的props,之前的state,和snapshot。第三个参数是getSnapshotBeforeUpdate返回的,如果触发某些回调函数时需要用到 DOM 元素的状态,则将对比或计算的过程迁移至 getSnapshotBeforeUpdate,然后在 componentDidUpdate 中统一触发回调或更新状态。
getSnapshotBeforeUpdate:触发时间: update发生的时候,在 render之后,在组件 dom渲染之前;返回一个值,作为 componentDidUpdate的第三个参数;配合 componentDidUpdate, 可以覆盖 componentWillUpdate的所有用法
卸载阶段
- componentWillUnmount
错误处理
- componentDidCatch
React16新的生命周期弃用了 componentWillMount、componentWillReceivePorps,componentWillUpdate新增了 getDerivedStateFromProps、getSnapshotBeforeUpdate来代替弃用的三个钩子函数。
配合getDerivedStateFromError组成一个catchErrorHoc
![87c1a817a37d169e1ee79a2d1e481146.png](https://i-blog.csdnimg.cn/blog_migrate/8586a9d02efd475356f5f086ecc7d060.jpeg)
React16并没有删除这三个钩子函数,但是不能和新增的钩子函数混用, React17将会删除这三个钩子函数,新增了对错误的处理( componentDidCatch)
2. setState是同步的还是异步的?setState使用需要注意哪些点? 为什么多次setState只一次生效?setState 之后发生了什么?
生命周期和合成事件中
在React的生命周期和合成事件中,React仍然处于他的更新机制中,这时无论调用多少次setState,都会不会立即执行更新,而是将要更新的·存入_pendingStateQueue,将要更新的组件存入dirtyComponent。
当上一次更新机制执行完毕,以生命周期为例,所有组件,即最顶层组件didmount后会将批处理标志设置为false。这时将取出dirtyComponent中的组件以及_pendingStateQueue中的state进行更新。这样就可以确保组件不会被重新渲染多次
componentDidMount() {
this.setState({
index: this.state.index + 1
})
console.log('state', this.state.index);
}
所以,如上面的代码,当我们在执行setState后立即去获取state,这时是获取不到更新后的state的,因为处于React的批处理机制中,state被暂存起来,待批处理机制完成之后,统一进行更新。所以。setState本身并不是异步的,而是React的批处理机制给人一种异步的假象。
- 异步代码和原生事件中
componentDidMount() {
setTimeout(() => {
console.log('调用setState');
this.setState({
index: this.state.index + 1
})
console.log('state', this.state.index);
}, 0);
}
如上面的代码,当我们在异步代码中调用setState时,根据JavaScript的异步机制,会将异步代码先暂存,等所有同步代码执行完毕后在执行,这时React的批处理机制已经走完,处理标志设被设置为false,这时再调用setState即可立即执行更新,拿到更新后的结果。
在原生事件中调用setState并不会出发React的批处理机制,所以立即能拿到最新结果。
setState使用需要注意哪些点?
- 尽量不要直接修改state中的数据
- 设置input数据时候,应先把数据拷贝一份,然后再进行setState,
- setState是异步的,如何保证同步
this.setState(prevState => ({
pictures: prevState.pictures.concat(pics)
}))
- setState可能造成不必要的渲染。
- setState并不能很有效的管理所有的组件状态
- 多次setState会合并
为什么多次setState只一次生效?
例如下面的代码,两次打印出的结果是相同的:
componentDidMount() {
this.setState({ index: this.state.index + 1 }, () => {
console.log(this.state.index);
})
this.setState({ index: this.state.index + 1 }, () => {
console.log(this.state.index);
})
}
原因就是React会批处理机制中存储的多个setState进行合并,来看下React源码中的_assign函数,类似于Object的assign:
_assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);
如果传入的是对象,很明显会被合并成一次,所以上面的代码两次打印的结果是相同的:
Object.assign(
nextState,
{index: state.index+ 1},
{index: state.index+ 1}
)
注意,assign
函数中对函数做了特殊处理,处理第一个参数传入的是函数,函数的参数preState
是前一次合并后的结果,所以计算结果是准确的:
componentDidMount() {
this.setState((state, props) => ({
index: state.index + 1
}), () => {
console.log(this.state.index);
})
this.setState((state, props) => ({
index: state.index + 1
}), () => {
console.log(this.state.index);
})
}
React会对多次连续的 setState进行合并,如果你想立即使用上次 setState后的结果进行下一次
setState,可以让 setState 接收一个函数而不是一个对象。
这个函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 做为第二个参数。
解决setState嵌套赋值
this.state = {
firstState: { secondState: 'secondStae', thirdState: 'thirdState'}
}
方案一:
this.setState(prevState => ({
firstState: {
...prevState.firstState,
secondState: 'secondState-change'
}
}))
方案二:
this.setState({
firstState: {
...this.state.firstState,
secondState: 'secondState-change'
}
})
方案三:
this.setState({
firstState: Object.assign({},this.state.firstState,{
secondState: 'secondState-change'
})
})
调用setState后会发生什么?
- 调用setState后,会触发enqueSetState函数,函数中获取当前组件对象,将要更新的state放入一个数组队列中。
- 然后调用enqueueUpdate函数,判断当前组件是否处于创建更新阶段,如果是则将当前组件放入dirtyComponent,如果不是则调用batchUpdates函数处理,
- batchUpdates将会以事物的方式处理组件的update,transcation被包装在wrapper中,TRANSACTION_WRAPPERS中包含[FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES],最终会调用RESET_BATCHED_UPDATES.close(),会将isBatchingUpdates设置为false,并且会执行FLUSH_BATCHED_UPDATES在close阶段,会循环遍历所有的dirtyComponents,调用updateComponent刷新组件,并执行它的pendingCallbacks, 也就是setState中设置的callback。 链接 链接 链接 链接
![4c82594a4bfc5432ca54d40721ab017b.png](https://i-blog.csdnimg.cn/blog_migrate/cb528df13f11ff45643897f4e62b01bf.png)
this.state = {
val: 0
};
componentDidMount() {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 1 次 log
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 2 次 log
setTimeout(() => {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 3 次 log
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 4 次 log
}, 0);
}
打印出来是 00 23
前两次 `setState` 并不是失效,而是他们被 batch update 了,可以被理解为两次 setState 被缓存了,因此没有立即生效。
所以第 2 次 log 的时候,val 还是 0。但是在第 3 次调 this.setState 时,前两次被「缓存」的 setState 已经执行,
因为两次 setState 实际上都是 `this.setState({val: 1})`,所以第 3 次 setState 时的 val 是 1。
因为在 componentDidMount 中调用 setState 时,batchingStrategy 的 isBatchingUpdates 已经被设为 true,
所以两次 setState 的结果并没有立即生效,而是被放进了 dirtyComponents 中。这也解释了两次打印this.state.val 都是 0 的原因,
新的 state 还没有被应用到组件中。
再反观 setTimeout 中的两次 setState,因为没有前置的 batchedUpdate 调用,所以 batchingStrategy 的 isBatchingUpdates
标志位是 false,也就导致了新的 state 马上生效,没有走到 dirtyComponents 分支。也就是,setTimeout 中第一次 setState 时,
this.state.val 为 1,而 setState 完成后打印时 this.state.val 变成了 2。第二次 setState 同理。
3 React如何实现自己的事件机制?为何React事件要自己绑定this?原生事件和React事件的区别?React的合成事件是什么?React和原生事件的执行顺序是什么?可以混用吗?
React
事件并没有绑定在真实的 Dom
节点上,而是通过事件代理,在最外层的 document
上对事件进行统一分发。
![ef8d59475b27cf1069252e19318558ee.png](https://i-blog.csdnimg.cn/blog_migrate/13292ac6f6c408304fa9aa97f3dda87e.jpeg)
组件挂载阶段:通过EventPluginHub的enqueuePutListener进行事件存储,在document上注册原生事件的回调dispatchEvent。
初始化时候:React通过EventPluginHub将React合成事件的 callback以事件类型以及元素唯一表示的结构存入listenerBank中
触发事件时:遍历触发元素所有父元素对每一级父元素构成合成事件并将其合成事件存储在eventQueue事件队列中,执行document的dispatchEvent事件,dispatchEvent通过循环调用所有层级的事件来模拟事件冒泡和捕获。
从最深元素开始冒泡执行 executeDispatch来执行所有合成事件
组件挂载、更新时:
- 通过
lastProps
、nextProps
判断是否新增、删除事件分别调用事件注册、卸载方法。 - 调用
EventPluginHub
的enqueuePutListener
进行事件存储 - 获取
document
对象。 - 根据事件名称(如
onClick
、onCaptureClick
)判断是进行冒泡还是捕获。 - 判断是否存在
addEventListener
方法,否则使用attachEvent
(兼容IE)。 - 给
document
注册原生事件回调为dispatchEvent
(统一的事件分发机制)。
事件初始化:
EventPluginHub
负责管理React
合成事件的callback
,它将callback
存储在listenerBank
中,另外还存储了负责合成事件的Plugin
。- 获取绑定事件的元素的唯一标识
key
。 - 将
callback
根据事件类型,元素的唯一标识key
存储在listenerBank
中。 listenerBank
的结构是:listenerBank[registrationName][key]
。
触发事件时:
- 触发
document
注册原生事件的回调dispatchEvent
- 获取到触发这个事件最深一级的元素
- 遍历这个元素的所有父元素,依次对每一级元素进行处理。
- 构造合成事件。
- 将每一级的合成事件存储在
eventQueue
事件队列中。 - 遍历
eventQueue
。 - 通过
isPropagationStopped
判断当前事件是否执行了阻止冒泡方法。 - 如果阻止了冒泡,停止遍历,否则通过
executeDispatch
执行合成事件。 - 释放处理完成的事件。
React
在自己的合成事件中重写了stopPropagation
方法,将isPropagationStopped
设置为true
,然后在遍历每一级事件的过程中根据此遍历判断是否继续执行。这就是React
自己实现的冒泡机制。
推荐阅读:【React深入】React事件机制
为何React事件要自己绑定this?
通过事件分发执行程序处理回调时直接调用,this值为undefined,所以需将手动绑定this值指向当前组件实例。
在上面提到的事件处理流程中, React
在 document
上进行统一的事件分发, dispatchEvent
通过循环调用所有层级的事件来模拟事件冒泡和捕获。
在 React
源码中,当具体到某一事件处理函数将要调用时,将调用 invokeGuardedCallback
方法。
function invokeGuardedCallback(name, func, a) {
try {
func(a);
} catch (x) {
if (caughtError === null) {
caughtError = x;
}
}
}
可见,事件处理函数是直接调用的,并没有指定调用的组件,所以不进行手动绑定的情况下直接获取到的 this
是不准确的,所以我们需要手动将当前组件绑定到 this
上。
原生事件和React事件的区别?
React
事件使用驼峰命名,而不是全部小写。- 通过
JSX
, 你传递一个函数作为事件处理程序,而不是一个字符串。 - 在
React
中你不能通过返回false
来阻止默认行为。必须明确调用preventDefault
。
React的合成事件是什么?
React
根据 W3C
规范定义了每个事件处理函数的参数,即合成事件。
事件处理程序将传递 SyntheticEvent
的实例,这是一个跨浏览器原生事件包装器。它具有与浏览器原生事件相同的接口,包括 stopPropagation()
和 preventDefault()
,在所有浏览器中他们工作方式都相同。React
合成的 SyntheticEvent
采用了事件池,这样做可以大大节省内存,而不会频繁的创建和销毁事件对象。
另外,不管在什么浏览器环境下,浏览器会将该事件类型统一创建为合成事件,从而达到了浏览器兼容的目的。
React和原生事件的执行顺序是什么?可以混用吗?
React
的所有事件都通过 document
进行统一分发。当真实 Dom
触发事件后冒泡到 document
后才会对 React
事件进行处理。
所以原生的事件会先执行,然后执行 React
合成事件,最后执行真正在 document
上挂载的事件React
事件和原生事件最好不要混用。原生事件中如果执行了 stopPropagation
方法,则会导致其他 React
事件失效。因为所有元素的事件将无法冒泡到 document
上,导致所有的 React
事件都将无法被触发。。
JavaScript 的事件机制
DOM 事件流分为三个阶段:捕获阶段、目标阶段、冒泡阶段,
![864856a436313b5c4cd55b5b49d65712.png](https://i-blog.csdnimg.cn/blog_migrate/c016ff02f3967eb347f4d81f05ccd8d9.png)
target.addEventListener(type, listener[, useCapture]); useCapture true 捕获,false 冒泡
removeEventListener
捕获冒泡顺序一般是按照先捕获后冒泡的原则,特殊情况是,在元素上同时绑定捕获事件和冒泡事件,如果通过此元素的子级元素触发,则优先触发捕获事件,若通过该元素自身触发,那么冒泡和捕获没有次序之分,则按照Javascript代码的先后顺序执行。。其他情况都是按照先捕获后冒泡的顺序来。
阻止时间冒泡: event.stopPropagation(阻止当前函数) event.stopImmediatePropagation(阻止一类事件泪如click)
return false 及组织事件默认行为也阻止事件冒泡
- event.target:指的是触发事件的那个节点,也就是事件最初发生的节点。
- event.currentTarget:指的是正在执行的监听函数的那个节点。
- event.preventDefault():取消事件的默认行为。
- event.stopPropagation():阻止事件的派发(包括了捕获和冒泡)。window.event.cancelBubble = true;
- event.stopImmediatePropagation():阻止同一个事件的其他监听函数被调用。
4 虚拟Dom是什么? 虚拟Dom比普通Dom更快吗? 虚拟Dom中的$$typeof属性的作用是什么?
虚拟Dom是什么?
![20442f15faa7b7ead92ecd314fc5e243.png](https://i-blog.csdnimg.cn/blog_migrate/c55d82f1eac064e19e3de7c61ea1f9c8.jpeg)
在原生的 JavaScript
程序中,我们直接对 DOM
进行创建和更改,而 DOM
元素通过我们监听的事件和我们的应用程序进行通讯。
而 React
会先将你的代码转换成一个 JavaScript
对象,然后这个 JavaScript
对象再转换成真实 DOM
。这个 JavaScript
对象就是所谓的虚拟 DOM
。
当我们需要创建或更新元素时, React
首先会让这个 VitrualDom
对象进行创建和更改,然后再将 VitrualDom
对象渲染成真实DOM。
当我们需要对 DOM
进行事件监听时,首先对 VitrualDom
进行事件监听, VitrualDom
会代理原生的 DOM
事件从而做出响应。
推荐阅读:【React深入】深入分析虚拟DOM的渲染过程和特性
虚拟Dom比普通Dom更快吗?
虚拟dom在重复渲染时候提升了更新的效率,二并不是比dom块
很多文章说 VitrualDom
可以提升性能,这一说法实际上是很片面的。
直接操作 DOM
是非常耗费性能的,这一点毋庸置疑。但是 React
使用 VitrualDom
也是无法避免操作 DOM
的。
如果是首次渲染, VitrualDom
不具有任何优势,甚至它要进行更多的计算,消耗更多的内存。VitrualDom
的优势在于 React
的 Diff
算法和批处理策略, React
在页面更新之前,提前计算好了如何进行更新和渲染 DOM
。实际上,这个计算过程我们在直接操作 DOM
时,也是可以自己判断和实现的,但是一定会耗费非常多的精力和时间,而且往往我们自己做的是不如 React
好的。所以,在这个过程中 React
帮助我们"提升了性能"。
所以,我更倾向于说, VitrualDom
帮助我们提高了开发效率,在重复渲染时它帮助我们计算如何更高效的更新,而不是它比 DOM
操作更快。
虚拟Dom中的$$typeof属性的作用是什么?
ReactElement
中有一个 $$typeof
属性,它被赋值为 REACT_ELEMENT_TYPE
:
var REACT_ELEMENT_TYPE =
(typeof Symbol === 'function' && Symbol.for && Symbol.for('react.element')) ||
0xeac7;
可见, $$typeof
是一个 Symbol
类型的变量,这个变量可以防止 XSS
。
如果你的服务器有一个漏洞,允许用户存储任意 JSON
对象, 而客户端代码需要一个字符串,这可能会成为一个问题:
// JSON
let expectedTextButGotJSON = {
type: 'div',
props: {
dangerouslySetInnerHTML: {
__html: '/* put your exploit here */'
},
},
};
let message = { text: expectedTextButGotJSON };
<p>
{message.text}
</p>
JSON
中不能存储 Symbol
类型的变量。ReactElement.isValidElement
函数用来判断一个 React
组件是否是有效的,下面是它的具体实现。
ReactElement.isValidElement = function (object) {
return typeof object === 'object' && object !== null && object.$$typeof === REACT_ELEMENT_TYPE;
};
可见 React
渲染时会把没有 $$typeof
标识,以及规则校验不通过的组件过滤掉。
当你的环境不支持 Symbol
时, $$typeof
被赋值为 0xeac7
,至于为什么, React
开发者给出了答案:
0xeac7
看起来有点像React
。
https://zhuanlan.zhihu.com/p/82840768
2 类组件(Class component)和函数式组件(Functional component)之间有何不同
1.函数组件中,你无法使用State,也无法使用组件的生命周期方法,函数组件中没有this,无状态组件
- 组件不能访问this对象
- 组件无法访问生命周期的方法
- 无状态组件只能访问输入的props,无副作用
- 、
1 HOC 为什么会出现? 实现原理? 实现方式? 使用场景?缺点
mixin为什么会出现?C++ 等一些年龄较大的 OOP 语言,有一个强大但危险的多重继承特性。现代语言为了权衡利弊,大都舍弃了多重继承,只采用单继承。但单继承在实现抽象时有着诸多不便之处,为了弥补缺失,如 Java 就引入 interface,其它一些语言引入了像 Mixin 的技巧,方法不同,但都是为创造一种类似多重继承的效果,事实上说它是组合更为贴切。
mixin有什么缺点?1 mixin导致名称冲突 2 mixin引入了隐式依赖关系,mixin依赖于其他mixin,去掉其中一个会破坏另一个。在这种情况下,很难判断数据是如何进出mixin的,以及它们的依赖关系图是什么样子的。与组件不同,mixin不形成层次结构:它们被扁平化并在同一个命名空间中操作。3 ES6 Classes 并不原生支持 mixin
HOC实现原理就是:函数返回组件。
HOC的实现方式有两种一: 属性代理:一个函数接受一个WrappedComponent组件作为参数传入,并返回一个继承了React.Component组件的类,且在该类的render()方法中返回被传入的WrappedComponent组件。
方法二:反向继承:一个函数接受一个WrappedComponent组件作为参数传入,并返回一个继承了该传入WrappedComponent组件的类,且在该类的render()方法中返回super.render()方法
会发现其属性代理和反向继承的实现有些类似的地方,都是返回一个继承了某个父类的子类,只不过属性代理中继承的是React.Component,反向继承中继承的是传入的组件WrappedComponent。
// 无状态
function HigherOrderComponent(WrappedComponent) {
return props => <WrappedComponent {...props} />;
}
// or
// 有状态
function HigherOrderComponent(WrappedComponent) {
return class extends React.Component {
render() {
return <WrappedComponent {...this.props} />;
}
};
function HigherOrderComponent(WrappedComponent) {
return class extends WrappedComponent {
render() {
return super.render();
}
};
高阶组件缺点:静态方法丢失
// 定义静态方法
WrappedComponent.staticMethod = function() {}
// 使用高阶组件
const EnhancedComponent = HigherOrderComponent(WrappedComponent);
// 增强型组件没有静态方法
typeof EnhancedComponent.staticMethod === 'undefined' // true
所以必须将静态方法做拷贝:
function HigherOrderComponent(WrappedComponent) {
class Enhance extends React.Component {}
// 必须得知道要拷贝的方法
Enhance.staticMethod = WrappedComponent.staticMethod;
return Enhance;
}
refs 属性不能透传
function withLogging(WrappedComponent) {
class Enhance extends WrappedComponent {
componentWillReceiveProps() {
console.log('Current props', this.props);
console.log('Next props', nextProps);
}
render() {
const {forwardedRef, ...rest} = this.props;
// 把 forwardedRef 赋值给 ref
return <WrappedComponent {...rest} ref={forwardedRef} />;
}
};
// React.forwardRef 方法会传入 props 和 ref 两个参数给其回调函数
return React.forwardRef((props, ref)=> {
return <Enhance {...props} forwardRef={ref} />
})
)
无状态
无生命周期
https://blog.csdn.net/weixin_34227447/article/details/87953929 HOC使用场景有哪些?
- 组件渲染性能追踪
- 权限控制
- 页面复用
高阶组件其实就是装饰器模式在 React 中的实现:通过给函数传入一个组件(函数或类)后在函数内部对该组件(函数或类)进行功能的增强(不修改传入参数的前提下),最后返回这个组件(函数或类),即允许向一个现有的组件添加新的功能,同时又不去修改该组件,属于 包装模式(Wrapper Pattern) 的一种。
什么是装饰者模式:在不改变对象自身的前提下在程序运行期间动态的给对象添加一些额外的属性或行为。
- 高阶组件 不是组件,是 一个把某个组件转换成另一个组件的 函数
- 高阶组件的主要作用是 代码复用
- 高阶组件是 装饰器模式在 React 中的实现
https://blog.csdn.net/weixin_34227447/article/details/87953929
2 setState是异步还是同步的?为什么是异步的?如何保证同步? setState使用需要注意哪些点?setState 之后发生了什么?
setState在合成事件和钩子函数中是异步的,在原生事件和setTimeout中都是同步的,
setState的异步并不是说内容由异步代码实现,其实本身执行过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立刻拿到更新后的值,形成所谓的异步,可以通过第二个函数回调拿到更新后的结构
- 尽量不要直接修改state中的数据
- 设置input数据时候,应先把数据拷贝一份,然后再进行setState,
- setState是异步的,如何保证同步
this.setState(prevState => ({
pictures: prevState.pictures.concat(pics)
}))
- setState可能造成不必要的渲染。
- setState并不能很有效的管理所有的组件状态
- 多次setState会合并
解决setState嵌套赋值
this.state = {
firstState: {
secondState: 'secondStae',
thirdState: 'thirdState'
}
}
方案一:
this.setState(prevState => ({
firstState: {
...prevState.firstState,
secondState: 'secondState-change'
}
}))
方案二:
this.setState({
firstState: {
...this.state.firstState,
secondState: 'secondState-change'
}
})
方案三:
this.setState({
firstState: Object.assign({},this.state.firstState,{
secondState: 'secondState-change'
})
})
- 4 对 react 虚拟dom理解
https://blog.csdn.net/VhWfR2u02Q/article/details/100011830
VirtualDOM的主要思想就是模拟DOM的树状结构,在内存中创建保存映射DOM信息的节点数据,在由于交互等因素需要视图更新时,先通过对节点数据进行diff后得到差异结果后,再一次性对DOM进行批量更新操作,所有复杂曲折的更新逻辑都在平行世界中的VirtualDOM处理完成,只将最终的更新结果发送给浏览器中的DOM树执行,这样就避免了冗余琐碎的DOM树操作负担,将 O(n^3) 复杂度的问题转换成 O(n) 复杂度。
react 对于diff算法比较的主要有以下三点:
- 对树进行分层比较,两棵树只会对同一层级进行比较,当出现dom跨层级进行比较,会直接将节点删除,然后再重建,不会进行比较。
- 按类型进行diff,VirtualDOM只对相同类型的同一个节点进行diff,当新旧节点发生了类型的改变时,则并不进行子树的比较,直接创建新类型的VirtualDOM,替换旧节点。
- 节点diff 1 分为新老节点是相同节点 ,则进行移动操作。具体:判断某一节点在老节点中的位置mountIndex,以及最近一次指针位置lastIndex,如果指针大于老节点位置则进行更新(lastIndex>mountIndex),如果小于则更新lastIndex=Math.max(_mountIndex, lastIndex),更新lastIndex为新旧节点位置中最大那个,然后将老节点的位置更新为新节点位置,nextindex++;执行下一个节点对比。
2 新老节点不同,存在插入以及删除。如果遇到新插入节点,老集合中不存在这样节点,维持lastIndex值。nextindex++;当完成所有节点diff之后,最后还要对老集合进行循环遍历,判断是否存在新集合没有但老集合中有的点,发现这样节点d,并且删除,diff完成。
![25b7540cfc5457eb00b2a7ad1311a8b9.png](https://i-blog.csdnimg.cn/blog_migrate/335d896d849825a42e787800bc43ce4d.jpeg)
从新集合中取得 D,判断老集合中存在相同节点 D,通过对比节点位置判断是否进行移动操作,D 在老集合中的位置 D._mountIndex = 3,此时 lastIndex = 1,不满足 child._mountIndex < lastIndex的条件,因此不对 D 进行移动操作;更新 lastIndex = Math.max(prevChild._mountIndex, lastIndex),则 lastIndex = 3,并将 D 的位置更新为新集合中的位置 prevChild._mountIndex = nextIndex,此时新集合中D._mountIndex = 2,nextIndex++ 进入下一个节点的判断。
![12fa412b9a38279f7c6e360f99953769.png](https://i-blog.csdnimg.cn/blog_migrate/f1c4cb1b1348f283c60117b9233f36cf.jpeg)
从新集合中取得 E,判断老集合中不存在相同节点 E,则创建新节点 E;更新 lastIndex = 1,并将 E 的位置更新为新集合中的位置,nextIndex++进入下一个节点的判断。
React16 的 diff 策略采用从链表头部开始比较的算法,是层次遍历,算法是建立在一个节点的插入、删除、移动等操作都是在节点树的同一层级中进行的。
diff分四种情况: TextNode(包含字符串和数字),单个 React Element(通过该节点是否有 $$typeof
区分),数组,可迭代的 children,跟数组的处理方式差不多
React 16.0 diff 算法新内容为React元素的diff:
placeSingleChild: 给differ后的Fiber添加副作用标签:Placement(替换),表明在之后需要将旧Fiber对应的DOM元素进行替换。reconcileSingleElement:因为类型不同,React将“删除”旧内容fiber以及其所有相邻Fiber(即给这些fiber添加副作用标签 Deletion(删除)), 并基于新内容生成新的Fiber。然后将新的Fiber设置为父Fiber的child。当新旧内容的元素类容相同,React会复用旧内容fiber,结合新内容属性,生成一个新的fiber。同样,将新的fiber设置位父fiber的child。
https://zhuanlan.zhihu.com/p/127812119 详解React16的Diff策略_前端大全-CSDN博客_reactdiff
精简:
判断新旧内容元素类型是否相同,
如果相同 就删除旧内容fiber的所有相邻fiber,使用就内容fiber结合新内容属性克隆生成新的fiber ,如果不相同,就删除就内容fiber及其所有相邻fiber,基于新内容创建新的fiber,
给新的fiber设置副作用标签替换,用来之后替换就内容fiber对应Dom,父fiber将新的fiber设置为child
新内容为文本:
判断旧内容fiber标签是否为HostText(表明已有且仅有一个文本)
如果是 则删除旧内容fiber的所有相邻fiber,使用旧内容fiber结合新内容文本克隆生成新的fiber,如果不是则删除旧内容fiber以及其所有相邻fiber,基于新内容文本创建新的fiber
新的标签fiber设置副作用标签替换,用来之后替换就内容fiber对应的dom,父fiber将新的fiber设置为child。
新内容为数组:
for循环遍历新内容的数组,通过 updateSlot判断新child类型,如果是对象,则更新react元素,与更新元素逻辑相同,如果是文本,与更新文本逻辑相同,
遍历完成后,若旧内容fiber及其相邻fiber的总个数大于或等于新内容数组长度,deleRemainingChildren 删除多余旧内容相邻fiber,给这些fiber添加副作用标签: Deleteion (删除),父fiber将新的fiber设置为child,
若旧内容fiber及其相邻fiber的总个数小于新内容数组长度,是新增了节点的情况则遍历多余新内容数组元素,基于新内容数组元素创建新的fiber,给新fiber添加替换副作用变迁,父fiber将新的fiber设置为child
Diff TextNode: 判断旧内容fiber标签是否为HostText(表明已有且仅有一个文本)
如果是 则删除旧内容fiber的所有相邻fiber,使用旧内容fiber结合新内容文本克隆生成新的fiber,如果不是则删除旧内容fiber以及其所有相邻fiber,基于新内容文本创建新的fiber
新的标签fiber设置副作用标签替换,用来之后替换就内容fiber对应的dom,父fiber将新的fiber设置为child。
Diff React Element: 判断新旧内容元素类型是否相同,
如果相同 就删除旧内容fiber的所有相邻fiber,使用就内容fiber结合新内容属性克隆生成新的fiber,如果不相同,就删除就内容fiber及其所有相邻fiber,基于新内容创建新的fiber,
给新的fiber设置副作用标签替换,用来之后替换就内容fiber对应Dom,父fiber将新的fiber设置为child,
5 谈谈你对redux的理解?
说道Redux不得不提到flux架构,flux是为了解决MVC中无法禁绝 View 和 Model 之 间的直接对话的问题产生的,在flux中Store 只有 get方法,没有 set方法,根本可能直接去修改 其内部状态, View 只能通过 get方法获取 Store 的状态,无法直接去修改状态,如果 View 想要修改 Store状态的话,只有派发一个 action对象给 Dispatcher。状态是从action到dispatcher到store然后更新至view
![095a6d424a0bcd210029f952fef8fe2c.png](https://i-blog.csdnimg.cn/blog_migrate/243be44805350468a9e249f17f70eba6.png)
redux具有单一数据源,状态是可读的,状态修改均由纯函数完成三个基本原则,不同于flux的多store,redux里面只有单store,并且在redux里面各子 reducer 都是由根reducer统一管理的,每个子reducer的变化都要经过根reducer的整合,
combineReucers将单个reducer塞到一个对象中,每个reducer对应一个唯一键值,单个reducer状态改变时,对应键值的值也会改变,然后返回整个state。
bindActionCreators将我们的action和dispatch连接起来
![4531a01cdb50bafc44a5e09e0602dd75.png](https://i-blog.csdnimg.cn/blog_migrate/3ca85845e658e42bcab45e2185c92454.jpeg)
Redux调用过程是在组件中通过Provider作为中间件作为组件和Store进行交互的桥梁,通过connect连接 Redux 和 React,它包在我们的容器组件的外一层,它接收上面 Provider 提供的 store 里面的 state 和 dispatch,页面可以通过this.props.[通过bindActionCreators绑定的值]来调用 dispatch一个 action, 然后触发Reducer(state,action)对state进行处理从而页面数据值得到改变。
单一数据源,store,state是只读的,使用纯函数来实现state归并操作,reducer
https://blog.csdn.net/sinat_17775997/article/details/90234385https://www.jianshu.com/p/3d4e720ff923
1 使用Provider :Provider组件的出现是作为中间件降低各组件与Store之间的耦合。相当于在子组件与Provider进行交互,Provider与store进行交互。Provider实现原理:实现是一个容器组件,通过getChildContext的的方式传递给子组件,保存了全局唯一的store,类似于全局变量,子组件后续可以通过this.context.store来访问。
https://www.jianshu.com/p/eba2b76b290bhttps://blog.csdn.net/sinat_17775997/article/details/90234385
2 使用connect 连接React组件与 Redux store。
用法: connect([mapStateToProps], [mapDispatchToProps], [mergeProps],[options])
mapStateToProps(state, ownProps) : stateProps: 这个函数允许我们将 store 中的数据作为 props 绑定到组件上。
第一参数state: 上面的state其实就是Redux的Store,如上操作之后,组件中的props就对多出一个字段,名为token
第二参数ownProps:组件自身的props
function mapStateToProps(state) {
return {
token: state.token
};
}
mapDispatchToProps(dispatch, ownProps): dispatchProps: 将dispatch绑定到本组件的props上
mapDispatchToProps函数返回了一个由Redux提供的方法bindActionCreators所生成的一个Action集合,来将 action 包装成直接可被调用的函数。调用使用this.props.Tragetactions
import { getUser } from './userAction';
function mapStateToProps(state) {
return {
token: state.token
};
}
function mapDispatchToProps(dispatch) {
return {
Tragetactions: bindActionCreators({
getUser
}, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(UserList);
[mergeProps(stateProps, dispatchProps, ownProps): props]
如果指定了这个参数,mapStateToProps()与mapDispatchToProps()的执行结果和组件自身的props将传入到这个回调函数中。该回调函数返回的对象将作为 props 传递到被包装的组件中。你也许可以用这个回调函数,根据组件的 props 来筛选部分的 state 数据,或者把 props 中的某个特定变量与 action creator 绑定在一起。如果你省略这个参数,默认情况下返回Object.assign({}, ownProps, stateProps, dispatchProps)的结果。
不管是 stateProps 还是 dispatchProps,都需要和 ownProps merge 之后才会被赋给组件。connect 的第三个参数就是用来做这件事。通常情况下,你可以不传这个参数,connect 就会使用 Object.assign 替代该方法。
最后还有一个options选项,比较简单,基本上也不大会用到(尤其是你遵循了其他的一些 React 的「最佳实践」的时候),本文就略过了。希望了解的同学可以直接看文档。
首先connect之所以会成功,是因为Provider组件:
- 在原应用组件上包裹一层,使原来整个应用成为Provider的子组件
- 接收Redux的store作为props,通过context对象传递给子孙组件上的connect
那connect做了些什么呢?
它真正连接 Redux 和 React,它包在我们的容器组件的外一层,它接收上面 Provider 提供的 store 里面的 state 和 dispatch,传给一个构造函数,返回一个对象,以属性形式传给我们的容器组件。
https://zhuanlan.zhihu.com/p/53829279https://www.jianshu.com/p/4af924709b35
connect是一个高阶函数,首先传入mapStateToProps、mapDispatchToProps,然后返回一个生产Component的函数(wrapWithConnect),然后再将真正的Component作为参数传入wrapWithConnect,这样就生产出一个经过包裹的Connect组件,该组件具有如下特点:
- 通过props.store获取祖先Component的store
- props包括stateProps、dispatchProps、parentProps,合并在一起得到nextState,作为props传给真正的Component
- componentDidMount时,添加事件this.store.subscribe(this.handleChange),实现页面交互
- shouldComponentUpdate时判断是否有避免进行渲染,提升页面性能,并得到nextState
- componentWillUnmount时移除注册的事件this.handleChange
由于connect的源码过长,我们只看主要逻辑:
export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) {
return function wrapWithConnect(WrappedComponent) {
class Connect extends Component {
constructor(props, context) {
// 从祖先Component处获得store
this.store = props.store || context.store
this.stateProps = computeStateProps(this.store, props)
this.dispatchProps = computeDispatchProps(this.store, props)
this.state = { storeState: null }
// 对stateProps、dispatchProps、parentProps进行合并
this.updateState()
}
shouldComponentUpdate(nextProps, nextState) {
// 进行判断,当数据发生改变时,Component重新渲染
if (propsChanged || mapStateProducedChange || dispatchPropsChanged) {
this.updateState(nextProps)
return true
}
}
componentDidMount() {
// 改变Component的state
this.store.subscribe(() = {
this.setState({
storeState: this.store.getState()
})
})
}
render() {
// 生成包裹组件Connect
return (
<WrappedComponent {...this.nextState} />
)
}
}
Connect.contextTypes = {
store: storeShape
}
return Connect;
}
}
bindActionCreators的作用就是:语法糖: bindActionCreators的作用是将一个或多个action和dispatch组合起来生成mapDispatchToProps需要生成的内容。https://www.jianshu.com/p/26356a9c5365
使用this.props.addTodo调用,会
不用
function mapDispatchToProps(dispatch) {
return {
onIncrement: (...args) => dispatch(actions.onIncrement(...args)),
}
}
用
function mapDispatchToProps(dispatch) {
return bindActionCreators({ actions.onIncrement }, dispatch);
}
const toggleTodo = (id) => {
return {
type: 'TOGGLE_TODO',
id
};
};
export { toggleTodo };
let boundActionCreators = bindActionCreators(toggleTodo, dispatch);
//此时boundActionCreators = (id) => dispatch(toggleTodo(id));
{
addTodo : text => dispatch(addTodo('text'));
removeTodo : id => dispatch(removeTodo('id'));
}
connect()方法用于将从 UI 组件生成容器组件,connect方法接受两个参数:mapStateToProps(state对象到(UI 组件的)props对象)和mapDispatchToProps(将action和用户组件链接起来)。
Provider组件,让容器组件拿到state,原理主要是React组件的context属性,通过getChildContext的的方式传递给子组件的
Redux源码一共6个文件?
createStore:
React-saga: 使用
1 在入口createSagaMiddleware,将新建的sagamid放入applyMiddleware中,并且sagaMiddleware.run(rootSaga);
2 使用redux-saga/effects的takeEvery监听我们action的type,回调函数中执行put一个action ,从而触发Reducer中的state改变。
saga原理: Saga是由一个个的effect组成的,一个 effect 就是一个 Plain Object JavaScript 对象,包含一些将被 saga middleware 执行的指令。redux-saga提供了很多Effect创建器,如call
、put
、take
等,已call
为例:call(genPromise)
生成的就是一个effect,它可能类似如下:{ isEffect: true, type: 'CALL', fn: genPromise } , effect只表明了意图, 实际的行为由类似于上文的nextWithYieldType完成
https://blog.csdn.net/sinat_17775997/article/details/83537270
applyMiddleware 原理剖析
applyMiddleware([A,B,C,D])
即最后面d用的原生的dispatch a(b(c(d(dispatch)))), C用的D的dispatch,
构造过程是从后往前,也就是排后面的是在里层,越往里,数组顺序越靠后,也就是洋葱模型从里到外(对应数组是后到钱),执行时候,从外到内,但是呢,外边用到dispatch是里面生成的,所以执行时候还是要从里到外执行,
const logger = store => next => action => {
console.log('dispatching', action)
return next(action)
}
这个next相当于我们dispatch,是上一层传过来的dispatch
执行过程是这样的: collectError(logger(dispatch)),所以开始执行是按照中间件的顺序执行的,但是collectError需要的next函数是 logger(dispatch)所以去执行logger, logger执行原生的dispatch完后,去执行collectError函数。
![d63b6978bc09d59a7b387982d383ec85.png](https://i-blog.csdnimg.cn/blog_migrate/1e5b0e92e38a174ee7733de0c1cd16df.jpeg)
![36b3cfa980dcc15e54a855e70f30104e.png](https://i-blog.csdnimg.cn/blog_migrate/ff662ac5f0e595b310696adb4a82d8bd.jpeg)
const logger = store => next => action => {
console.log('dispatching', action)
return next(action)
}
const collectError = store => next => action => {
try {
return next(action)
} catch (err) {
console.error('Error!', err)
}
}
// 调用的时候从后往前执行
applyMiddleWare([collectError, logger])
---------------
let next = store.dispatch;
let dispatch1 = logger(store)(next);
// 这时候的console.log('dispatching', action) 是没有执行的
let dispatch2 = collectError(store)(dispatch1);
// 这时候的console.log('Error!', err) 也是没有执行的
store.dispatch = dispatch2;
dispatch 的时候,先执行 dispatch2 ,然后 dispatch2 里面使用了 next函数,即 dispatch1,
所以转去执行1,1执行完成后,2继续执行,有点像递归。
![29c378c2772fc72d5520a1a591b1b29c.png](https://i-blog.csdnimg.cn/blog_migrate/a160fb80a47a73e7f0228f85f6c6aa9e.png)
![6eb06207ea79d84baf255f6d208f9549.png](https://i-blog.csdnimg.cn/blog_migrate/76f4a8daaaffda916696673eb0828ea3.jpeg)
React hook原理?
1 Hooks只是数组,初始化时创建两个空数组: setters
和state
,将光标设置为0
第一次渲染时候,根据 useState 顺序,逐个声明 state 并且将其放入全局 Array 中。每次声明 state,都要将 cursor 增加 1。
更新 state,触发再次渲染的时候。cursor 被重置为 0。按照 useState 的声明顺序,依次拿出最新的 state 的值,视图更新。
每个setter都有一个对它的光标位置的引用,因此通过触发对任何setter的调用,它将改变状态数组中该位置的状态值。
为什么不能在循环,条件判断,嵌套函数中使用hooks:
因为useState中的state以及setState基于cursor,如果第一次进入if条件,第二次没有进入,就会导致整个cursor的位置发生错乱,导致第三次的跑到cursor1中
常用的hooks:
useState:
6 谈谈你对fiber架构的理解?
Fiber架构是对React核心算法reconciliation的优化,React渲染页面分为两个阶段:调度阶段(reconciliation)以及渲染阶段(commit),调度阶段(在这个阶段 React 会更新数据生成新的 Virtual DOM,然后通过 Diff 算法,快速找出需要更新的元素,放到更新队列中去,得到新的更新队列。)渲染阶段(commit)(这个阶段 React 会遍历更新队列,将其所有的变更一次性更新到 DOM 上。)
Fiber架构是对React核心算法reconciliation的优化,React渲染页面分为两个阶段:调度阶段(reconciliation)以及渲染阶段(commit)。在React 16之前,调度阶段(reconciliation)和渲染阶段(commit)按顺序执行,相当于没分阶段,形成同步渲染,采用自顶向下递归,从根组件或 setState() 后的组件开始,更新整个子树。如果组件层级深,组件树很大,就会一直占用浏览器主线程,导致布局、动画以及交互事件无法响应。
React 16 之后,reconciler(被称为 fiber reconciler )为了解决主线程阻塞问题,使用了Fiber架构,以fiber(fiber tree)为单位,Fiber可以在调度阶段(reconciliation)对fiber tree做调度处理,行成异步渲染,但渲染阶段(commit)不能调度不能暂停。
Fiber 在调度的时候会执行如下流程:
将一个state更新任务拆分成多个时间小片,形成一个 Fiber 任务队列。2 任务队列中选出优先级高的 Fiber 执行,如果执行时间超过了deathLine,则设置为pending状态挂起状态(即执行一段时间,会跳出找Fiber任务队列中更高级的任务,如果有就放弃当前任务,即使当前任务执行了一半,可能已经经历了一些生命周期,都会被打断从来)。3 一个 Fiber 执行结束或挂起,会调用基于requestIdleCallback/requestAnimationFrame实现的调度器,返回一个新的 Fiber 任务队列继续进行上述过程(requestIdleCallback 会让一个低优先级的任务在空闲期被调用 .requestAnimationFrame 会让一个高优先级的任务在下一个栈被调用,从而保证了主线程按照优先级执行 fiber 单元 不同类型的任务会被分配不同的优先级)
fiber执行过程是?
- ReactDOM.render() 和 setState 的时候开始创建更新。
- 将创建的更新加入任务队列,等待调度。
- 在 requestIdleCallback 空闲时执行任务。
- 从根节点开始遍历 Fiber Node,并且构建 WokeInProgress Tree。
- 生成 effectList。
- 根据 EffectList 更新 DOM。
React 性能优化?
React 优化
优化的方向有两个,一个减少 render 次数,也就是减少 diff 计算。还有一个是减少计算的量,主要是减少重复计算,对于函数式组件来说,每次 render 都会重新从头开始执行函数调用。在类组件中主要使用 shouldComponentUpdate 生命周期和 PureComponent 组件去减少 render 次数,函数式组件主要使用:
- React.memo:等同于 PureComponent,用它包裹子组件,当父组件需要重新 render 的时候,如果传给自己的 props 不变,就不会触发重新 render。memo 可以添加第二个参数,是个函数,参数为前后 props,返回 true 不需要重新 render。
- useCallback:应用场景是父组件向子组件传递方法,当父组件重新渲染时,代码都会重新执行。所以就算子组件包裹了 React.memo,也会重新渲染。可以通过 useCallback 进行记忆传递的方法,并将记忆的方法传递给子组件。
- useMemo:如果在组件有个变量的值需要大量的计算才可以得出,因为函数组件重新渲染就会重新执行代码,所以该变量的值也会重新计算,就可以 useMemo 做计算结果缓存。
React 新的方法
React性能优化
三个方面:
1 结合Immutable(对Immutable对象的任何修改或添加删除操作都会返回一个新的Immutable对象,mmutable使用了Structural Sharing(结构共享),即如果对象树结点发生变化,只修改这个结点和受它影响的父节点,其他结点进行共享。)使用Immutable.is比较的是两个对象的hashCode值,避免了深度遍历比较,性能好
2 使用PureComponent对一些展示类的组件进行优化,因为进行的是浅比较,数组对象这种不会更新,基于classChildComponentextendsReact.PureComponent,继承自component,唯一区别就是内部实现了shaowcompare浅比较
2 React.memo高阶组件来进行优化函数是组件,实现原理就是HOC以及浅比较
3 React.lazy以及Suspense进行优化
使用场景: 1. 父组件有多个动态加载组件,Suspense可以直接将loading状态聚合,只有所有组件加载完成才显示,避免复杂逻辑判断。
Subspense的实现主要就是利用了**ComponentDidCatch这个生命周期,父组件渲染到子组件时发现异步请求,直接抛出错误,捕获的结果是一个Promise对象,ComponentDidCatch捕获这个Promise对象,pending状态下渲染fallback的,当resolve时重新render,遇到下一个异步请求重复上面操作,直到整个父组件的抛出的promise对象都为resolve,将loading替换为真正的组件.
性能分析的工具React Profiler
React中,更新过程是同步的,如果更新数量较多的组件时,浏览器的主线程就会一直被React占据,用户的输入就会造成影响,fiber为了解决这个问题,将把更新过程碎片化每执行完一段更新过程,就把控制权交还给React负责任务协调的模块,看看有没有其他紧急任务要做,如果没有就继续去更新,如果有紧急任务,那就去做紧急任务。React Fiber一个更新过程被分为两个阶段(Phase):第一个阶段Reconciliation Phase和第二阶段Commit Phase。
能简单介绍一下 react 执行过程吗?
1. jsx 经过 babel 转变成 render 函数
create update
enqueueUpdate
scheduleWork 更新 expiration time
requestWork
workLoop大循环
- performUnitOfWork
- beginWork
- completeUnitOfWork
Effect List
commit
react hooks?
能介绍一下 hook 吗
- 比起 hoc,hook 的复用性高
- useState、useEffect、useRef 用法
- 优化 usecallback、useMemo