Vue 3源码深度解析之:`Vue`的`Composition API`:`setup`函数的执行顺序与生命周期。

大家好,我是老码农,今天咱们来聊聊Vue 3 Composition API里的“扛把子”—— setup 函数!

别被“深度解析”吓到,咱们的目标是用最接地气的方式,把setup函数扒个精光,让大家以后用起来得心应手。

开场白:为什么要有 setup

在Vue 2时代,咱们写组件,数据、方法、生命周期钩子一股脑儿全塞进 datamethodsmounted 这些选项里。组件大了,代码就容易乱成一锅粥,逻辑复用也比较麻烦。

Vue 3 的 Composition API 就像一剂良药,它允许我们把组件的逻辑按功能模块组织起来,每个模块就是个“composable function”(组合式函数)。而 setup 函数,就是这些 “composable functions” 的舞台,我们可以在里面定义响应式数据、方法,并且把它们return出去,供模板使用。

setup 函数:组件的“初始化司令部”

setup 函数是 Vue 3 组件中一个全新的选项,它在组件实例创建之前执行,可以把它看作是组件的“初始化司令部”。

特点:

  • 只执行一次: 组件每次创建实例,setup 都只会执行一次。
  • 访问不到 this 因为在 setup 执行时,组件实例还没创建呢,所以访问不到 this
  • 必须返回值: setup 函数必须返回一个对象,里面的属性和方法会被合并到组件实例的渲染上下文中,这样模板才能访问到。 当然也可以不返回。

基本结构:

import { ref, reactive, onMounted } from 'vue';

export default {
  setup() {
    // 1. 定义响应式数据
    const count = ref(0);
    const state = reactive({
      message: 'Hello Vue 3!'
    });

    // 2. 定义方法
    const increment = () => {
      count.value++; // 注意:ref的值需要通过 .value 访问
    };

    // 3. 注册生命周期钩子
    onMounted(() => {
      console.log('Component mounted!');
    });

    // 4. 返回值 (必须是一个对象)
    return {
      count,
      state,
      increment
    };
  },
  template: `
    <div>
      <p>{{ state.message }}</p>
      <p>Count: {{ count }}</p>
      <button @click="increment">Increment</button>
    </div>
  `
};

setup 函数的参数:propscontext

setup 函数可以接收两个参数:

  • props 一个响应式对象,包含父组件传递给当前组件的所有 props。
  • context 一个对象,暴露了组件的一些内部属性和方法。

1. props

props 类似于 Vue 2 中的 this.$props,但它是响应式的。如果父组件传递的 props 发生了变化,setup 函数中的 props 对象也会自动更新。

例子:

export default {
  props: {
    name: {
      type: String,
      required: true
    }
  },
  setup(props) {
    console.log('Props:', props.name); // 访问 props

    // 使用 watch 监听 props 的变化
    watch(() => props.name, (newValue, oldValue) => {
      console.log('Name changed from', oldValue, 'to', newValue);
    });

    return {}; // 必须返回一个对象
  },
  template: `<div>Hello, {{ name }}!</div>`
};

注意点:

  • 不要在 setup 函数中解构 props 对象,因为那样会失去响应性。
    • 错误示范: const { name } = props; 这样 name 就不是响应式的了。
  • 如果需要解构 props,可以使用 toRefs 函数。
import { toRefs } from 'vue';

export default {
  props: {
    name: {
      type: String,
      required: true
    },
    age: {
        type: Number,
        default: 18
    }
  },
  setup(props) {
    const { name, age } = toRefs(props); // 使用 toRefs 解构 props

    console.log('Name:', name.value); // 访问 ref 的值

    // 使用 watch 监听 props 的变化
    watch(name, (newValue, oldValue) => {
      console.log('Name changed from', oldValue, 'to', newValue);
    });

    return {
      name,
      age
    };
  },
  template: `<div>Hello, {{ name }}!  Age: {{age}}</div>`
};

2. context

context 对象提供了一些有用的属性和方法:

属性/方法 说明
attrs 一个非响应式的对象,包含组件的所有 attribute (除了 classstyle)。 相当于 Vue 2 的 $attrs
emit 一个函数,用于触发自定义事件。相当于 Vue 2 的 $emit
slots 一个对象,包含组件的所有插槽。相当于 Vue 2 的 $slots
expose 一个函数,用于显式地暴露组件的属性和方法给父组件。

例子:

export default {
  setup(props, context) {
    const { emit, attrs, slots, expose } = context;

    // 触发自定义事件
    const handleClick = () => {
      emit('my-event', 'Hello from child component!');
    };

    // 访问 attrs
    console.log('Attrs:', attrs);

    // 访问 slots
    console.log('Slots:', slots);

    //暴露方法
    const childMethod = () => {
        console.log("child method called!")
    }
    expose({
        childMethod
    })

    return {
      handleClick
    };
  },
  template: `
    <div>
      <button @click="handleClick">Click me</button>
      <slot></slot>
    </div>
  `
};

父组件:

