useState和useEffect - KingQueenie/share GitHub Wiki

useState和useEffect的原理与简单实现

useState

实现一个state

useState接收状态初始值,返回一个数组,第一个是状态的当前值,第二个是函数,用来更新状态.

const [count, setCount] = useState(0)为例:
组件第一渲染时,从useState拿到初始值0。当调用setCount(count+1)时,调用dispatch,state更新为1,之后执行render,组件重新渲染,此时拿到新的count(每一次都拿到独立的count状态,但是这个状态在一次渲染过程中是常量)

let _state
function useState(initialState) {
	_state = _state || (typeof initialState === 'function' ? initialState() : initialState)
	const dispatch = action => {
		const curState = _state
		const newState = basicStateReducer(curState, action)
		if (curState === newState) return
		_state = newState
		render()
	}
	return [_state, dispatch]
}
function basicStateReducer(state, action) {
	return typeof action === 'function' ? action(state) : action
}

实现多个state

若是多个state,该如何实现?

通过数组,把多个state放在数组memoizedState中,根据索引index,更修改相应的state

  • useState会读取memoizedState[index]
  • index由useState调用的顺序决定
  • setState会修改state,并触发组件更新
let memoizedState = [] // hooks 存放在这个数组
let index = 0 // 当前 memoizedState 下标

function useState(initialState) {
	const currentIndex = index
	index++
	memoizedState[currentIndex] = memoizedState[currentIndex] || (typeof initialState === 'function' ? initialState() : initialState)
	const dispatch = action => {
		const curState = memoizedState[currentIndex]
		const newState = basicStateReducer(curState, action)
		if (curState === newState) return
		memoizedState[currentIndex] = newState
		render()
	}
	return [memoizedState[currentIndex], dispatch]
}
function basicStateReducer(state, action) {
	return typeof action === 'function' ? action(state) : action
}

const render = () => {
	index = 0
	ReactDOM.render(<App/>, rootElement)
}

从上面可以看出,useState要按顺序执行,不能使用条件语句,因为可能会打乱顺序。 注:源码中,useState是通过链表结构来实现的

hook1 => Fiber.memoizedState 
state1 === hook1.memoizedState

hook1.next => hook2 
state2 === hook2.memoizedState

实现useEffect

useEffect接收两个参数。第一个是函数,放所需执行的代码;第二个参数是一个数组,里面是Effect的依赖项,数组发生变化,useEffect就会执行。第二个参数可以省略,每次渲染就会执行useEffect中的函数。

setState时,组件会重新渲染,此时会重新触发useEffect函数。(并不是count的值在“不变”的effect中发生了改变,而是effect函数本身在每一次渲染中都不相同)

let effectHOOKS = []
let effectIndex = 0 // 当前effect的下标
function useEffect(callback, depArray) {
	const effect = effectHOOKS[effectIndex]
	const deps = effect && effect.deps
	const hasNoDeps = !depArray
	const hasChangedDeps = deps
		? !depArray.every((el, i) => el === deps[i])
		: true

	if (hasNoDeps || hasChangedDeps) {
		const destroy = effect && effect.destroy
		// 上一次的effect会在重新渲染后被清除掉
		destroy && typeof destroy === 'function' && destroy()
		// 执行本次的回调函数
		const callbackRes = callback()
		effectHOOKS[effectIndex] = {
			...effectHOOKS[effectIndex],
			deps: depArray, destroy: callbackRes
		}
	}
	effectIndex++
}

从上述代码可知,当depArray=[]时,useEffect中的callback回调函数只执行一次。上一次的effect只会在重新渲染后被清除掉。 有问题的定时器组件写法,count永远是0。

const [count, setCount] = useState(0)
useEffect(() => {
    const id = setInterval(() => {
        // count永远是0
        setCount(count + 1)
    }, 1000)
    return () => clearInterval(id)
}, [])

优化:

useEffect(() => {
    const id = setInterval(() => {
        // count永远是0
        setCount(count + 1)
    }, 1000)
    return () => clearInterval(id)
}, [count])
=> 
useEffect(() => {
    const id = setInterval(() => {
        // count传入的是当前state的值
        setCount(count => count + 1)
    }, 1000)
    return () => {
        clearInterval(id)
        console.log('clearInterval')
    }
}, [])

