Vue3.2單文件組件setup的語法糖與新特性總結
前言
滿滿的幹貨,建議收藏慢慢看,可以當作Vue3.0的學習資料。
在vue2.0時期,組件裡定義的各類變量、方法、計算屬性等是分別存放到data、methods、computed…選項裡,這樣編寫的代碼不便於後期的查閱(查找一個業務邏輯需要在各個選項來回切換)。setup函數的推出就是為瞭解決這個問題,讓新手開發者更容易上手…
setup語法糖
setup是Vue3.0後推出的語法糖,並且在Vue3.2版本進行瞭大更新,像寫普通JS一樣寫vue組件,對於開發者更加友好瞭;按需引入computed、watch、directive等選項,一個業務邏輯可以集中編寫在一起,讓代碼更加簡潔便於瀏覽。
一、基本用法
隻需在<script>裡添加一個setup屬性,編譯時會把<script setup></script>裡的代碼編譯成一個setup函數
<script setup> console.log('hello script setup') </script>
普通的<script>隻會在組件被首次引入的時候執行一次,<script setup>裡的代碼會在每次組件實例被創建的時候執行。
二、data和methods
<script setup>裡聲明的變量和函數,不需要return暴露出去,就可以直接在template使用
<script setup> import { ref, reactive } from 'vue' // 普通變量 const msg = 'Hello!' // 響應式變量 let num = ref(1111) // ref聲明基本類型變量 const obj = reactive({ // reactive聲明對象類型變量,如Object、Array、Date... key: 'this is a object' }) // 函數 function log() { console.log(msg) // Hello console.log(num.value) // 1111(可根據input輸入值而改變) console.log(obj.key) // this is a object } </script> <template> <h1>{{ msg }}</h1> <p>{{obj.key}}</p> <input v-model="num" type="text" /> <button @click="log">打印日志</button> </template>
三、計算屬性computed
<script setup> import { ref, computed } from 'vue' let count = ref(0) const countPlus = computed(()=>{ return count.value+1 }) </script> <template> <h1>計數:{{ countPlus }}</h1> </template>
四、監聽器watch、watchEffect
1、watch監聽器除瞭使用方式有區別之外,其他的與vue2.0沒啥變化
<script setup> import { ref, reactive, watch } from 'vue' // 監聽ref let count = ref(0) watch(count, (newVal, oldVal)=> { console.log('修改後', newVal) console.log('修改前', oldVal) }) // 監聽reactive屬性 const obj = reactive({ count: 0 }) watch( ()=> obj.count, // 一個函數,返回監聽屬性 (newVal, oldVal)=> { console.log('修改後', newVal) console.log('修改前', oldVal) }, { immediate: true, // 立即執行,默認為false deep: true // 深度監聽,默認為false } ) const onChange = function(){ count.value++ obj.count++ } </script> <template> <button @click="onChange">改變count</button> </template>
2、watchEffect
watchEffect是Vue3.0新增的一個監聽屬性的方法,它與watch的區別在於watchEffect不需要指定監聽對象,回調函數裡可直接獲取到修改後的屬性的值
<script setup> import { ref, reactive, watchEffect } from 'vue' let count = ref(0) const obj = reactive({ count: 0 }) setTimeout(()=>{ count.value++ obj.count++ }, 1000) watchEffect(()=> { console.log('修改後的count', count.value) console.log('修改前的obj', obj.value) }) </script>
五、自定義指令directive
以 vNameOfDirective
的形式來命名本地自定義指令,可以直接在模板中使用
<script setup> // 導入指令可重命名 // import { myDirective as vMyDirective } from './MyDirective.js' // 自定義指令 const vMyDirective = { beforeMount: (el) => { // 在元素上做些操作 } } </script> <template> <h1 v-my-directive>This is a Heading</h1> </template>
六、import導入的內容可直接使用
1、導入的模塊內容,不需要通過 methods
來暴露它
// utils.js export const onShow = function(name) { return 'my name is ' + name }
// Show.vue <script setup> import { onShow } from './utils.js' </script> <template> <div>{{ onShow('jack') }}</div> </template>
2、導入外部組件,不需要通過components註冊使用
// Child.vue <template> <div>I am a child</div> </template>
// Parent.vue <script setup> import Child from './Child.vue' </script> <template> <child></child> </template>
七、聲明props和emits
在 <script setup>
中必須使用 defineProps
和 defineEmits
API 來聲明 props
和 emits
,它們具備完整的類型推斷並且在 <script setup>
中是直接可用的
// Child.vue <script setup> // 聲明props const props = defineProps({ info: { type: String, default: '' } }) // 聲明emits const $emit = defineEmits(['change']) const onChange = function() { $emit('change', 'child返回值') } </script> <template> <h1>信息:{{ info }}</h1> <button @click="onChange">點擊我</button> </template>
// Parent.vue <script setup> import { ref } from 'vue' import Child from './Child.vue' const msg = ref('hello setup !') // 響應式變量 const onAction = function(event) { console.log(event) // child返回值 } </script> <template> <child :info="msg" @change="onAction"></child> </template>
如果使用瞭 Typescript,使用純類型聲明來聲明 prop 和 emits 也是可以的。
八、父組件獲取子組件的數據
父組件要想通過ref獲取子組件的變量或函數,子組件須使用defineExpose暴露出去
// Child.vue <script setup> import { ref, defineExpose } from 'vue' const info = ref('I am child') const onChange = function() { console.log('Function of child') } // 暴露屬性 defineExpose({ info, onChange }) </script> <template> <h1>信息:{{ info }}</h1> <button @click="onChange">點擊我</button> </template>
// Parent.vue <script setup> import { ref } from 'vue' import Child from './Child.vue' const childRef = ref() const onAction = function() { console.log(childRef.value.info) // I am child console.log(childRef.value.onChange()) // Function of child } </script> <template> <child ref="childRef"></child> <button @click="onAction">獲取子值</button> </template>
九、provide和inject傳值
無論組件層次結構有多深,父組件都可以通過provide
選項來其所有子組件提供數據,子組件通過inject接收數據
// Parent.vue <script setup> import { ref, provide } from 'vue' import Child from './Child.vue' const msg = ref('Hello, my son') const onShow = function() { console.log('I am your parent') } provide('myProvide', { msg, onShow }) </script> <template> <child></child> </template>
// Child.vue <script setup> import { inject } from 'vue' const provideState = inject('myProvide') // 接收參數 const getData = function() { console.log(provideState.msg) // Hello, my son console.log(provideState.onShow()) // I am your parent } </script> <template> <button @click="getData">獲取父值</button> </template>
十、路由useRoute和useRouter
<script setup> import { useRoute, useRouter } from 'vue-router' const $route = useRoute() const $router = userRouter() // 路由信息 console.log($route.query) // 路由跳轉 $router.push('/login') </script>
十一、對await異步的支持
<script setup>
中可以使用頂層 await
。結果代碼會被編譯成 async setup()
<script setup> const post = await fetch(`/api/post/1`).then(r => r.json()) </script>
十二、nextTick
// 方式一 <script setup> import { nextTick } from 'vue' nextTick(()=>{ console.log('Dom已更新!') }) </script>
// 方式二 <script setup> import { nextTick } from 'vue' await nextTick() // nextTick是一個異步函數,返回一個Promise實例 // console.log('Dom已更新!') </script>
十三、全局屬性globalProperties
// main.js裡定義 import { createApp } from 'vue' import App from './App.vue' const app = createApp(App) // 定義一個全局屬性$global app.config.globalProperties.$global = 'This is a global property.' app.mount('#app')
// 組件內使用 <script setup> import { getCurrentInstance } from 'vue' // 獲取vue實例 const { proxy } = getCurrentInstance() // 輸出 console.log(proxy.$global) // This is a global property. </script>
十四、生命周期
setup()裡訪問組件的生命周期需要在生命周期鉤子前加上“on”,並且沒有beforeCreate和created生命周期鉤子
因為
setup
是圍繞beforeCreate
和created
生命周期鉤子運行的,所以不需要顯式地定義它們。換句話說,在這些鉤子中編寫的任何代碼都應該直接在setup
函數中編寫。
// 使用方式 <script setup> import { onMounted } from 'vue' onMounted(()=> { console.log('onMounted') }) </script>
十五、與普通的script標簽一起使用
<script setup>
可以和普通的 <script>
一起使用。普通的 <script>
在有這些需要的情況下或許會被使用到:
- 無法在
<script setup>
聲明的選項,例如inheritAttrs
或通過插件啟用的自定義的選項; - 聲明命名導出,
<script setup>
定義的組件默認以組件文件的名稱作為組件名; - 運行副作用或者創建隻需要執行一次的對象。
<script> // 普通 <script>, 在模塊范圍下執行(隻執行一次) runSideEffectOnce() // 聲明額外的選項 export default { name: 'ComponentName', // 組件重命名 inheritAttrs: false, customOptions: {} } </script> <script setup> // 在 setup() 作用域中執行 (對每個實例皆如此) </script>
十六、v-memo新指令
該指令與v-once類似,v-once是隻渲染一次之後的更新不再渲染,而v-memo是根據條件來渲染。該指令接收一個固定長度的數組作為依賴值進行記憶比對,如果數組中的每個值都和上次渲染的時候相同,則該元素(含子元素)不刷新。
1、應用於普通元素或組件;
<template> <-- 普通元素 --> <div v-memo="[valueA, valueB]"> ... </div> <-- 組件 --> <component v-memo="[valueA, valueB]"></component> </template> <script setup> import component from "../compoents/component.vue" </script>
當組件重新渲染的時候,如果 valueA
與 valueB
都維持不變,那麼對這個 <div>
以及它的所有子節點的更新都將被跳過。
2、結合v-for
使用
v-memo
僅供性能敏感場景的針對性優化,會用到的場景應該很少。渲染 v-for
長列表 (長度大於 1000) 可能是它最有用的場景:
<template> <div v-for="item in list" :key="item.id" v-memo="[item.id === selected]"> <p>ID: {{ item.id }} - selected: {{ item.id === selected }}</p> <p>...more child nodes</p> </div> </template>
當selected發生變化時,隻有item.id===selected的該項重新渲染,其餘不刷新。
style新特性
Vue3.2版本對單文件組件的style樣式進行瞭很多升級,如局部樣式、css變量以及樣式暴露給模板使用等。
一、局部樣式
當 <style>
標簽帶有 scoped
attribute 的時候,它的 CSS 隻會應用到當前組件的元素上:
<template> <div class="example">hi</div> </template> <style scoped> .example { color: red; } </style>
二、深度選擇器
處於 scoped
樣式中的選擇器如果想要做更“深度”的選擇,也即:影響到子組件,可以使用 :deep()
這個偽類:
<style scoped> .a :deep(.b) { /* ... */ } </style>
通過
v-html
創建的 DOM 內容不會被作用域樣式影響,但你仍然可以使用深度選擇器來設置其樣式。
三、插槽選擇器
默認情況下,作用域樣式不會影響到 <slot/>
渲染出來的內容,因為它們被認為是父組件所持有並傳遞進來的。使用 :slotted
偽類以確切地將插槽內容作為選擇器的目標:
<style scoped> :slotted(div) { color: red; } </style>
四、全局選擇器
如果想讓其中一個樣式規則應用到全局,比起另外創建一個 <style>
,可以使用 :global
偽類來實現:
<style scoped> :global(.red) { color: red; } </style>
五、混合使用局部與全局樣式
你也可以在同一個組件中同時包含作用域樣式和非作用域樣式:
<style> /* global styles */ </style> <style scoped> /* local styles */ </style>
六、支持CSS Modules
<style module>
標簽會被編譯為 CSS Modules 並且將生成的 CSS 類鍵暴露給組件:
1、 默認以$style
對象暴露給組件;
<template> <p :class="$style.red"> This should be red </p> </template> <style module> .red { color: red; } </style>
2、可以自定義註入module名稱
<template> <p :class="classes.red">red</p> </template> <style module="classes"> .red { color: red; } </style>
七、與setup一同使用
註入的類可以通過 useCssModule API 在 setup()
和 <script setup>
中使用:
<script setup> import { useCssModule } from 'vue' // 默認, 返回 <style module> 中的類 const defaultStyle = useCssModule() // 命名, 返回 <style module="classes"> 中的類 const classesStyle = useCssModule('classes') </script>
八、動態 CSS
單文件組件的 <style>
標簽可以通過 v-bind
這一 CSS 函數將 CSS 的值關聯到動態的組件狀態上:
<script setup> const theme = { color: 'red' } </script> <template> <p>hello</p> </template> <style scoped> p { color: v-bind('theme.color'); } </style>
參考文獻:
SFC 語法規范 | Vue.js
Vue3.2 setup語法糖、Composition API、狀態庫Pinia歸納總監
總結
到此這篇關於Vue3.2單文件組件setup的語法糖與新特性的文章就介紹到這瞭,更多相關Vue3.2單文件組件setup語法糖內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- vue3常用的API使用簡介
- Vue3 Composition API的使用簡介
- 一文搞懂Vue3中watchEffect偵聽器的使用
- vue 3 中watch 和watchEffect 的新用法
- Vue3常用的通訊方式總結與實例代碼