揭示 Vue 3 setup 函数的奥秘

setup 是 Vue 3 的新特性,也是组合式 API(Composition API)的核心。它提供了一种全新的方式设计组件的逻辑,便于复用同时也增加代码可读性。

1. 理解 setup

1、组合式 API 的入口

setup 是在组件实例创建之前执行的,因此无法访问 this,也没有生命周期钩子函数。可以使用 setup 定义组件的逻辑、状态和方法。

<script>
export default {
  setup(props, context) {
    console.log('this: ', this); // undefined
  }
}
</script>

2、函数式组件的支持

setup 让 Vue 组件变得更像一个函数式组件,在 setup 内部可以使用组合式 API 声明响应式数据、计算属性、监听器等。它替代了传统的选项式 API 中的 data、computed、methods 等,让逻辑关注点更加集中和模块化。

举个 🌰

<template>
  <div>
    <p>num1: {{ num1 }}</p>
    <p>num2: {{ num2 }}</p>
    <p>sum: {{ sum }}</p>
    <button @click="calculateSum" class="btn">计算</button>
  </div>
</template>

传统写法:

export default {
  name: 'Calculator',
  data() {
    return {
      num1: 10,
      num2: 20,
      sum: 0,
    };
  },
  computed: {
    // 也可以将 sum 声明为一个计算属性而不是方法
    // sum() {
    //   return this.num1 + this.num2;
    // }
  },
  // 使用 methods 选项来声明组件方法
  methods: {
    calculateSum() {
      this.sum = parseFloat(this.num1) + parseFloat(this.num2);
    }
  },
};

setup 写法:

import { computed, ref } from 'vue'
export default {
  name: 'Calculator',
  setup() {
    const num1 = ref(10)
    const num2 = ref(20)
    const sum = ref(0);
    // const sum = computed(() => num1 + num2);
    const calculateSum = () => {
      sum.value = num1.value + num2.value
    }
    return {
      num1,
      num2,
      sum,
      calculateSum
    }
  }
}

3、增强逻辑复用

使用组合函数(Composition Function)可以将 setup 中的逻辑,抽离到独立的函数或组件中,便于复用。

举个 🌰

<template>
  <div>
    <h3>count: {{ count }}</h3>
    <button @click="increment" style="margin-top: 13px; padding: 4px 6px">increment</button>
  </div>
</template>

<script>
import { ref } from 'vue'
function useCounter() {
  const count = ref(0)
  const increment = () => {
    count.value++
  }
  return {
    count,
    increment
  }
}

export default {
  setup() {
    const { count, increment } = useCounter()
    return { count, increment }
  }
}
</script>

使用 useCounter 的方式,不仅可以复用,还可以保持代码的简洁和模块化。  

2. 使用 setup

1、基本语法

setup 函数接收两个参数:props 和 context。props 是传入组件的属性,context 包含了 attrs、slots、emit 等对象。

export default {
  setup(props, context) {
    console.log('props: ', props);
    console.log('context: ', context);
  }
}

举个 🌰 

<template>
  <div>
    <h2>count: {{ count }}</h2>
    <h2>doubleCount: {{ doubleCount }}</h2>
    <button @click="increment" style="margin-top: 13px; padding: 4px 6px">Increment</button>
  </div>
</template>

<script>
import { ref, computed } from 'vue'
export default {
  setup(props, context) {
    const count = ref(0)
    const doubleCount = computed(() => count.value * 2)
    const increment = () => {
      count.value++;
      console.log('count: ', count.value);
    }
    return {
      count,
      doubleCount,
      increment
    }
  }
}
</script>

在这个代码中,count 是一个响应式数据,doubleCount 是一个计算属性,increment 是一个方法。这些都是通过 setup 函数声明,并通过返回值暴露给模板使用。

2、访问 props 和 context

- props 是响应式对象,可以在 setup 中直接使用。

- context 包含了 attrs(非 props 的属性)、slots(插槽)、emit(事件触发)等对象。

举个 🌰

<!-- 子组件 -->
<template>
  <div>
    <p>当前计数: {{ count }}</p>
    <button @click="increment" style="margin: 10px 0; padding: 4px 6px">增加计数</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue'

