Vue3 筆記

安裝


安裝Vue3,初始化npm:

npm init vue@3
cd dirname
npm i
npm run dev

一路回車即可。

修改App.vue:

<script>
export default {
  data() {
    return {
      message: 'LuckyStar'
    }
  }
}
</script>

<template>
  <h1>Hello {{ message }}</h1>
</template>

選項式API


  • 模板引用

特別的,如果一定要操作某個DOM,可以使用 模板引用

<script>
export default {
  mounted() {
    this.$refs.p.textContent = "hello world!"
  }
}
</script>

<template>
  <p ref="p">hello</p>
</template>
  • Vue3 生命周期

生命周期如下。多了一個 setup() 函數(shù)


Vue3 生命周期

組合式API(Composition API)


  • setup() 函數(shù)

如上圖所示,setup() 函數(shù)比 beforeCreate() 更早被調(diào)用。

1. Props 參數(shù)
export default {
  props: {
    title: String
  },
  setup(props) {
    console.log(props.title)
  }
}
2. Context 參數(shù)
export default {
  setup(props, context) {
    // 透傳 Attributes(非響應(yīng)式的對象,等價于 $attrs)
    console.log(context.attrs)

    // 插槽(非響應(yīng)式的對象,等價于 $slots)
    console.log(context.slots)

    // 觸發(fā)事件(函數(shù),等價于 $emit)
    console.log(context.emit)

    // 暴露公共屬性(函數(shù))
    console.log(context.expose)
  }
}

context 是非響應(yīng)式的,可以安全解構(gòu)。

2.1 expose 暴露公共屬性
export default {
  setup(props, { expose }) {
    // 讓組件實例處于 “關(guān)閉狀態(tài)”
    // 即不向父組件暴露任何東西
    expose()

    const publicCount = ref(0)
    const privateCount = ref(0)
    // 有選擇地暴露局部狀態(tài)
    expose({ count: publicCount })
  }
}
3. 返回渲染函數(shù)

setup 也可以返回一個渲染函數(shù),此時在渲染函數(shù)中可以直接使用在同一作用域下聲明的響應(yīng)式狀態(tài):

import { h, ref } from 'vue'

export default {
  setup() {
    const count = ref(0)
    return () => h('div', count.value)
  }
}

返回一個渲染函數(shù)將會阻止我們返回其他東西。對于組件內(nèi)部來說,這樣沒有問題,但如果我們想通過模板引用將這個組件的方法暴露給父組件,那就有問題了。

我們可以通過調(diào)用 expose() 解決這個問題:

import { h, ref } from 'vue'

export default {
  setup(props, { expose }) {
    const count = ref(0)
    const increment = () => ++count.value

    expose({
      increment
    })

    return () => h('div', count.value)
  }
}
  • <script setup>

<script setup> 是在單文件組件 (SFC) 中使用組合式 API 的編譯時語法糖。當同時使用 SFC 與組合式 API 時該語法是默認推薦。相比于普通的 <script> 語法,它具有更多優(yōu)勢:

  • 更少的樣板內(nèi)容,更簡潔的代碼。
  • 能夠使用純 TypeScript 聲明 props 和自定義事件。
  • 更好的運行時性能 (其模板會被編譯成同一作用域內(nèi)的渲染函數(shù),避免了渲染上下文代理對象)。
  • 更好的 IDE 類型推導性能 (減少了語言服務(wù)器從代碼中抽取類型的工作)。
<script setup>
console.log('hello script setup')
</script>
1. 頂層的綁定會被暴露給模板

當使用 <script setup> 的時候,任何在 <script setup> 聲明的頂層的綁定 (包括變量,函數(shù)聲明,以及 import 導入的內(nèi)容) 都能在模板中直接使用:

<script setup>
// 變量
const msg = 'Hello!'

// 函數(shù)
function log() {
  console.log(msg)
}
</script>

<template>
  <button @click="log">{{ msg }}</button>
</template>

