Vue 3 中的 setup 语法糖:更简洁的组件配置

287 阅读5分钟

Vue 3 引入了许多新的特性,其中之一是 setup 函数,它为组件的配置提供了更灵活的方式。为了进一步简化组件的语法,Vue 3 还提供了 <script setup> 的语法糖,使得我们能够更加轻松地处理常见的场景。在本文中,我们将深入了解 Vue 3 中的 setup 语法糖。

1. setup语法糖是什么

<script setup> 是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。当同时使用 SFC 与组合式 API 时该语法是默认推荐。相比于普通的 <script> 语法,它具有更多优势:

-   更少的样板内容,更简洁的代码。
-   能够使用纯 TypeScript 声明 props 和自定义事件。
-   更好的运行时性能 (其模板会被编译成同一作用域内的渲染函数,避免了渲染上下文代理对象)。
-   更好的 IDE 类型推导性能 (减少了语言服务器从代码中抽取类型的工作)。

2. 如何使用

在 Vue 3 的单文件(SFC),只需要在声明JS的顶部标签中标注setup即可。

 <script setup></script>

在你声明了该指令之后,无需再注册组件以及引入的各种内容,直接引入即可使用。

 <template>
   <div @click="log">{{ msg }}</div>
    <p>{{getToday()}}</p>
 </template>
 ​
 <script setup>
 //import引入的内容
 import { getToday } from './utils'  
 const msg = 'Hello111!'
 const log = () => {
    console.log(msg)
 }
 </script>

下面是之前的写法,相比与之前的写法,更简洁,而且对IDE的兼容性更好,我在修改公司的老版本代码的时候,很多方法只是return了,但是依然没有调用。

 <template>
   <div @click="log">{{ msg }}</div>
    <p>{{getToday()}}</p>
 </template>
 <script>
 //import引入的内容
 import { getToday } from './utils'  
 export default{
  setup(){
     // 变量
     const msg = 'Hello111!'
     // 函数
     function log() {
       console.log(msg)
     }
     //想在tempate里面使用需要在setup内return暴露出来
     return{
        msg,
        log,
        getToday 
     }
  }
 }
 </script>

而且在setup标签内部引用的组件也会自动注册

 <script setup>
 import MyComponent from './MyComponent.vue'
 </script>
 <template>
   <MyComponent />
 </template>

以前:

 <script>
 import MyComponent from './MyComponent.vue'
 components:{MyComponent}  不需要注册直接使用
 </script>
 •
 <template>
   <MyComponent />
 </template>

当然,如果使用了setup之后,以前引入propsemit的方法也需要改变

 <template>
   <div>父组件</div>
   <Child v-model:title="msg" />
 </template>
 •
 <script setup>
 //这里如果想要实现子组件通知父组件更新,需要使用v-model进行绑定
 import {ref} from 'vue'
 import Child from './child.vue'
 const msg = ref('parent')  //ref会将包裹的对象变为响应式
 </script>
 <template>
   <div>子组件</div>
   <div>父组件传递的值:{{title}}</div>
 </template>
 •
 <script setup>
 //import {defineProps} from 'vue'   不需要引入
 •
 //语法糖必须使用defineProps替代props
 const  props = defineProps({
   title: {
     type: String
     default:'11',
     requrid:true
   }
 });
 const emits = defineEimit(['update:title'])
 //如果要通知父组件更新子组件的值的话,需要调用emits('update:title',title)
 console.log(props.title) //父的值
 </script>

如果我们要使用子组件的方法的话,需要获取子组件的ref,然后把该方法暴露出去,这样父组件就能使用子组件的方法。

 <script setup>
 import { ref } from 'vue'
 const a = 1
 const b = ref(2)
 //主动暴露组件属性
 defineExpose({
   a,
   b
 })
 </script>
 <template>
   <div>父组件</div>
   <Child  ref='chil' />
   <button @click='getChilData'>通过ref获取子组件的属性 </button>
 </template>
 •
 <script setup>
 import {ref} from 'vue'
 import Child from './child.vue'
 const chil= ref(null)  //Vue3如果要获取到组件的ref的话需要先设置这个ref为空或者null,Vue会自动实现绑定
 const getChildData =()=>{
   //子组件接收暴露出来得值
   console.log(chil.value.a) //1
   console.log(chil.value.b) //2  响应式数据
 }    
 </script>

如果要使用插槽的话,需要使用辅助函数useSlotsuseAttrs

 <script setup>
 import { useSlots, useAttrs } from 'vue'
 ​
 const slots = useSlots()
 const attrs = useAttrs()
 </script>

