vue3 文檔梳理快速入門

一、setup

組合式 API:

  • setup 選項應該為一個函數
  • setup 選項函數接受兩個參數: props context
  • setup 選項函數需要返回要暴露給組件的內容
  • setup 需要使用 return 返回,才能給組件使用,包含數據和方法

1. setup 函數中的第一個參數 —— props

setup 函數中的 props 是響應式的,當傳入新的 prop 時,它將被更新

export default {
  props: {
    title: String
  },
  setup(props) {
    console.log(props.title)
  }
}

但是,因為 props 是響應式的,你不能使用 ES6 解構,因為它會消除 prop 的響應性。
如果需要解構 prop,可以通過使用 setup 函數中的 toRefs 來安全地完成此操作。

import { toRefs } from 'vue'

setup(props) {
    const { title } = toRefs(props)

    console.log(title.value)
}

2.  contextcontext

setup 函數中的第二個參數 —— contextcontext 上下文是一個普通的 JavaScript 對象,它暴露三個組件的 property

xport default {
  setup(props, context) {
    // Attribute (非響應式對象)
    console.log(context.attrs)

    // 插槽 (非響應式對象)
    console.log(context.slots)

    // 觸發事件 (方法)
    console.log(context.emit)
  }
}

context 是一個普通的 JavaScript 對象,也就是說,它不是響應式的,這意味著你可以安全地對 context 使用 ES6 解構

export default {
  setup(props, { attrs, slots, emit }) {
    ...
  }
}

二、setup 函數的返回值

1.setup 函數的返回值 —— 對象

如果 setup 返回一個對象,則可以在組件的模板中像傳遞給 setup props property 一樣訪問該對象的 property:

<template>
  <!-- 模板中使用會被自動解開,所以不需要 .value  -->
  <div>{{ readersNumber }} {{ book.title }}</div>
</template>

<script>
  import { ref, reactive } from 'vue'

  export default {
    setup() {
      const readersNumber = ref(0)
      const book = reactive({ title: 'Vue 3 Guide' })

      // expose to template
      return {
        readersNumber,
        book
      }
    }
  }
</script>

註意:從 setup 返回的 refs 在模板中訪問時是被自動解開的,因此不應在模板中使用 .value

三、響應式系統 API

1. reactive

reactive() 接收一個普通對象然後返回該普通對象的響應式代理。等同於 2.x 的 Vue.observable()

const obj = reactive({ count: 0 })

響應式轉換是“深層的”:會影響對象內部所有嵌套的屬性。基於 ES2015 的 Proxy 實現,返回的代理對象不等於原始對象。建議僅使用代理對象而避免依賴原始對象。

<template>
  <div id="app">{ state.count }</div>
</template>

<script>
import { reactive } from 'vue'
export default {
  setup() {
    // state 現在是一個響應式的狀態
    const state = reactive({
      count: 0,
    })
  }
}
</script>

2. ref

接受一個參數值並返回一個響應式且可改變的 ref 對象。ref 對象擁有一個指向內部值的單一屬性 .value

const count = ref(0)
console.log(count.value) // 0

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

如果傳入 ref 的是一個對象,將調用 reactive 方法進行深層響應轉換。

模板中訪問:

ref 作為渲染上下文的屬性返回(即在setup() 返回的對象中)並在模板中使用時,它會自動解套,無需在模板內額外書寫 .value:

<template>
  <div>{{ count }}</div>
</template>

<script>
  export default {
    setup() {
      return {
        count: ref(0),
      }
    },
  }
</script>

作為響應式對象的屬性訪問:

當 ref 作為 reactive 對象的 property 被訪問或修改時,也將自動解套 value 值,其行為類似普通屬性:

const count = ref(0)
const state = reactive({
  count,
})

console.log(state.count) // 0

state.count = 1
console.log(count.value) // 1

註意:如果將一個新的 ref 分配給現有的 ref, 將替換舊的 ref

const otherCount = ref(2)

state.count = otherCount
console.log(state.count) // 2
console.log(count.value) // 1

註意:當嵌套在 reactive Object 中時,ref 才會解套。從 Array 或者 Map 等原生集合類中訪問 ref 時,不會自動解套:

const arr = reactive([ref(0)])
// 這裡需要 .value
console.log(arr[0].value)

