Jarweb / thinking-in-deep

一些思考

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

preact 源码阅读

Jarweb opened this issue · comments

代码版本

  • preact:10.4.4

应用入口

function MyApp (props) {
  return (
  	<div className="wrap">
      <div className="inner">hi</div>
    </div>
  )
}
render(<MyApp />, root)

经过 babel-transform-jsx 编译后

// 将 jsx 的 dsl 代码转成 createElement 函数调用
// createElement(type, props, children, ...)

function MyApp (props) {
  return createElement(
    'div', // type
    {className: 'wrap'}, // props
    createElement( // children
    	'div',
      {className: 'inner'},
      'hi'
    )
  )
}
render(createElement(MyApp, null, undefined), root)

type 的类型

  • function:class 组件,function 组件,内建组件( 如 Fragment 等)都是 function 类型
  • string:html tag / text (如 : div )

props 上有什么

  • css className ,style
  • key,ref
  • html attribute

children

  • createElement 函数的

整体流程

// vnode
constructor: undefined
key: null
props: {}
ref: null
type: ƒ App() // 组件/dom tag
_children: null
_component: null
_depth: 0
_dom: null
_nextDom: undefined
_original: {props: {}, key: null, ref: null, _children: null, type: ƒ,}
_parent: null

// parentDom => dom root

// 入口 render
function render(vnode, parentDom, replaceNode) {
  ...
  // 包裹一层 fragment
  vnode = createElement(Fragment, null, [vnode])
  ...
  
  let commitQueue = []
  diff(....)
  commitRoot(commitQueue, vnode)
}


function diff(
	parentDom, // dom root
	newVNode, // fragment
	oldVNode, // {}
	globalContext, // {}
	isSvg, // false
	excessDomChildren, // null
	commitQueue, // []
	oldDom, // {}
	isHydrating // false
) {
  ...
  // class 组件
  if ('prototype' in newType && newType.prototype.render) {
  	// 实例化
    newVNode._component = c = new newType(newProps, componentContext);
  } 
  // 函数组件
  else {
  	// 通过 Component 进行实例化,所以类组件和函数组件是没啥区别的,函数组件的没有定义生命函数
  	// fragment 也是函数组件
    newVNode._component = c = new Component(newProps, componentContext);
    c.constructor = newType;
    c.render = (props, state, context) {
      return this.constructor(props, context);
    };
  }
	
  // 往 c 上挂着 props/state/context/_dirty/_rendeCallback 等
  
  // 类组件,执行生命函数 getDerivedStateFromProps(newprops,newstate),得到的返回值合并到 state 上
  
  // 首次渲染阶段
  // 类组件,执行生命函数 componentWillMount,把 componentDidMount 存到 _rendeCallback 数组里
  // 执行组件的 render 函数
  c.render(c.props, c.state, c.context)
  // 当 vnode 是组件/内置组件,执行 diffChildren(...)
  // 当 vnode 是 dom tag,执行 diffElementNodes(...)
  
  // 更新阶段
  // 类组件,执行 componentWillReceiveProps
  // 执行 shouldComponentUpdate
  // 将当前组件存到 commitQueue 里。等待递归子组件后,再拿出来执行其他生命函数
  // 执行 c.componentWillUpdate
  // 执行组件的 render 函数
  // 执行 c.getSnapshotBeforeUpdate(oldProps, oldState)
  // diffChildren(...)
}