由于setup函数中已经没有this了,所以要获取router的话需要使用hooks的方法来获取

 import { useRouter, useRoute } from 'vue-router'
 const route = useRoute()
 const router = useRouter()

3. 区别

这两种写法除了代码风格上的不同还有没有别的不同呢,是否更合适的写法?

这里推荐使用setup的写法,使用vite-plugin-inspect可以知道两个代码编译出来的不一样.

 import { defineConfig } from "vite";
 import vue from "@vitejs/plugin-vue";
 import Inspect from "vite-plugin-inspect";
 ​
 // https://vitejs.dev/config/
 //在vite中配置
 export default defineConfig({
   plugins: [Inspect(), vue()],
 });
 
 <template>
   <div>
     <div>{{ testData }}</div>
     <button @click="changeData">add</button>
   </div>
 </template>
 ​
 <script setup>
 import { ref, vue } from "vue";
 const testData = ref(0);
 const changeData = () => {
   testData.value++;
 };
 </script>
 ​
 //编辑结果
 import { ref, vue } from "vue";
 ​
 const _sfc_main = {
   __name: 'Test',
   setup(__props, { expose: __expose }) {
   __expose();
 ​
 const testData = ref(0);
 const changeData = () => {
   testData.value++;
 };
 ​
 const __returned__ = { testData, changeData, ref, vue }
 Object.defineProperty(__returned__, '__isScriptSetup', { enumerable: false, value: true })
 return __returned__
 }
 ​
 }
 import { toDisplayString as _toDisplayString, createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
 ​
 function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
   return (_openBlock(), _createElementBlock("div", null, [
     _createElementVNode("div", null, _toDisplayString($setup.testData), 1 /* TEXT */),
     _createElementVNode("button", { onClick: $setup.changeData }, "add")
   ]))
 }
 ​
 ​
 _sfc_main.__hmrId = "0904fc8e"
 typeof __VUE_HMR_RUNTIME__ !== 'undefined' && __VUE_HMR_RUNTIME__.createRecord(_sfc_main.__hmrId, _sfc_main)
 import.meta.hot.accept(mod => {
   if (!mod) return
   const { default: updated, _rerender_only } = mod
   if (_rerender_only) {
     __VUE_HMR_RUNTIME__.rerender(updated.__hmrId, updated.render)
   } else {
     __VUE_HMR_RUNTIME__.reload(updated.__hmrId, updated)
   }
 })
 import _export_sfc from 'plugin-vue:export-helper'
 <template>
   <div>
     <div>{{ testData }}</div>
     <button @click="changeData">add</button>
   </div>
 </template>
 ​
 <script>
 import { ref, vue } from "vue";
 export default {
   setup() {
     const testData = ref(0);
     const changeData = () => {
       testData.value++;
     };
     return {
       testData,
       changeData,
     };
   },
 };
 </script>
 //以下是编译结果
 import { toDisplayString as _toDisplayString, createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
 ​
 function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
   return (_openBlock(), _createElementBlock("div", null, [
     _createElementVNode("div", null, _toDisplayString($setup.testData), 1 /* TEXT */),
     _createElementVNode("button", {
       onClick: _cache[0] || (_cache[0] = (...args) => ($setup.changeData && $setup.changeData(...args)))
     }, "add")
   ]))
 }
 ​
 ​
 _sfc_main.__hmrId = "f9bd8a48"
 typeof __VUE_HMR_RUNTIME__ !== 'undefined' && __VUE_HMR_RUNTIME__.createRecord(_sfc_main.__hmrId, _sfc_main)
 import.meta.hot.accept(mod => {
   if (!mod) return
   const { default: updated, _rerender_only } = mod
   if (_rerender_only) {
     __VUE_HMR_RUNTIME__.rerender(updated.__hmrId, updated.render)
   } else {
     __VUE_HMR_RUNTIME__.reload(updated.__hmrId, updated)
   }
 })
 import _export_sfc from 'plugin-vue:export-helper'

可以看出setup会暴露一个expose函数,这个函数会暴露出你自己想暴露出的东西。如果没有通用setupVue不会帮你调用该方法,这样如果使ref获取子组件,可以调用内部的许多方法,如果调用了expos(),那么就会值暴露 出你想暴露的方法,保持了Vue的数据单项流通。

结语

Vue 3 中的 setup 语法糖使得组件的配置更加简洁和直观。通过这些简写形式,我们可以更容易地处理常见场景,提高代码的可读性和可维护性。在开发 Vue 3 应用时,不妨充分利用这些语法糖,让你的代码更加优雅。