const map = reactive(new Map([['foo', ref(0)]]))
// 這裡需要 .value
console.log(map.get('foo').value)

類型定義:

interface Ref<T> {
  value: T
}

function ref<T>(value: T): Ref<T>

有時我們可能需要為 ref 做一個較為復雜的類型標註。我們可以通過在調用 ref 時傳遞泛型參數來覆蓋默認推導:

const foo = ref<string | number>('foo') // foo 的類型: Ref<string | number>

foo.value = 123 // 能夠通過!

3. computed

使用響應式 computed API 有兩種方式:

(1)傳入一個 getter 函數,返回一個默認不可手動修改的 ref 對象。

const count = ref(1)
const plusOne = computed(() => count.value + 1)

console.log(plusOne.value) // 2

plusOne.value++ // 錯誤!

(2)傳入一個擁有 get set 函數的對象,創建一個可手動修改的計算狀態。

const count = ref(1)
const plusOne = computed({
  get: () => count.value + 1,
  set: (val) => {
    count.value = val - 1
  },
})

plusOne.value = 1
console.log(count.value) // 0

類型定義:

// 隻讀的
function computed<T>(getter: () => T): Readonly<Ref<Readonly<T>>>

// 可更改的
function computed<T>(options: {
  get: () => T
  set: (value: T) => void
}): Ref<T>

4. readonly

傳入一個對象(響應式或普通)或 ref,返回一個原始對象的隻讀代理。一個隻讀的代理是“深層的”,對象內部任何嵌套的屬性也都是隻讀的。

const original = reactive({ count: 0 })

const copy = readonly(original)

watchEffect(() => {
  // 依賴追蹤
  console.log(copy.count)
})

// original 上的修改會觸發 copy 上的偵聽
original.count++

// 無法修改 copy 並會被警告
copy.count++ // warning!

5. watchEffect

立即執行傳入的一個函數,並響應式追蹤其依賴,並在其依賴變更時重新運行該函數。

const count = ref(0)

watchEffect(() => console.log(count.value))
// -> 打印出 0

setTimeout(() => {
  count.value++
  // -> 打印出 1
}, 100)

5.1 停止偵聽

watchEffect 在組件的 setup() 函數或生命周期鉤子被調用時, 偵聽器會被鏈接到該組件的生命周期,並在組件卸載時自動停止。

在一些情況下,也可以顯式調用返回值以停止偵聽:

const stop = watchEffect(() => {
  /* ... */
})

// 之後
stop()

5.2 清除副作用

有時副作用函數會執行一些異步的副作用, 這些響應需要在其失效時清除(即完成之前狀態已改變瞭)。所以偵聽副作用傳入的函數可以接收一個 onInvalidate 函數作入參, 用來註冊清理失效時的回調。當以下情況發生時,這個失效回調會被觸發:

  • 副作用即將重新執行時
  • 偵聽器被停止 (如果在 setup() 或 生命周期鉤子函數中使用瞭 watchEffect, 則在卸載組件時)
watchEffect((onInvalidate) => {
  const token = performAsyncOperation(id.value)
  onInvalidate(() => {
    // id 改變時 或 停止偵聽時
    // 取消之前的異步操作
    token.cancel()
  })
})

我們之所以是通過傳入一個函數去註冊失效回調,而不是從回調返回它(如 React useEffect 中的方式),是因為返回值對於異步錯誤處理很重要。

在執行數據請求時,副作用函數往往是一個異步函數:

const data = ref(null)
watchEffect(async () => {
  data.value = await fetchData(props.id)
})

我們知道異步函數都會隱式地返回一個 Promise,但是清理函數必須要在 Promise resolve 之前被註冊。另外,Vue 依賴這個返回的 Promise 來自動處理 Promise 鏈上的潛在錯誤。

5.3 副作用刷新時機

Vue 的響應式系統會緩存副作用函數,並異步地刷新它們,這樣可以避免同一個 tick 中多個狀態改變導致的不必要的重復調用。在核心的具體實現中, 組件的更新函數也是一個被偵聽的副作用。當一個用戶定義的副作用函數進入隊列時, 會在所有的組件更新後執行:

<template>
  <div>{{ count }}</div>
</template>

<script>
  export default {
    setup() {
      const count = ref(0)

      watchEffect(() => {
        console.log(count.value)
      })

      return {
        count,
      }
    },
  }
