源码分析 | 透过表象看本质, Vue3来了、看看里面到底有什么

9,305 阅读8分钟

哈喽,大家好!我是陈坚强。一个有代码洁癖的前端攻城狮( 哈哈,生活很邋遢(* ̄︶ ̄) )

前言

作为一名优秀的前端攻城狮(自吹一波),我想说Vue是我们中国程序员的骄傲,是尤雨溪大神呕心沥血的杰作,Vue.js正式发布于2014年2月,对于目前的Vue.js的在开发人数上,总共有 370多构建者。在受关注程度上,目前行业的黑话:不会Vue的前端不是合格的前端工程师!

由于对技术的追求、热爱,和好奇心的驱使,今天也对Vue3.0尝了尝鲜。果断将本地的@vue/cli更新一下

npm i @vue/cli@latest -g

之后,vue create vue3_demo就会提示可以选择vue的版本,肯定是选择vue3.0了,赶紧把项目距跑起来,做了一个特别的操作,😬

import * as vue from 'vue';
console.log(vue)

那么vue里面到底有什么呢?让我们来看看

{
  BaseTransition: Object,
  Comment: Symbol(Comment),
  Fragment: Symbol(Fragment),
  KeepAlive: Object,
  Static: Symbol(Static),
  Suspense: Object,
  Teleport: Object,
  Text: Symbol(Text)
  Transition: (props, { slots }) => {…},
  TransitionGroup: Object,
  callWithAsyncErrorHandling: ƒ callWithAsyncErrorHandling(fn, instance, type, args),
  callWithErrorHandling: ƒ callWithErrorHandling(fn, instance, type, args),
  camelize: (str) => {…},
  capitalize: (str) => {…},
  cloneVNode: ƒ cloneVNode(vnode, extraProps, mergeRef = false),
  compile: () => {…},
  computed: ƒ computed(getterOrOptions),
  createApp: (...args) => {…},
  createBlock: ƒ createBlock(type, props, children, patchFlag, dynamicProps),
  createCommentVNode: ƒ createCommentVNode(text = ''){}, 
  createHydrationRenderer: ƒ createHydrationRenderer(options),
  createRenderer: ƒ createRenderer(options),
  createSSRApp: (...args) => {…},
  createSlots: ƒ createSlots(slots, dynamicSlots),
  createStaticVNode: ƒ createStaticVNode(content, numberOfNodes),
  createTextVNode: ƒ createTextVNode(text = ' ', flag = 0),
  createVNode: (...args) => {…},
  customRef: ƒ customRef(factory),
  defineAsyncComponent: ƒ defineAsyncComponent(source),
  defineComponent: ƒ defineComponent(options),
  devtools: Object,
  getCurrentInstance: () => currentInstance || currentRenderingInstance,
  getTransitionRawChildren: ƒ getTransitionRawChildren(children, keepComment = false),
  h: ƒ h(type, propsOrChildren, children),
  handleError: ƒ handleError(err, instance, type, throwInDev = true),
  hydrate: (...args) => {…},
  inject: ƒ inject(key, defaultValue, treatDefaultAsFactory = false),
  isProxy: ƒ isProxy(value),
  isReactive: ƒ isReactive(value),
  isReadonly: ƒ isReadonly(value),
  isRef: ƒ isRef(r),
  isVNode: ƒ isVNode(value),
  markRaw: ƒ markRaw(value),
  mergeProps: ƒ mergeProps(...args),
  nextTick: ƒ nextTick(fn),
  onActivated: ƒ onActivated(hook, target),
  onBeforeMount: (hook, target = currentInstance) => {…},
  onBeforeUnmount: (hook, target = currentInstance) => {…},
  onBeforeUpdate: (hook, target = currentInstance) => {…},
  onDeactivated: ƒ onDeactivated(hook, target),
  onErrorCaptured: (hook, target = currentInstance) => {…},
  onMounted: (hook, target = currentInstance) => {…},
  onRenderTracked: (hook, target = currentInstance) => {…},
  onRenderTriggered: (hook, target = currentInstance) => {…},
  onUnmounted: (hook, target = currentInstance) => {…},
  onUpdated: (hook, target = currentInstance) => {…},
  openBlock: ƒ openBlock(disableTracking = false),
  popScopeId: ƒ popScopeId(),
  provide: ƒ provide(key, value),
  proxyRefs: ƒ proxyRefs(objectWithRefs),
  pushScopeId: (...),
  queuePostFlushCb: (...),
  reactive: (...),
  readonly: (...),
  ref: (...),
  registerRuntimeCompiler: (...),
  render: (...),
  renderList: (...),
  renderSlot: (...),
  resolveComponent: (...),
  resolveDirective: (...),
  resolveDynamicComponent: (...),
  resolveTransitionHooks: (...),
  setBlockTracking: (...),
  setDevtoolsHook: (...),
  setTransitionHooks: (...),
  shallowReactive: (...),
  shallowReadonly: (...),
  shallowRef: (...),
  ssrContextKey: (...),
  ssrUtils: (...),
  toDisplayString: (...),
  toHandlers: (...),
  toRaw: (...),
  toRef: (...),
  toRefs: (...),
  transformVNodeArgs: (...),
  triggerRef: (...),
  unref: (...),
  useCssModule: (...),
  useCssVars: (...),
  useSSRContext: (...),
  useTransitionState: (...),
  vModelCheckbox: (...),
  vModelDynamic: (...),
  vModelRadio: (...),
  vModelSelect: (...),
  vModelText: (...),
  vShow: (...),
  version: (...),
  warn: (...),
  watch: (...),
  watchEffect: (...),
  withCtx: (...),
  withDirectives: (...),
  withKeys: (...),
  withModifiers: (...),
  withScopeId: (...)
}

