Vue3 Get 与 Set

51 阅读3分钟

源码调试

github.com/vuejs/core

pnpm install
pnpm run dev

目录下生产 vue 文件夹

vue/dist 下新建 index.html

<html>

<head></head>

<body>
    <div id="app">
        {{ c }}
    </div>
</body>
<script src="vue.global.js"></script>
<script>
    const { createApp, ref } = Vue
    createApp({
        props: {
            i: {
                type: String,
                default: 'inject'
            }
        },
        data() {
            return {
                a: 'data'
            }
        },
        setup(props) {
            let a = ref('setup')
            return {
                a
            }
        },
        computed: {
            c() {
                return 'computed'
            }
        },
        mounted() {
          console.log(this.a) => setup
        },
        methods: {
            m() { }
        },
    }).mount('#app')
</script>

</html>

即可开始调试,源码在 vue/dist/vue.global.js 中

获取与写入

先看两个关键实参 instance 和 ctx,在实例中 this 指向的就是 ctx

instance

  function createComponentInstance(vnode, parent, suspense) {
    const type = vnode.type;
    const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;
    const instance = {
      uid: uid2++,
      vnode,
      type,
      parent,
      appContext,
      root: null,
      next: null,
      subTree: null,
      effect: null,
      update: null,
      scope: new EffectScope(true),
      render: null,
      proxy: null,
      exposed: null,
      exposeProxy: null,
      withProxy: null,
      provides: parent ? parent.provides : Object.create(appContext.provides),
      accessCache: null,
      renderCache: [],
      components: null,
      directives: null,
      propsOptions: normalizePropsOptions(type, appContext),
      emitsOptions: normalizeEmitsOptions(type, appContext),
      emit: null,
      emitted: null,
      propsDefaults: EMPTY_OBJ,
      inheritAttrs: type.inheritAttrs,
      ctx: EMPTY_OBJ,
      data: EMPTY_OBJ,
      props: EMPTY_OBJ,
      attrs: EMPTY_OBJ,
      slots: EMPTY_OBJ,
      refs: EMPTY_OBJ,
      setupState: EMPTY_OBJ,
      setupContext: null,
      suspense,
      suspenseId: suspense ? suspense.pendingId : 0,
      asyncDep: null,
      asyncResolved: false,
      isMounted: false,
      isUnmounted: false,
      isDeactivated: false,
      bc: null,
      c: null,
      bm: null,
      m: null,
      bu: null,
      u: null,
      um: null,
      bum: null,
      da: null,
      a: null,
      rtg: null,
      rtc: null,
      ec: null,
      sp: null
    };
    
    instance.ctx = createDevRenderContext(instance);
    instance.root = parent ? parent.root : instance;
    instance.emit = emit2.bind(null, instance);
    
    if (vnode.ce) {
      vnode.ce(instance);
    }
    
    return instance;
  }

当前实例的所有参数、生命周期函数都在 instance 中

ctx

LD}W7ML%X)FNPM)MU08TF{K.png

  function createDevRenderContext(instance) {
    const target = {};
    Object.defineProperty(target, `_`, {
      configurable: true,
      enumerable: false,
      get: () => instance
    });
    Object.keys(publicPropertiesMap).forEach((key) => {
      Object.defineProperty(target, key, {
        configurable: true,
        enumerable: false,
        get: () => publicPropertiesMap[key](instance),
        set: NOOP
      });
    });
    return target;
  }
  
   var publicPropertiesMap = /* @__PURE__ */ extend(/* @__PURE__ */ Object.create(null), {
    $: (i) => i,
    $el: (i) => i.vnode.el,
    $data: (i) => i.data,
    $props: (i) => true ? shallowReadonly(i.props) : i.props,
    $attrs: (i) => true ? shallowReadonly(i.attrs) : i.attrs,
    $slots: (i) => true ? shallowReadonly(i.slots) : i.slots,
    $refs: (i) => true ? shallowReadonly(i.refs) : i.refs,
    $parent: (i) => getPublicInstance(i.parent),
    $root: (i) => getPublicInstance(i.root),
    $emit: (i) => i.emit,
    $options: (i) => true ? resolveMergedOptions(i) : i.type,
    $forceUpdate: (i) => i.f || (i.f = () => queueJob(i.update)),
    $nextTick: (i) => i.n || (i.n = nextTick.bind(i.proxy)),
    $watch: (i) => true ? instanceWatch.bind(i) : NOOP
  });
  • $ 指向 instance
  • $el 指向 instance虚拟DOM节点信息
  • $data 指向 instance 下 data
  • propsprops、attrs、slotsslots和refs 也指向 instance 下对应位置,但是是只读的

instance 与 ctx 也就有了循环引用。ctx._ctx.$ 指向 instanceinstance.ctx 指向 ctx

Get

非 $ 开头数据

get({ _: instance }, key) {
      const { ctx, setupState, data, props, accessCache, type, appContext } = instance;
      if (key === "__isVue") {
        return true;
      }
      // <script setup> 语法糖中执行代码, __isScriptSetup 为 true。
      if (setupState !== EMPTY_OBJ && setupState.__isScriptSetup && hasOwn(setupState, key)) {
        return setupState[key];
      }
      
      let normalizedProps;
     
      if (key[0] !== "$") {
        // accessCache 缓存,记录 变量 所在位置。
        const n = accessCache[key];
        if (n !== void 0) {
          switch (n) {
            case 1 /* SETUP */:
              return setupState[key];
            case 2 /* DATA */:
              return data[key];
            case 4 /* CONTEXT */:
              return ctx[key];
            case 3 /* PROPS */:
              return props[key];
          }
        } else if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) {
          accessCache[key] = 1 /* SETUP */;
          return setupState[key];
        } else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
          accessCache[key] = 2 /* DATA */;
          return data[key];
        } else if ((normalizedProps = instance.propsOptions[0]) && hasOwn(normalizedProps, key)) {
          accessCache[key] = 3 /* PROPS */;
          return props[key];
        } else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) {
          // methods、inject、computed等参数都在 ctx 中获取
          accessCache[key] = 4 /* CONTEXT */;
          return ctx[key];
        } else if (shouldCacheAccess) {
          accessCache[key] = 0 /* OTHER */;
        }
      }
      ...
    }
  1. setupState
  2. data
  3. props
  4. ctx

