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!

推薦閱讀: