学习资料来自于下面链接。视频代码有些地方感觉不清晰,另外text对象创建跟我理解有些偏差,所以改了些地方,新版react会自动解析jsx语法,这儿写页面demo可以直接用createElement方法创建虚拟dom。
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map(child => {
return typeof child === 'object' ? child : createTextElement(child);
})
}
}
}
function createTextElement(text) {
return text
}
function render(vdom, container) {
if (typeof vdom === 'string') {
container.innerText = vdom;
} else {
const dom = document.createElement(vdom.type);
Object.keys(vdom.props)
.filter(key => key !== 'children')
.forEach(name => dom[name] = vdom.props[name]);
vdom.props.children.forEach(child => {
render(child, dom);
})
container.appendChild(dom)
}
// container.innerHTML = `<pre>${JSON.stringify(vdom,null,2)}</pre>`
}
export default {
createElement,
render
}
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map(child => {
return typeof child === 'object' ? child : createTextElement(child);
})
}
}
}
function createTextElement(text) {
return {
type: 'text',
props: {
nodeValue: text,
children: []
}
}
}
function render(vdom, container) {
nextUnitOfWork = {
dom: container,
props: {
children: [vdom]
},
}
// container.innerHTML = `<pre>${JSON.stringify(vdom,null,2)}</pre>`
}
let nextUnitOfWork = null;
// render初始化第一个任务
function workLoop() {
// 有下一个任务,当前帧未结束
while (nextUnitOfWork) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
}
// while (false) {
// }
requestIdleCallback(workLoop)
}
// 启动空闲时间处理
requestIdleCallback(workLoop)
// add to dom
// create fibers for children
// return next unit of work
function performUnitOfWork(fiber) {
// 根据当前任务获取下一个
if (!fiber.dom) {
fiber.dom = createDom(fiber);
}
if (fiber.return) {
fiber.return.dom.appendChild(fiber.dom);
}
// 递归处理子元素,给子元素创建fiber
const elements = fiber.props.children;
let index = 0;
let prevSibling = null;
while (index < elements.length) {
let element = elements[index];
const newFiber = {
type: element.type,
props: element.props,
return: fiber,
dom: null
}
if (index === 0) {
fiber.child = newFiber
} else {
prevSibling.sibling = newFiber;
}
prevSibling = newFiber;
index++;
//fiber基本结构构建完成
}
if (fiber.child) {
return fiber.child
}
let nextFiber = fiber;
while (nextFiber) {
if (nextFiber.sibling) {
return nextFiber.sibling
}
nextFiber = nextFiber.return;
}
}
function createDom(vdom) {
const dom = vdom.type === 'text' ? document.createTextNode('') :
document.createElement(vdom.type);
Object.keys(vdom.props)
.filter(key => key !== 'children')
.forEach(name => dom[name] = vdom.props[name]);
return dom;
}
export default {
createElement,
render
}
/**
* 为什么要有虚拟dom,原生的dom上挂有非常多的属性,浏览器原生操作dom性能开销很大
* 使用js对象来描述dom,有任何频繁的操作不去操作dom而是操作对象,会后统一尽可能少
* 地去操作实际的dom
*/
/**
* 1.workLoop不断循环调用,处理
* nextUnitOfWork(不停遍历fiber tree,最后变成null,初始值为workInProgressRoot)
* workInProgressRoot (fibre tree的根节点,nextUnitOfWork变null时,
* 表示一次构建fiber tree完成,commitRoot会统一提交,生成真实dom)
*/
/**
* 2.render 初始化nextUnitOfWork 和 workInProgressRoot
* workLoop观察到nextUnitOfWork有值,开始进行深度遍历,执行performUnitOfWork
*/
// workInProgressRoot = {
// dom: container,
// props: {
// children: [vdom]
// },
// base: currentRoot
// }
/**
* 3.判断fiber是函数组件还是类组件,先考虑类组件
* updateHostComponent
* fiber.dom不存在,即不是入口,需要给fiber创建dom,添加事件 fiber.dom = createDom(fiber)->updateDom(fiber,{},fiber.props);
* reconcileChildren(fiber,elements); elements = fiber.props.children 给fiber添加子节点,给fiber添加tag,把子节点连起来并指向父节点
* !如果fiber存在子节点,则返回fiber子节点
* !如果fiber存在兄弟结点,则返回fiber兄弟结点
* !如果fiber的父节点存在兄弟结点,则返回fiber父节点的兄弟结点,返回值给到nextUnitOfWork
*/
/**
* 3.1 reconcileChildren遍历fiber tree,更新 todo 添加tag的代码有问题
*/
/**
* 4.commit阶段,commitRoot(),nextUnitOfWork已经完成fiber tree的构建,
* workInProgressRoot根节点还在
* 删除deletions中存的结点forEach(commitWorker)
* commitWorker(workInprogressRoot.children)真正执行dom渲染
* 重置双缓存指针指向
* currentRoot = workInProgressRoot
* workInProgressRoot = null
*/
/**
* 4.1对函数组件return上可能不存在父节点,要循环找到父节点
* 根据fiber.effectTag,判断如何处理fiber
* 1.updateDom(fiber.dom,fiber.base.props,fiber.props)
* 2.replace parentDom.append(fiber.dom)
* 3.deletion commitDeletion(fiber,parentDom)=>fiber.dom若不存在会递归调用,函数组件的逻辑
*/
/**
* 补充:updateDom(dom,prevProps,nextProps)
* 1.删除调prevProps中存在nextProps中不存在的属性,移除时间
* 2.nextProps中的属性和事件全部添加
*/
/**
* 全局变量
* nextUnitOfWork
* workInProgressRoot
* currentRoot
* deletions
*/
/**
* 入口1:requestIdleCallback(workLoop),循环调用
* 入口2:render,初始化触发workLoop 中 performUnitOfWork构建新的fiber tree
* 待添加:setState会重置nextUnitOfWork和workInProgressRoot,引起fiber tree重新执行
*/
/**
* vdom生成后,在render中构建fiber tree
* fiber tree为链表结构,调用render,把vdom转化成fiber tree,(之前的 x 错误 x 理解,xxx fiber tree是改变了之前生成vdom方式 xxx);
* vdom形式还是未变
*/
// render会递归创建vdom,如果vdom变得很庞大,render的遍历也就会非常耗时,
// 利用浏览器api requestIdleCallback ,让浏览器在空闲时执行diff任务,
// 动画交互等会先执行
/**
* 异步任务调度逻辑
*/
// 下一个单元任务
// render会初始化第一个任务
let nextUnitOfWork = null;
let workInProgressRoot = null;
let currentRoot = null;
let deletions = null;
/**
* 遍历对象,append创建真实dom
* @param {虚拟dom} vdom
* @param {容器} container
*/
function render(vdom, container) {
/**
* 这个就是fiber
*/
workInProgressRoot = {
dom: container,
props: {
children: [vdom]
},
base: currentRoot
}
nextUnitOfWork = workInProgressRoot;
// container.innerHTML = `<pre>${JSON.stringify(vdom, null, 2)}</pre>`
}
// 调度diff或者渲染任务
function workLoop(deadline) {
while (nextUnitOfWork && deadline.timeRemaining() > 1) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
if (!nextUnitOfWork && workInProgressRoot) {
// 没有任务了 并且根节点还在
commitRoot();
}
requestIdleCallback(workLoop)
}
// 启动空闲时间处理
requestIdleCallback(workLoop);
/**
* !!重要 构建fiber结构
* @param {} fiber
*/
function performUnitOfWork(fiber) {
const ifFunctionComponent = fiber.type instanceof Function;
if (ifFunctionComponent) {
updateFunctionComponent(fiber);
} else {
// dom
updateHostComponent(fiber);
}
function updateFunctionComponent(fiber) {
const children = [fiber.type(fiber.props)];
reconcileChildren(fiber, children);
}
function updateHostComponent(fiber) {
// 根据当前任务,获取下一个任务
if (!fiber.dom) {
// 不是入口
fiber.dom = createDom(fiber)
}
//真实的dom操作
// if (fiber.return) {
// fiber.return.dom.append(fiber.dom)
// }
// 将前面的vdom转换成fiber结构
const elements = fiber.props.children;
// 调和子元素
reconcileChildren(fiber, elements);
}
// 这个函数返回的下一个任务,当前fiber的子节点
// 或者兄弟结点,
// 或者父元素的下一个兄弟结点,
// 最后到根节点结束
if (fiber.child) {
return fiber.child
}
let nextFiber = fiber;
while (nextFiber) {
if (nextFiber.sibling) {
return nextFiber.sibling;
}
nextFiber = nextFiber.return;
}
}
/**
* 为了fiber随时可以调度,把创建dom的逻辑提取出来,不再通过递归render
* 根据vdom创建真实dom挂到fiber上
* @param {虚拟dom}} vdom
*/
function createDom(vdom) {
const dom = vdom.type === 'text' ? document.createTextNode('') :
document.createElement(vdom.type);
// Object.keys(vdom.props)
// .filter(key => key !== 'children')
// .forEach(name => {
// /**
// * todo 事件处理,属性兼容
// */
// dom[name] = vdom.props[name]
// });
updateDom(dom, {}, vdom.props)
return dom;
}
function updateDom(dom, prevProps, nextProps) {
// 规避child属性
// 老的存在,取消
// 新的存在,新增
Object.keys(prevProps)
.filter(name => name !== 'children')
.filter(name => !(name in nextProps))
.forEach(name => {
if (name.slice(0, 2) === 'on') {
dom.removeEventListener(name.slice(2).toLocaleLowerCase(), prevProps[name], false)
} else {
dom[name] = ''
}
})
// if(!nextProps){
// return
// }
Object.keys(nextProps)
.filter(name => name !== 'children')
.forEach(name => {
if (name.slice(0, 2) === 'on') {
// console.log(nextProps[name],dom,name.slice(2))
// dom[name] = nextProps[name]
dom.addEventListener(name.slice(2).toLocaleLowerCase(), nextProps[name], false)
} else {
dom[name] = nextProps[name];
}
})
}
function reconcileChildren(workInProgressFiber, elements) {
// 构建fiber结构
let index = 0;
let prevSibling = null;
// let next = beginWork(fiber)
let oldFiber = workInProgressFiber.base && workInProgressFiber.base.child;
while (index < elements.length
// && oldFiber !== null
) {
const element = elements[index];
let newFiber = null;
const sameType = oldFiber && element && oldFiber.type === element.type;
// 复用节点,更新
if (sameType) {
// props是新的
newFiber = {
type: oldFiber.type,
props: element.props,
dom: oldFiber.dom,
return: workInProgressFiber,
base: oldFiber,
effectTag: 'UPDATE',
}
}
// 新增节点
else if (!sameType && element) {
newFiber = {
type: element.type,
props: element.props,
dom: null,
return: workInProgressFiber,
base: null,
effectTag: 'PLACEMENT'
}
}
// 删除节点
else if (!sameType && oldFiber) {
oldFiber.effectTag = 'DELETION';
deletions.push(oldFiber);
}
if (oldFiber) {
oldFiber = oldFiber.sibling;
}
if (index === 0) {
workInProgressFiber.child = newFiber;
} else {
prevSibling.sibling = newFiber;
}
prevSibling = newFiber;
index++
}
}
// workInProgressRoot指向fiber的根节点
function commitRoot() {
deletions && deletions.forEach(commitWorker);
commitWorker(workInProgressRoot.child);
console.log(workInProgressRoot)
currentRoot = workInProgressRoot;
workInProgressRoot = null;
}
function commitWorker(fiber) {
if (!fiber) {
return
}
// const domParent = fiber.return.dom;
let domParentFiber = fiber.return;
while (!domParentFiber.dom) {
domParentFiber = domParentFiber.return;
}
const domParent = domParentFiber.dom;
if (fiber.effectTag === 'UPDATE' && fiber.dom !== null) {
updateDom(fiber.dom, fiber.base.props, fiber.props);
} else if (fiber.effectTag === 'PLACEMENT') {
domParent.append(fiber.dom)
} else if (fiber.effectTag === 'DELETION' && fiber.dom !== null) {
commitDeletion(fiber, domParent);
// domParent.removeChild(fiber.dom)
}
commitWorker(fiber.child);
commitWorker(fiber.sibling);
}
function commitDeletion(fiber, domParent) {
if (fiber.dom) {
domParent.removeChild(fiber.dom)
} else {
commitDeletion(fiber.child, domParent);
}
}
/**
* createElement返回虚拟dom
* 1.参数 type,props,children
*/
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map(child => {
return typeof child === 'object' ? child : createTextElement(child)
})
},
}
}
function createTextElement(text) {
return {
type: 'text',
props: {
nodeValue: text,
children: []
}
}
}
/**
* fiber架构实现
* vdom之前是树,fiber是用双向链表实现
*
*/
// fiber = {
// dom:真实dom,
// return:父亲,
// child:第一个子元素,
// slibing:兄弟
// }
/**
* 提交commit,因为渲染过程中如果被打断
* ui会变得很奇怪,所以应该把dom操作独立出来,
* 用一个全局变量存储正在工作的fiber根节点
* WorkInprogress tree
* 在fiber构建完成后统一操作dom,
* 构建计算的过程可以被中断,但操作dom的过程需要一气呵成
*/
export default {
createElement,
render
}