参数的获取顺序,所以同名参数都会被保存,但是有优先级问题

propsOptions

将 props 按照 全局mixins、extends、当前实例mixins和props 组合成 props数据。详情可见 normalizePropsOptions 初始化props

props: {
  i: {
    type: String,
    default: 'inject'
  }
}
normalized : { i: {0: false, 1: true, default: 'inject', type: ƒ}, ... }
needCastKeys : [ 'i' ]
instance.propsOptions = [normalized, needCastKeys]

所有数据

// 获取 publicPropertiesMap 中的参数
const publicGetter = publicPropertiesMap[key];
  let cssModule, globalProperties;
  if (publicGetter) {
    // instance、el、data、props、attrs、slots等
    return publicGetter(instance);
  } else if ((cssModule = type.__cssModules) && (cssModule = cssModule[key])) {
    // css模块(由vue loader注入)
    return cssModule;
  } else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) {
    accessCache[key] = 4 /* CONTEXT */;
    return ctx[key];
  } else if (globalProperties = appContext.config.globalProperties, hasOwn(globalProperties, key)) {
      // app.config.globalProperties 一个用于注册能够被应用内所有组件实例访问到的全局属性的对象。
      return globalProperties[key];
  } else if (currentRenderingInstance && (!isString(key) || key.indexOf("__v") !== 0)) {
    if (data !== EMPTY_OBJ && isReservedPrefix(key[0]) && hasOwn(data, key)) {
          // data 中的数据不能以 $和_开头
          warn2(`Property ${JSON.stringify(key)} must be accessed via $data because it starts with a reserved character ("$" or "_") and is not proxied on the render context.`);
        } else if (instance === currentRenderingInstance) {
          // 未定义
          warn2(`Property ${JSON.stringify(key)} was accessed during render but is not defined on instance.`);
    }
  }

优先级

  1. instance、el、data、props、attrs、slots、emit等数据获取
  2. css模块
  3. ctx:methods、inject、computed等参数
  4. config.globalProperties 全局属性
  5. data 中的数据不能以 $和_开头,其他的可以

Set

  set({ _: instance }, key, value) {
      const { data, setupState, ctx } = instance;
      if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) {
        setupState[key] = value;
        return true;
      } else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
        data[key] = value;
        return true;
      } else if (hasOwn(instance.props, key)) {
        warn2(`Attempting to mutate prop "${key}". Props are readonly.`, instance);
        return false;
      }
      if (key[0] === "$" && key.slice(1) in instance) {
        warn2(`Attempting to mutate public property "${key}". Properties starting with $ are reserved and readonly.`, instance);
        return false;
      } else {
        if (key in instance.appContext.config.globalProperties) {
          Object.defineProperty(ctx, key, {
            enumerable: true,
            configurable: true,
            value
          });
        } else {
          ctx[key] = value;
        }
      }
      return true;
    }

优先级

  1. setupState
  2. data
  3. props,数据不能修改
  4. instance 中 $ 开头的数据不能修改
  5. appContext.config.globalProperties
  6. ctx
GetSet
非$开头数据setupState
setupStatedata
dataprops,数据不能修改
propsinstance , $ 开头的数据不能修改
ctxconfig.globalProperties
$开头数据ctx
publicPropertiesMap
css模块
ctx
config.globalProperties