useState的源码解析

下面分析16.14.2版本react中useState的源码实现

介绍

数据结构

当前fiber节点的数据结构是:

const fiberNode = {
	tag: xxx, // 标记不同的组件类型
	key: xxx,
	memoizedState: { // hooks
		baseState: xxx, // 初始化 initialState, 每次 dispatch 之后的newState
		memoizedState: xxx, // 上次更新完之后的最终值
		queue: { // 缓存的更新队列,
			lastRenderedReducer: basicStateReducer(state, action),
			lastRenderedState: xxx, // 上次state的值
			pending: null // 存放即将更新的newState信息
		},
		next: null, // link到下一个hooks,通过next串联每一个hooks
	}
}

React 的 Hooks 是一个单向链表,Hook.next 指向下一个 Hook。

Fiber.memoizedState => hook1  state1 === hook1.memoizedState 
hook1.next => hook2           state2 === hook2.memoizedState
hook2.next => hook3           state3 === hook2.memoizedState

入口文件

react/cjs/react.development.js

function useState(initialState) {
  var dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}

resolveDispatcher返回的是ReactCurrentDispatcher.current,所以useState = ReactCurrentDispatcher.current.useState

首次渲染

ReactDOM.render(<App/>, rootElement),react从render开始执行

render => ... => beginWork => updateFunctionComponent =>  renderWithHooks

hooks的核心渲染逻辑入口是renderWithHooks。

renderWithHooks

// 14762
renderWithHooks()
    // 14787 核心逻辑
    if (current !== null && current.memoizedState !== null) {
      ReactCurrentDispatcher.current = HooksDispatcherOnUpdateInDEV;
    } else { // 组件首次挂载时
      ReactCurrentDispatcher.current = HooksDispatcherOnMountInDEV;
    }
    =>
    15694 HooksDispatcherOnMountInDEV.useState = {
      return mountState(initialState) // 15766
    }
    15881 HooksDispatcherOnUpdateInDEV.useState = {
      return updateState(initialState) // 15939
    }
    // 14851
    currentlyRenderingFiber$1 = null;
    currentHook = null;
    workInProgressHook = null;
}

从上述代码可知,首次挂载时,useState = HooksDispatcherOnMountInDEV.useState = mountState;state更新时,useState = HooksDispatcherOnUpdateInDEV.useState = mountState

mountState

// 15213 第一次调用组件的 useState 时实际调用的方法
function mountState(initialState) {
  var hook = mountWorkInProgressHook(); // / 创建一个新的 Hook,并返回当前 workInProgressHook

  if (typeof initialState === 'function') {
    initialState = initialState();
  }

  hook.memoizedState = hook.baseState = initialState;
  var queue = hook.queue = {
    pending: null,
    dispatch: null,
    lastRenderedReducer: basicStateReducer,
    lastRenderedState: initialState
  };
  // 绑定当前 fiber 和 queue 到 dispatchAction 上
  // currentlyRenderingFiber$1是一个全局变量,表示当前正在渲染的Fiber节点
  var dispatch = queue.dispatch = dispatchAction.bind(null, currentlyRenderingFiber$1, queue);
  // const [name, setName] = useState('king')
  // king 赋值给了 hook.memoizedState, setName表示dispatch,当调用setName('queen')会执行dispatchAction('queen')
  return [hook.memoizedState, dispatch];
}

(1)执行mountWorkInProgressHook(),更新hook链表,并返回当前 workInProgressHook

// 14921 创建一个新的 hook,并返回当前 workInProgressHook
function mountWorkInProgressHook() {
  var hook = {
    memoizedState: null,
    baseState: null,
    baseQueue: null,
    queue: null,
    next: null
  };
  if (workInProgressHook === null) {
		// 只有在第一次打开页面的时候,workInProgressHook 为空
    currentlyRenderingFiber$1.memoizedState = workInProgressHook = hook;
  } else {
		// 已经存在 workInProgressHook 就将新创建的这个 Hook 接在 workInProgressHook 的尾部
    workInProgressHook = workInProgressHook.next = hook;
  }
  return workInProgressHook;
}