import 導入的內(nèi)容也會以同樣的方式暴露。這意味著我們可以在模板表達式中直接使用導入的 helper 函數(shù),而不需要通過 methods 選項來暴露它:

<script setup>
import { capitalize } from './helpers'
</script>

<template>
  <div>{{ capitalize('hello') }}</div>
</template>
2. 響應(yīng)式

響應(yīng)式狀態(tài)需要明確使用 響應(yīng)式 API 來創(chuàng)建。和 setup() 函數(shù)的返回值一樣,ref 在模板中使用的時候會自動解包:

<script setup>
import { ref } from 'vue'

const count = ref(0)
</script>

<template>
  <button @click="count++">{{ count }}</button>
</template>
3. 使用 Component
<script setup>
import MyComponent from './MyComponent.vue'
</script>

<template>
  <MyComponent />
</template>
4. 動態(tài) Component
<script setup>
import Foo from './Foo.vue'
import Bar from './Bar.vue'
</script>

<template>
  <component :is="Foo" />
  <component :is="someCondition ? Foo : Bar" />
</template>

Vue 響應(yīng)式基礎(chǔ)


https://cn.vuejs.org/guide/essentials/reactivity-fundamentals.html

Vue 響應(yīng)式的本質(zhì),實際上是一個 JavaScript Proxy,其行為表現(xiàn)與一般對象相似。不同之處在于 Vue 能夠跟蹤對響應(yīng)式對象屬性的訪問與更改操作。

要在組件模板中使用響應(yīng)式狀態(tài),需要在 setup() 函數(shù)中定義并返回。

import { reactive } from 'vue'

export default {
  // `setup` 是一個專門用于組合式 API 的特殊鉤子函數(shù)
  setup() {
    const state = reactive({ count: 0 })

    // 暴露 state 到模板
    return {
      state
    }
  }
}
<div>{{ state.count }}</div>

自然,我們也可以在同一個作用域下定義一個更新響應(yīng)式狀態(tài)的函數(shù),并作為一個方法與狀態(tài)一起暴露出去:

import { reactive } from 'vue'

export default {
  setup() {
    const state = reactive({ count: 0 })

    function increment() {
      state.count++
    }

    // 不要忘記同時暴露 increment 函數(shù)
    return {
      state,
      increment
    }
  }
}
<button @click="increment">
  {{ state.count }}
</button>
<script setup> 簡化代碼

在 setup() 函數(shù)中手動暴露大量的狀態(tài)和方法非常繁瑣。幸運的是,我們可以通過使用構(gòu)建工具來簡化該操作。當使用單文件組件(SFC)時,我們可以使用 <script setup> 來大幅度地簡化代碼。

<script setup>
import { reactive } from 'vue'

const state = reactive({ count: 0 })

function increment() {
  state.count++
}
</script>

<template>
  <button @click="increment">
    {{ state.count }}
  </button>
</template>
nextTick()

當你更改響應(yīng)式狀態(tài)后,DOM 會自動更新。然而,你得注意 DOM 的更新并不是同步的。相反,Vue 將緩沖它們直到更新周期的 “下個時機” 以確保無論你進行了多少次狀態(tài)更改,每個組件都只需要更新一次。
若要等待一個狀態(tài)改變后的 DOM 更新完成,你可以使用 nextTick() 這個全局 API:

import { nextTick } from 'vue'

function increment() {
  state.count++
  nextTick(() => {
    // 訪問更新后的 DOM
  })
}
深層響應(yīng)性

在 Vue 中,狀態(tài)都是默認深層響應(yīng)式的。這意味著即使在更改深層次的對象或數(shù)組,你的改動也能被檢測到。

import { reactive } from 'vue'

const obj = reactive({
  nested: { count: 0 },
  arr: ['foo', 'bar']
})

function mutateDeeply() {
  // 以下都會按照期望工作
  obj.nested.count++
  obj.arr.push('baz')
}

當然也可以直接創(chuàng)建一個淺層響應(yīng)式對象。

響應(yīng)式代理 vs. 原始對象

值得注意的是,reactive() 返回的是一個原始對象的 Proxy,它和原始對象是不相等的:

