首頁 > 軟體

Vue3.2單檔案元件setup的語法糖與新特性總結

2022-07-24 14:01:34

前言

滿滿的乾貨,建議收藏慢慢看,可以當作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語法糖內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com