(2)获取当前的workInProgressHook后,初始化了hook.memoizedState、hook.baseState、hook.queue和queue.dispatch

(3)basicStateReducer的源码如下:

// 15007
function basicStateReducer(state, action) {
  return typeof action === 'function' ? action(state) : action;
}

(4)最终返回一个数组 [hook.memoizedState, dispatch] 执行setName('queen'),其实就是执行dispatch('queen')

第一次const [name, setName] = useState('king'),mountWorkInProgressHook()后,hook的值为

hook = {
	baseQueue: null,
	baseState: 'king',
	memoizedState: 'king',
	next: null,
	queue: {
            dispatch: fucntion,
            lastRenderedReducer: basicStateReducer(state, action),
            lastRenderedState: 'king',
            pending: null
	}
}

第二次const [count, setCount] = useState(0),mountWorkInProgressHook()后,hook的值为

hook = {
	baseQueue: null,
	baseState: 'king',
	memoizedState: 'king',
	queue: {
        dispatch: fucntion,
		lastRenderedReducer: basicStateReducer(state, action),
		lastRenderedState: 'king',
		pending: null
	},
	next: {
		baseQueue: null,
		baseState: 0,
		memoizedState: 0,
		queue: {
            dispatch: fucntion,
			lastRenderedReducer: basicStateReducer(state, action),
			lastRenderedState: 0,
			pending: null
		},
	},
}

dispatchAction

const [name, setName] = useState('king') setName('queen')为例:

执行setName('queen'),其实就是执行dispatchAction(fiber, queue, 'queen')

  • dispatchAction() 会创建 update 对象({action:'queen'})
  • 将 update 加至 hook.queue 的末尾:hook.queue.pending = update
  • 执行 scheduleWork(),走 updateFunctionComponent() 流程 核心代码:
// 15564
function dispatchAction(fiber, queue, action) {
  // action 就是传进来要更新的 state->'queen'
  var update = {
    expirationTime: expirationTime,
    suspenseConfig: suspenseConfig,
    action: action,
    eagerReducer: null,
    eagerState: null,
    next: null
  };
  // 这里的queue,是之前传入的hook对象中的queue,这里保留了一个引用!!(即queue发生变化,当前fiber节点的hook数据也是同步变更的)
  var pending = queue.pending;
  if (pending === null) {
    // This is the first update. Create a circular list.
    update.next = update;
  } else {
    update.next = pending.next;
    pending.next = update;
  }
  //  将update对象加至hook.queue的末尾pending中
  queue.pending = update;

  var currentState = queue.lastRenderedState;
  // currentState: king,action: queen
  // 将新的state queen 赋值到 update.eagerState
  var eagerState = lastRenderedReducer(currentState, action); 
  update.eagerReducer = lastRenderedReducer;
  update.eagerState = eagerState;
  // ...
  scheduleWork(fiber, expirationTime)
}

setName('queen'),即执行dispatchAction()后,hook变为

hook = {
	baseQueue: null,
	baseState: 'king',
	memoizedState: 'king',
	queue: {
		lastRenderedReducer: basicStateReducer(state, action),
		lastRenderedState: 'king',
		pending: {
			action: 'queen',
			eagerState: 'queen',
			next: pending // pedding对象,单链表
		}
	},
	next: {
		baseQueue: null,
		baseState: 0,
		memoizedState: 0,
		queue: {
			lastRenderedReducer: basicStateReducer(state, action),
			lastRenderedState: 0,
			pending: null
		},
	},
}

最后会执行scheduleWork(fiber, expirationTime),经过React的调度,会带上action(setName的传参),再次进入hook组件核心渲染逻辑:renderWithHooks。

dispatchAction => scheduleUpdateOnFiber => ensureRootIsScheduled => performConcurrentWorkOnRoot => performUnitOfWork => beginWork => updateFunctionComponent 
=> renderWithHooks

此时,由于并非首次渲染组件,React会使用HooksDispatcherOnUpdateInDEV对象上的useState,useState = HooksDispatcherOnUpdateInDEV.useState = mountState。在这个useState中,会使用一个叫做updateState的函数来更新最新的state值。