const raw = {}
const proxy = reactive(raw)

// 代理對象和原始對象不是全等的
console.log(proxy === raw) // false

只有代理對象是響應(yīng)式的,更改原始對象不會觸發(fā)更新。因此,使用 Vue 的響應(yīng)式系統(tǒng)的最佳實踐是 僅使用你聲明對象的代理版本。

為保證訪問代理的一致性,對同一個原始對象調(diào)用 reactive() 會總是返回同樣的代理對象,而對一個已存在的代理對象調(diào)用 reactive() 會返回其本身:

// 在同一個對象上調(diào)用 reactive() 會返回相同的代理
console.log(reactive(raw) === proxy) // true

// 在一個代理上調(diào)用 reactive() 會返回它自己
console.log(reactive(proxy) === proxy) // true
reactive() 的局限性

reactive() API 有兩條限制:

  1. 僅對對象類型有效(對象、數(shù)組和 Map、Set 這樣的集合類型),而對 string、numberboolean 這樣的 原始類型 無效。

  2. 因為 Vue 的響應(yīng)式系統(tǒng)是通過屬性訪問進行追蹤的,因此我們必須始終保持對該響應(yīng)式對象的相同引用。這意味著我們不可以隨意地“替換”一個響應(yīng)式對象,因為這將導致對初始引用的響應(yīng)性連接丟失:

let state = reactive({ count: 0 })

// 上面的引用 ({ count: 0 }) 將不再被追蹤(響應(yīng)性連接已丟失?。?state = reactive({ count: 1 })

用 ref() 定義響應(yīng)式變量


reactive() 的種種限制歸根結(jié)底是因為 JavaScript 沒有可以作用于所有值類型的 “引用” 機制。為此,Vue 提供了一個 ref() 方法來允許我們創(chuàng)建可以使用任何值類型的響應(yīng)式 ref

import { ref } from 'vue'
const count = ref(0)

ref() 函數(shù)將傳入?yún)?shù)的值包裝為一個帶 .value 屬性的 ref 對象:

const count = ref(0)

console.log(count) // { value: 0 }
console.log(count.value) // 0

count.value++
console.log(count.value) // 1

和響應(yīng)式對象的屬性類似,ref 的 .value 屬性也是響應(yīng)式的。同時,當值為對象類型時,會用 reactive() 自動轉(zhuǎn)換它的 .value。

一個包含對象類型值的 ref 可以響應(yīng)式地替換整個對象:

const objectRef = ref({ count: 0 })

// 這是響應(yīng)式的替換
objectRef.value = { count: 1 }

Vue3 生命周期


每個 Vue 組件實例在創(chuàng)建時都需要經(jīng)歷一系列的初始化步驟,比如設(shè)置好數(shù)據(jù)偵聽,編譯模板,掛載實例到 DOM,以及在數(shù)據(jù)改變時更新 DOM。在此過程中,它也會運行被稱為生命周期鉤子的函數(shù),讓開發(fā)者有機會在特定階段運行自己的代碼。

注冊周期鉤子

舉例來說,onMounted 鉤子可以用來在組件完成初始渲染并創(chuàng)建 DOM 節(jié)點后運行代碼:

<script setup>
import { onMounted } from 'vue'

onMounted(() => {
  console.log(`the component is now mounted.`)
})
</script>

偵聽器 Watch


在組合式 API 中,我們可以使用 watch 函數(shù)在每次響應(yīng)式狀態(tài)發(fā)生變化時觸發(fā)回調(diào)函數(shù):

<script setup>
import { ref, watch } from 'vue'

const question = ref('')
const answer = ref('Questions usually contain a question mark. ;-)')

// 可以直接偵聽一個 ref
watch(question, async (newQuestion, oldQuestion) => {
  if (newQuestion.indexOf('?') > -1) {
    answer.value = 'Thinking...'
    try {
      const res = await fetch('https://yesno.wtf/api')
      answer.value = (await res.json()).answer
    } catch (error) {
      answer.value = 'Error! Could not reach the API. ' + error
    }
  }
})
</script>

