Vue.js 3.0源码解读:Composition API -Setup

451 阅读1分钟

Vue.js 3.0设计了一个很强大的API-Compositon API, 它主要用来优化代码逻辑的组织和复用。它提供了一个Setup的启动函数。接下我们来看看它到底是怎么运行的。这里主要调用的一个函数是:setupComponent:

function setupComponent(instance, isSSR = false) {
      isInSSRComponentSetup = isSSR;
      const { props, children } = instance.vnode;
      // 判断是否是一个有状态的组件
      const isStateful = isStatefulComponent(instance);
      // 初始化props
      initProps(instance, props, isStateful, isSSR);
      // 初始化插槽
      initSlots(instance, children);
      // 设置有状态的组件实例
      const setupResult = isStateful
          ? setupStatefulComponent(instance, isSSR)
          : undefined;
      isInSSRComponentSetup = false;
      return setupResult;
  }

我们从组件vnode中获取了props, children属性,然后分别对props和插槽进行了初始化。这里面有一个比较重要的函数:setupStatefulComponent:

function setupStatefulComponent(instance, isSSR) {
    ...
    const { setup } = Component;
    if (setup) {
        ...
        const setupContext = (instance.setupContext =
              setup.length > 1 ? createSetupContext(instance) : null);
        currentInstance = instance;
        pauseTracking();
        const setupResult = callWithErrorHandling(setup, instance, 0 /* SETUP_FUNCTION */, [shallowReadonly(instance.props) , setupContext]);
        resetTracking();
        ...
    }
}

从源码上看,应该就是在这个函数,这几句代码开始处理Setup的方法。创建了一个setupContext。这里主要有三个步骤:创建setup函数上下文、执行setup函数并获取结果、处理setup函数的执行结果。

const setupContext = (instance.setupContext = setup.length > 1 ? createSetupContext(instance) : null);

这是判断setup函数的参数长度,如果大于1,则创建setupContext上下文。 举个例子: HelloWorld组件,代码如下:

<template>
   <p>{{msg}}</p>
   <button @click="onClick">Toggle</button>
</template>
<script>
export default{
    props:{
        msg: string
    },
    setup(props, {emit}){
       function onClick(){
          emit('toggle')
       }
       return {
           onClick
       }
    }
}
</script>

父组件:

<template>
    <HelloWorld @toggle="toggle" :msg="msg"></HelloWorld>
</template>
<script>
    import { ref } from 'vue'
    import HelloWorld from './HelloWorld.js'
    export default{
        components: {
            HelloWorld,
        },
        setup(){
            const msg = ref('Hello World')
            function toggle(){
                msg.value = msg.value === 'Hello World' ? 'Hello Vue' : 'Hello World'
            }
            return {
                msg,
                toggle
            }
        }
    }
</script>

其中HelloWorld组件中的第二个参数:emit,实际上就是setupContext。 我们来看一下创建这个对象的函数:createSetupContext

function createSetupContext(instance) {
      const expose = exposed => {
          if (instance.exposed) {
              warn(`expose() should be called only once per setup().`);
          }
          instance.exposed = proxyRefs(exposed);
      };
      {
          // We use getters in dev in case libs like test-utils overwrite instance
          // properties (overwrites should not be done in prod)
          return Object.freeze({
              get attrs() {
                  return new Proxy(instance.attrs, attrHandlers);
              },
              get slots() {
                  return shallowReadonly(instance.slots);
              },
              get emit() {
                  return (event, ...args) => instance.emit(event, ...args);
              },
              expose
          });
      }
  }

这里返回了一个对象,包括attrs、slots、emit三个属性。