好了,上面的render终于得到了,再继续看 applyOptions:
function applyOptions(instance, options, deferredData = [], deferredWatch = [], deferredProvide = [], asMixin = false) {
const {
// composition
mixins, extends: extendsOptions,
// state
data: dataOptions, computed: computedOptions, methods, watch: watchOptions, provide: provideOptions, inject: injectOptions,
// assets
components, directives,
// lifecycle
beforeMount, mounted, beforeUpdate, updated, activated, deactivated, beforeDestroy, beforeUnmount, destroyed, unmounted, render, renderTracked, renderTriggered, errorCaptured,
// public API
expose } = options;
// 上面的信息表示了我们可以在跟组件中设置的字段以及生命周期钩子
// 我们平常用的钩子函数中的this就是下面这个哦
const publicThis = instance.proxy;
const ctx = instance.ctx;
...
// 设置方法到组件实例上
if (methods) {
for (const key in methods) {
const methodHandler = methods[key];
if (isFunction(methodHandler)) {
// In dev mode, we use the `createRenderContext` function to define methods to the proxy target,
// and those are read-only but reconfigurable, so it needs to be redefined here
{
Object.defineProperty(ctx, key, {
value: methodHandler.bind(publicThis),
configurable: true,
enumerable: true,
writable: true
});
}
{
checkDuplicateProperties("Methods" /* METHODS */, key);
}
}
else {
warn(`Method "${key}" has type "${typeof methodHandler}" in the component definition. ` +
`Did you reference the function correctly?`);
}
}
}
if (!asMixin) {
if (deferredData.length) {
deferredData.forEach(dataFn => resolveData(instance, dataFn, publicThis));
}
if (dataOptions) {
// @ts-ignore dataOptions is not fully type safe
// 设置data 单独分析
resolveData(instance, dataOptions, publicThis);
}
{
const rawData = toRaw(instance.data);
// 把我们配置的根组件的data返回的字段 同步到 ctx上
for (const key in rawData) {
checkDuplicateProperties("Data" /* DATA */, key);
// expose data on ctx during dev
if (key[0] !== '$' && key[0] !== '_') {
Object.defineProperty(ctx, key, {
configurable: true,
enumerable: true,
get: () => rawData[key],
set: NOOP
});
}
}
}
}
else if (dataOptions) {
deferredData.push(dataOptions);
}
...
// 也执行了beforeCreate生命周期钩子
// 注册对应钩子函数 同时绑定了 this
if (beforeMount) {
onBeforeMount(beforeMount.bind(publicThis));
}
if (mounted) {
onMounted(mounted.bind(publicThis));
}
...
}
我们这里mounted里面是有值的:
function injectHook(type, hook, target = currentInstance, prepend = false) {
if (target) {
// 其实就是把我们设置的方法存入数组中 保存在组件实例的 type 字段中
const hooks = target[type] || (target[type] = []);
// cache the error handling wrapper for injected hooks so the same hook
// can be properly deduped by the scheduler. "__weh" stands for "with erro
// handling".
const wrappedHook = hook.__weh ||
(hook.__weh = (...args) => {
if (target.isUnmounted) {
return;
}
// disable tracking inside all lifecycle hooks
// since they can potentially be called inside effects.
pauseTracking();
// Set currentInstance during hook invocation.
// This assumes the hook does not synchronously trigger other hooks, which
// can only be false when the user does something really funky.
setCurrentInstance(target);
const res = callWithAsyncErrorHandling(hook, target, type, args);
setCurrentInstance(null);
resetTracking();
return res;
});
if (prepend) {
hooks.unshift(wrappedHook);
}
else {
hooks.push(wrappedHook);
}
return wrappedHook;
}
else {
const apiName = toHandlerKey(ErrorTypeStrings[type].replace(/ hook$/, ''));
warn(`${apiName} is called when there is no active component instance to be ` +
`associated with. ` +
`Lifecycle injection APIs can only be used during execution of setup().` +
(` If you are using async setup(), make sure to register lifecycle ` +
`hooks before the first await statement.`
));
}
}
const createHook = (lifecycle) => (hook, target = currentInstance) =>
// post-create lifecycle registrations are noops during SSR
!isInSSRComponentSetup && injectHook(lifecycle, hook, target);
const onBeforeMount = createHook("bm" /* BEFORE_MOUNT */);
const onMounted = createHook("m" /* MOUNTED */);
const onBeforeUpdate = createHook("bu" /* BEFORE_UPDATE */);
const onUpdated = createHook("u" /* UPDATED */);
const onBeforeUnmount = createHook("bum" /* BEFORE_UNMOUNT */);
const onUnmounted = createHook("um" /* UNMOUNTED */);
const onRenderTriggered = createHook("rtg" /* RENDER_TRIGGERED */);
const onRenderTracked = createHook("rtc" /* RENDER_TRACKED */);
const onErrorCaptured = (hook, target = currentInstance) => {
injectHook("ec" /* ERROR_CAPTURED */, hook, target);
};
接下来仔细看 resolveData :
function resolveData(instance, dataFn, publicThis) {
if (!isFunction(dataFn)) {
warn(`The data option must be a function. ` +
`Plain object usage is no longer supported.`);
}
const data = dataFn.call(publicThis, publicThis);
if (isPromise(data)) {
warn(`data() returned a Promise - note data() cannot be async; If you ` +
`intend to perform data fetching before component renders, use ` +
`async setup() + <Suspense>.`);
}
if (!isObject(data)) {
warn(`data() should return an object.`);
}
else if (instance.data === EMPTY_OBJ) {
// 其实就是这里 把 组件实例的data变成响应式的
instance.data = reactive(data);
}
else {
// existing data: this is a mixin or extends.
extend(instance.data, data);
}
}
来看下 reactive :
function reactive(target) {
// if trying to observe a readonly proxy, return the readonly version.
if (target && target["__v_isReadonly" /* IS_READONLY */]) {
return target;
}
return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers);
}
暂且先不看常规handlers和集合类型的handlers的具体代理内容
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers) {
if (!isObject(target)) {
{
console.warn(`value cannot be made reactive: ${String(target)}`);
}
return target;
}
// target is already a Proxy, return it.
// exception: calling readonly() on a reactive object
if (target["__v_raw" /* RAW */] &&
!(isReadonly && target["__v_isReactive" /* IS_REACTIVE */])) {
return target;
}
// target already has corresponding Proxy
const proxyMap = isReadonly ? readonlyMap : reactiveMap;
const existingProxy = proxyMap.get(target);
if (existingProxy) {
return existingProxy;
}
// only a whitelist of value types can be observed.
const targetType = getTargetType(target);
if (targetType === 0 /* INVALID */) {
return target;
}
// 设置代理 并且返回这个代理
const proxy = new Proxy(target, targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers);
proxyMap.set(target, proxy);
return proxy;
}
可以看到,目前只是直接对组件实例的data做了一次代理,handlers在普通对象情况下为 baseHandlers
至此,applyOptions的作用大致清楚了,就是把我们设置的对象的属性一一映射到组件实例上,同时把组件实例的data变成代理,注册各类钩子函数到实例上等等
setupComponent方法结束后,再看 setupRenderEffect:
function createDevEffectOptions(instance) {
return {
scheduler: queueJob,
allowRecurse: true,
onTrack: instance.rtc ? e => invokeArrayFns(instance.rtc, e) : void 0,
onTrigger: instance.rtg ? e => invokeArrayFns(instance.rtg, e) : void 0
};
}
const setupRenderEffect = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) => {
// create reactive effect for rendering
// 创建一个 effect 函数 它同时也是数据发送改变后再次触发的 effect 函数
instance.update = effect(function componentEffect() {
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);
}
// rende
{
startMeasure(instance, `render`);
}
// 得到可以被渲染的子DOM树
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`);
}
// 再次调用path
patch(null, subTree, container, anchor, instance, parentSuspense, isSVG);
{
endMeasure(instance, `patch`);
}
initialVNode.el = subTree.el;
}
// mounted hook
// mounted钩子执行
if (m) {
queuePostRenderEffect(m, parentSuspense);
}
// onVnodeMounted
if ((vnodeHook = props && props.onVnodeMounted)) {
const scopedInitialVNode = initialVNode;
queuePostRenderEffect(() => {
invokeVNodeHook(vnodeHook, parent, scopedInitialVNode);
}, parentSuspense);
}
// activated hook for keep-alive roots.
// #1742 activated hook must be accessed after first rende
// 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;
{
devtoolsComponentAdded(instance);
}
// #2458: deference mount-only object parameters to prevent memleaks
initialVNode = container = anchor = null;
}
else {
...
}
}, createDevEffectOptions(instance) );
};
我们先看effect函数:
function effect(fn, options = EMPTY_OBJ) {
if (isEffect(fn)) {
fn = fn.raw;
}
const effect = createReactiveEffect(fn, options);
// 我们的函数会马上执行一次
if (!options.lazy) {
effect();
}
return effect;
}
let uid = 0;
function createReactiveEffect(fn, options) {
const effect = function reactiveEffect() {
if (!effect.active) {
return options.scheduler ? undefined : fn();
}
if (!effectStack.includes(effect)) {
cleanup(effect);
try {
enableTracking();
effectStack.push(effect);
// activeEffect赋值 在render函数调用过程中 它就是变量被修改后触发的effect
activeEffect = effect;
return fn();
}
finally {
effectStack.pop();
resetTracking();
activeEffect = effectStack[effectStack.length - 1];
}
}
};
effect.id = uid++;
effect.allowRecurse = !!options.allowRecurse;
effect._isEffect = true;
effect.active = true;
effect.raw = fn;
effect.deps = [];
effect.options = options;
return effect;
}
从上面可以看到我们在effect内部定义的函数被包裹后会执行一次:
先重点看下 renderComponentRoot(instance) :
function renderComponentRoot(instance) {
const { type: Component, vnode, proxy, withProxy, props, propsOptions: [propsOptions], slots, attrs, emit, render, renderCache, data, setupState, ctx } = instance;
let result;
currentRenderingInstance = instance;
{
accessedAttrs = false;
}
try {
let fallthroughAttrs;
if (vnode.shapeFlag & 4 /* STATEFUL_COMPONENT */) {
// withProxy is a proxy with a different `has` trap only fo
// runtime-compiled render functions using `with` block.
const proxyToUse = withProxy || proxy;
// 主要就是这行 指定this后执行render得到数据被取值后的vnode树 在这个过程中 由于之前设置的proxy代理 劫持了get和set 导致当前effect会被收集到counter的effect集合中
// 最后渲染得到的是一个简单的字符串 然后normalizeVNode把字符串变成一个 textnode类型的vnode 供path方法使用
result = normalizeVNode(render.call(proxyToUse, proxyToUse, renderCache, props, setupState, data, ctx));
fallthroughAttrs = attrs;
}
else {
...
}
...
}
catch (err) {
handleError(err, instance, 1 /* RENDER_FUNCTION */);
result = createVNode(Comment);
}
currentRenderingInstance = null;
return result;
}
简单看下 proxy的handlers做了什么:
const mutableHandlers = {
get,
set,
deleteProperty,
has,
ownKeys
};
const get = /*#__PURE__*/ createGetter();
function createGetter(isReadonly = false, shallow = false) {
return function get(target, key, receiver) {
if (key === "__v_isReactive" /* IS_REACTIVE */) {
return !isReadonly;
}
else if (key === "__v_isReadonly" /* IS_READONLY */) {
return isReadonly;
}
else if (key === "__v_raw" /* RAW */ &&
receiver === (isReadonly ? readonlyMap : reactiveMap).get(target)) {
return target;
}
const targetIsArray = isArray(target);
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver);
}
const res = Reflect.get(target, key, receiver);
if (isSymbol(key)
? builtInSymbols.has(key)
: isNonTrackableKeys(key)) {
return res;
}
if (!isReadonly) {
// render函数调用时 这里会触发
track(target, "get" /* GET */, key);
}
if (shallow) {
return res;
}
if (isRef(res)) {
// ref unwrapping - does not apply for Array + integer key.
const shouldUnwrap = !targetIsArray || !isIntegerKey(key);
return shouldUnwrap ? res.value : res;
}
if (isObject(res)) {
// Convert returned value into a proxy as well. we do the isObject check
// here to avoid invalid value warning. Also need to lazy access readonly
// and reactive here to avoid circular dependency.
// 初始化过程中只是简单的代理了一层 如果对象下面字段也是对象 推迟到get触发后再设置代理
return isReadonly ? readonly(res) : reactive(res);
}
return res;
};
}
function track(target, type, key) {
if (!shouldTrack || activeEffect === undefined) {
return;
}
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
if (!dep.has(activeEffect)) {
dep.add(activeEffect);
activeEffect.deps.push(dep);
if (activeEffect.options.onTrack) {
activeEffect.options.onTrack({
effect: activeEffect,
target,
type,
key
});
}
}
}
可以看到effect是如何而被收集起来且被收集到那去了。在赋值修改组件实例的字段时,它还会被派上用场的
在得到新的vonde子树并且完成依赖收集后,可以看 path 了,我们再次碰到它了,之前由于type是个组件类型导致它走了其他逻辑,现在它是 textnode类型了:
// type = Symbol(Text)
const { type, ref, shapeFlag } = n2;
switch (type) {
case Text:
processText(n1, n2, container, anchor);
break;
// n2 vnode节点的children就是 " Counter: 0" 已经被之前处理过了 可以直接插入了
const processText = (n1, n2, container, anchor) => {
if (n1 == null) {
hostInsert((n2.el = hostCreateText(n2.children)), container, anchor);
}
else {
const el = (n2.el = n1.el);
if (n2.children !== n1.children) {
hostSetText(el, n2.children);
}
}
};
好了,我们终于看到真实也是最终的dom操作了。至此,静态插入内容已经走完,接下来看动态修改的部分。
// mounted hook
// mounted钩子执行
if (m) {
queuePostRenderEffect(m, parentSuspense);
}
之前看到setupRenderEffect中在path完之后,调用了m钩子,执行了我们设置的方法;
setTimeout(() => {
this.counter++
}, 1000)
这个this就是绑定了 组件实例上的proxy字段,而对它上面任何一个字段的修改都会触发handlers的set:
const set = /*#__PURE__*/ createSetter();
function createSetter(shallow = false) {
return function set(target, key, value, receiver) {
const oldValue = target[key];
if (!shallow) {
value = toRaw(value);
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
oldValue.value = value;
return true;
}
}
const hadKey = isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key);
const result = Reflect.set(target, key, value, receiver);
// don't trigger if target is something up in the prototype chain of original
if (target === toRaw(receiver)) {
if (!hadKey) {
trigger(target, "add" /* ADD */, key, value);
}
else if (hasChanged(value, oldValue)) {
// 触发trigge
trigger(target, "set" /* SET */, key, value, oldValue);
}
}
return result;
};
}
function trigger(target, type, key, newValue, oldValue, oldTarget) {
const depsMap = targetMap.get(target);
if (!depsMap) {
// never been tracked
return;
}
const effects = new Set();
const add = (effectsToAdd) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
if (effect !== activeEffect || effect.allowRecurse) {
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
switch (type) {
case "add" /* ADD */:
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY));
if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY));
}
}
else if (isIntegerKey(key)) {
// new index added to array -> length changes
add(depsMap.get('length'));
}
break;
case "delete" /* DELETE */:
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY));
if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY));
}
}
break;
case "set" /* SET */:
if (isMap(target)) {
add(depsMap.get(ITERATE_KEY));
}
break;
}
}
const run = (effect) => {
if (effect.options.onTrigger) {
effect.options.onTrigger({
effect,
target,
key,
type,
newValue,
oldValue,
oldTarget
});
}
// rendereffect的是设置了 scheduler 的
if (effect.options.scheduler) {
effect.options.scheduler(effect);
}
else {
effect();
}
};
effects.forEach(run);
}
看起来挺多,简单总结一下就是取出之前每个观察对象的effect集合,按照字段分别取,然后依次执行它们,目前我们就 一个rendereffect,先看下 scheduler 被设置了啥:
const setupRenderEffect = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) => {
// create reactive effect for rendering
instance.update = effect(function componentEffect() {
if (!instance.isMounted) {
...
}
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) {
next.el = vnode.el;
updateComponentPreRender(instance, next, optimized);
}
else {
next = vnode;
}
// beforeUpdate hook
if (bu) {
invokeArrayFns(bu);
}
// onVnodeBeforeUpdate
if ((vnodeHook = next.props && next.props.onVnodeBeforeUpdate)) {
invokeVNodeHook(vnodeHook, parent, next, vnode);
}
// rende
{
startMeasure(instance, `render`);
}
const nextTree = renderComponentRoot(instance);
{
endMeasure(instance, `render`);
}
const prevTree = instance.subTree;
instance.subTree = nextTree;
{
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) );
};
createDevEffectOptions(instance) :
function createDevEffectOptions(instance) {
return {
scheduler: queueJob,
allowRecurse: true,
onTrack: instance.rtc ? e => invokeArrayFns(instance.rtc, e) : void 0,
onTrigger: instance.rtg ? e => invokeArrayFns(instance.rtg, e) : void 0
};
}
function queueJob(job) {
// 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) {
const pos = findInsertionIndex(job);
if (pos > -1) {
queue.splice(pos, 0, job);
}
else {
queue.push(job);
}
queueFlush();
}
}
function queueFlush() {
if (!isFlushing && !isFlushPending) {
isFlushPending = true;
currentFlushPromise = resolvedPromise.then(flushJobs);
}
}
const getId = (job) => job.id == null ? Infinity : job.id;
function flushJobs(seen) {
isFlushPending = false;
isFlushing = true;
{
seen = seen || new Map();
}
flushPreFlushCbs(seen);
// Sort queue before flush.
// This ensures that:
// 1. Components are updated from parent to child. (because parent is always
// created before the child so its render effect will have smalle
// priority number)
// 2. If a component is unmounted during a parent component's update,
// its update can be skipped.
queue.sort((a, b) => getId(a) - getId(b));
try {
for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {
const job = queue[flushIndex];
if (job) {
if (true) {
checkRecursiveUpdates(seen, job);
}
callWithErrorHandling(job, null, 14 /* SCHEDULER */);
}
}
}
finally {
flushIndex = 0;
queue.length = 0;
flushPostFlushCbs(seen);
isFlushing = false;
currentFlushPromise = null;
// some postFlushCb queued jobs!
// keep flushing until it drains.
if (queue.length || pendingPostFlushCbs.length) {
flushJobs(seen);
}
}
}
简单总结一下:插入待处理队列中,按照id从小到大排序,然后在then下个tick中执行
而在此执行的effect走的是 mounted已经是true的情况:
主要工作也是2个:
renderComponentRoot(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);
renderComponentRoot由于我们没改变dom结构,在此调用得到的也是一个textnode只不过它的children值变了
还是主要看patch:
const { type, ref, shapeFlag } = n2;
switch (type) {
case Text:
processText(n1, n2, container, anchor);
break;
跟之前一样的逻辑路线:
const processText = (n1, n2, container, anchor) => {
if (n1 == null) {
hostInsert((n2.el = hostCreateText(n2.children)), container, anchor);
}
else {
// 走这里
const el = (n2.el = n1.el);
if (n2.children !== n1.children) {
hostSetText(el, n2.children);
}
}
};
只不过由于n1前vnode节点有值,所以是修改路线。
其实每个节点都会初次插入以及修改赋值2种情况,然后每个根据节点类型做的处理也不同,我们目前看到的只是最简单的一种。
好了,至此更新的过程也过了一遍。
总结一下:其实和Vue文档中关于生命周期状态的图一样,Vue为了实现前文所述的效果做了很多很多工作。我们按照最简单的情况简要的分析了一下主体流程,在其他文章再逐步分析其他的一些复杂场景。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。