PReact10.5.13源码理解之hook
options._diff = vnode => { currentComponent = null; if (oldBeforeDiff) oldBeforeDiff(vnode); };
options._render = vnode => { if (oldBeforeRender) oldBeforeRender(vnode); currentComponent = vnode._component; currentIndex = 0; const hooks = currentComponent.__hooks; if (hooks) { hooks._pendingEffects.forEach(invokeCleanup); hooks._pendingEffects.forEach(invokeEffect); hooks._pendingEffects = []; } };
function getHookState(index, type) { if (options._hook) { options._hook(currentComponent, index, currentHook || type); } currentHook = 0; // 可能有别的用,目前在源码中没有看到用处 // Largely inspired by: // * https://github.com/michael-klein/funcy.js/blob/f6be73468e6ec46b0ff5aa3cc4c9baf72a29025a/src/hooks/core_hooks.mjs // * https://github.com/michael-klein/funcy.js/blob/650beaa58c43c33a74820a3c98b3c7079cf2e333/src/renderer.mjs // Other implementations to look at: // * https://codesandbox.io/s/mnox05qp8 const hooks = // 如果没有用过hook就在组件上添加一个__hooks属性 currentComponent.__hooks || (currentComponent.__hooks = { _list: [], _pendingEffects: [] }); // 如果index大于当前list长度就产生一个新的对象 // 所以除了useEffect外其他都不会用到_pendingEffects属性 if (index >= hooks._list.length) { hooks._list.push({}); } return hooks._list[index]; // 返回当前的hook state }
export function useMemo(factory, args) { /** @type {import('./internal').MemoHookState} */ const state = getHookState(currentIndex++, 7); // 获取一个hook的state if (argsChanged(state._args, args)) { // 可以看到只有当参数改变时,hook的state会被重新修改;旧的参数被存储在state中 state._value = factory(); // 通过factory生成,如果args不变那么久不会执行factory state._args = args; state._factory = factory; } return state._value; // 返回状态值 }
export function useRef(initialValue) { currentHook = 5; // 可以看到useRef只是一个有current的一个对象; return useMemo(() => ({ current: initialValue }), []); } export function useCallback(callback, args) { currentHook = 8; return useMemo(() => callback, args); }
/** * @param {any[]} oldArgs * @param {any[]} newArgs */ function argsChanged(oldArgs, newArgs) { return ( !oldArgs || oldArgs.length !== newArgs.length || newArgs.some((arg, index) => arg !== oldArgs[index]) ); }
/** * @param {import('./internal').Effect} callback * @param {any[]} args */ export function useEffect(callback, args) { /** @type {import('./internal').EffectHookState} */ const state = getHookState(currentIndex++, 3); if (!options._skipEffects && argsChanged(state._args, args)) { state._value = callback; state._args = args; currentComponent.__hooks._pendingEffects.push(state); } } /** * @param {import('./internal').Effect} callback * @param {any[]} args */ export function useLayoutEffect(callback, args) { /** @type {import('./internal').EffectHookState} */ const state = getHookState(currentIndex++, 4); if (!options._skipEffects && argsChanged(state._args, args)) { state._value = callback; state._args = args; currentComponent._renderCallbacks.push(state); } }
options._commit = (vnode, commitQueue) => { commitQueue.some(component => { try { component._renderCallbacks.forEach(invokeCleanup); component._renderCallbacks = component._renderCallbacks.filter(cb => // 如果是useLayoutEffect产生的,就直接执行,否则返回true保证其他的renderCallbacks在正常的阶段执行 cb._value ? invokeEffect(cb) : true ); } catch (e) { commitQueue.some(c => { if (c._renderCallbacks) c._renderCallbacks = []; }); commitQueue = []; options._catchError(e, component._vnode); } }); if (oldCommit) oldCommit(vnode, commitQueue); };
options._render = vnode => { if (oldBeforeRender) oldBeforeRender(vnode); currentComponent = vnode._component; currentIndex = 0; const hooks = currentComponent.__hooks; if (hooks) { hooks._pendingEffects.forEach(invokeCleanup); hooks._pendingEffects.forEach(invokeEffect); hooks._pendingEffects = []; } }; options.diffed = vnode => { if (oldAfterDiff) oldAfterDiff(vnode); const c = vnode._component; // 如果hooks中存在pendingEffects数组,那么就在渲染结束后执行 if (c && c.__hooks && c.__hooks._pendingEffects.length) { afterPaint(afterPaintEffects.push(c)); } currentComponent = previousComponent; };
function afterPaint(newQueueLength) { if (newQueueLength === 1 || prevRaf !== options.requestAnimationFrame) { prevRaf = options.requestAnimationFrame; (prevRaf || afterNextFrame)(flushAfterPaintEffects); } }
let HAS_RAF = typeof requestAnimationFrame == 'function'; function afterNextFrame(callback) { const done = () => { clearTimeout(timeout); if (HAS_RAF) cancelAnimationFrame(raf); setTimeout(callback); }; const timeout = setTimeout(done, RAF_TIMEOUT); let raf; if (HAS_RAF) { raf = requestAnimationFrame(done); } }
function flushAfterPaintEffects() { afterPaintEffects.forEach(component => { if (component._parentDom) { // 有父组件的组件才会进行,第一次渲染如果么有挂载到父组件可能不会执行 try { component.__hooks._pendingEffects.forEach(invokeCleanup); component.__hooks._pendingEffects.forEach(invokeEffect); component.__hooks._pendingEffects = []; } catch (e) { component.__hooks._pendingEffects = []; options._catchError(e, component._vnode); } } }); afterPaintEffects = []; }
options._render = vnode => { if (oldBeforeRender) oldBeforeRender(vnode); currentComponent = vnode._component; currentIndex = 0; const hooks = currentComponent.__hooks; if (hooks) { hooks._pendingEffects.forEach(invokeCleanup); hooks._pendingEffects.forEach(invokeEffect); hooks._pendingEffects = []; } };
- 如果设置第二个参数为空数组,这种情况下在diffed和_render中都会将pendingEffects进行清除,永远不会执行到清除函数。
- 当useEffect没有第二个参数,那么第一次渲染后options.diffed函数中的state._value执行,生成state._cleanup,清除pendingEffects;如果函数任意状态改变,在options._render阶段没有pendingEffects不会执行cleanup和state._value;在组件render阶段,state._value被重新改变,将state装入pendingEffects中;在options.diffed中执行invokeCleanup和invokeEffect
- 当useEffect设置第二个参数为非空数组,那么第一次渲染后options.diffed函数中的state._value执行,生成state._cleanup,清除pendingEffects;只有当useEffect的依赖项改变时(非依赖项变动不会执行该useEffect的清除函数),在options._render阶段没有pendingEffects不会执行cleanup和state._value;在组件render阶段,state._value被重新改变,将state装入pendingEffects中;在options.diffed中执行invokeCleanup和invokeEffect
action => { // 通过action来执行reducer获取到下一个状态 const nextValue = hookState._reducer(hookState._value[0], action); // 状态不等就进行重新赋值,并且触发渲染,新的渲染还是返回hookState._value,但是_value的值已经被修改了 if (hookState._value[0] !== nextValue) { hookState._value = [nextValue, hookState._value[1]]; // 在diff/index.js中可以看到如果是函数组件没有render方法,那么会对PReact.Component进行实例化 // 这时候调用setState方法同样会触发组件的渲染流程 hookState._component.setState({}); } }
export function useReducer(reducer, initialState, init) { const hookState = getHookState(currentIndex++, 2); hookState._reducer = reducer; // 挂载reducer if (!hookState._component) { // hookState么有_component属性代表第一次渲染 hookState._value = [ !init ? invokeOrReturn(undefined, initialState) : init(initialState), action => { // 通过action来执行reducer获取到下一个状态 const nextValue = hookState._reducer(hookState._value[0], action); // 状态不等就进行重新赋值,并且触发渲染,新的渲染还是返回hookState._value,但是_value的值已经被修改了 if (hookState._value[0] !== nextValue) { hookState._value = [nextValue, hookState._value[1]]; // 在diff/index.js中可以看到如果是函数组件没有render方法,那么会对PReact.Component进行实例化 // 这时候调用setState方法同样会触发组件的渲染流程 hookState._component.setState({}); } } ]; hookState._component = currentComponent; } return hookState._value; }
而useState就很简单了,只是调用一下useReducer, export function useState(initialState) { currentHook = 1; return useReducer(invokeOrReturn, initialState); } function invokeOrReturn(arg, f) { return typeof f == 'function' ? f(arg) : f; }
export function useContext(context) { // create-context中返回的是一个context对象,得到provide对象 // Provider组件在diff时,判断没有render方法时,会先用Compoent来实例化一个对象 // 并将render方法设置为doRender,并将constructor指向newType(当前函数),在doRender中调用this.constructor方法 const provider = currentComponent.context[context._id]; const state = getHookState(currentIndex++, 9); state._context = context; // 挂载到state的_context属性中 if (!provider) return context._defaultValue; // 如果么有provider永远返回context的初始值。 if (state._value == null) { // 初次渲染则将组件对provider进行订阅 state._value = true; provider.sub(currentComponent); } return provider.props.value; } useContext使用示例: import React, { useState ,,useContext, createContext} from 'react'; import './App.css'; // 创建一个 context const Context = createContext(0) // 组件一, useContext 写法 function Item3 () { const count = useContext(Context); return ( <div>{ count }</div> ) } function App () { const [ count, setCount ] = useState(0) return ( <div> 点击次数: { count } <button onClick={() => { setCount(count + 1)}}>点我</button> <Context.Provider value={count}> {/* <Item1></Item1> <Item2></Item2> */} <Item3></Item3> </Context.Provider> </div> ) } export default App;
博客园我也真是服了,一个以技术为主的博客网站,竟然每次进入编辑器,插入代码功能只能用一次,第二次死活提交不上,必须关闭浏览器重新打开,我真特么服!!!!
这是TMD把人往掘金、简书、知乎、segmentfault上逼啊!!!!!!!!!!!!!!!!!!