function diffChildren(
	parentDom, // dom root
	renderResult, // 父组件 render 函数的返回值,是一个数组,元素是 vnode
	newParentVNode, // 父组件的 vnode
	oldParentVNode, // {}
	globalContext, // {}
	isSvg, // false
	excessDomChildren, // null
	commitQueue, // []
	oldDom, // {}
	isHydrating // false
) {
	// 遍历 renderResult 数组,根据元素的 vnode 类型进行不同的处理
	childVNode = renderResult[i]
	// 如果是 null/boolean,统一转成 null
	// 如果是 string/number,创建新的 vnode,createVNode
	// 如果是 数组,包一层 fragment
	// 如果 vnode._dom / vnode._component 存在,创建新的 vnode
	
	// 绑定 vnode 的 _parent 和 _depth。父 vnode 和 当前 vnode 的层级
	
	// 从 oldParentVNode 里得到 oldChildren ,进行新旧自组件 vnode 的调和。更新阶段
  ...
  newDom = diff(...)
  ...
  // 当前 vnode 的父 parentDom 插入当前 vnode 的 dom
}

function diffElementNodes(
	dom, // undefined
	newVNode, // vnode dom tag 类型
	oldVNode, // {}
	globalContext, // {}
	isSvg, // false
	excessDomChildren, // null
	commitQueue, // []
	isHydrating // false
) { 
	// 首次渲染
	// 创建 dom/text
	// diffProps(...), 绑定 class ,style,dom 属性,事件
	// diffChildren(...)
}


// 首次渲染
App 组件
	<Main></Main>

Main 组件
	<div>hi</div>
	
fragment vnode
App vnode
Main vnode
div vnode
hi vnode

render
 	diff fragment vnode
 		diffChildren App vnode
    	diff App vnode, App 组件实例化,执行生命函数
    		diffChildren Main vnode
    			diff Main vnode,Main 组件实例化,执行生命函数
    				diffChildren div vnode
    					diff div vnode
    						diffElement div vnode, div dom 创建,属性/事件挂载
    							diffChildren div vnode
    								diff text vnode
    									diffElement text vnode,text dom 创建
    										diffChildren text vnode, div append text。root append div。一层层往根 append
    										递归结束,一层层返回...
...
commitRoot,执行剩余的生命函数和 callback


// 类组件生命函数执行
实例化
父 getDerivedStateFromProps
父 componentWillMount,旧的,后面会移除
父 render
子 getDerivedStateFromProps
子 componentWillMount
子 render
子 componentDidMount
父 componentDidMount

// 更新阶段生命函数
父 getDerivedStateFromProps
父 componentWillReceiveProps,旧的,会被移除
父 shouldComponentUpdate
父 componentWillUpdate
父 render
子 getSnapshotBeforeUpdate,在子组件 diff 之前执行
子 getDerivedStateFromProps
子 componentWillReceiveProps,旧的,会被移除
子 shouldComponentUpdate
子 componentWillUpdate
子 render
子 getSnapshotBeforeUpdate,在子组件 diff 之前执行。dom 还没更新。返回值将作为第三参数传给 componentDidUpdate
子 componentDidUpdate
父 componentDidUpdate

// 卸载阶段
unapply ref
父 componentWillUnmout
子 componentWillUnmout


// setState
// 合并更新 state 为 this._nextstate
// 执行 enqueueRender(this) // this 就是当前 setstate 的组件实例

// 组件实例
context: {}
handle: ƒ () // 事件
props: {path: "/", url: "/", matches: {}} // props
state: {count: 1} // 旧的 state
_dirty: false
_force: false
_globalContext: {}
_nextState: {count: 2} // 新的 state
_parentDom: div#app
_renderCallbacks: []
_vnode: {props: {}, key: undefined, ref: undefined, __k: null, type: ƒ, } // 组件对应的 vnode

function enqueueRender(c) {
  // 标记当前组件实例为 _dirty: true
  // 将需要更新的组件存到 rerenderQueue 数组中
  // 执行 defer(process)
  
 // 该组件第一次触发更新时,就会调用 defer(process)
 // 后续的重复 setState 都不会再进入下面逻辑。后续重复 setState 只会直接更新 state,而渲染要等到当前宏任务执行完
  if (
		(!c._dirty &&
			(c._dirty = true) &&
			rerenderQueue.push(c) &&
			!rerenderCount++) ||
		prevDebounce !== options.debounceRendering
	) {
		prevDebounce = options.debounceRendering;
		(prevDebounce || defer)(process);
	}
}