export default defineComponent({
  name: 'Counter',
  props: {
    initialCount: {
      type: Number,
      required: true, // 必填
    },
  },
  setup(props, { emit }) {
    const count = ref(props.initialCount);
    const increment = () => {
      count.value++;
      // 触发一个名为 'update:count' 的事件,传递当前的计数值
      emit('update:count', count.value);
    };
    // 将响应式数据和方法返回,以便在模板中使用
    return {
      count,
      increment,
    };
  },
})
</script>
<!-- 父组件 -->
<template>
  <div>
    <Counter :initialCount="5" @update:count="handleCountUpdate" />
    <p>父组件中的计数: {{ parentCount }}</p>
    <Calculator />
  </div>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue';
import Counter from './components/Counter.vue';

export default defineComponent({
  components: {
    Counter,
  },
  setup() {
    const parentCount = ref(0);
    const handleCountUpdate = (newCount: number) => {
      parentCount.value = newCount;
    };
    return {
      parentCount,
      handleCountUpdate,
    };
  },
});
</script>

3. 对比 Vue 2 好处

1、更好的逻辑组织

在 Vue 2 中,组件的逻辑代码往往分散在多个选项(如 data、methods、computed、watch 等)中。对于复杂组件而言,不利于逻辑组织和后期维护。

在 Vue 3 中,setup 函数让所有的逻辑集中在一起,便于模块化和维护。

2、更强的类型支持和推断

在 Vue 2 中,类型推断主要依赖于外部工具(如 TypeScript),而在选项式 API (Option API)中,类型推断往往不够直观,特别是对于 data 返回的对象。

在 Vue 3 中,setup 函数提供了更好的 TypeScript 支持。因为 setup 是一个普通的JS 函数,所有的类型信息都可以直接使用函数签名定义,并且 Vue 3 内置了类型推断支持,使得类型检查更加精确和友好。

3、更简洁的代码

在 Vue 2 中,必须通过 this 访问组件的状态和方法,这增加了代码的复杂性,并且 this 的上下文在某些情况下可能不够清晰。

在 Vue 3 中,setup 函数中不再使用 this,直接通过变量引用即可访问状态和方法。这不仅使代码更简洁,还避免了 this 绑定的问题。

4、更容易的逻辑复用

在 Vue 2 中,逻辑复用主要通过混入(mixins)和高阶组件(HOC)实现。混入容易导致命名冲突和代码难以追踪,而高阶组件也会增加组件嵌套的复杂度。

在 Vue 3 的组合式 API 和 setup 函数让逻辑复用变得更加直观。开发者可以编写自定义的组合函数来复用逻辑。

4. 注意事项

1、不能使用 this

在 setup 中,组件实例还未创建,因此无法使用 this(undefined)。所有组件的状态和方法都需要通过返回值暴露。

2、生命周期钩子的使用

虽然 setup 中没有直接提供生命周期钩子,但可以通过 onMounted、onUpdated 等组合式 API 来注册生命周期钩子。

import { onMounted } from 'vue'

export default defineComponent({
  setup() {
    onMounted(() => {
      console.log('Component mounted')
    })
  }
})

3、setup 的返回值

setup 的返回值决定了组件模版中可以访问的内容。如果返回一个对象,那该对象的属性将直接暴露给模版使用。

4、setup 中的异步操作

虽然 setup 支持异步操作,但如果需要在组件渲染之前完成某些异步任务,最好不要直接使用 async setup,而是在 setup 中手动处理异步逻辑。

export default {
  setup() {
    const data = ref(null)
    const fetchData = async () => {
      data.value = await fetchSomeData()
    }
    fetchData()
    return { data }
  }
}

5、与 Vuex 和 Router 的结合

可以直接在 setup 中使用 useStore 和 useRouter 钩子来访问全局状态和路由。

import { useStore } from 'vuex';
import { useRouter } from 'vue-router';

export default defineComponent({
  setup() {
    const store = useStore();  // 获取 Vuex store 实例
    const router = useRouter(); // 获取 Vue Router 实例
  },
});

下文介绍,Vue 3 中 <script setup> 的使用和特点。 

详细讲述 Vue3 的 <script setup>-CSDN博客

  • 22
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值