一 Fiber之前的React
下面代码实现了一个简单的react手写
let element = (
<div id = "A1">
<div id = "B1">
<div id="C1"></div>
<div id="C2"></div>
</div>
<div id="B2"></div>
</div>
)
console.log(JSON.stringify(element, null, 2))
// 如果节点多,层级特别深
// 因为js是单线程,而且ui渲染和js执行是互斥的
function render(element, parentDom) {
// 创建DOM元素
let dom = document.createElement(element.type);
// 处理属性
Object.keys(element.props)
.filter(key => key !== 'children')
.forEach(key => {
dom[key] = element.props[key];
});
if(Array.isArray(element.props.children)) {
// 把每个子虚拟DOM变成真实DOM插入到DOM节点里
element.props.children.forEach(child => render(child, dom));
}
parentDom.appendChild(dom);
}
render(
element,
document.getElementById('root')
);
打印出的element如下:
{
"type": "div",
"key": null,
"ref": null,
"props": {
"id": "A1",
"children": [
{
"type": "div",
"key": null,
"ref": null,
"props": {
"id": "B1",
"children": [
{
"type": "div",
"key": null,
"ref": null,
"props": {
"id": "C1"
},
"_owner": null,
"_store": {}
},
{
"type": "div",
"key": null,
"ref": null,
"props": {
"id": "C2"
},
"_owner": null,
"_store": {}
}
]
},
"_owner": null,
"_store": {}
},
{
"type": "div",
"key": null,
"ref": null,
"props": {
"id": "B2"
},
"_owner": null,
"_store": {}
}
]
},
"_owner": null,
"_store": {}
}
效果如下:
jsx标签化是嵌套的结构,如代码所示,最终会编译成递归执行的代码,要想中断递归是很困难的。即react16之前的调度器为栈调度器,栈浅显易懂,代码量少,但不能随意break掉,continue掉,要维护一系列的栈上下文;
二 帧
- 目前大多数设备的屏幕刷新频率为60次/秒
- 当每秒绘制的帧数(FPS)达到60时,页面是流畅的,小于这个值时,用户会感觉到卡顿;
- 每个帧的预算时间是16.66毫秒(1秒/60)
- 每个帧的开头包括样式计算,布局和绘制
- JavaScript执行JavaScript引擎和页面渲染引擎在同一个渲染线程,GUI渲染和JavaScript执行两者是互斥的
- 如果某个任务执行时间过长,浏览器就会推迟渲染;
三 什么是Fiber
我们可以通过某些调度策略合理分配CPU资源,从而提高用户的响应速度
通过Fiber架构,让自己的协调过程变成可被中断,适时地让出CPU执行权,可以让浏览器即使地响应用户的交互;
fiber:就是一个数据结构,它有很多属性,虚拟dom是对真实dom的一种简化;一些真实的dom都做不到的事情,那虚拟dom更做不到,于是就有了fiber,有很多属性,希望借由fiber上的这堆属性来做一些比较厉害的事情;
fiber架构
为了弥补一些不足,就设计了一些新的算法,而为了能让这些算法跑起来,所以出现了fiber这种数据结构; fiber数据结构+算法 = fiber架构;
react应用从始至终管理着基本的三样东西:
-
Root(整个应用的根儿,一个对象,不是fiber,有个属性指向current树,同时也有个属性指向workInProgress树)
-
current树(树上的每一个节点都是fiber,保存的是上一次的状态 并且每个fiber节点,都对应着一个jsx节点)
-
workInProgress树(树上的每一个节点都是fiber,保存的是本次新的状态,并且每个fiber节点都对应一个jsx节点)
初次渲染的时候,没有current树 react在一开始创建Root,就会同时创建一个unintialFiber的东西(未初始化的fiber) 让react的current指向了uninitialFiber 之后再去创建一个本次要用到的workInProgress
react 中主要分两个阶段
render阶段(指的是创建fiber的过程)
-
为每个节点创建新的fiber(workInProgress)(可能是复用) 生成一颗有新状态的workInProgress树
-
初次渲染的时候(或新创建了某个节点的时候) 会将这个fiber创建真实的dom实例 并且对当前节点的子节点进行插入appendChildren,
-
如果不是初次渲染的话,就对比新旧的fiber的状态,将产生了更新的fiber节点,最终通过链表的形式,挂载到RootFiber
commit阶段****才是真正的要操作页面的阶段
-
执行生命周期
-
会从RootFiber上获取到那条链表,根据链表上的标识来操作页面;
3.1 Fiber是一个执行单元
Fiber是一个执行单元,每次执行完一个执行单元,React就会检查还剩下多少时间,如果没有时间就把控制权让出去
3.2 Fiber是一种数据结构
react目前的做法是使用链表,每个虚拟节点内部表示为一个Fiber;
代码如下所示:
class FiberNode {
constructor(tag, key, pendingProps) {
this.tag = tag; // 表示当前fiber的类型
this.key = key;
this.type = null // 'div' || 'h1' || Ding
this.stateNode = null; // 表示当前fiber的实例
this.child = null; // 表示当前fiber的子节点 每一个fiber有且只有一个指向它的firstChild
this.sibling = null; // 表示当前节点的兄弟节点 每个fiber有且只有一个属性指向隔壁的兄弟节点
this.return = null; // 表示当前fiber的父节点
this.index = 0;
this.memoizedState = null; // 表示当前fiber的state
this.memoizedProps = null; // 表示当前fiber的props
this.pendingProps = pendingProps; // 表示新进来的props
this.effecTag = null; // 表示当前节点要进行何种更新
this.firstEffect = null; // 表示当前节点的有更新的第一个子节点
this.lastEffect = null; // 表示当前节点的有更新的最后一个子节点
this.nextEffect = null; // 表示下一个要更新的子节点
this.alternate = null; // 用来连接current和workInProgress的
this.updateQueue = null; // 一条链表上面挂载的是当前fiber的新状态
// 其实还有很多其他的属性
// expirationTime: 0
}
}
四 rAF
requestAnimationFrame回调函数会在绘制之前执行
-
requestAnimationFrame(callback)会在浏览器每次重绘前执行callback回调,每次callback执行的时机都是浏览器刷新下一帧渲染周期的起点上
-
requestAnimationFrame(callback)的回调callback回调参数timestamp是回调被调用的时间,也就是当前帧的起始时间
-
rAfTime performance.timing.navigationStart + performance.now()约等于Date.now();
下面代码实现了一个绘制进度条的功能;
<script>
let div = document.querySelector('div');
let button = document.querySelector('button');
let startTime;
function progress() {
div.style.width = div.offsetWidth + 1 +'px';
div.innerHTML = div.offsetWidth + '%';
if(div.offsetWidth < 100) {
console.log(Date.now() - startTime + 'ms');
startTime = Date.now();
requestAnimationFrame(progress);
}
}
button.onclick = function() {
div.style.width = 0;
startTime = Date.now();
// 浏览器会在每一帧渲染前执行progress
requestAnimationFrame(progress);
}
</script>
五 requestIdleCallbac
-
我们希望快速响应用户,让用户觉得够快,不能阻塞用户的交互
-
requestIdleCallback使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应
-
正常帧任务完成后没超过16ms,说明时间有富余,此时就会执行requestIdleCallback里注册的任务
-
requestAnimationFrame的回调会在每一帧确定执行,属于高优先级任务,而requestIdleCallback的回调则不一定,属于低优先级任务;
<script type="text/javascript">
function sleep(duration) {
let start = Date.now();
while(start + duration > Date.now()) {}
}
function progress() {
console.log('progress');
requestAnimationFrame(progress);
}
// requestAnimationFrame(progress);
let channel = new MessageChannel();
let activeFrameTime = 1000/60; // 16.6
let frameDeadline; // 这一帧的截止时间
let pendingCallback;
let timeRemaining = () => frameDeadline - performance.now();
channel.port2.onmessage = function() {
let currentTime = performance.now();
// 如果帧的截止时间已经小于当前时间,说明已经过期了
let didTimeout = frameDeadline <= currentTime;
if(didTimeout || timeRemaining() > 0) {
if(pendingCallback) {
pendingCallback({didTimeout, timeRemaining});
}
}
}
window.requestIdleCallback = (callback, options) => {
requestAnimationFrame((rafTime) => {
console.log('rafTime', rafTime);
// 每一帧开始的时间加上16.6 就是一帧的截止时间了
frameDeadline = rafTime + activeFrameTime;
pendingCallback = callback;
// 其实发消息只会,相当于添加一个宏任务
channel.port1.postMessage('hello');
});
}
const works = [
() => {
console.log('A1开始');
sleep(20);
console.log('A1结束');
},
() => {
console.log('B1开始');
sleep(20);
console.log('B1结束');
},
() => {
console.log('C1开始');
sleep(20);
console.log('C1结束');
},
() => {
console.log('C2开始');
sleep(20);
console.log('C2结束');
},
() => {
console.log('B2开始');
sleep(20);
console.log('B2结束');
},
]
// 告诉浏览器 你可以在空闲的时间执行任务,但是如果已经过期了 不管你有没有空 都要帮我执行
requestIdleCallback(workLoop, {timeout: 1000});
// 循环执行工具
function workLoop(deadline) {
console.log('本帧的剩余时间', parseInt(deadline.timeRemaining()));
// 如果说还有剩余时间 并且还有没有完成的任务
while((deadline.timeRemaining() > 0 || deadline.didTimeout) && works.length > 0){
performUnitWork();
}
if(works.length > 0) {
console.log(`只剩下${deadline.timeRemaining()}, 时间片已经到期,等待下次调试`);
requestIdleCallback(workLoop);
}
}
function performUnitWork() {
let work = works.shift();
work();
}
六 MessageChannel
-
目前requestIdleCallback只要Chrome支持
-
所以目前React利用MessageChannel模拟了requestIdleCallback,将回调延迟到绘制操作只后执行
-
MessageChannel API允许我们创建一个新的消息通道,并通过它的两个MessagePort 属性发送数据;
-
MessageChannel创建了一个通信的管道,这个管道有两个端口,每个端口都可以通过postMessage发送数据,而一个端口只要绑定了,就能收到另一个端口传过来的数据
-
MessageChannel是一个宏任务;
七 Fiber执行阶段
每次渲染有两个阶段:Reconciliation(协调render阶段)和Commit(提交阶段)
- 协调阶段:可以认为是diff阶段,这个阶段可以被中断,这个阶段会找出所有节点变更,例如节点新增,删除,属性变更等等,这些变更React称之为副作用;
- 提交阶段:将上一个阶段计算出来的需要处理的副作用(Effetcs)一次性执行了。这个阶段必须同步执行,不能被打断;
7.1 render阶段
- 从顶点开始遍历
- 如果有第一个儿子,先遍历第一个儿子
- 如果没有第一个儿子,标志着此节点遍历完成
- 如果有弟弟遍历弟弟
- 如果没有下一个弟弟,返回父节点标志完成父节点遍历,如果有叔叔遍历叔叔
- 没有父节点遍历结束
先儿子,后弟弟,再叔叔,辈分越小越优先
什么时候一个节点遍历完成,没有子节点,或者所有子节点都遍历完成了,没爹了就表示全部遍历完成了;
7.2 commit阶段
下面代码实现了一个简易的Fiber架构,只有初次render过程;
element.js代码
export default {
"type": "div",
"props": {
"id": "A1",
"children": [
{
"type": "div",
"props": {
"id": "B1",
"children": [
{
"type": "div",
"props": {
"id": "C1"
}
},
{
"type": "div",
"props": {
"id": "C2"
}
}
]
},
},
{
"type": "div",
"props": {
"id": "B2"
}
}
]
}
}
index.js代码
import element from './element.js';
let container = document.getElementById('root');
const PLACEMENT = 'PLACEMENT';
// 下一个工作单元
// fiber其实也是一个普通的JS对象
let workInProgressRoot = {
stateNode: container, // 此fiber对应的dom节点
props: {children: [element]} // fiber的属性
}
let nextUnitOfWork = workInProgressRoot;
function workLoop(deadline) {
// 如果有当前的工作单元,就执行它,并返回一个工作单元
while(nextUnitOfWork && deadline.timeRemaining() > 0) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
if(!nextUnitOfWork) {
commitRoot();
}
}
function commitRoot() {
let currentFiber = workInProgressRoot.firstEffect;
while(currentFiber) {
console.log('commitRoot', currentFiber.props.id);
if(currentFiber.effectTag === 'PLACEMENT') {
currentFiber.return.stateNode.appendChild(currentFiber.stateNode);
}
currentFiber = currentFiber.nextEffect;
}
workInProgressRoot = null;
}
function performUnitOfWork(workingProgressFiber) {
// 1 创建真实的dom, 并没有挂载 2,创建fiber子树
beginWork(workingProgressFiber);
if(workingProgressFiber.child) {
return workingProgressFiber.child; // 如果有儿子,返回儿子
}
while(workingProgressFiber) {
// 如果没有儿子当前节点其实就结束完成了
completeUnitOfWork(workingProgressFiber);
if(workingProgressFiber.sibling) { // 如果有弟弟,返回弟弟
return workingProgressFiber.sibling;
}
workingProgressFiber = workingProgressFiber.return; // 先指向父亲
}
}
function beginWork(workingProgressFiber) {
console.log('beginWork', workingProgressFiber.props.id);
if(!workingProgressFiber.stateNode) {
workingProgressFiber.stateNode = document.createElement(workingProgressFiber.type);
for (let key in workingProgressFiber.props) {
if(key !== 'children') {
workingProgressFiber.stateNode[key] = workingProgressFiber.props[key];
}
}
}
// 在beginwork里是不会挂载的
let previousFiber;
if(Array.isArray(workingProgressFiber.props.children)) {
workingProgressFiber.props.children.forEach((child, index) => {
let childFiber = {
type: child.type,
props: child.props,
return: workingProgressFiber,
effectTag: 'PLACEMENT', // 这个fiber对应的dom节点需要被插入到页面中
}
if(index === 0) {
workingProgressFiber.child = childFiber;
} else {
previousFiber.sibling = childFiber;
}
previousFiber = childFiber;
});
}
}
function completeUnitOfWork(workingProgressFiber) {
console.log('completeUnitOfWork', workingProgressFiber.props.id);
// 构建副作用链effectList 只有那些有副作用的节点
let returnFiber = workingProgressFiber.return;
if(returnFiber) {
// 把当前fiber的有副作用子链表挂到父亲身上
if(!returnFiber.firstEffect) {
returnFiber.firstEffect = workingProgressFiber.firstEffect;
}
if(workingProgressFiber.lastEffect) {
if(returnFiber.lastEffect) {
returnFiber.lastEffect.nextEffect = workingProgressFiber.firstEffect;
}
returnFiber.lastEffect = workingProgressFiber.lastEffect;
}
// 再把自己挂到后面
if(workingProgressFiber.effectTag) {
if(returnFiber.lastEffect) {
returnFiber.lastEffect.nextEffect = workingProgressFiber;
} else {
returnFiber.firstEffect = workingProgressFiber;
}
returnFiber.lastEffect = workingProgressFiber;
}
}
}
// 告诉浏览器在空闲的时间执行workLoop
requestIdleCallback(workLoop);
上面代码可以实现第一章的dom结构;
八 fiber小demo
下面demo实现了初次渲染功能的fiber架构,代码已验证可以跑通;目录结构如下图所示
webpack配置
let path = require('path');
let HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'bundle.[hash:8].js',
path: path.resolve(__dirname, 'build'),
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html',
}),
],
module: {
rules: [
{
test: /\.html$/,
use: 'html-withimg-loader',
},
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: { // 用babel-loader需要把es6-es5
presets: [
'@babel/preset-env',
'@babel/preset-react'
],
plugins: [
// '@babel/plugin-proposal-class-properties'
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose" : true }],
"@babel/plugin-transform-runtime",
]
}
},
include: path.resolve(__dirname, 'src'),
exclude: /node_modules/,
}
]
}
}
src/index.js
import React from './react/react.js';
import ReactDom from './react-dom/react-dom.js';
class Stoney extends React.Component {
state = {
Stoney: 999
}
handleClick = () => {
console.log('click')
}
render() {
return (
<div onClick = {this.handleClick}>
<h1 style={{color: 'red'}}>abc</h1>
<h2>
123
<p>456</p>
</h2>
<h3>
<span></span>
</h3>
</div>
)
}
}
ReactDom.render(
<Stoney key="789"></Stoney>,
document.querySelector('#app')
)
react/react.js
const REACT_ELEMENT_TYPE = Symbol.for('react.element');
function ReactElement(type, key, props) {
let element = {
$$typeof: REACT_ELEMENT_TYPE,
type,
key,
props
}
return element;
}
function createElement(type, props = {}, children) {
let _props = Object.assign({}, props);
let _key = _props.key || null;
let children_length = children.length;
_props.children = children_length === 0 ? null : children_length === 1 ? children[0] : children;
return ReactElement(type, _key, _props);
}
class Component {
constructor(props, context, updater) {
this.props = props;
this.context = context;
this.updater = updater || null;
}
get isReactComponent() {
return true;
}
setState(partialState, callback) {
if(partialState instanceof Object || typeof partialState === 'function') {
let _setState = this.updater.enqueueSetState;
_setState && _setState(this, partialState, callback, 'setState')
}
}
}
const React = {
createElement: function(type, props, ...children) {
let element = createElement(type, props, children);
return element;
},
Component
}
export default React;
react-dom/react-dom.js
import React from '../react/react.js';
let isFirstRender = false;
let HostRoot = 'HostRoot'; // 标识RootFiber类型
let ClassComponent = 'ClassComponent'; // 表示类组件的类型
let HostComponent = 'HostComponent'; // 表示原生dom类型
let HostText = 'HostText'; // 表示文本类型
// let FunctionComponent = 'FunctionComponent'; // 表示函数组件类型
let NoWork = 'NoWork'; // 表示没有任何工作
let Placement = 'Placement'; // 表示这个节点是新插入的
let Update = 'Update'; // 表示当前节点有更新
let Deletion = 'Deletion'; // 表示当前节点要被删除
let PlacementAndUpdate = 'PlacementAndUpdate' // 一般是节点换位置同时更新了
let nextUnitOfWork = null;
let ifError = (function() {
// 这个函数没用 就是while循环万一卡死了可以退出
let _name = '';
let _time = 0;
return function(name, time) {
_name = _name !== name ? name : _name;
_time++;
if(_time >= time) {
throw `${name}函数的执行超过了${time}次`
}
}
})()
let eventsName = {
onClick: 'click',
onChange: 'change',
onInput: 'input',
}
class FiberNode {
constructor(tag, key, pendingProps) {
this.tag = tag; // 表示当前fiber的类型
this.key = key;
this.type = null // 'div' || 'h1' || Ding
this.stateNode = null; // 表示当前fiber的实例
this.child = null; // 表示当前fiber的子节点 每一个fiber有且只有一个指向它的firstChild
this.sibling = null; // 表示当前节点的兄弟节点 每个fiber有且只有一个属性指向隔壁的兄弟节点
this.return = null; // 表示当前fiber的父节点
this.index = 0;
this.memoizedState = null; // 表示当前fiber的state
this.memoizedProps = null; // 表示当前fiber的props
this.pendingProps = pendingProps; // 表示新进来的props
this.effectTag = NoWork; // 表示当前节点要进行何种更新
this.firstEffect = null; // 表示当前节点的有更新的第一个子节点
this.lastEffect = null; // 表示当前节点的有更新的最后一个子节点
this.nextEffect = null; // 表示下一个要更新的子节点
this.alternate = null; // 用来连接current和workInProgress的
this.updateQueue = null; // 一条链表上面挂载的是当前fiber的新状态
// 其实还有很多其他的属性
// expirationTime: 0
}
}
function createFiber(tag, key, pendingProps) {
return new FiberNode(tag, key, pendingProps);
}
function createWorkInProgress(current, pendingProps) {
// 复用current.alternate
let workInProgress = current.alternate;
if (!workInProgress) {
workInProgress = createFiber(current.tag, current.key, pendingProps);
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
// 要让这俩东西互相指向
workInProgress.alternate = current;
current.alternate = workInProgress;
} else {
workInProgress.pendingProps = pendingProps;
workInProgress.effectTag = NoWork;
workInProgress.firstEffect = null;
workInProgress.lastEffect = null;
workInProgress.nextEffect = null;
}
// 要保证current和current.alternate上的updateQueue是同步的;
// 因为每次执行setState的时候会创建新的更新 把更新挂载到组件对应的fiber上;
// 这个fiber在奇数次更新时,存在于current树上 在偶数次更新时存在于current.alternate
// 咱们每次创建(或复用)workInProgress 是从current.alternate上拿的对象
// 复用的这个alternate上 updateQueue上不一定有新的更新
// 所以这里要判断如果current.alternate上没有新的更新的话 就说明本轮更新找到的这个fiber 存在于current树上
// 源码中没有这个判断
// 在执行createWorkInProgress之前,调用了一个叫做enqueueUpdate的方法
// 这个方法中 它将fiber和current,alternate上的updateQueue的新状态 进行了同步
// 只有初次渲染的时候 会给组件的实例一个属性 指向它的fiber
// 以后这个fiber 就不会再改变了
if (!!workInProgress && !!workInProgress.updateQueue && !workInProgress.updateQueue.lastUpdate) {
workInProgress.updateQueue = current.updateQueue;
}
workInProgress.child = current.child;
workInProgress.memoizedState = current.memoizedState;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.sibling = current.sibling;
workInProgress.index = current.index;
return workInProgress;
}
function reconcileSingleElement(returnFiber, element) {
let type = element.type;
let flag = null;
if(element.$$typeof === Symbol.for('react.element')) {
if (typeof type === 'function') {
if (type.prototype && type.prototype.isReactComponent) {
flag = ClassComponent;
}
} else if (typeof type === 'string') {
flag = HostComponent;
}
let fiber = createFiber(flag, element.key, element.props);
fiber.type = type;
fiber.return = returnFiber;
return fiber;
}
}
function reconcileSingTextNode(returnFiber, text) {
let fiber = createFiber(HostText, null, text);
fiber.return = returnFiber;
return fiber;
}
function reconcileChildrenArray(workInProgress, nextChildren) {
// 这个方法中 要通过index和key值去尽可能多的找到可以复用的dom节点
// 这个函数就是react中最复杂的diff算法
let nowWorkInProgress = null;
if (isFirstRender) {
nextChildren.forEach((reactElement, index) => {
if(index === 0) {
if(typeof reactElement === 'string' || typeof reactElement === 'number') {
workInProgress.child = reconcileSingTextNode(workInProgress, reactElement)
} else {
workInProgress.child = reconcileSingleElement(workInProgress, reactElement)
}
nowWorkInProgress = workInProgress.child;
} else {
if(typeof reactElement === 'string' || typeof reactElement === 'number') {
nowWorkInProgress.sibling = reconcileSingTextNode(workInProgress, reactElement);
} else {
nowWorkInProgress.sibling = reconcileSingleElement(workInProgress, reactElement)
}
nowWorkInProgress = nowWorkInProgress.sibling;
}
})
// debugger;
return workInProgress.child;
}
}
function reconcileChildFiber(workInProgress, nextChildren) {
// debugger;
if (typeof nextChildren === 'object' && !!nextChildren && !!nextChildren.$$typeof) {
// 说明它是一个独生子 并且是react元素
return reconcileSingleElement(workInProgress, nextChildren)
}
if (nextChildren instanceof Array) {
return reconcileChildrenArray(workInProgress, nextChildren)
}
if(typeof nextChildren === 'string' || typeof nextChildren === 'number') {
return reconcileSingTextNode(workInProgress, nextChildren)
}
return null;
}
function reconcileChildren(workInProgress, nextChildren) {
if (isFirstRender && !!workInProgress.alternate) {
workInProgress.child = reconcileChildFiber(workInProgress, nextChildren);
workInProgress.child.effectTag = Placement;
} else {
workInProgress.child = reconcileChildFiber(workInProgress, nextChildren);
}
return workInProgress.child;
}
function updateHostRoot(workInProgress) {
// debugger;
let children = workInProgress.memoizedState.element;
return reconcileChildren(workInProgress, children);
}
function updateClassComponennt(workInProgress) {
// debugger;
let component = workInProgress.type;
let nextProps = workInProgress.pendingProps;
if(!!component.defaultProps) {
nextProps = Object.assign({}, component.defaultProps, nextProps)
}
let shouldUpdate = null;
let instance = workInProgress.stateNode;
if(!instance) {
// 没有实例 说明是初次渲染 或者是一个新创建的节点
instance = new component(nextProps);
workInProgress.memoizedState = instance.state;
workInProgress.stateNode = instance;
instance._reactInternalFiber = workInProgress;
instance.updater = classComponentUpdater;
// 用来代替componentWillReceiveProps
let getDerivedStateFromProps = component.getDerivedStateFromProps;
if (!!getDerivedStateFromProps) {
let prevState = workInProgress.memoizedState
let newState = getDerivedStateFromProps(nextProps, prevState);
if(!(newState === null || newState === undefined)) {
if(typeof newState === 'object' && !(newState instanceof Array)) {
workInProgress.memoizedState = Object.assign({}, nextProps, newState)
}
}
instance.state = workInProgress.memoizedState;
}
// 要处理一些生命周期之类的
shouldUpdate = true;
} else {
// 说明不是初次渲染
}
// debugger;
let nextChild = instance.render();
return reconcileChildren(workInProgress, nextChild);
}
function updateHostComponent(workInProgress) {
// debugger;
let nextProps = workInProgress.pendingProps;
let nextChildren = nextProps.children;
// 对于文本类型的节点
// 不一定每次都创建对应的fiber
// 当这个节点有兄弟节点的时候会创建对应的fiber
// 当它是独生子的时候不会创建fiber 直接返回null
if (typeof nextChildren === 'string' || typeof nextChildren === 'number') {
nextChildren = null;
}
return reconcileChildren(workInProgress, nextChildren)
}
function beginWork(workInProgress) {
// debugger;
let tag = workInProgress.tag;
let next = null;
if (tag === HostRoot) {
next = updateHostRoot(workInProgress);
} else if (tag === ClassComponent) {
next = updateClassComponennt(workInProgress);
} else if(tag === HostComponent) {
next = updateHostComponent(workInProgress);
} else if (tag === HostText) {
next = null;
}
return next;
}
function completeWork(workInProgress) {
// 1, 创建真实的dom实例
let tag = workInProgress.tag;
let instance = workInProgress.stateNode;
if(tag === HostComponent) {
if(!instance) {
// 说明这个节点是初次挂载
// 也可能是一个新创建的节点
let domElement = document.createElement(workInProgress.type);
domElement.__reactInternalInstance = workInProgress;
workInProgress.stateNode = domElement;
// 2,对子节点进行插入
let node = workInProgress.child;
wapper: while(!!node) {
let tag = node.tag;
if(tag === HostComponent || tag === HostText) {
domElement.appendChild(node.stateNode)
} else {
node.child.return = node;
node = node.child;
continue;
}
if(node === workInProgress) break;
while(node.sibling === null) {
if(node.return === null || node.return === workInProgress) {
break wapper
}
node = node.return;
}
node.sibling.return = node.return;
node = node.sibling;
}
// 3,把属性给它
let props = workInProgress.pendingProps;
for (let propKey in props) {
let propValue = props[propKey];
if(propKey === 'children') {
if(typeof propValue === 'string' || typeof propValue === 'number') {
domElement.textContent = propValue;
}
} else if(propKey === 'style') {
for (let stylePropKey in propValue) {
if(!propValue.hasOwnProperty(stylePropKey)) continue;
let styleValue = propValue[stylePropKey].trim();
if(styleValue === 'float') {
stylePropKey = 'cssFloat';
}
domElement.style[stylePropKey] = styleValue
}
} else if(eventsName.hasOwnProperty(propKey)) {
// react中所有写在jsx模板上的事件 都是合成事件
// 合成事件不会立即执行传进来的数据
// 而是先执行一些其他东西
// 比如事件源对象做一些处理进行合并
// 会把你所有的事件都代理到根节点
// 最事件代理的好处就是全局你可能只用绑定一个事件就够了
// 再比如它内部会自己写个什么阻止冒泡的方法或阻止默认的方法
domElement.addEventListener(eventsName[propKey], event, false);
} else {
domElement.setAttribute(propKey, propValue);
}
}
}
} else if (tag === HostText) {
let oldText = workInProgress.memoizedprops;
let newText = workInProgress.pendingProps;
if(!instance) {
instance = document.createTextNode(newText);
workInProgress.stateNode = instance;
} else {
}
}
}
function completeUnitOfWork(workInProgress) {
while(true) {
let returnFiber = workInProgress.return;
let siblingFiber = workInProgress.sibling;
completeWork(workInProgress);
let effectTag = workInProgress.effectTag;
let hasChange = (
effectTag === Update ||
effectTag === Deletion ||
effectTag === Placement ||
effectTag === PlacementAndUpdate
)
if (hasChange) {
if(!!returnFiber.lastEffect) {
returnFiber.lastEffect.nextEffect = workInProgress;
} else {
returnFiber.firstEffect = workInProgress;
}
returnFiber.lastEffect = workInProgress;
}
if(!!siblingFiber) return siblingFiber;
if(!!returnFiber) {
workInProgress = returnFiber;
continue
}
return null;
}
}
function performUnitOfWork(workInProgress) {
// beginWork的目的就是根据传进去的这个workInProgress
// 生成它的子节点的fiber
let next = beginWork(workInProgress);
if(next === null) {
next = completeUnitOfWork(workInProgress);
}
return next;
}
function workLoop(nextUnitOfWork) {
while(!!nextUnitOfWork) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
}
}
let classComponentUpdater = {
enqueueSetState() {
}
}
function commitRoot(root, finishedWork) {
let isWorking = true;
let isCommitting = true;
// 有三个循环
// 第一个循环 用来执行getSnapshotBeforeUpdate
// 第二个循环 真正用来操作页面 将有更新的节点 该插入的插入 该更新的更新 该删除的删除
// 第三个循环 执行剩下的生命周期 componentDidUodate 或者 componnentDidMount
let firstEffect = finishedWork.firstEffect;
let nextEffect = null;
// nextEffect = firstEffect;
// while() {
//
// }
nextEffect = firstEffect;
while(!!nextEffect) {
ifError('第二个循环', 50);
let effectTag = nextEffect.effectTag;
if(effectTag.includes(Placement)) {
// 说明是新插入的节点
// 1,先找到一个能被插进来的父节点
// 2,在找能往父节点中插入的子节点
let parentFiber = nextEffect.return;
let parent = null;
while(!!parentFiber) {
let tag = parentFiber.tag;
if(tag === HostComponent || tag === HostRoot) {
break
}
}
if(parentFiber.tag === HostComponent) {
parent = parentFiber.stateNode;
} else if(parentFiber.tag === HostRoot) {
parent = parentFiber.stateNode.container;
}
if(isFirstRender) {
let tag = nextEffect.tag;
if(tag === HostComponent || tag === HostText) {
parent.appendChild(nextEffect.stateNode);
} else {
let child = nextEffect;
while(true) {
ifError('第二个循环中的循环', 50);
let tag = child.tag;
if(tag === HostComponent || tag === HostText) {
break;
}
child = child.child;
}
parent.appendChild(child.stateNode);
}
}
} else if(effectTag === Update) {
// 说明可能有更新
} else if(effectTag === Deletion) {
// 说明该节点要被删除
} else if(effectTag === PlacementAndUpdate) {
// 说明该节点可能是换了位置并且属性上有更新
}
}
// nextEffect = firstEffect;
// while() {
//
// }
isWorking = false;
isCommitting = false;
}
function completeRoot(root, finishedWork) {
root.finishedWork = null;
commitRoot(root, finishedWork);
}
class ReactRoot {
constructor(container) {
this._internalRoot = this._createRoot(container)
}
_createRoot(container) {
let uninitialFiber = this._createUninitialFiber(HostRoot, null, null);
let root = {
container: container,
current: uninitialFiber,
finishedWork: null
}
uninitialFiber.stateNode = root;
return root
}
_createUninitialFiber(tag, key, pendingProps) {
return createFiber(tag, key, pendingProps)
}
render(reactElement, callback) {
console.log('render')
let root = this._internalRoot;
// RootFiber
let workInProgress = createWorkInProgress(root.current, null);
// react 源码里头是先把这个reactElement先放到了current
workInProgress.memoizedState = { element: reactElement }
nextUnitOfWork = workInProgress;
workLoop(nextUnitOfWork);
root.finishedWork = root.current.alternate;
if(!!root.finishedWork) {
completeRoot(root, root.finishedWork)
}
}
}
const ReactDOM = {
render(reactElement, container, callback) {
isFirstRender = true;
let root = new ReactRoot(container)
container._reactRootContainer = root;
root.render(reactElement, callback)
isFirstRender = false;
}
}
export default ReactDOM;
九 fiber架构本质
循环条件:利用requestIdleCallback空闲时间递减
遍历过程:利用链表,找孩子找兄弟找父亲;