// 在当前宏任务后的微任务中执行更新 process,从而达到批量更新的效果
const defer =
	typeof Promise == 'function'
		? Promise.prototype.then.bind(Promise.resolve())
		: setTimeout;
		
function process() {
	let queue;
	while ((rerenderCount = rerenderQueue.length)) {
		// 将所有需要更新的组件实例排序,从层级小到大,即父到子
		queue = rerenderQueue.sort((a, b) => a._vnode._depth - b._vnode._depth);
		rerenderQueue = [];
		queue.some(c => {
			if (c._dirty) renderComponent(c);
		});
	}
}

// 最外一个脏组件开始更新
function renderComponent(component) {
  diff(...)
  commitRoot(...)
  updateParentDomPointers(...)
}


// hook
function getHookState(index, type) {
	if (options._hook) {
		options._hook(currentComponent, index, currentHook || type);
	}
	currentHook = 0;

	// 初始化组件实例上的 __hooks 属性
	const hooks =
		currentComponent.__hooks ||
		(currentComponent.__hooks = {
			_list: [],
			_pendingEffects: []
		});
		
	// 新的 hook
	if (index >= hooks._list.length) {
		hooks._list.push({});
	}
	
	// 已存在的 hook,直接返回。当组件多次渲染时,hook 不会重复创建
	return hooks._list[index]; // 返回当前的 hook
}


// useState
function useState(initialState) {
	currentHook = 1; // 全局属性
	return useReducer(invokeOrReturn, initialState);
}

// useReducer
function useReducer(reducer, initialState, init) {
	const hookState = getHookState(currentIndex++, 2);
	hookState._reducer = reducer;
	
	// 首次添加,每个 hook 里维护一个组件实例,来判断这个 hook 是否已经维护
	if (!hookState._component) {
		hookState._component = currentComponent;

		// [count, set]
		hookState._value = [
			!init ? invokeOrReturn(undefined, initialState) : init(initialState),

			action => {
				// 计算新的 state
				const nextValue = hookState._reducer(hookState._value[0], action);
				if (hookState._value[0] !== nextValue) {
					// 更新 state
					hookState._value[0] = nextValue;
					// 通过 setState 来触发渲染
					hookState._component.setState({});
				}
			}
		];
	}

	return hookState._value;
}


// useEffect
function useEffect(callback, args) {
	const state = getHookState(currentIndex++, 3);
	// 当依赖变化,会重新存储 useEffect callback
	if (!options._skipEffects && argsChanged(state._args, args)) {
		state._value = callback;
		state._args = args;
		// 当前 hook 所属的组件实例
		currentComponent.__hooks._pendingEffects.push(state);
	}
}

function useLayoutEffect(callback, args) {
	const state = getHookState(currentIndex++, 4);
	if (!options._skipEffects && argsChanged(state._args, args)) {
		state._value = callback;
		state._args = args;

		// 与 useEffect 的区别,执行的时机不一样。在 didmount 时执行
		currentComponent._renderCallbacks.push(state);
	}
}

  
// options,通过 options 上的钩子,在组件的不同生命函数中执行,来达到函数组件也有生命周期
diffed: vnode => {} // 所有组件 diff 完成后执行,在 commitRoot 前
unmount: vnode => {} // 组件卸载时执行
_catchError: ƒ _catchError(error, vnode) // 组件出错时执行
_commit: (vnode, commitQueue) => {} // 组件 didMount 时执行,即 commitRoot
_render: vnode => {} // 组件 render 前执行


// render,在组件 render 前执行
options._render = vnode => {
	// 执行上一次 render
	if (oldBeforeRender) oldBeforeRender(vnode);

	currentComponent = vnode._component;
	currentIndex = 0;

	const hooks = currentComponent.__hooks;
	if (hooks) {
		// 每次执行时,先清除 useeffect 请求副作用
		hooks._pendingEffects.forEach(invokeCleanup);
		// 执行 useeffect
		hooks._pendingEffects.forEach(invokeEffect);
		hooks._pendingEffects = [];
	}
}