<template>
  <p>
    Ask a yes/no question:
    <input v-model="question" />
  </p>
  <p>{{ answer }}</p>
</template>
watch 數(shù)據(jù)源類型

watch 的第一個參數(shù)可以是不同形式的“數(shù)據(jù)源”:它可以是一個 ref (包括計算屬性)、一個響應(yīng)式對象、一個 getter 函數(shù)、或多個數(shù)據(jù)源組成的數(shù)組:

const x = ref(0)
const y = ref(0)

// 單個 ref
watch(x, (newX) => {
  console.log(`x is ${newX}`)
})

// getter 函數(shù)
watch(
  () => x.value + y.value,
  (sum) => {
    console.log(`sum of x + y is: ${sum}`)
  }
)

// 多個來源組成的數(shù)組
watch([x, () => y.value], ([newX, newY]) => {
  console.log(`x is ${newX} and y is ${newY}`)
})

注意,你不能直接偵聽響應(yīng)式對象的屬性值,例如:

const obj = reactive({ count: 0 })

// 錯誤,因為 watch() 得到的參數(shù)是一個 number
watch(obj.count, (count) => {
  console.log(`count is: ${count}`)
})

這里需要用一個返回該屬性的 getter 函數(shù):

// 提供一個 getter 函數(shù)
watch(
  () => obj.count,
  (count) => {
    console.log(`count is: ${count}`)
  }
)
深層偵聽器

直接給 watch() 傳入一個響應(yīng)式對象,會隱式地創(chuàng)建一個深層偵聽器——該回調(diào)函數(shù)在所有嵌套的變更時都會被觸發(fā):

const obj = reactive({ count: 0 })

watch(obj, (newValue, oldValue) => {
  // 在嵌套的屬性變更時觸發(fā)
  // 注意:`newValue` 此處和 `oldValue` 是相等的
  // 因為它們是同一個對象!
})

obj.count++

而 一個返回響應(yīng)式對象的 getter 函數(shù),只有在返回不同的對象時,才會觸發(fā)回調(diào):

watch(
  () => state.someObject,
  () => {
    // 僅當 state.someObject 被替換時觸發(fā)
  }
)

你也可以給上面這個例子顯式地加上 deep 選項,強制轉(zhuǎn)成深層偵聽器:

watch(
  () => state.someObject,
  (newValue, oldValue) => {
    // 注意:`newValue` 此處和 `oldValue` 是相等的
    // *除非* state.someObject 被整個替換了
  },
  { deep: true }
)
watchEffect()

watch() 是懶執(zhí)行的:僅當數(shù)據(jù)源變化時,才會執(zhí)行回調(diào)。但在某些場景中,我們希望在創(chuàng)建偵聽器時,立即執(zhí)行一遍回調(diào)。舉例來說,我們想請求一些初始數(shù)據(jù),然后在相關(guān)狀態(tài)更改時重新請求數(shù)據(jù)。我們可以這樣寫:

const url = ref('https://...')
const data = ref(null)

async function fetchData() {
  const response = await fetch(url.value)
  data.value = await response.json()
}

// 立即獲取
fetchData()
// ...再偵聽 url 變化
watch(url, fetchData)

我們可以用 watchEffect 來簡化上面的代碼。watchEffect() 會立即執(zhí)行一遍回調(diào)函數(shù),如果這時函數(shù)產(chǎn)生了副作用,Vue 會自動追蹤副作用的依賴關(guān)系,自動分析出響應(yīng)源。上面的例子可以重寫為:

watchEffect(async () => {
  const response = await fetch(url.value)
  data.value = await response.json()
})

這個例子中,回調(diào)會立即執(zhí)行。在執(zhí)行期間,它會自動追蹤 url.value 作為依賴(和計算屬性的行為類似)。每當 url.value 變化時,回調(diào)會再次執(zhí)行。

watch vs. watchEffect