// 14762
renderWithHooks()
    // 14787 核心逻辑
    if (current !== null && current.memoizedState !== null) {
      ReactCurrentDispatcher.current = HooksDispatcherOnUpdateInDEV;
    } else { // 组件首次挂载时
      ReactCurrentDispatcher.current = HooksDispatcherOnMountInDEV;
    }
    =>
    15694 HooksDispatcherOnMountInDEV.useState = {
      return mountState(initialState) // 15766
    }
    15881 HooksDispatcherOnUpdateInDEV.useState = {
      return updateState(initialState) // 15939
    }
    // ...
    var children = Component(props, secondArg)
}

renderWithHooks()中有一个Component()方法,用来执行App(),此时又会执行 const [name, setName] = useState('king'),最终调用的是HooksDispatcherOnUpdateInDEV.useState(king') => updateState('king')

更新

updateState()

由updateState可以看出,实际调用的是 updateReducer

// 15233
function updateState(initialState) {
  return updateReducer(basicStateReducer);
}
function basicStateReducer(state, action) {
  return typeof action === 'function' ? action(state) : action;
}

updateReducer()

updateReducer的核心代码:

// 15033
funtion updateReducer() {
    var hook = updateWorkInProgressHook();
    var queue = hook.queue;
    var pendingQueue = queue.pending
    current.baseQueue = baseQueue = pendingQueue;
    queue.pending = null;
    var first = baseQueue.next;
    var newState = current.baseState;
    var update = first;
    if (update.eagerReducer === reducer) {
      newState = update.eagerState;
    } 
    hook.memoizedState = newState;
    hook.baseState = newBaseState;
    var dispatch = queue.dispatch;
    return [hook.memoizedState, dispatch]; // 返回['queen', dispatch]
}

(1)第一步执行updateWorkInProgressHook(),获取当前正在工作中的 hook

// 14941
function updateWorkInProgressHook() {
  // 此时currentlyRenderingFiber$1.memoizedState,但是为fiber的副本,保留着fiber.memoizedState的内容
  // 核心代码
   var nextCurrentHook;
  if (currentHook === null) { // 第一次useState
    var current = currentlyRenderingFiber$1.alternate;
    if (current !== null) {
      nextCurrentHook = current.memoizedState;
    } else {
      nextCurrentHook = null;
    }
  } else {
    nextCurrentHook = currentHook.next;
  }
  var nextWorkInProgressHook;
  if (workInProgressHook === null) { // 第一次useState
    nextWorkInProgressHook = currentlyRenderingFiber$1.memoizedState;
  } else {
    nextWorkInProgressHook = workInProgressHook.next;
  }
  currentHook = nextCurrentHook;
  var newHook = {
    memoizedState: currentHook.memoizedState,
    baseState: currentHook.baseState,
    baseQueue: currentHook.baseQueue,
    queue: currentHook.queue,
    next: null
  }
  if (workInProgressHook === null) {
    currentlyRenderingFiber$1.memoizedState = workInProgressHook = newHook;
  } else {
    workInProgressHook = workInProgressHook.next = newHook;
  }
  return workInProgressHook
}

第一次const [name, setName] = useState('king'),workInProgressHook的值为

workInProgressHook={
	memoizedState: "king",
	baseState: "king",
	queue:{
		pending:{
			action: "queen",
			eagerState: "queen",
		}
	},
	next: null
}

第二次const [count, setCount] = useState(0),workInProgressHook的值为

workInProgressHook={
	memoizedState: 0,
	baseState: 0,
	queue:{
		pending:{
			action: 0,
			eagerState: 0,
		}
	},
	next: null
}

(2)从workInProgressHook的数据结构可以看出,我们需要更新的值就在queue.pending.eagerState/action中

if (update.eagerReducer === reducer) {
   newState = update.eagerState;
} 

(3)更新state

hook.memoizedState = newState;
hook.baseState = newBaseState;
var dispatch = queue.dispatch;
return [hook.memoizedState, dispatch]; // 返回['queen', dispatch]

'queen'赋值给hook.memoizedState,返回['queen', diapatch],此时的name已经更新为'queen'

由上述源码可知,useState按顺序执行的原因是,useState是通过next来查找下一个hooks

⚠️ **GitHub.com Fallback** ⚠️