虚拟dom和代理可以参考其他博客,此博客介绍vue3运行流程。方便理解vue的运行流程。
一、初次渲染流程
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方法。 setupRenderEffect方法instance.update = effect(fn,createDevEffectOptions(instance)),挂载effect,执行渲染逻辑。createDevEffectOptions创建批量更新方法。执行完这两个函数,就完成了数据的代理,和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属性在createDevEffectOptions方法上定义,就是在挂载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);
大部分流程就结束了。