watch 和 watchEffect 都能響應(yīng)式地執(zhí)行有副作用的回調(diào)。它們之間的主要區(qū)別是追蹤響應(yīng)式依賴的方式:

  • watch 只追蹤明確偵聽的數(shù)據(jù)源。它不會追蹤任何在回調(diào)中訪問到的東西。另外,僅在數(shù)據(jù)源確實改變時才會觸發(fā)回調(diào)。watch 會避免在發(fā)生副作用時追蹤依賴,因此,我們能更加精確地控制回調(diào)函數(shù)的觸發(fā)時機。
  • watchEffect,則會在副作用發(fā)生期間追蹤依賴。它會在同步執(zhí)行過程中,自動追蹤所有能訪問到的響應(yīng)式屬性。這更方便,而且代碼往往更簡潔,但有時其響應(yīng)性依賴關(guān)系會不那么明確。

<Transition> & <TransitionGroup>


Vue 提供了兩個內(nèi)置組件,可以幫助你制作基于狀態(tài)變化的過渡和動畫:

  • <Transition> 會在一個元素或組件進入和離開 DOM 時應(yīng)用動畫。本章節(jié)會介紹如何使用它。

  • <TransitionGroup> 會在一個 v-for 列表中的元素或組件被插入,移動,或移除時應(yīng)用動畫。

<Transition> 組件

該組件可以將進入和離開動畫應(yīng)用到通過默認插槽傳遞給它的元素或組件上。進入或離開可以由以下的條件之一觸發(fā):

  • 由 v-if 所觸發(fā)的切換
  • 由 v-show 所觸發(fā)的切換
  • 由特殊元素 <component> 切換的動態(tài)組件

<Transition> 僅支持單個元素或者組件作為其插槽內(nèi)容。如果內(nèi)容是組件,則組件必須僅有一個根元素。

基礎(chǔ)用法
<button @click="show = !show">Toggle</button>
<Transition>
  <p v-if="show">hello</p>
</Transition>
/* 下面我們會解釋這些 class 是做什么的 */
.v-enter-active,
.v-leave-active {
  transition: opacity 0.5s ease;
}

.v-enter-from,
.v-leave-to {
  opacity: 0;
}
CSS 過渡效果 class
過渡效果 class
  1. v-enter-from:進入動畫的起始狀態(tài)。在元素插入之前添加,在元素插入完成后的下一幀移除。

  2. v-enter-active:進入動畫的生效狀態(tài)。應(yīng)用于整個進入動畫階段。在元素被插入之前添加,在過渡或動畫完成之后移除。這個 class 可以被用來定義進入動畫的持續(xù)時間、延遲與速度曲線類型。

  3. v-enter-to:進入動畫的結(jié)束狀態(tài)。在元素插入完成后的下一幀被添加 (也就是 v-enter-from 被移除的同時),在過渡或動畫完成之后移除。

  4. v-leave-from:離開動畫的起始狀態(tài)。在離開過渡效果被觸發(fā)時立即添加,在一幀后被移除。

  5. v-leave-active:離開動畫的生效狀態(tài)。應(yīng)用于整個離開動畫階段。在離開過渡效果被觸發(fā)時立即添加,在過渡或動畫完成之后移除。這個 class 可以被用來定義離開動畫的持續(xù)時間、延遲與速度曲線類型。

  6. v-leave-to:離開動畫的結(jié)束狀態(tài)。在一個離開動畫被觸發(fā)后的下一幀被添加 (也就是 v-leave-from 被移除的同時),在過渡或動畫完成之后移除。

v-enter-active 和 v-leave-active 給我們提供了為進入和離開動畫指定不同速度曲線的能力,我們將在下面的小節(jié)中看到一個示例。

命名 Transition
<Transition name="fade">
  ...
</Transition>

對于一個有名字的過渡效果,對它起作用的過渡 class 會以其名字而不是 v 作為前綴。比如,上方例子中被應(yīng)用的 class 將會是 fade-enter-active 而不是 v-enter-active。這個“fade”過渡的 class 應(yīng)該是這樣:

.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容