本文是对React Fiber知识点的学习记录,网上有很多大佬对React Fiber的详细讲解,想要深入了解React Fiber的可以查看文章后面的引用。
一、React Fiber是什么,解决了什么问题
React Fiber在React v16引入,相当于是对核心渲染机制的一次重构。在没有引入这种算法之前,React在所有更新都没有完成时会一直占用主线程,直接导致的现象是渲染期间页面的其他js动效会卡住直到主线程继续,使页面出现卡顿的现象。
看一个官方例子:
<!DOCTYPE html>
<html style="width: 100%; height: 100%; overflow: hidden">
<head>
<meta charset="utf-8">
<title>Fiber Example</title>
</head>
<body>
<h1>Fiber Example</h1>
<div id="container">
<p>
To install React, follow the instructions on
<a href="https://github.com/facebook/react/">GitHub</a>.
</p>
<p>
If you can see this, React is <strong>not</strong> working right.
If you checked out the source from GitHub make sure to run <code>npm run build</code>.
</p>
</div>
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
<script type="text/babel">
var dotStyle = {
position: 'absolute',
background: '#61dafb',
font: 'normal 15px sans-serif',
textAlign: 'center',
cursor: 'pointer',
};
var containerStyle = {
position: 'absolute',
transformOrigin: '0 0',
left: '50%',
top: '50%',
width: '10px',
height: '10px',
background: '#eee',
};
var targetSize = 25;
class Dot extends React.Component {
constructor() {
super();
this.state = { hover: false };
}
enter() {
this.setState({
hover: true
});
}
leave() {
this.setState({
hover: false
});
}
render() {
var props = this.props;
var s = props.size * 1.3;
var style = {
...dotStyle,
width: s + 'px',
height: s + 'px',
left: (props.x) + 'px',
top: (props.y) + 'px',
borderRadius: (s / 2) + 'px',
lineHeight: (s) + 'px',
background: this.state.hover ? '#ff0' : dotStyle.background
};
return (
<div style={style} onMouseEnter={() => this.enter()} onMouseLeave={() => this.leave()}>
{this.state.hover ? '*' + props.text + '*' : props.text}
</div>
);
}
}
class SierpinskiTriangle extends React.Component {
shouldComponentUpdate(nextProps) {
var o = this.props;
var n = nextProps;
return !(
o.x === n.x &&
o.y === n.y &&
o.s === n.s &&
o.children === n.children
);
}
render() {
let {x, y, s, children} = this.props;
if (s <= targetSize) {
return (
<Dot
x={x - (targetSize / 2)}
y={y - (targetSize / 2)}
size={targetSize}
text={children}
/>
);
return r;
}
var newSize = s / 2;
var slowDown = true;
if (slowDown) {
var e = performance.now() + 0.8;
while (performance.now() < e) {
// Artificially long execution time.
}
}
s /= 2;
return [
<SierpinskiTriangle x={x} y={y - (s / 2)} s={s}>
{children}
</SierpinskiTriangle>,
<SierpinskiTriangle x={x - s} y={y + (s / 2)} s={s}>
{children}
</SierpinskiTriangle>,
<SierpinskiTriangle x={x + s} y={y + (s / 2)} s={s}>
{children}
</SierpinskiTriangle>,
];
}
}
class ExampleApplication extends React.Component {
constructor() {
super();
this.state = {
seconds: 0,
useTimeSlicing: true,
};
this.tick = this.tick.bind(this);
this.onTimeSlicingChange = this.onTimeSlicingChange.bind(this);
}
componentDidMount() {
this.intervalID = setInterval(this.tick, 1000);
}
tick() {
if (this.state.useTimeSlicing) {
// Update is time-sliced.
// 使用时间分片的方式更新
// https://github.com/facebook/react/pull/13488 将此api移除
// deferredUpdates是将更新推迟 https://juejin.im/entry/59c4885f6fb9a00a4456015d
ReactDOM.unstable_deferredUpdates(() => {
this.setState(state => ({ seconds: (state.seconds % 10) + 1 }));
});
} else {
// Update is not time-sliced. Causes demo to stutter.
// 更新没有做时间分片,导致卡顿 stutter(结巴)
this.setState(state => ({ seconds: (state.seconds % 10) + 1 }));
}
}
onTimeSlicingChange(value) {
this.setState(() => ({ useTimeSlicing: value }));
}
componentWillUnmount() {
clearInterval(this.intervalID);
}
render() {
const seconds = this.state.seconds;
const elapsed = this.props.elapsed;
const t = (elapsed / 1000) % 10;
const scale = 1 + (t > 5 ? 10 - t : t) / 10;
const transform = 'scaleX(' + (scale / 2.1) + ') scaleY(0.7) translateZ(0.1px)';
return (
<div>
<div>
<h3>Time-slicing</h3>
<p>Toggle this and observe the effect</p>
<Toggle
onLabel="On"
offLabel="Off"
onChange={this.onTimeSlicingChange}
value={this.state.useTimeSlicing}
/>
</div>
<div style={{ ...containerStyle, transform }}>
<div>
<SierpinskiTriangle x={0} y={0} s={1000}>
{this.state.seconds}
</SierpinskiTriangle>
</div>
</div>
</div>
);
}
}
class Toggle extends React.Component {
constructor(props) {
super();
this.onChange = this.onChange.bind(this);
}
onChange(event) {
this.props.onChange(event.target.value === 'on');
}
render() {
const value = this.props.value;
return (
<label onChange={this.onChange}>
<label>
{this.props.onLabel}
<input type="radio" name="value" value="on" checked={value} />
</label>
<label>
{this.props.offLabel}
<input type="radio" name="value" value="off" checked={!value} />
</label>
</label>
);
}
}
var start = new Date().getTime();
function update() {
ReactDOM.render(
<ExampleApplication elapsed={new Date().getTime() - start} />,
document.getElementById('container')
);
requestAnimationFrame(update);
}
requestAnimationFrame(update);
</script>
</body>
</html>
react fiber针对这种情况做了什么优化呢?主要是两点:1、将任务拆分成一小块一小块,2、获取到时间片才执行任务
下面主要来讲讲这两点
二、React Fiber的任务拆分
学过React的都知道,React有虚拟dom树,vDom-tree,要把计算任务拆分,那就要有任务恢复和任务完成后提交等功能,普通的vDom并不具备记录这些信息的能力,因此React Fiber从虚拟节点衍生出了一套Fiber节点,来记录任务状态。每一个Fiber节点都会对应一个虚拟节点,这样计算任务就拆分成了一个个小块,类似于下图,会有一个对应关系
简单描述下它的流程:
1.数据状态发生改变,即发生setState
2.开始diff算法
3.到达一个节点,生成对应的fiber节点,记录状态
4.查看当前是否有执行时间
5.有执行时间,计算完当前节点(节点的增删改),到下一个节点,继续这个步骤
6.到某个节点没有执行时间,则保存fiber状态,每个fiber都记录了下一个任务指向
7.重新获取到了执行时间,从当前记录的fiber节点开始往下继续
8.执行到最后的节点,将结果进行一级一级提交,这样直到根节点,组件就知道它已经完成了数据计算
9.最后一步,将最终确认的dom结果渲染到页面中
三、如何获取到时间片的
这个就比较简单了,刚开始我还以为React使用了什么骚操作来弄得,看了大佬们的文章后才知道,原来是用了浏览器的api: requestIdleCallback和requestAnimationFrame,requestAnimationFrame会在每一帧结束后确定调用它的回调函数,用于处理高优先级任务,为了达到不影响页面效果requestIdleCallback用于处理低优先任务,它的执行时机不确定。主要说下requestIdleCallback,每次调用requestIdleCallback,会告诉你现在是否是空闲时间,空闲时间有多久,它的用法:
// 一窥它的行为
requestIdleCallback(function(){
console.log('1');
let a = 1000000000;
while(a > 0){ a--; }
})
requestIdleCallback(function(){console.log('2')})
requestIdleCallback(function(){console.log('3')}, {timeout:10})
// 使用方式
const tasks = [...] // 任务队列
function taskHandler(deadline) {
// deadline.timeRemaining() 可以获取到当前帧剩余时间
while (deadline.timeRemaining() > 0 && tasks.length > 0) {
// do something
}
// 没时间了,但还有任务,继续注册一个,等到下次有时间了会执行
if (tasks.length > 0){
requestIdleCallback(taskHandler);
}
}
requestIdelCallback(taskHandler);
上面说到任务的优先级,在fiber任务执行完进行dom更新的时候,这块是没法做任务拆分的,如果遇到dom变化很大,更新耗时的情况也会造成卡顿,这个没法避免,如果此时有用户交互发生,造成卡顿会降低用户体验,Fiber针对这种情况也做了优化,将任务分成优先级,像用户输入,交互等行为属于高优先级,会优先处理,然后页面渲染,diff计算等属于次优先级。
几篇对React Fiber不错的介绍:
React-从源码分析React Fiber工作原理
理解react16.3的fiber架构
React Fiber
React Fiber是什么
React Fiber初探
react-fiber-resources
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。