于是乎,赶紧去看了看node_modules下的vue到底有什么 各模块规范的vue.js,具体核心模块还是在node_modules/@vue 看图比较直观一些,下面我们来看一下,这些模块里都有什么,

@vue/reactivity

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);
}
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers) {
    if (!shared.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;
}

简单使用

readonly

获取一个对象 (响应式或纯对象) 或 ref 并返回原始代理的只读代理。只读代理是深层的:访问的任何嵌套 property 也是只读的。

源码相关函数

function readonly(target) {
    return createReactiveObject(target, true, readonlyHandlers, readonlyCollectionHandlers);
}

function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers) {
    if (!shared.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;
}

简单使用

ref

接受参数并返回它包装在具有 value property 的对象中,然后可以使用该 property 访问或更改响应式变量的值,同时也可以作用的组件或者元素上

源码相关函数

function ref(value) {
    return createRef(value);
}
function createRef(rawValue, shallow = false) {
    if (isRef(rawValue)) {
        return rawValue;
    }
    return new RefImpl(rawValue, shallow);
}
class RefImpl {
    constructor(_rawValue, _shallow = false) {
        this._rawValue = _rawValue;
        this._shallow = _shallow;
        this.__v_isRef = true;
        this._value = _shallow ? _rawValue : convert(_rawValue);
    }
    get value() {
        track(toRaw(this), "get" /* GET */, 'value');
        return this._value;
    }
    set value(newVal) {
        if (shared.hasChanged(toRaw(newVal), this._rawValue)) {
            this._rawValue = newVal;
            this._value = this._shallow ? newVal : convert(newVal);
            trigger(toRaw(this), "set" /* SET */, 'value', newVal);
        }
    }
}

简单使用

toRef

可以用来为源响应式对象上的 property 性创建一个 ref。然后可以将 ref 传递出去,从而保持对其源 property 的响应式连接。

源码相关函数

function toRef(object, key) {
    return isRef(object[key])
        ? object[key]
        : new ObjectRefImpl(object, key);
}
class ObjectRefImpl {
    constructor(_object, _key) {
        this._object = _object;
        this._key = _key;
        this.__v_isRef = true;
    }
    get value() {
        return this._object[this._key];
    }
    set value(newVal) {
        this._object[this._key] = newVal;
    }
}

简单使用

toRefs

将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 propertyref

源码相关函数

function toRefs(object) {
    if ( !isProxy(object)) {
        console.warn(`toRefs() expects a reactive object but received a plain one.`);
    }
    const ret = shared.isArray(object) ? new Array(object.length) : {};
    for (const key in object) {
        ret[key] = toRef(object, key);
    }
    return ret;
}

简单使用

computed

refwatch 类似,也可以使用从 Vue 导入的 computed 函数在 Vue 组件外部创建计算属性。

源码相关函数

function computed(getterOrOptions) {
    let getter;
    let setter;
    if (shared.isFunction(getterOrOptions)) {
        getter = getterOrOptions;
        setter =  () => {
                console.warn('Write operation failed: computed value is readonly');
            }
            ;
    }
    else {
        getter = getterOrOptions.get;
        setter = getterOrOptions.set;
    }
    return new ComputedRefImpl(getter, setter, shared.isFunction(getterOrOptions) || !getterOrOptions.set);
}

class ComputedRefImpl {
    constructor(getter, _setter, isReadonly) {
        this._setter = _setter;
        this._dirty = true;
        this.__v_isRef = true;
        this.effect = effect(getter, {
            lazy: true,
            scheduler: () => {
                if (!this._dirty) {
                    this._dirty = true;
                    trigger(toRaw(this), "set" /* SET */, 'value');
                }
            }
        });
        this["__v_isReadonly" /* IS_READONLY */] = isReadonly;
    }
    get value() {
        if (this._dirty) {
            this._value = this.effect();
            this._dirty = false;
        }
        track(toRaw(this), "get" /* GET */, 'value');
        return this._value;
    }
    set value(newValue) {
        this._setter(newValue);
    }
}

简单使用

customRef

创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。它需要一个工厂函数,该函数接收 tracktrigger 函数作为参数,并应返回一个带有 getset 的对象。

源码相关函数

function customRef(factory) {
    return new CustomRefImpl(factory);
}
class CustomRefImpl {
    constructor(factory) {
        this.__v_isRef = true;
        const { get, set } = factory(() => track(this, "get" /* GET */, 'value'), () => trigger(this, "set" /* SET */, 'value'));
        this._get = get;
        this._set = set;
    }
    get value() {
        return this._get();
    }
    set value(newVal) {
        this._set(newVal);
    }
}

简单使用

toRaw

返回 reactivereadonly 代理的原始对象。这是一个转义口,可用于临时读取而不会引起代理访问/跟踪开销,也可用于写入而不会触发更改。不建议保留对原始对象的持久引用。请谨慎使用。 源码相关函数

function toRaw(observed) {
    return ((observed && toRaw(observed["__v_raw" /* RAW */])) || observed);
}

简单使用

shallowReactive/shallowReadonly/shallowRef

shallowReactive 创建一个响应式代理,该代理跟踪其自身 property 的响应性,但不执行嵌套对象的深度响应式转换 (暴露原始值)。

shallowReadonly创建一个代理,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换 (暴露原始值)。

shallowRef 创建一个 ref,它跟踪自己的 .value 更改,但不会使其值成为响应式的。

源码相关函数

function shallowReactive(target) {
    return createReactiveObject(target, false, shallowReactiveHandlers, shallowCollectionHandlers);
}
function shallowReadonly(target) {
    return createReactiveObject(target, true, shallowReadonlyHandlers, readonlyCollectionHandlers);
}
function shallowRef(value) {
    return createRef(value, true);
}
//createReactiveObject 上文已提,创建一个响应式对象
//createRef 上文已提,创建一个ref对象

简单使用

@vue/runtime-core

watch/watchEffect

watch 需要侦听特定的 data (响应式) 源,并在单独的回调函数中副作用。默认情况下,它也是惰性的——即,回调是仅在侦听源发生更改时调用。

watchEffect 在响应式地跟踪其依赖项时立即运行一个函数,并在更改依赖项时重新运行它。

源码相关函数

function watch(source, cb, options) {
    if ( !shared.isFunction(cb)) {
        warn(`\`watch(fn, options?)\` signature has been moved to a separate API. ` +
            `Use \`watchEffect(fn, options?)\` instead. \`watch\` now only ` +
            `supports \`watch(source, cb, options?) signature.`);
    }
    return doWatch(source, cb, options);
}
function watchEffect(effect, options) {
    return doWatch(effect, null, options);
}
function doWatch(source, cb, { immediate, deep, flush, onTrack, onTrigger } = shared.EMPTY_OBJ, instance = currentInstance) {
    if ( !cb) {
        if (immediate !== undefined) {
            warn(`watch() "immediate" option is only respected when using the ` +
                `watch(source, callback, options?) signature.`);
        }
        if (deep !== undefined) {
            warn(`watch() "deep" option is only respected when using the ` +
                `watch(source, callback, options?) signature.`);
        }
    }
    const warnInvalidSource = (s) => {
        warn(`Invalid watch source: `, s, `A watch source can only be a getter/effect function, a ref, ` +
            `a reactive object, or an array of these types.`);
    };
    let getter;
    const isRefSource = reactivity.isRef(source);
    if (isRefSource) {
        getter = () => source.value;
    }
    else if (reactivity.isReactive(source)) {
        getter = () => source;
        deep = true;
    }
    else if (shared.isArray(source)) {
        getter = () => source.map(s => {
            if (reactivity.isRef(s)) {
                return s.value;
            }
            else if (reactivity.isReactive(s)) {
                return traverse(s);
            }
            else if (shared.isFunction(s)) {
                return callWithErrorHandling(s, instance, 2 /* WATCH_GETTER */);
            }
            else {
                 warnInvalidSource(s);
            }
        });
    }
    else if (shared.isFunction(source)) {
        if (cb) {
            // getter with cb
            getter = () => callWithErrorHandling(source, instance, 2 /* WATCH_GETTER */);
        }
        else {
            // no cb -> simple effect
            getter = () => {
                if (instance && instance.isUnmounted) {
                    return;
                }
                if (cleanup) {
                    cleanup();
                }
                return callWithErrorHandling(source, instance, 3 /* WATCH_CALLBACK */, [onInvalidate]);
            };
        }
    }
    else {
        getter = shared.NOOP;
         warnInvalidSource(source);
    }
    if (cb && deep) {
        const baseGetter = getter;
        getter = () => traverse(baseGetter());
    }
    let cleanup;
    const onInvalidate = (fn) => {
        cleanup = runner.options.onStop = () => {
            callWithErrorHandling(fn, instance, 4 /* WATCH_CLEANUP */);
        };
    };
    // in SSR there is no need to setup an actual effect, and it should be noop
    // unless it's eager
    if ( isInSSRComponentSetup) {
        if (!cb) {
            getter();
        }
        else if (immediate) {
            callWithAsyncErrorHandling(cb, instance, 3 /* WATCH_CALLBACK */, [
                getter(),
                undefined,
                onInvalidate
            ]);
        }
        return shared.NOOP;
    }
    let oldValue = shared.isArray(source) ? [] : INITIAL_WATCHER_VALUE;
    const job = () => {
        if (!runner.active) {
            return;
        }
        if (cb) {
            // watch(source, cb)
            const newValue = runner();
            if (deep || isRefSource || shared.hasChanged(newValue, oldValue)) {
                // cleanup before running cb again
                if (cleanup) {
                    cleanup();
                }
                callWithAsyncErrorHandling(cb, instance, 3 /* WATCH_CALLBACK */, [
                    newValue,
                    // pass undefined as the old value when it's changed for the first time
                    oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue,
                    onInvalidate
                ]);
                oldValue = newValue;
            }
        }
        else {
            // watchEffect
            runner();
        }
    };
    // important: mark the job as a watcher callback so that scheduler knows it
    // it is allowed to self-trigger (#1727)
    job.allowRecurse = !!cb;
    let scheduler;
    if (flush === 'sync') {
        scheduler = job;
    }
    else if (flush === 'post') {
        scheduler = () => queuePostRenderEffect(job, instance && instance.suspense);
    }
    else {
        // default: 'pre'
        scheduler = () => {
            if (!instance || instance.isMounted) {
                queuePreFlushCb(job);
            }
            else {
                // with 'pre' option, the first call must happen before
                // the component is mounted so it is called synchronously.
                job();
            }
        };
    }
    const runner = reactivity.effect(getter, {
        lazy: true,
        onTrack,
        onTrigger,
        scheduler
    });
    recordInstanceBoundEffect(runner);
    // initial run
    if (cb) {
        if (immediate) {
            job();
        }
        else {
            oldValue = runner();
        }
    }
    else if (flush === 'post') {
        queuePostRenderEffect(runner, instance && instance.suspense);
    }
    else {
        runner();
    }
    return () => {
        reactivity.stop(runner);
        if (instance) {
            shared.remove(instance.effects, runner);
        }
    };
}

简单使用

nextTick

将回调推迟到下一个 DOM 更新周期之后执行。在更改了一些数据以等待 DOM 更新后立即使用它。

源码相关函数

function nextTick(fn) {
    const p = currentFlushPromise || resolvedPromise;
    return fn ? p.then(fn) : p;
}
$nextTick: () => nextTick,

简单使用

provide/inject

provideinject 启用依赖注入。只有在使用当前活动实例的 setup() 期间才能调用这两者。

源码相关函数

function provide(key, value) {
    if (!currentInstance) {
        {
            warn(`provide() can only be used inside setup().`);
        }
    }
    else {
        let provides = currentInstance.provides;
        // by default an instance inherits its parent's provides object
        // but when it needs to provide values of its own, it creates its
        // own provides object using parent provides object as prototype.
        // this way in `inject` we can simply look up injections from direct
        // parent and let the prototype chain do the work.
        const parentProvides = currentInstance.parent && currentInstance.parent.provides;
        if (parentProvides === provides) {
            provides = currentInstance.provides = Object.create(parentProvides);
        }
        // TS doesn't allow symbol as index type
        provides[key] = value;
    }
}
function inject(key, defaultValue, treatDefaultAsFactory = false) {
    // fallback to `currentRenderingInstance` so that this can be called in
    // a functional component
    const instance = currentInstance || currentRenderingInstance;
    if (instance) {
        const provides = instance.provides;
        if (key in provides) {
            // TS doesn't allow symbol as index type
            return provides[key];
        }
        else if (arguments.length > 1) {
            return treatDefaultAsFactory && shared.isFunction(defaultValue)
                ? defaultValue()
                : defaultValue;
        }
        else {
            warn(`injection "${String(key)}" not found.`);
        }
    }
    else {
        warn(`inject() can only be used inside setup() or functional components.`);
    }
}

简单使用

getCurrentInstance

获取当前组件的实例,类似于2.0的this

源码相关函数

const getCurrentInstance = () => currentInstance || currentRenderingInstance;

简单使用

@vue/runtime-dom

createApp/createSSRApp

返回一个提供应用上下文的应用实例。应用实例挂载的整个组件树共享同一个上下文。

源码相关函数

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 (!shared.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;
});
const createSSRApp = ((...args) => {
    const app = ensureHydrationRenderer().createApp(...args);
    {
        injectNativeTagCheck(app);
    }
    const { mount } = app;
    app.mount = (containerOrSelector) => {
        const container = normalizeContainer(containerOrSelector);
        if (container) {
            return mount(container, true);
        }
    };
    return app;
});

简单使用

结束语

以上是对vue3.0的学习总结,vue2.0vue3.0最明显的区别就是从options-apicomposition-api。更方便利于生产环境的tree shaking,当然肯定不止这一点

Vue3.0的文章很多,希望与大家一起学习。写的不好大家多多指正。如果对你有帮助,请不要吝啬赞👍

文章推荐: