一文幫你理解PReact10.5.13源碼
React源碼看過幾次,每次都沒有堅(jiān)持下來,索性學(xué)習(xí)一下PReact部分,網(wǎng)上講解源碼的不少,但是基本已經(jīng)過時,所以自己來梳理下
render.js部分
import { EMPTY_OBJ, EMPTY_ARR } from './constants';
import { commitRoot, diff } from './diff/index';
import { createElement, Fragment } from './create-element';
import options from './options';
/**
* Render a Preact virtual node into a DOM element
* @param {import('./internal').ComponentChild} vnode The virtual node to render
* @param {import('./internal').PreactElement} parentDom The DOM element to
* render into
* @param {import('./internal').PreactElement | object} [replaceNode] Optional: Attempt to re-use an
* existing DOM tree rooted at `replaceNode`
*/
export function render(vnode, parentDom, replaceNode) {
if (options._root) options._root(vnode, parentDom);
// We abuse the `replaceNode` parameter in `hydrate()` to signal if we are in
// hydration mode or not by passing the `hydrate` function instead of a DOM
// element..
let isHydrating = typeof replaceNode === 'function';
// To be able to support calling `render()` multiple times on the same
// DOM node, we need to obtain a reference to the previous tree. We do
// this by assigning a new `_children` property to DOM nodes which points
// to the last rendered tree. By default this property is not present, which
// means that we are mounting a new tree for the first time.
// 為了支持多次在一個dom節(jié)點(diǎn)上調(diào)用render函數(shù),需要在dom節(jié)點(diǎn)上添加一個飲用,用來獲取指向上一次渲染的虛擬dom樹。
// 這個屬性默認(rèn)是指向空的,也意味著我們第一次正在裝備一顆新的樹
// 所以開始時這里的oldVNode是空(不論isHydrating的值),但是如果重復(fù)在這個節(jié)點(diǎn)上調(diào)用render那oldVNode是有值的
let oldVNode = isHydrating
? null
: (replaceNode && replaceNode._children) || parentDom._children;
// 用Fragment包裹一下vnode,同時給replaceNode和parentDom的_children賦值
vnode = (
(!isHydrating && replaceNode) ||
parentDom
)._children = createElement(Fragment, null, [vnode]);
// List of effects that need to be called after diffing.
// 用來放置diff之后需要進(jìn)行各種生命周期處理的Component,比如cdm、cdu;componentWillUnmount在diffChildren的unmount函數(shù)中執(zhí)行不在commitRoot時執(zhí)行
let commitQueue = [];
diff(
parentDom, // 這個使用parentDom的_children屬性已經(jīng)指向[vnode]了
// Determine the new vnode tree and store it on the DOM element on
// our custom `_children` property.
vnode,
oldVNode || EMPTY_OBJ, // 舊的樹
EMPTY_OBJ,
parentDom.ownerSVGElement !== undefined,
// excessDomChildren,這個參數(shù)用來做dom復(fù)用的作用
!isHydrating && replaceNode
? [replaceNode]
: oldVNode
? null
: parentDom.firstChild // 如果parentDom有子節(jié)點(diǎn)就會把整個子節(jié)點(diǎn)作為待復(fù)用的節(jié)點(diǎn)使用
? EMPTY_ARR.slice.call(parentDom.childNodes)
: null,
commitQueue,
// oldDom,在后續(xù)方法中用來做標(biāo)記插入位置使用
!isHydrating && replaceNode
? replaceNode
: oldVNode
? oldVNode._dom
: parentDom.firstChild,
isHydrating
);
// Flush all queued effects
// 調(diào)用所有commitQueue中的節(jié)點(diǎn)_renderCallbacks中的方法
commitRoot(commitQueue, vnode);
}
/**
* Update an existing DOM element with data from a Preact virtual node
* @param {import('./internal').ComponentChild} vnode The virtual node to render
* @param {import('./internal').PreactElement} parentDom The DOM element to
* update
*/
export function hydrate(vnode, parentDom) {
render(vnode, parentDom, hydrate);
}
create-context.js部分
Context的使用:
Provider的props中有value屬性
Consumer中直接獲取傳值
import { createContext, h, render } from 'preact';
const FontContext = createContext(20);
function Child() {
return <FontContext.Consumer>
{fontSize=><div style={{fontSize:fontSize}}>child</div>}
</FontContext.Consumer>
}
function App(){
return <Child/>
}
render(
<FontContext.Provider value={26}>
<App/>
</FontContext.Provider>,
document.getElementById('app')
);
看一下源碼:
import { enqueueRender } from './component';
export let i = 0;
export function createContext(defaultValue, contextId) {
contextId = '__cC' + i++; // 生成一個唯一ID
const context = {
_id: contextId,
_defaultValue: defaultValue,
/** @type {import('./internal').FunctionComponent} */
Consumer(props, contextValue) {
// return props.children(
// context[contextId] ? context[contextId].props.value : defaultValue
// );
return props.children(contextValue);
},
/** @type {import('./internal').FunctionComponent} */
Provider(props) {
if (!this.getChildContext) { // 第一次調(diào)用時進(jìn)行一些初始化操作
let subs = [];
let ctx = {};
ctx[contextId] = this;
// 在diff操作用,如果判斷一個組件在Comsumer中,會調(diào)用sub進(jìn)行訂閱;
// 同時這個節(jié)點(diǎn)后續(xù)所有diff的地方都會帶上這個context,調(diào)用sub方法進(jìn)行調(diào)用
// context具有層級優(yōu)先級,組件會先加入最近的context中
this.getChildContext = () => ctx;
this.shouldComponentUpdate = function(_props) {
if (this.props.value !== _props.value) {
// I think the forced value propagation here was only needed when `options.debounceRendering` was being bypassed:
// https://github.com/preactjs/preact/commit/4d339fb803bea09e9f198abf38ca1bf8ea4b7771#diff-54682ce380935a717e41b8bfc54737f6R358
// In those cases though, even with the value corrected, we're double-rendering all nodes.
// It might be better to just tell folks not to use force-sync mode.
// Currently, using `useContext()` in a class component will overwrite its `this.context` value.
// subs.some(c => {
// c.context = _props.value;
// enqueueRender(c);
// });
// subs.some(c => {
// c.context[contextId] = _props.value;
// enqueueRender(c);
// });
// enqueueRender最終會進(jìn)入renderComponent函數(shù),進(jìn)行diff、commitRoot、updateParentDomPointers等操作
subs.some(enqueueRender);
}
};
this.sub = c => {
subs.push(c);// 進(jìn)入訂閱數(shù)組,
let old = c.componentWillUnmount;
c.componentWillUnmount = () => { // 重寫componentWillUnmount
subs.splice(subs.indexOf(c), 1);
if (old) old.call(c);
};
};
}
return props.children;
}
};
// Devtools needs access to the context object when it
// encounters a Provider. This is necessary to support
// setting `displayName` on the context object instead
// of on the component itself. See:
// https://reactjs.org/docs/context.html#contextdisplayname
// createContext最終返回的是一個context對象,帶著Provider和Consumer兩個函數(shù)
// 同時Consumber函數(shù)的contextType和Provider函數(shù)的_contextRef屬性都指向context
return (context.Provider._contextRef = context.Consumer.contextType = context);
}
所以對于Provider組件,在渲染時會判斷有沒有g(shù)etChildContext方法,如果有的話調(diào)用得到globalContext并一直向下傳遞下去
if (c.getChildContext != null) {
globalContext = assign(assign({}, globalContext), c.getChildContext());
}
if (!isNew && c.getSnapshotBeforeUpdate != null) {
snapshot = c.getSnapshotBeforeUpdate(oldProps, oldState);
}
let isTopLevelFragment =
tmp != null && tmp.type === Fragment && tmp.key == null;
let renderResult = isTopLevelFragment ? tmp.props.children : tmp;
diffChildren(
parentDom,
Array.isArray(renderResult) ? renderResult : [renderResult],
newVNode,
oldVNode,
globalContext,
isSvg,
excessDomChildren,
commitQueue,
oldDom,
isHydrating
);
當(dāng)渲染遇到Consumer時,即遇到contextType屬性,先從Context中拿到provider,然后拿到provider的props的value值,作為組件要獲取的上下文信息。同時這時候會調(diào)用provider的sub方法,進(jìn)行訂閱,當(dāng)調(diào)用到Provider的shouldComponentUpdate中發(fā)現(xiàn)value發(fā)生變化時就會將所有的訂閱者進(jìn)入enqueueRender函數(shù)。

