这是我个人第一次发表文章,没错我也是崔学社的一员,主要还是说说感受之类的,大家就当水文看看便好。
任务拆分,提一个case,解决一个case
下面我举几个例子吧: (下面的代码需要结合上下文才能看懂,我这里就简单列一下未列全,感兴趣可以点击文末链接查看)
1.如何在函数式组件中实现一个简单的useEffect
- 支持单个useEffect
/**
@typedef {{callback:(function():function?),deps:any[],cleanUp:function()}}
EffectHook*/
/**
*
* @typedef {{type?:string,props:{[key:string]:any,children:WorkUnit[]},child?:WorkUnit,
* dom?:HTMLElement,sibling?:WorkUnit,parent?:WorkUnit,effectTag?:'placement'|'update',
* alternate?:WorkUnit,effectHooks?:EffectHook[]}} WorkUnit
*
* */
/**
* @param {function():function?} callback
* @param {any[]} deps
*
*/
/**@param {WorkUnit} work*/
const updateFunctionalComponent = (work) => {
need_update_work = work;
state_hook_index = 0;
state_hooks = [];
TreeToLinkList(work, true);
};
const commitRoot = () => {
deletions.forEach((work) => {
commitDeletion(work);
});
commitWork(root.child);
//处理副作用
commitEffectHooks();
deletions = [];
};
const commitEffectHooks = () => {
/**@param {WorkUnit} work*/
const run = (work) => {
if (!work) return;
//1.首次初始化的时候
if (!work.alternate) {
work.effectHook.callback()
} else {
//2.更新的时候
let oldEffectHook = work.alternate.effectHook;
let newEffectHook=work.effectHook
if (newEffctHook.deps.length > 0) {
let needUpdate = oldEffectHook.deps.some(
(dep, depIndex) => dep !== newEffectHook.deps[depIndex]
);
needUpdate && .callback();
}
}
run(work.child);
run(work.sibling);
};
run(root);
};
/**
* @param {function():function?} callback
* @param {any[]} deps
*
*/
const useEffect = (callback, deps) => {
let currentWork = need_update_work;
let effecthook = {
callback,
deps,
};
currentWork.effectHook = effect_hook;
};
- 支持多个useEffect
/**
@typedef {{callback:(function():function?),deps:any[],cleanUp:function()}}
EffectHook*/
/**
*
* @typedef {{type?:string,props:{[key:string]:any,children:WorkUnit[]},child?:WorkUnit,
* dom?:HTMLElement,sibling?:WorkUnit,parent?:WorkUnit,effectTag?:'placement'|'update',
* alternate?:WorkUnit,effectHooks?:EffectHook[]}} WorkUnit
*
* */
/**@param {WorkUnit} work*/
const updateFunctionalComponent = (work) => {
need_update_work = work;
state_hook_index = 0;
state_hooks = [];
effect_hooks = [];
TreeToLinkList(work, true);
};
const commitRoot = () => {
deletions.forEach((work) => {
commitDeletion(work);
});
commitWork(root.child);
//处理副作用
commitEffectHooks();
deletions = [];
};
/**
* @param {function():function?} callback
* @param {any[]} deps
*
*/
const commitEffectHooks = () => {
/**@param {WorkUnit} work*/
const run = (work) => {
if (!work) return;
//1.首次初始化的时候
if (!work.alternate) {
work.effectHooks?.forEach((newhook, index) => {
newhook.callback();
});
} else {
//2.更新的时候
let oldEffectHooks = work.alternate.effectHooks;
work.effectHooks?.forEach((newhook, index) => {
if (newhook.deps.length > 0) {
let oldEffect = oldEffectHooks[index];
let needUpdate = oldEffect.deps.some(
(dep, depIndex) => dep !== newhook.deps[depIndex]
);
needUpdate && newhook?.callback();
}
});
}
run(work.child);
run(work.sibling);
};
run(root);
};
let effect_hooks = [];
/**
* @param {function():function?} callback
* @param {any[]} deps
*
*/
const useEffect = (callback, deps) => {
let currentWork = need_update_work;
let effecthook = {
callback,
deps,
};
effect_hooks.push(effecthook);
currentWork.effectHooks = effect_hooks;
};
- 实现cleanUp
/**
@typedef {{callback:(function():function?),deps:any[],cleanUp:function()}}
EffectHook*/
/**
*
* @typedef {{type?:string,props:{[key:string]:any,children:WorkUnit[]},child?:WorkUnit,
* dom?:HTMLElement,sibling?:WorkUnit,parent?:WorkUnit,effectTag?:'placement'|'update',
* alternate?:WorkUnit,effectHooks?:EffectHook[]}} WorkUnit
*
* */
/**@param {WorkUnit} work*/
const updateFunctionalComponent = (work) => {
need_update_work = work;
state_hook_index = 0;
state_hooks = [];
effect_hooks = [];
TreeToLinkList(work, true);
};
const commitRoot = () => {
deletions.forEach((work) => {
commitDeletion(work);
});
commitWork(root.child);
//处理副作用
commitEffectHooks();
deletions = [];
};
/**
* @param {function():function?} callback
* @param {any[]} deps
*
*/
const commitEffectHooks = () => {
/**@param {WorkUnit} work*/
const run = (work) => {
if (!work) return;
//1.首次初始化的时候
if (!work.alternate) {
work.effectHooks?.forEach((newhook, index) => {
newhook.cleanUp = newhook.callback();
});
} else {
//2.更新的时候
let oldEffectHooks = work.alternate.effectHooks;
work.effectHooks?.forEach((newhook, index) => {
if (newhook.deps.length > 0) {
let oldEffect = oldEffectHooks[index];
let needUpdate = oldEffect.deps.some(
(dep, depIndex) => dep !== newhook.deps[depIndex]
);
needUpdate && (newhook.cleanUp = newhook?.callback());
}
});
}
run(work.child);
run(work.sibling);
};
/**@param {WorkUnit} work*/
const runCleanUp = (work) => {
if (!work) return;
work.alternate?.effectHooks?.forEach((effecthook) => {
if (effecthook.deps.length > 0) {
effecthook?.cleanUp?.();
}
});
runCleanUp(work.child);
runCleanUp(work.sibling);
};
runCleanUp(root);
run(root);
};
let effect_hooks = [];
/**
* @param {function():function?} callback
* @param {any[]} deps
*
*/
const useEffect = (callback, deps) => {
let currentWork = need_update_work;
let effecthook = {
callback,
deps,
cleanUp:undefined
};
effect_hooks.push(effecthook);
currentWork.effectHooks = effect_hooks;
};
2.如何在函数式组件中实现一个简单的useState
- 实现useState
/**
@typedef {{callback:(function():function?),deps:any[],cleanUp:function()}}
EffectHook*/
/**
*
* @typedef {{type?:string,props:{[key:string]:any,children:WorkUnit[]},child?:WorkUnit,
* dom?:HTMLElement,sibling?:WorkUnit,parent?:WorkUnit,effectTag?:'placement'|'update',
* alternate?:WorkUnit,effectHooks?:EffectHook[]}} WorkUnit
*
* */
/**@param {WorkUnit} work*/
const updateFunctionalComponent = (work) => {
need_update_work = work;
state_hook_index = 0;
state_hooks = [];
effect_hooks = [];
TreeToLinkList(work, true);
};
let state_hooks = null;
let state_hook_index = null;
/**
* @template T
* @param {T} initalState An input element
*/
const useState = (initalState) => {
/**@type {WorkUnit}*/
let currentWork = need_update_work;
let oldState = currentWork?.alternate?.stateHooks[state_hook_index];
let hookState = {
state: oldState ? oldState.state : initalState,
};
state_hooks.push(hookState);
currentWork.stateHooks = state_hooks;
state_hook_index++;
/**@param {function(T) | T} action */
const setState = (action) => {
hookState.state=action(hookState.state)
NextWorkUnit = {
...currentWork,
alternate: currentWork,
};
root = NextWorkUnit;
};
return [hookState.state, setState];
};
- 批量更新
/**
@typedef {{callback:(function():function?),deps:any[],cleanUp:function()}}
EffectHook*/
/**
*
* @typedef {{type?:string,props:{[key:string]:any,children:WorkUnit[]},child?:WorkUnit,
* dom?:HTMLElement,sibling?:WorkUnit,parent?:WorkUnit,effectTag?:'placement'|'update',
* alternate?:WorkUnit,effectHooks?:EffectHook[]}} WorkUnit
*
* */
/**@param {WorkUnit} work*/
const updateFunctionalComponent = (work) => {
need_update_work = work;
state_hook_index = 0;
state_hooks = [];
effect_hooks = [];
TreeToLinkList(work, true);
};
let state_hooks = null;
let state_hook_index = null;
/**
* @template T
* @param {T} initalState An input element
*/
const useState = (initalState) => {
/**@type {WorkUnit}*/
let currentWork = need_update_work;
let oldState = currentWork?.alternate?.stateHooks[state_hook_index];
let hookState = {
state: oldState ? oldState.state : initalState,
queue: oldState ? oldState.queue : [],
};
hookState.queue.forEach((action) => {
hookState.state = action(hookState.state);
});
hookState.queue = [];
state_hooks.push(hookState);
currentWork.stateHooks = state_hooks;
state_hook_index++;
/**@param {function(T) | T} action */
const setState = (action) => {
const isActionFunc = typeof action === 'function';
hookState.queue.push(isActionFunc ? action : () => action);
NextWorkUnit = {
...currentWork,
alternate: currentWork,
};
root = NextWorkUnit;
};
return [hookState.state, setState];
};
- 减少不必要的更新
/**
@typedef {{callback:(function():function?),deps:any[],cleanUp:function()}}
EffectHook*/
/**
*
* @typedef {{type?:string,props:{[key:string]:any,children:WorkUnit[]},child?:WorkUnit,
* dom?:HTMLElement,sibling?:WorkUnit,parent?:WorkUnit,effectTag?:'placement'|'update',
* alternate?:WorkUnit,effectHooks?:EffectHook[]}} WorkUnit
*
* */
/**@param {WorkUnit} work*/
const updateFunctionalComponent = (work) => {
need_update_work = work;
state_hook_index = 0;
state_hooks = [];
effect_hooks = [];
TreeToLinkList(work, true);
};
let state_hooks = null;
let state_hook_index = null;
/**
* @template T
* @param {T} initalState An input element
*/
const useState = (initalState) => {
/**@type {WorkUnit}*/
let currentWork = need_update_work;
let oldState = currentWork?.alternate?.stateHooks[state_hook_index];
let hookState = {
state: oldState ? oldState.state : initalState,
queue: oldState ? oldState.queue : [],
};
hookState.queue.forEach((action) => {
hookState.state = action(hookState.state);
});
hookState.queue = [];
state_hooks.push(hookState);
currentWork.stateHooks = state_hooks;
state_hook_index++;
/**@param {function(T) | T} action */
const setState = (action) => {
const isActionFunc = typeof action === 'function';
const estimateValue = isActionFunc ? action(hookState.state) : action;
if (estimateValue === hookState.state) return;
hookState.queue.push(isActionFunc ? action : () => action);
NextWorkUnit = {
...currentWork,
alternate: currentWork,
};
root = NextWorkUnit;
};
return [hookState.state, setState];
};
可能这就是任务拆分的好处吧,可以不断在上一个case的情况下完善代码,以离目标越来越近,其实配上测试会更好一些,刚好配合一个case一个case地去推动任务的实现。
学会requestIdleCallback的基本使用
requestIdleCallback 是浏览器提供的一个 API,用于在浏览器的空闲时段执行任务,以避免阻塞主线程。在 React 中,requestIdleCallback 可以被用来执行一些不紧急的任务,以提高性能和用户体验。
在 React 中,通常使用 requestIdleCallback 来执行一些较为耗时的任务,如:
- 渲染大型列表或表格: 当需要渲染大量数据时,可以将渲染任务分解为多个小任务,并在空闲时段逐步完成,以避免一次性阻塞主线程。
- 懒加载组件: 如果你有一些组件在初始加载时并不是必需的,可以使用
requestIdleCallback来懒加载它们,以减少初始加载时间。 - 后台数据同步: 在空闲时段可以执行一些后台数据同步或缓存更新的任务,以提高应用的响应速度。
/**
* @param {IdleDeadline} deadline
*
*/
const WorkLoop = (deadline) => {
let shouldField = false;
while (!shouldField && NextWorkUnit) {
/*需要执行task,也就是渲染工作*/
NextWorkUnit = PerformWorkOfUnit(NextWorkUnit);
if (NextWorkUnit?.type === root?.sibling?.type) NextWorkUnit = undefined;
shouldField = deadline.timeRemaining > 1;
}
if (!NextWorkUnit && root) {
//能到这里说明树全部转化成链表,可以在这里一并提交dom的渲染
commitRoot();
currentRoot = root;
root = null;
}
requestIdleCallback(WorkLoop);
};
requestIdleCallback(WorkLoop);
我个人认为这段代码是mini-react最核心的一部分,本质就是不断地循环,看看有没有work(实际上react的fiber节点,我个人认为就是加强版的vdom,除了比较重要的元素类型属性等信息,还包含了child,parent,sibling等节点间的联系信息)的存在,如果有且剩余时间也就是deadline.timeRemaining
时间还充足的情况下的话,进行了work间关系的建立,即child,parent,sibling等节点的关系的确立,本来渲染也应该发生在这阶段的,但是可能有些渲染单元任务过长,可能会一段时间后再渲染成功,让用户感觉看到感觉卡顿,所以转换成了先确立关系,把关系树转化成关系链表,然后再一并渲染的策略。
树转链表
if (work.child) return work.child;
//如果两个组件嵌套过深且互为兄弟组件可能出现找不到的现象,所以要不断地往上找
// if (work.sibling) return work.sibling;
let nextwork = work;
while (nextwork) {
if (nextwork.sibling) return nextwork.sibling;
nextwork = nextwork.parent;
}
返回顺序即代表执行顺序
双树交替更新
/**
* @param {WorkUnit} work
* @param { boolean} isFunctionalComponent
*/
const TreeToLinkList = (work, isFunctionalComponent) => {
// console.log(work);
const children = isFunctionalComponent
? [work.type(work.props)]
: work.props.children;
let prevChild = null;
let oldWork = work.alternate?.child;
children.forEach((child, index) => {
//每个child是一个vdom
//先根据vdom创建一个newWork,work相当vdom对象的扩充版
//如果传入新的vdom的标签是相同就定为是更新操作,否则就是添加操作
const isSameType = oldWork && oldWork.type === child.type;
/**@type {WorkUnit | null}*/
let newWork = null;
if (isSameType) {
newWork = {
type: child.type,
props: child.props,
parent: work,
sibling: null,
child: null,
effectTag: 'update',
dom: oldWork.dom,
alternate: oldWork,
};
} else {
// console.log(child);
if (child) {
newWork = {
type: child.type,
props: child.props,
parent: work,
sibling: null,
dom: null,
child: null, //这里的属性名我感觉叫做child指针更好些,因为链表指针一个指针只有一个指向
effectTag: 'placement',
};
}
if (oldWork) {
// console.log('delete :', oldWork);
deletions.push(oldWork);
}
}
if (oldWork) oldWork = oldWork.sibling;
if (index === 0) {
work.child = newWork;
} else {
prevChild.sibling = newWork;
}
if (newWork) {
prevChild = newWork;
}
});
// console.log(oldWork);
while (oldWork) {
deletions.push(oldWork);
oldWork = oldWork.sibling;
}
};
这个alternate属性很关键,指向了的要更新的老的节点,后面还在useEffect中用来判断是首次渲染还是需要更新渲染的时候派上了用场。因为初始的时候是没有这个属性,是在更新的时候在浅拷贝的头部节点上加了alternate然后再放入生成关系链表的函数中处理,处理了的时候生成新的work的时候也加了alternate属性指向了老的节点,并打上effectTag标记,方编判断后续的更新和添加处理。 如果标签相同就判断是相同的节点执行更新,更新props又分成三种 :
const updateProps = (dom, nextwork, prevwork) => {
let oldProps = prevwork?.props;
let newProps = nextwork.props;
//1.旧的work的props中有的属性,新的work中没有 需要删除
if (oldProps) {
Object.keys(oldProps).forEach((prop) => {
if (prop !== 'children') {
if (!(prop in newProps)) {
dom.removeAttribute(prop);
}
}
});
}
// 2.旧的work中的props中有的属性,新的work中也有 需要更新
//3.旧的work中props没有的属性,新的work中有 需要添加
Object.keys(newProps).forEach((prop) => {
// console.log(prop);
if (prop !== 'children') {
if (dom) {
if (prop.startsWith('on')) {
const evnetType = prop.slice(2).toLocaleLowerCase();
if (oldProps) {
dom.removeEventListener(evnetType, oldProps[prop]);
}
dom.addEventListener(evnetType, newProps[prop]);
} else {
dom[prop] = newProps[prop];
}
}
}
});
};
标签不一致的话就是删除旧的添加新的
这里也分为两种,能对应到老节点,以及对应不上的:
注意红圈圈起来的就是要删除的
结语
这次跟着崔大坚持打卡七天还是有很多收获的,群内学习氛围也相当好,同学们学习热情高涨,群里提出疑问总是有很多小伙伴热心解答,大家都是互帮互助的,下一次有这种类似活动还会继续参加,下面贴出我的源码链接,有需要的小伙伴请自提