</script>

在這個例子中:

count 會在初始運行時同步打印出來
更改 count 時,將在組件更新後執行副作用。

請註意:初始化運行是在組件 mounted 之前執行的。因此,如果你希望在編寫副作用函數時訪問 DOM(或模板 ref),請在 onMounted 鉤子中進行:

onMounted(() => {
  watchEffect(() => {
    // 在這裡可以訪問到 DOM 或者 template refs
  })
})

如果副作用需要同步或在組件更新之前重新運行,我們可以傳遞一個擁有 flush 屬性的對象作為選項(默認為 ‘post‘):

// 同步運行
watchEffect(
  () => {
    /* ... */
  },
  {
    flush: 'sync',
  }
)

// 組件更新前執行
watchEffect(
  () => {
    /* ... */
  },
  {
    flush: 'pre',
  }
)

5.4  偵聽器調試

onTrack onTrigger 選項可用於調試一個偵聽器的行為。

  • 當一個 reactive 對象屬性或一個 ref 作為依賴被追蹤時,將調用 onTrack
  • 依賴項變更導致副作用被觸發時,將調用 onTrigger

這兩個回調都將接收到一個包含有關所依賴項信息的調試器事件。建議在以下回調中編寫 debugger 語句來檢查依賴關系:

watchEffect(
  () => {
    /* 副作用的內容 */
  },
  {
    onTrigger(e) {
      debugger
    },
  }
)

onTrack onTrigger 僅在開發模式下生效。

類型定義:

function watchEffect(
  effect: (onInvalidate: InvalidateCbRegistrator) => void,
  options?: WatchEffectOptions
): StopHandle

interface WatchEffectOptions {
  flush?: 'pre' | 'post' | 'sync'
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
}

interface DebuggerEvent {
  effect: ReactiveEffect
  target: any
  type: OperationTypes
  key: string | symbol | undefined
}

type InvalidateCbRegistrator = (invalidate: () => void) => void

type StopHandle = () => void

6. watch

watch API 完全等效於 2.x this.$watch (以及 watch 中相應的選項)。watch 需要偵聽特定的數據源,並在回調函數中執行副作用。默認情況是懶執行的,也就是說僅在偵聽的源變更時才執行回調。

  • 對比 watchEffect,watch 允許我們:
  1. 懶執行副作用;
  2. 更明確哪些狀態的改變會觸發偵聽器重新運行副作用;
  3. 訪問偵聽狀態變化前後的值。
  • 偵聽單個數據源
  1. 偵聽器的數據源可以是一個擁有返回值的 getter 函數,也可以是 ref:
// 偵聽一個 getter
const state = reactive({ count: 0 })
watch(
  () => state.count,
  (count, prevCount) => {
    /* ... */
  }
)

// 直接偵聽一個 ref
const count = ref(0)
watch(count, (count, prevCount) => {
  /* ... */
})

6.1 偵聽多個數據源

watcher 也可以使用數組來同時偵聽多個源:

watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
  /* ... */
})

6.2 與 watchEffect 共享的行為

watch watchEffect 在停止偵聽, 清除副作用 (相應地 onInvalidate 會作為回調的第三個參數傳入),副作用刷新時機 和 偵聽器調試 等方面行為一致.

類型定義:

// 偵聽單數據源
function watch<T>(
  source: WatcherSource<T>,
  callback: (
    value: T,
    oldValue: T,
    onInvalidate: InvalidateCbRegistrator
  ) => void,
  options?: WatchOptions
): StopHandle

// 偵聽多數據源
function watch<T extends WatcherSource<unknown>[]>(
  sources: T
  callback: (
    values: MapSources<T>,
    oldValues: MapSources<T>,
    onInvalidate: InvalidateCbRegistrator
  ) => void,
  options? : WatchOptions
): StopHandle

type WatcherSource<T> = Ref<T> | (() => T)

type MapSources<T> = {
  [K in keyof T]: T[K] extends WatcherSource<infer V> ? V : never
}

// 共有的屬性 請查看 `watchEffect` 的類型定義
interface WatchOptions extends WatchEffectOptions {
  immediate?: boolean // default: false
  deep?: boolean
}

到此這篇關於vue3 文檔梳理快速入門的文章就介紹到這瞭,更多相關vue3 文檔梳理內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: