vue 3ソース分析-プロセス分析の実行
仮想domおよびエージェントは、vue 3の実行プロセスを説明する他のブログを参照できます.vueの実行プロセスを簡単に理解できます.一、初回レンダリングプロセス
createAppAPIはcreateApp関数を返します.createAppはappオブジェクトを作成します.appオブジェクトにはmountメソッドがあります.mountはrenderメソッドを呼び出し、renderはpatchメソッドを呼び出し、patchメソッドはshapeFlag&6/*COMPONENT*/を判断し、processComponentメソッドを実行し、この時点でまだ仮想domは生産されていない.processComponentは、最初のレンダリングがmountComponentを実行すると判断します.mountComponentはinstanceオブジェクトを作成し、setupComponentメソッドを実行します.setupComponentの主な役割はprops、render、typeなど、さまざまな属性とメソッドを生産することです.setupComponentはsetupStatefulComponentメソッドを実行し、setupStatefulComponentでfinishComponentSetupメソッドはcompileメソッドを実行し、renderメソッドをコンパイルします.renderはinstanceオブジェクトの属性で、rendeメソッドは仮想domをコンパイルすることができます.
compileを実行した後、applyOptionsメソッドを実行し続け、applyOptionsでresolveDataを実行し、resolveDataでinstanceを実行する.data = reactive(data);プロキシオブジェクトを生成します.setupComponentはsetupRenderEffectメソッドの実行を完了します.义齿update=effect(fn,createDeviceOptions)は、effectをマウントし、レンダリングロジックを実行します.createDevEffectOptionsは、一括更新メソッドを作成します.この2つの関数を実行すると、データのエージェントとeffectのマウントが完了します.
effect関数をマウントすると、すぐにeffect関数が呼び出されます.
effect関数の呼び出しrenderComponentRoot関数.renderComponentRootはinstance上のrenderメソッドを実行して仮想domを生産します.構造は次のとおりです.
次にpatchメソッドを実行します.patch(null, subTree, container, anchor, instance, parentSuspense, isSVG);ページは最初にレンダリングされます.
二、更新プロセス
エージェントオブジェクトのプロパティが変更されると、Proxy関連は他のブログを参考にすることができ、理解しにくいわけではありません.triggerメソッドがトリガーされます.
最後にeffectを呼び出す.options.scheduler(effect)法.schedulerプロパティはcreateDeviceffectOptionsメソッドで定義され、effect関数をマウントするときに定義されます.schedulerプロパティはqueueJobメソッドを指します.queueJobメソッドは、まず、グローバル変数queueデータに同じタイプのjob、すなわちeffectが含まれているかどうかを判断し、なければキューに追加します.
queueFlushメソッド非同期マイクロタスクメソッド、すなわちeffectsを呼び出す.forEach(run)が終了したらeffect関数をもう一度実行し、effectが一度だけ実行されることを保証します.efect関数は、最初にレンダリングされた関数です.effect関数を実行することは、このメソッドconst nextTree=renderComponentRoot(instance)を実行することである.新しい仮想domメソッドを生産します.さらにpatchメソッドを実行します.すなわち、仮想domノードを比較してページをレンダリングします.prevTreeは、前回のレンダリングページの仮想domであり、instanceのプロパティです.patch(prevTree, nextTree,//parent may have changed if it’s in a teleport hostParentNode(prevTree.el),//anchor may have changed if it’s in a fragment getNextHostNode(prevTree), instance, parentSuspense, isSVG); ほとんどのプロセスは終了しました.
const createApp = ((...args) => {
const app = ensureRenderer().createApp(...args);
{
injectNativeTagCheck(app);
}
const {
mount } = app;
app.mount = (containerOrSelector) => {
const container = normalizeContainer(containerOrSelector);
if (!container)
return;
const component = app._component;
if (!isFunction(component) && !component.render && !component.template) {
component.template = container.innerHTML;
}
// clear content before mounting
container.innerHTML = '';
const proxy = mount(container);
container.removeAttribute('v-cloak');
container.setAttribute('data-v-app', '');
return proxy;
};
return app;
});
ensureRenderer: render
return {
render,
hydrate,
createApp: createAppAPI(render, hydrate)
};
createAppAPIはcreateApp関数を返します.createAppはappオブジェクトを作成します.appオブジェクトにはmountメソッドがあります.mountはrenderメソッドを呼び出し、renderはpatchメソッドを呼び出し、patchメソッドはshapeFlag&6/*COMPONENT*/を判断し、processComponentメソッドを実行し、この時点でまだ仮想domは生産されていない.processComponentは、最初のレンダリングがmountComponentを実行すると判断します.mountComponentはinstanceオブジェクトを作成し、setupComponentメソッドを実行します.setupComponentの主な役割はprops、render、typeなど、さまざまな属性とメソッドを生産することです.setupComponentはsetupStatefulComponentメソッドを実行し、setupStatefulComponentでfinishComponentSetupメソッドはcompileメソッドを実行し、renderメソッドをコンパイルします.renderはinstanceオブジェクトの属性で、rendeメソッドは仮想domをコンパイルすることができます.
"const _Vue = Vue
const {
createVNode: _createVNode, createTextVNode: _createTextVNode } = _Vue
const _hoisted_1 = /*#__PURE__*/_createVNode("h1", null, "Latest Vue.js Commits", -1 /* HOISTED */)
const _hoisted_2 = /*#__PURE__*/_createTextVNode(" - ")
const _hoisted_3 = {
class: "message" }
const _hoisted_4 = /*#__PURE__*/_createVNode("br", null, null, -1 /* HOISTED */)
const _hoisted_5 = /*#__PURE__*/_createTextVNode(" by ")
const _hoisted_6 = {
class: "author" }
const _hoisted_7 = /*#__PURE__*/_createTextVNode(" at ")
const _hoisted_8 = {
class: "date" }
return function render(_ctx, _cache) {
with (_ctx) {
const {
createVNode: _createVNode, renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, vModelRadio: _vModelRadio, withDirectives: _withDirectives, toDisplayString: _toDisplayString, createTextVNode: _createTextVNode } = _Vue
return (_openBlock(), _createBlock(_Fragment, null, [
_hoisted_1,
(_openBlock(true), _createBlock(_Fragment, null, _renderList(branches, (branch) => {
return (_openBlock(), _createBlock(_Fragment, null, [
_withDirectives(_createVNode("input", {
type: "radio",
id: branch,
value: branch,
name: "branch",
"onUpdate:modelValue": $event => (currentBranch = $event)
}, null, 8 /* PROPS */, ["id", "value", "onUpdate:modelValue"]), [
[_vModelRadio, currentBranch]
]),
_createVNode("label", {
for: branch }, _toDisplayString(branch), 9 /* TEXT, PROPS */, ["for"])
], 64 /* STABLE_FRAGMENT */))
}), 256 /* UNKEYED_FRAGMENT */)),
_createVNode("p", null, "vuejs/vue@" + _toDisplayString(currentBranch), 1 /* TEXT */),
_createVNode("ul", null, [
(_openBlock(true), _createBlock(_Fragment, null, _renderList(commits, ({
html_url, sha, author, commit }) => {
return (_openBlock(), _createBlock("li", null, [
_createVNode("a", {
href: html_url,
target: "_blank",
class: "commit"
}, _toDisplayString(sha.slice(0, 7)), 9 /* TEXT, PROPS */, ["href"]),
_hoisted_2,
_createVNode("span", _hoisted_3, _toDisplayString(truncate(commit.message)), 1 /* TEXT */),
_hoisted_4,
_hoisted_5,
_createVNode("span", _hoisted_6, [
_createVNode("a", {
href: author.html_url,
target: "_blank"
}, _toDisplayString(commit.author.name), 9 /* TEXT, PROPS */, ["href"])
]),
_hoisted_7,
_createVNode("span", _hoisted_8, _toDisplayString(formatDate(commit.author.date)), 1 /* TEXT */)
]))
}), 256 /* UNKEYED_FRAGMENT */))
])
], 64 /* STABLE_FRAGMENT */))
}
}"
compileを実行した後、applyOptionsメソッドを実行し続け、applyOptionsでresolveDataを実行し、resolveDataでinstanceを実行する.data = reactive(data);プロキシオブジェクトを生成します.setupComponentはsetupRenderEffectメソッドの実行を完了します.义齿update=effect(fn,createDeviceOptions)は、effectをマウントし、レンダリングロジックを実行します.createDevEffectOptionsは、一括更新メソッドを作成します.この2つの関数を実行すると、データのエージェントとeffectのマウントが完了します.
function createDevEffectOptions(instance) {
return {
scheduler: queueJob,
onTrack: instance.rtc ? e => invokeArrayFns(instance.rtc, e) : void 0,
onTrigger: instance.rtg ? e => invokeArrayFns(instance.rtg, e) : void 0
};
}
effect関数をマウントすると、すぐにeffect関数が呼び出されます.
instance.update = effect(function componentEffect() {
// debugger;
if (!instance.isMounted) {
let vnodeHook;
const {
el, props } = initialVNode;
const {
bm, m, parent } = instance;
// beforeMount hook
if (bm) {
invokeArrayFns(bm);
}
// onVnodeBeforeMount
if ((vnodeHook = props && props.onVnodeBeforeMount)) {
invokeVNodeHook(vnodeHook, parent, initialVNode);
}
// render
{
startMeasure(instance, `render`);
}
const subTree = (instance.subTree = renderComponentRoot(instance));
{
endMeasure(instance, `render`);
}
if (el && hydrateNode) {
{
startMeasure(instance, `hydrate`);
}
// vnode has adopted host node - perform hydration instead of mount.
hydrateNode(initialVNode.el, subTree, instance, parentSuspense);
{
endMeasure(instance, `hydrate`);
}
}
else {
{
startMeasure(instance, `patch`);
}
// debugger
patch(null, subTree, container, anchor, instance, parentSuspense, isSVG);
{
endMeasure(instance, `patch`);
}
initialVNode.el = subTree.el;
}
// mounted hook
if (m) {
queuePostRenderEffect(m, parentSuspense);
}
// onVnodeMounted
if ((vnodeHook = props && props.onVnodeMounted)) {
queuePostRenderEffect(() => {
invokeVNodeHook(vnodeHook, parent, initialVNode);
}, parentSuspense);
}
// activated hook for keep-alive roots.
// #1742 activated hook must be accessed after first render
// since the hook may be injected by a child keep-alive
const {
a } = instance;
if (a &&
initialVNode.shapeFlag & 256 /* COMPONENT_SHOULD_KEEP_ALIVE */) {
queuePostRenderEffect(a, parentSuspense);
}
instance.isMounted = true;
}
else {
// updateComponent
// This is triggered by mutation of component's own state (next: null)
// OR parent calling processComponent (next: VNode)
let {
next, bu, u, parent, vnode } = instance;
let originNext = next;
let vnodeHook;
{
pushWarningContext(next || instance.vnode);
}
if (next) {
updateComponentPreRender(instance, next, optimized);
}
else {
next = vnode;
}
next.el = vnode.el;
// beforeUpdate hook
if (bu) {
invokeArrayFns(bu);
}
// onVnodeBeforeUpdate
if ((vnodeHook = next.props && next.props.onVnodeBeforeUpdate)) {
invokeVNodeHook(vnodeHook, parent, next, vnode);
}
// render
{
startMeasure(instance, `render`);
}
const nextTree = renderComponentRoot(instance);
{
endMeasure(instance, `render`);
}
const prevTree = instance.subTree;
instance.subTree = nextTree;
// reset refs
// only needed if previous patch had refs
if (instance.refs !== EMPTY_OBJ) {
instance.refs = {
};
}
{
startMeasure(instance, `patch`);
}
patch(prevTree, nextTree,
// parent may have changed if it's in a teleport
hostParentNode(prevTree.el),
// anchor may have changed if it's in a fragment
getNextHostNode(prevTree), instance, parentSuspense, isSVG);
{
endMeasure(instance, `patch`);
}
next.el = nextTree.el;
if (originNext === null) {
// self-triggered update. In case of HOC, update parent component
// vnode el. HOC is indicated by parent instance's subTree pointing
// to child component's vnode
updateHOCHostEl(instance, nextTree.el);
}
// updated hook
if (u) {
queuePostRenderEffect(u, parentSuspense);
}
// onVnodeUpdated
if ((vnodeHook = next.props && next.props.onVnodeUpdated)) {
queuePostRenderEffect(() => {
invokeVNodeHook(vnodeHook, parent, next, vnode);
}, parentSuspense);
}
{
devtoolsComponentUpdated(instance);
}
{
popWarningContext();
}
}
}, createDevEffectOptions(instance) );
effect関数の呼び出しrenderComponentRoot関数.renderComponentRootはinstance上のrenderメソッドを実行して仮想domを生産します.構造は次のとおりです.
anchor: null
appContext: null
children: Array(13)
0: {
__v_isVNode: true, __v_skip: true, type: Symbol(Comment), props: null, key: null, …}
1: {
__v_isVNode: true, __v_skip: true, type: Symbol(Comment), props: null, key: null, …}
2: {
__v_isVNode: true, __v_skip: true, type: Symbol(Comment), props: null, key: null, …}
3: {
__v_isVNode: true, __v_skip: true, type: Symbol(Comment), props: null, key: null, …}
4: {
__v_isVNode: true, __v_skip: true, type: Symbol(Comment), props: null, key: null, …}
5: {
__v_isVNode: true, __v_skip: true, type: Symbol(Comment), props: null, key: null, …}
6: {
__v_isVNode: true, __v_skip: true, type: Symbol(Comment), props: null, key: null, …}
7: {
__v_isVNode: true, __v_skip: true, type: Symbol(Comment), props: null, key: null, …}
8: {
__v_isVNode: true, __v_skip: true, type: Symbol(Comment), props: null, key: null, …}
9: {
__v_isVNode: true, __v_skip: true, type: Symbol(Comment), props: null, key: null, …}
10: {
__v_isVNode: true, __v_skip: true, type: "div", props: null, key: null, …}
11: {
__v_isVNode: true, __v_skip: true, type: "div", props: null, key: null, …}
12: {
__v_isVNode: true, __v_skip: true, type: "button", props: {
…}, key: null, …}
length: 13
__proto__: Array(0)
component: null
dirs: null
dynamicChildren: (3) [{
…}, {
…}, {
…}]
dynamicProps: null
el: null
key: null
patchFlag: 64
props: null
ref: null
scopeId: null
shapeFlag: 16
staticCount: 0
suspense: null
target: null
targetAnchor: null
transition: null
type: Symbol(Fragment)
const subTree = (instance.subTree = renderComponentRoot(instance));
次にpatchメソッドを実行します.patch(null, subTree, container, anchor, instance, parentSuspense, isSVG);ページは最初にレンダリングされます.
二、更新プロセス
エージェントオブジェクトのプロパティが変更されると、Proxy関連は他のブログを参考にすることができ、理解しにくいわけではありません.triggerメソッドがトリガーされます.
function trigger(target, type, key, newValue, oldValue, oldTarget) {
// debugger;
const depsMap = targetMap.get(target);
if (!depsMap) {
// never been tracked
return;
}
const effects = new Set();
const add = (effectsToAdd) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => effects.add(effect));
}
};
if (type === "clear" /* CLEAR */) {
// collection being cleared
// trigger all effects for target
depsMap.forEach(add);
}
else if (key === 'length' && isArray(target)) {
depsMap.forEach((dep, key) => {
if (key === 'length' || key >= newValue) {
add(dep);
}
});
}
else {
// schedule runs for SET | ADD | DELETE
if (key !== void 0) {
add(depsMap.get(key));
}
// also run for iteration key on ADD | DELETE | Map.SET
const shouldTriggerIteration = (type === "add" /* ADD */ &&
(!isArray(target) || isIntegerKey(key))) ||
(type === "delete" /* DELETE */ && !isArray(target));
if (shouldTriggerIteration ||
(type === "set" /* SET */ && target instanceof Map)) {
add(depsMap.get(isArray(target) ? 'length' : ITERATE_KEY));
}
if (shouldTriggerIteration && target instanceof Map) {
add(depsMap.get(MAP_KEY_ITERATE_KEY));
}
}
const run = (effect) => {
if ( effect.options.onTrigger) {
effect.options.onTrigger({
effect,
target,
key,
type,
newValue,
oldValue,
oldTarget
});
}
if (effect.options.scheduler) {
console.log(effect);
effect.options.scheduler(effect);
}
else {
effect();
}
};
effects.forEach(run);
}
最後にeffectを呼び出す.options.scheduler(effect)法.schedulerプロパティはcreateDeviceffectOptionsメソッドで定義され、effect関数をマウントするときに定義されます.schedulerプロパティはqueueJobメソッドを指します.queueJobメソッドは、まず、グローバル変数queueデータに同じタイプのjob、すなわちeffectが含まれているかどうかを判断し、なければキューに追加します.
function queueJob(job) {
// debugger;
// the dedupe search uses the startIndex argument of Array.includes()
// by default the search index includes the current job that is being run
// so it cannot recursively trigger itself again.
// if the job is a watch() callback, the search will start with a +1 index to
// allow it recursively trigger itself - it is the user's responsibility to
// ensure it doesn't end up in an infinite loop.
if ((!queue.length ||
!queue.includes(job, isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex)) &&
job !== currentPreFlushParentJob) {
console.log(1,job);
queue.push(job);
queueFlush();
}
console.log(2,job);
}
function queueFlush() {
debugger;
if (!isFlushing && !isFlushPending) {
isFlushPending = true;
currentFlushPromise = resolvedPromise.then(flushJobs);
}
}
queueFlushメソッド非同期マイクロタスクメソッド、すなわちeffectsを呼び出す.forEach(run)が終了したらeffect関数をもう一度実行し、effectが一度だけ実行されることを保証します.efect関数は、最初にレンダリングされた関数です.effect関数を実行することは、このメソッドconst nextTree=renderComponentRoot(instance)を実行することである.新しい仮想domメソッドを生産します.さらにpatchメソッドを実行します.すなわち、仮想domノードを比較してページをレンダリングします.prevTreeは、前回のレンダリングページの仮想domであり、instanceのプロパティです.patch(prevTree, nextTree,//parent may have changed if it’s in a teleport hostParentNode(prevTree.el),//anchor may have changed if it’s in a fragment getNextHostNode(prevTree), instance, parentSuspense, isSVG); ほとんどのプロセスは終了しました.