function invokeEffect(hook) {
	hook._cleanup = hook._value(); // 副作用清除存到 _cleanup 上
}

function invokeCleanup(hook) {
	if (typeof hook._cleanup == 'function') hook._cleanup();
}

// 在 diff 递归结束后执行,所有组件都 diff 完成。但 commitRoot 之前
options.diffed = vnode => {
	if (oldAfterDiff) oldAfterDiff(vnode);

	const c = vnode._component;
	if (c && c.__hooks && c.__hooks._pendingEffects.length) {
		// 下一帧的 raf 中执行
		// 也是执行 c.__hooks._pendingEffects 里的 effect
		afterPaint(afterPaintEffects.push(c));
	}
};

// 在 commitRoot 时执行,即 dom 插入后。didmount 执行前
// 可认为是函数组件的 didmount 钩子
options._commit = (vnode, commitQueue) => {
	commitQueue.some(component => {
		try {
			// useLayoutEffect 的执行时机
			component._renderCallbacks.forEach(invokeCleanup);
			component._renderCallbacks = component._renderCallbacks.filter(cb =>
				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.unmount = vnode => {
	if (oldBeforeUnmount) oldBeforeUnmount(vnode);

	const c = vnode._component;
	if (c && c.__hooks) {
		try {
			c.__hooks._list.forEach(invokeCleanup);
		} catch (e) {
			options._catchError(e, c._vnode);
		}
	}
}

// hook 对应的组件实例
constructor: ƒ HookDemo()
context: {}
props: {}
render: ƒ doRender(props, state, context)
state: {}
__hooks: {
  _list: [
    {
    	_component: Component {} // 组件实例
    	_reducer: ƒ invokeOrReturn(arg, f) // 
    	_value: (2) [0, ƒ] // [count, set] // 每次 set 后就执行当前组件的 setState 来触发渲染。所以多个更新会合并渲染
    },
    {
      _args: []
			_value: ƒ () // useEffect 的回调函数
    }
  ]
_pendingEffects: [
  {
    _args: []
		_value: ƒ ()
  }
]
__proto__: Object
}
_dirty: false
_globalContext: {}
_nextState: {}
_parentDom: div.page
_renderCallbacks: []
_vnode: {props: {}, key: null, ref: null, _children: null, type: ƒ,}


// 分组
useState, useReduce
useEffect
useLayoutEffect, useImperativeHandle
useRef, useMemo, useCallback
useContext
useErrorBoundary


// hook 的生命周期顺序
函数组件实例化
// getDerivedStateFromProps
// componentWillMount
options._render // hook useEffect
render
// 子 diff
options.diffed // hook useEffect,下一帧 raf 中执行
options._commit // hook useLayoutEffect
// componentDidMount


// ref
当该组件内的所有子组件都递归完成后,把子组件的dom插入到父的dom内后
进行 ref  dom 实例的绑定
ref 绑定在 compnentDidMount 之前全部完成
更新阶段,在 ref 绑定之前会进行组件的移除(如果存在)

// context
const MyContext = createContext('a')

<MyContext.Provider value="b">
  <Demo />
</MyContext.Provider>

<MyContext.Consumer>
  {value => (
      <div>{value}</div>
  )}
</MyContext.Consumer>

// 当 diff 到 Provider 组件时,Provider 的 vnode 结构:
constructor: undefined
key: undefined
props: {value: "b", children: {}} // value 是 Provider 接收的数据
ref: undefined
type: Provider(props) {...}
	_contextRef: {_id: "__cC0", _defaultValue: "a", Consumer: ƒ, Provider: ƒ} // Provider 函数的属性
_children: null

// Provider 组件也是函数组件,也是通过 Component 来实例化
// 得到的实例是:
constructor: Provider(props) {...}
context: {}
props: {value: "b", children: {}} // props.value 是 Consumer 组件要拿到的值
render: ƒ doRender(props, state, context) // render 返回子组件 Demo

globalContext = assign(assign({}, globalContext), c.getChildContext()) // 返回当前的组件实例和 contextid 的映射对象

// globalContext
__cC0: Component {...} // 通过 id 拿到对应的组件实例

this.getChildContext = () => {
  ctx[context._id] = this;
  return ctx;
}

// globalContext 会通过 diff 传给子组件,然后挂到子组件的实例上
c.context = componentContext;
c._globalContext = globalContext;

// 当 diff 到 Consumer 组件时,Consumer 组件的 vnode:
constructor: undefined
key: null
props: {children: ƒ}
ref: null
type: ƒ Consumer(props, context)
   contextType: {_id: "__cC0", _defaultValue: "a", Consumer: ƒ, Provider: ƒ} // Consumer 函数上的属性
                  
// 通过 diff 传入的 globalContext 和 contextType 上的 _id 可以拿到 Provider 组件实例的 props。
let componentContext = tmp // componentContext 就是 props.value
  ? provider
    ? provider.props.value
    : tmp._defaultValue
  : globalContext;
                  
// 当 consumner 组件实例化时,c = new Component(newProps, componentContext) // value 作为 context 参数传入给 Component 进行实例化
context: "b"
props: {children: ƒ}

// Provider 组件实例收集了 Consumer 组件的实例。当 provider 上的 context value 变化时,会触发 consumer 组件实例更新
if (provider) provider.sub(c)
                  
c.context = componentContext; // Consumer 实例的 context 
c._globalContext = globalContext;

// render,即 Consumer 函数执行。就是一个 render props
c.render(c.props, c.state, c.context)

// Consumer 组件定义
Consumer(props, context) {
  return props.children(context);
}

// context 是如何更新的?
// 当 Provider 组件的 props 更新时,会触发 Provider 组件的更新,会执行 shouldComponentUpdate 生命函数
this.shouldComponentUpdate = _props => {
  if (this.props.value !== _props.value) { // 浅比较,有性能优化
    // 之前 Provider 组件实例收集的 Consumer 组件实例
    subs.some(c => {
      c.context = _props.value; // 更新 context 的值
      enqueueRender(c); // 触发渲染。当 context 频繁更新时,也会合并渲染
    });
  }
}
// vnode
constructor: undefined
key: null
props: {}
ref: null
type: ƒ App() // 组件函数/类
_children: null // 子组件 vnode,组件实例化后 render 的返回值
_component: null // 组件实例
_depth: 0 // 层级
_dom: null // dom 实例
_nextDom: undefined // 新的 dom 实例
_original: {props: {…}, key: null, ref: null, _children: null, type: ƒ, …} // vnode 备份
_parent: null // 父组件 vnode,子组件 vnode 通过 _parent 一层层往上连接父 vnode
// 组件实例
base: div.page // dom 实例
constructor: ƒ Fragment(props)
context: {} // context value
props: {children: Array(1)}
render: ƒ doRender(props, state, context) // render 函数
state: {}
_dirty: false // 是否需要更新
_force: false // 是否强制更新
_globalContext: {} // 组件实例和 context id 的映射
_nextState: {}
_parentDom: div#app // 父的 dom 实例,父创建好了才创建子的 dom 实例
_renderCallbacks: [] // render 后的生命函数/callback
_vnode: {props: {…}, key: null, ref: null, _children: Array(1), type: ƒ, …} // 对应的 vnode

image-20200630160442884

// setState 总是批量的异步的?
setState 会直接更新 state,当渲染总是在当前宏任务后的微任务中更新
当两个 setTimeout 进行 setstate, 这些渲染更新会分成两次
当在 一个 setTimeout 里多次 setstate,那么渲染更新是批量的
 react 有很大的不同