<template>
  <div>
    <ChildComponent @my-event="handleMyEvent" message="Parent message">
      <template #default>
        This is a slot content from parent.
      </template>
    </ChildComponent>
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
  components: {
    ChildComponent
  },
  methods: {
    handleMyEvent(message) {
      console.log('Received event:', message);
    }
  },
  mounted(){
      this.$refs.childComponent.childMethod()
  }
};
</script>

注意点:

  • attrs 是非响应式的,如果需要监听 attribute 的变化,可以使用 watch
  • slots 是只读的,不能在 setup 函数中修改插槽内容。

setup 函数的执行顺序与生命周期

setup 函数的执行时机非常重要,它直接影响着我们对组件生命周期的理解。

执行顺序:

  1. beforeCreate 在组件实例被创建之前执行。 Vue2 的生命周期
  2. setupbeforeCreate 之后,created 之前执行。这是 Composition API 的入口。
  3. created 在组件实例创建完成后执行。 Vue2 的生命周期
  4. beforeMount 在组件挂载到 DOM 之前执行。
  5. onBeforeMount Composition API 提供的钩子,等同于 beforeMount
  6. mounted 在组件挂载到 DOM 后执行。
  7. onMounted Composition API 提供的钩子,等同于 mounted
  8. beforeUpdate 在组件更新之前执行。
  9. onBeforeUpdate Composition API 提供的钩子,等同于 beforeUpdate
  10. updated 在组件更新之后执行。
  11. onUpdated Composition API 提供的钩子,等同于 updated
  12. beforeUnmount 在组件卸载之前执行。
  13. onBeforeUnmount Composition API 提供的钩子,等同于 beforeUnmount
  14. unmounted 在组件卸载之后执行。
  15. onUnmounted Composition API 提供的钩子,等同于 unmounted
  16. errorCapturedonErrorCaptured:当捕获一个来自后代组件的错误时被调用。Composition API 提供了 onErrorCaptured 钩子。
  17. onRenderTrackedonRenderTriggered:这两个钩子是用于调试的。Composition API 提供了对应的 onRenderTrackedonRenderTriggered 钩子。
  18. deactivatedactivated 这两个生命周期钩子是专门用于 <keep-alive> 组件的。当一个组件被 keep-alive 缓存时,deactivated 会在组件被移除时调用,activated 会在组件被重新插入时调用。Vue 3 的 Composition API 中,我们可以使用 onDeactivatedonActivated 钩子来注册相应的回调函数。

生命周期钩子:

Vue 3 的 Composition API 提供了与 Vue 2 类似的生命周期钩子,但它们的使用方式略有不同。

Vue 2 选项 Composition API 钩子 说明
beforeCreate N/A setup 函数替代了 beforeCreatecreated 的作用。
created N/A setup 函数替代了 beforeCreatecreated 的作用。
beforeMount onBeforeMount 在组件挂载到 DOM 之前执行。
mounted onMounted 在组件挂载到 DOM 后执行。
beforeUpdate onBeforeUpdate 在组件更新之前执行。
updated onUpdated 在组件更新之后执行。
beforeUnmount onBeforeUnmount 在组件卸载之前执行。
unmounted onUnmounted 在组件卸载之后执行。
errorCaptured onErrorCaptured 当捕获一个来自后代组件的错误时被调用。

例子:

import { onMounted, onUpdated, onUnmounted } from 'vue';

export default {
  setup() {
    console.log('setup() is running');

    onMounted(() => {
      console.log('Component mounted!');
    });

    onUpdated(() => {
      console.log('Component updated!');
    });

    onUnmounted(() => {
      console.log('Component unmounted!');
    });

    return {};
  },
  template: `<div>Hello, Vue 3!</div>`
};

注意点:

  • Composition API 的生命周期钩子需要在 setup 函数中注册。
  • 不要在 Composition API 的生命周期钩子中使用 this

案例分析:一个简单的计数器组件

咱们来用 setup 函数实现一个简单的计数器组件,加深理解。

import { ref, onMounted, onUnmounted } from 'vue';

export default {
  setup() {
    // 1. 定义响应式数据
    const count = ref(0);

    // 2. 定义方法
    const increment = () => {
      count.value++;
    };

    const decrement = () => {
      count.value--;
    };

    // 3. 注册生命周期钩子
    onMounted(() => {
      console.log('Counter component mounted!');
      // 可以在这里执行一些初始化操作
    });

    onUnmounted(() => {
      console.log('Counter component unmounted!');
      // 可以在这里清理一些资源
    });

    // 4. 返回值
    return {
      count,
      increment,
      decrement
    };
  },
  template: `
    <div>
      <p>Count: {{ count }}</p>
      <button @click="increment">+</button>
      <button @click="decrement">-</button>
    </div>
  `
};

总结:setup 函数的价值

  • 更好的代码组织: 让我们能够按功能模块组织代码,提高代码的可读性和可维护性。
  • 逻辑复用: 我们可以把通用的逻辑抽离成 composable functions,在不同的组件中复用。
  • 更好的类型推断: TypeScript 对 Composition API 的支持更好,可以提供更准确的类型推断。

setup 函数是 Vue 3 Composition API 的核心,掌握它,就能更好地利用 Composition API 的优势,编写更优雅、更高效的 Vue 组件。

好了,今天的分享就到这里,希望对大家有所帮助。下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注