1.hook对象数据结构
一个函数组件中可以有多个hook,那么怎么保存呢?
函数组件fiber数据结构
let fiber = {
memoiezdState:null,//函数组件的memoiezdState是保存函数组件内的所有的hook对象链表,不像类组件是保存state,state是单独的hook对象里面保存
stateNode:App,
updateQueue:null//函数组件的updateQueue是保存需要执行useeffect的hook的链表
}
hook对象数据结构
let hook = {
queue:{
pending:update // 这个才是保存useState中updateNumber产生的update的链表 指向最后一个update
},
merizedState:null//保存useState中的state变量 相当于小的class组件
next:null//执行函数组件中下一个hook对象
}
//创建hook的时候,fiber.memoiezdState = firstHook,然后后面创建的Hook对象就.next下去,执行的时候也是根据fiber.memoiezdState这个列表来顺序执行。
hook的update数据结构
let update = {
action:function/state;//存储updateNum的新的state
next:null//下一个update
}
2.极简版useState实现
在render阶段都会重新调用函数组件的函数 去diffchildren 所以都会走useState,所以无论是调用updateNum还是mount阶段都会进入render阶段都会重新执行函数
其他hooks实现只是hook.memerized不同和触发时机不同而已
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>useState的极简版实现</title>
</head>
<body>
</body>
<script>
let mouted = true
let workingProgressHook = null
let fiber = {
stateNode:App,
updateQueue:null,
memorizedState:null,
}
function App(){
const [number,updateNumber] = useState(0)
const [status,updateStatus] = useState(false)
console.log('number',number);
console.log('status',status);
console.log('mouted',mouted);
return {
onclick(){
updateNumber(number=>number+1)
updateNumber(number=>number+1)
updateNumber(number=>number+1)
},
trigger(){
updateStatus(status=>!status)
},
}
}
function run(){
workingProgressHook = fiber.memorizedState//第一个hook
let app = fiber.stateNode()//!render阶段执行函数 如果执行这个函数触发啦更新就叫render阶段触发的更新
mouted = false
return app
}
function useState(baseState){
let hook = null
if(!mouted){//!当前hook已经存在,说明不是初始化
hook = workingProgressHook
workingProgressHook = workingProgressHook.next
}else{
newState = baseState
hook ={
queue:{
pending:null
},
memorizedState:baseState,
next:null
}
if(!fiber.memorizedState){
fiber.memorizedState = hook
}else{
workingProgressHook.next = hook
}
workingProgressHook = hook//函数组件中下一个hook
}
baseState = hook.memorizedState
if(hook.queue.pending){
let firstUpdate = hook.queue.pending.next
do{
const action = firstUpdate.action
baseState = action(baseState)
firstUpdate = firstUpdate.next
}while(firstUpdate!==hook.queue.pending.next)
}
hook.queue.pending = null//这些update计算完了 删除
hook.memorizedState = baseState
return [baseState,dispatchAction.bind(null,hook.queue)]
}
//!必须拿到queue才能加入update到queue里
function dispatchAction(queue,action){
//创建update
let update = {
action,
next:null
}
//加入queue
if(queue.pending){
update.next = queue.pending.next
queue.pending.next = update
}else{
update.next=update
}
queue.pending = update
//开始render
run()
}
window.app = run()
</script>
</html>
3.useEffect/useLayoutEffect
useEffect的hook对象的memorizedState对应的是effect对象 effect对象的数据结构如下
const effect: Effect = {
tag,
create,//回调函数
destroy,//卸载函数
deps,
// Circular
next: (null: any),
};
同时这些effect会以链表形式存储到函数组件fiber这次的updateQueue中,但是只有deps改变的effect我们才会执行这个effect 且这个函数fiber才会到commit阶段的effect-list上
function mountEffectImpl(fiberFlags, hookFlags, create, deps): void {
const hook = mountWorkInProgressHook();//!更新阶段是当前fiber的memorizedState链表下的Hook 或者mount阶段新建的hook
const nextDeps = deps === undefined ? null : deps;//!依赖项
currentlyRenderingFiber.flags |= fiberFlags;//!fiberFlags 就是useEffect/layputeffect 在fiber上加这个tag用于区分这两个
hook.memoizedState = pushEffect(//!useState的memoizedState 是State的值 而useEffect的memoizedState就是effect数据结构
HookHasEffect | hookFlags,
create,
undefined,
nextDeps,
);
}
function updateEffectImpl(fiberFlags, hookFlags, create, deps): void {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
let destroy = undefined;
if (currentHook !== null) {
const prevEffect = currentHook.memoizedState;//!上一次useEffect的effect对象
destroy = prevEffect.destroy;//!上一次useEffect的effect对象的销毁函数
if (nextDeps !== null) {
const prevDeps = prevEffect.deps;
if (areHookInputsEqual(nextDeps, prevDeps)) {//!deps相同的时候 我们会把这个effect加入到fiber的udpatequeue中 但是不会执行 因为tag只穿了hookFlags
pushEffect(hookFlags, create, destroy, nextDeps);
return;
}
}
}
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushEffect(/!只有deps不相同的时候 我们会把这个effect加入到fiber的udpatequeue中 才是需要执行的effect 因为tag传啦HookHasEffect | hookFlags,
HookHasEffect | hookFlags,
create,
destroy,
nextDeps,
);
}
为什么deps没改变的时候也要push effect到fiber.updateQueue里面呢 因为我们一个函数组件里面的effect是有顺序的 我们不能少了某一个那么后面的索引关系全乱啦
uselayoutEffect 只是第二个tag不同 逻辑一样 只是在commit阶段执行时机不同
4.useRef
ref可以作为一种数据结构和一种生命周期
hostcomponent,classcomponent,forwardRef都可以赋值ref属性,其中forwardRef只是把ref传参下去,不执行ref的逻辑
useRef 这个hook对象的memoizedState就是一个对象 {current:value} 返回的也是这个对象 所以这个对象就是存储到hook对象的memoizedState上的
ref在render阶段和commit阶段都会有操作
render阶段:
判断如果是首屏渲染阶段或者更新阶段两次ref不一样 会给这个函数fiber加上ref这个tag
commit阶段:
首先是ref移除阶段:即在commitmutationEffect这个函数里
在mutationEffects函数,对应是更新阶段的即两次ref不一样的情况下 我们会移除之前的ref(即ref.current=null)且如果某个fiber被移除 他的ref和子节点的fiber会全部移除
接下来是ref赋值阶段即在commitlayouteffects这个函数
对应不同的fiber类型 我们把ref赋值为新的对应的实例
5.useCallback&useMemo
useMemo:hook对象的memorizedState就存储这个值和依赖项
update时候 usememo就是获取hook的memorizedState的deps和本次传入的deps对比,看是否返回这次还是这次的
useCallback类似:hook对象的memorizedState就存储这个函数和依赖项