所以源碼中,globalContext對象的每一個key指向一個Context.Provider;componentContext代表組件所在的Consumer傳遞的上下文信息即配對的Provider的props的value;
同時Provider的shouldComponentUpdate方法中用到了 ·this.props.value !== _props.value· 那么這里的this.props是哪來的?Provider中并沒有相關(guān)屬性。
主要是下面這個地方,當(dāng)判斷沒有render方法時,會先用Compoent來實(shí)例化一個對象,并將render方法設(shè)置為doRender,并將constructor指向newType(當(dāng)前函數(shù)),在doRender中調(diào)用this.constructor方法
// Instantiate the new component
if ('prototype' in newType && newType.prototype.render) {
// @ts-ignore The check above verifies that newType is suppose to be constructed
newVNode._component = c = new newType(newProps, componentContext); // eslint-disable-line new-cap
} else {
// @ts-ignore Trust me, Component implements the interface we want
newVNode._component = c = new Component(newProps, componentContext);
c.constructor = newType;
c.render = doRender;
}
/** The `.render()` method for a PFC backing instance. */
function doRender(props, state, context) {
return this.constructor(props, context);
}
diff部分
diff部分比較復(fù)雜,整體整理了一張大圖

真是不得不吐槽,博客園的編輯器bug太多了,尤其是mac上使用,比如第二次上傳代碼提交不了;賦值粘貼用不了。。。
只有情懷讓我繼續(xù)在這里更新
到此這篇關(guān)于一文幫你理解PReact10.5.13源碼的文章就介紹到這了,更多相關(guān)PReact10.5.13源碼內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用BootStrapValidator完成前端輸入驗(yàn)證
這篇文章主要為大家詳細(xì)介紹了使用BootStrapValidator來完成前端輸入驗(yàn)證,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09
總結(jié)兩個Javascript的哈稀對象的一些編程技巧
總結(jié)兩個Javascript的哈稀對象的一些編程技巧...2007-04-04
將HTML格式的String轉(zhuǎn)化為HTMLElement的實(shí)現(xiàn)方法
本節(jié)主要介紹了將HTML格式的String轉(zhuǎn)化為HTMLElement的實(shí)現(xiàn)方法,需要的朋友可以參考下2014-08-08
JS循環(huán)遍歷JSON數(shù)據(jù)的方法
這篇文章主要介紹了JS 循環(huán)遍歷JSON數(shù)據(jù)的方法,需要的朋友可以參考下2014-07-07

