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之后,以前引入props和emit的方法也需要改变
<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>
如果要使用插槽的话,需要使用辅助函数useSlots和useAttrs
<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函数,这个函数会暴露出你自己想暴露出的东西。如果没有通用setup,Vue不会帮你调用该方法,这样如果使ref获取子组件,可以调用内部的许多方法,如果调用了expos(),那么就会值暴露 出你想暴露的方法,保持了Vue的数据单项流通。
结语
Vue 3 中的 setup 语法糖使得组件的配置更加简洁和直观。通过这些简写形式,我们可以更容易地处理常见场景,提高代码的可读性和可维护性。在开发 Vue 3 应用时,不妨充分利用这些语法糖,让你的代码更加优雅。