欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Vue開發(fā)手冊Function-based?API?RFC

 更新時間:2022年11月07日 11:40:15   作者:尤雨溪  
這篇文章主要為大家介紹了Vue開發(fā)手冊Function-based?API?RFC使用示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

概要

2020 年一月又注:RFC 已經(jīng)被完全重寫,最新版本請以 https://composition-api.vuejs.org/ 為準。以下內(nèi)容會有部分與最新的 API 有出入,但依然可以幫助理解。

譯注:這是 3.0 最重要的 RFC,因此特意翻譯成中文。

將 2.x 中與組件邏輯相關(guān)的選項以 API 函數(shù)的形式重新設(shè)計。

基本例子

import { ref, computed, watch, onMounted } from 'vue'
const App = {
  template: `
    <div>
      <span>count is {{ count }}</span>
      <span>plusOne is {{ plusOne }}</span>
      <button @click="increment">count++</button>
    </div>
  `,
  setup() {
    // reactive state
    const count = ref(0)
    // computed state
    const plusOne = computed(() => count.value + 1)
    // method
    const increment = () => { count.value++ }
    // watch
    watch(() => count.value * 2, val => {
      console.log(`count * 2 is ${val}`)
    })
    // lifecycle
    onMounted(() => {
      console.log(`mounted`)
    })
    // expose bindings on render context
    return {
      count,
      plusOne,
      increment
    }
  }
}

設(shè)計動機

邏輯組合與復(fù)用

組件 API 設(shè)計所面對的核心問題之一就是如何組織邏輯,以及如何在多個組件之間抽取和復(fù)用邏輯?;?Vue 2.x 目前的 API 我們有一些常見的邏輯復(fù)用模式,但都或多或少存在一些問題。這些模式包括:

  • Mixins
  • 高階組件 (Higher-order Components, aka HOCs)
  • Renderless Components (基于 scoped slots / 作用域插槽封裝邏輯的組件)

網(wǎng)絡(luò)上關(guān)于這些模式的介紹很多,這里就不再贅述細節(jié)??傮w來說,以上這些模式存在以下問題:

  • 模版中的數(shù)據(jù)來源不清晰。舉例來說,當一個組件中使用了多個 mixin 的時候,光看模版會很難分清一個屬性到底是來自哪一個 mixin。HOC 也有類似的問題。
  • 命名空間沖突。由不同開發(fā)者開發(fā)的 mixin 無法保證不會正好用到一樣的屬性或是方法名。HOC 在注入的 props 中也存在類似問題。
  • 性能。HOC 和 Renderless Components 都需要額外的組件實例嵌套來封裝邏輯,導(dǎo)致無謂的性能開銷。

Function-based API 受 React Hooks 的啟發(fā),提供了一個全新的邏輯復(fù)用方案,且不存在上述問題。使用基于函數(shù)的 API,我們可以將相關(guān)聯(lián)的代碼抽取到一個 "composition function"(組合函數(shù))中 —— 該函數(shù)封裝了相關(guān)聯(lián)的邏輯,并將需要暴露給組件的狀態(tài)以響應(yīng)式的數(shù)據(jù)源的方式返回出來。這里是一個用組合函數(shù)來封裝鼠標位置偵聽邏輯的例子:

function useMouse() {
  const x = ref(0)
  const y = ref(0)
  const update = e => {
    x.value = e.pageX
    y.value = e.pageY
  }
  onMounted(() => {
    window.addEventListener('mousemove', update)
  })
  onUnmounted(() => {
    window.removeEventListener('mousemove', update)
  })
  return { x, y }
}
// 在組件中使用該函數(shù)
const Component = {
  setup() {
    const { x, y } = useMouse()
    // 與其它函數(shù)配合使用
    const { z } = useOtherLogic()
    return { x, y, z }
  },
  template: `<div>{{ x }} {{ y }} {{ z }}</div>`
}

從以上例子中可以看到:

  • 暴露給模版的屬性來源清晰(從函數(shù)返回);
  • 返回值可以被任意重命名,所以不存在命名空間沖突;
  • 沒有創(chuàng)建額外的組件實例所帶來的性能損耗。

文末附錄中有與 React Hooks 的一些細節(jié)對比。

類型推導(dǎo)

3.0 的一個主要設(shè)計目標是增強對 TypeScript 的支持。原本我們期望通過 Class API 來達成這個目標,但是經(jīng)過討論和原型開發(fā),我們認為 Class 并不是解決這個問題的正確路線,基于 Class 的 API 依然存在類型問題。

基于函數(shù)的 API 天然對類型推導(dǎo)很友好,因為 TS 對函數(shù)的參數(shù)、返回值和泛型的支持已經(jīng)非常完備。更值得一提的是基于函數(shù)的 API 在使用 TS 或是原生 JS 時寫出來的代碼幾乎是完全一樣的。下文會提供新 API 類型推導(dǎo)的更多細節(jié),此外文末附錄中有關(guān)于 Class API 類型問題的更多細節(jié)。

打包尺寸

基于函數(shù)的 API 每一個函數(shù)都可以作為 named ES export 被單獨引入,這使得它們對 tree-shaking 非常友好。沒有被使用的 API 的相關(guān)代碼可以在最終打包時被移除。同時,基于函數(shù) API 所寫的代碼也有更好的壓縮效率,因為所有的函數(shù)名和 setup 函數(shù)體內(nèi)部的變量名都可以被壓縮,但對象和 class 的屬性/方法名卻不可以。

設(shè)計細節(jié)

setup() 函數(shù)

我們將會引入一個新的組件選項,setup()。顧名思義,這個函數(shù)將會是我們 setup 我們組件邏輯的地方,它會在一個組件實例被創(chuàng)建時,初始化了 props 之后調(diào)用。setup() 會接收到初始的 props 作為參數(shù):

const MyComponent = {
  props: {
    name: String
  },
  setup(props) {
    console.log(props.name)
  }
}

需要留意的是這里傳進來的 props 對象是響應(yīng)式的 —— 它可以被當作數(shù)據(jù)源去觀測,當后續(xù) props 發(fā)生變動時它也會被框架內(nèi)部同步更新。但對于用戶代碼來說,它是不可修改的(會導(dǎo)致警告)。

在 setup 內(nèi)部可以使用 this,但你大部分時候不會需要它。

組件狀態(tài)

類似 data(),setup() 可以返回一個對象 —— 這個對象上的屬性將會被暴露給模版的渲染上下文:

const MyComponent = {
  props: {
    name: String
  },
  setup(props) {
    return {
      msg: `hello ${props.name}!`
    }
  },
  template: `<div>{{ msg }}</div>`
}

上面這個例子跟 data() 一模一樣:msg 可以在模版中被直接使用,它甚至可以被模版中的內(nèi)聯(lián)函數(shù)修改。但如果我們想要創(chuàng)建一個可以在 setup() 內(nèi)部被管理的值,可以使用 ref 函數(shù):

import { ref } from 'vue'
const MyComponent = {
  setup(props) {
    const msg = ref('hello')
    const appendName = () => {
      msg.value = `hello ${props.name}`
    }
    return {
      msg,
      appendName
    }
  },
  template: `<div @click="appendName">{{ msg }}</div>`
}

ref() 返回的是一個 value reference (包裝對象)。一個包裝對象只有一個屬性:.value ,該屬性指向內(nèi)部被包裝的值。在上面的例子中,msg 包裝的是一個字符串。包裝對象的值可以被直接修改:

// 讀取
console.log(msg.value) // 'hello'
// 修改
msg.value = 'bye'

為什么需要包裝對象?

我們知道在 JavaScript 中,原始值類型如 string 和 number 是只有值,沒有引用的。如果在一個函數(shù)中返回一個字符串變量,接收到這個字符串的代碼只會獲得一個值,是無法追蹤原始變量后續(xù)的變化的。

因此,包裝對象的意義就在于提供一個讓我們能夠在函數(shù)之間以引用的方式傳遞任意類型值的容器。這有點像 React Hooks 中的 useRef —— 但不同的是 Vue 的包裝對象同時還是響應(yīng)式的數(shù)據(jù)源。有了這樣的容器,我們就可以在封裝了邏輯的組合函數(shù)中將狀態(tài)以引用的方式傳回給組件。組件負責(zé)展示(追蹤依賴),組合函數(shù)負責(zé)管理狀態(tài)(觸發(fā)更新):

setup() {
  const valueA = useLogicA() // valueA 可能被 useLogicA() 內(nèi)部的代碼修改從而觸發(fā)更新
  const valueB = useLogicB()
  return {
    valueA,
    valueB
  }
}

包裝對象也可以包裝非原始值類型的數(shù)據(jù),被包裝的對象中嵌套的屬性都會被響應(yīng)式地追蹤。用包裝對象去包裝對象或是數(shù)組并不是沒有意義的:它讓我們可以對整個對象的值進行替換 —— 比如用一個 filter 過的數(shù)組去替代原數(shù)組:

const numbers = ref([1, 2, 3])
// 替代原數(shù)組,但引用不變
numbers.value = numbers.value.filter(n => n > 1)

如果你依然想創(chuàng)建一個沒有包裝的響應(yīng)式對象,可以使用 reactiveAPI(和 2.x 的 Vue.observable()等同):

import { reactive } from 'vue'
const object = reactive({
  count: 0
})
object.count++

Ref Unwrapping(包裝對象的自動展開)

在上面的一個例子中你可能注意到了,雖然 setup()返回的 msg是一個包裝對象,但在模版中我們直接用了 {{ msg }}這樣的綁定,沒有用 .value。這是因為當包裝對象被暴露給模版渲染上下文,或是被嵌套在另一個響應(yīng)式對象中的時候,它會被自動展開 (unwrap) 為內(nèi)部的值。

比如一個包裝對象的綁定可以直接被模版中的內(nèi)聯(lián)函數(shù)修改:

const MyComponent = {
  setup() {
    return {
      count: ref(0)
    }
  },
  template: `<button @click="count++">{{ count }}</button>`
}

當一個包裝對象被作為另一個響應(yīng)式對象的屬性引用的時候也會被自動展開:

const count = ref(0)
const obj = reactive({
  count
})
console.log(obj.count) // 0
obj.count++
console.log(obj.count) // 1
console.log(count.value) // 1
count.value++
console.log(obj.count) // 2
console.log(count.value) // 2

以上這些關(guān)于包裝對象的細節(jié)可能會讓你覺得有些復(fù)雜,但實際使用中你只需要記住一個基本的規(guī)則:只有當你直接以變量的形式引用一個包裝對象的時候才會需要用 .value 去取它內(nèi)部的值 —— 在模版中你甚至不需要知道它們的存在。

配合手寫 Render 函數(shù)使用

如果你的組件不使用模版,你也可以選擇在 setup() 中直接返回一個渲染函數(shù):

import { ref, createElement as h } from 'vue'
const MyComponent = {
  setup(initialProps) {
    const count = ref(0)
    const increment = () => { count.value++ }
    return (props, slots, attrs, vnode) => (
      h('button', {
        onClick: increment
      }, count.value)
    )
  }
} 

返回的函數(shù)應(yīng)當遵循 RFC#28 中提出的函數(shù)簽名。你可能注意到了 setup() 和其返回的渲染函數(shù)的第一個參數(shù)都是 props —— 它們的行為是一樣的,但是渲染函數(shù)接收到的 props 在生產(chǎn)模式下將會是一個普通對象,因此它的性能會更好些。

和 2.x 一樣的 render 選項也可以使用,但如果用了 setup(),就應(yīng)該盡量使用內(nèi)聯(lián)返回的渲染函數(shù),因為這樣可以避免先返回一堆綁定然后再在另一個函數(shù)里解構(gòu)出來,同時類型推導(dǎo)也會更簡單直接一些。

Computed Value (計算值)

除了直接包裝一個可變的值,我們也可以包裝通過計算產(chǎn)生的值:

import { ref, computed } from 'vue'
const count = ref(0)
const countPlusOne = computed(() => count.value + 1)
console.log(countPlusOne.value) // 1
count.value++
console.log(countPlusOne.value) // 2

計算值的行為跟計算屬性 (computed property) 一樣:只有當依賴變化的時候它才會被重新計算。

computed() 返回的是一個只讀的包裝對象,它可以和普通的包裝對象一樣在 setup() 中被返回 ,也一樣會在渲染上下文中被自動展開。默認情況下,如果用戶試圖去修改一個只讀包裝對象,會觸發(fā)警告。

雙向計算值可以通過傳給 computed 第二個參數(shù)作為 setter 來創(chuàng)建:

const count = value(0)
const writableComputed = computed(
  // read
  () => count.value + 1,
  // write
  val => {
    count.value = val - 1
  }
)

Watchers

watch() API 提供了基于觀察狀態(tài)的變化來執(zhí)行副作用的能力。

watch() 接收的第一個參數(shù)被稱作 “數(shù)據(jù)源”,它可以是:

  • 一個返回任意值的函數(shù)
  • 一個包裝對象
  • 一個包含上述兩種數(shù)據(jù)源的數(shù)組

第二個參數(shù)是回調(diào)函數(shù)?;卣{(diào)函數(shù)只有當數(shù)據(jù)源發(fā)生變動時才會被觸發(fā):

watch(
  // getter
  () => count.value + 1,
  // callback
  (value, oldValue) => {
    console.log('count + 1 is: ', value)
  }
)
// -> count + 1 is: 1
count.value++
// -> count + 1 is: 2

和 2.x 的 $watch 有所不同的是,watch() 的回調(diào)會在創(chuàng)建時就執(zhí)行一次。這有點類似 2.x watcher 的 immediate: true 選項,但有一個重要的不同:默認情況下 watch() 的回調(diào)總是會在當前的 renderer flush 之后才被調(diào)用 —— 換句話說,watch()的回調(diào)在觸發(fā)時,DOM 總是會在一個已經(jīng)被更新過的狀態(tài)下。 這個行為是可以通過選項來定制的。

在 2.x 的代碼中,我們經(jīng)常會遇到同一份邏輯需要在 mounted 和一個 watcher 的回調(diào)中執(zhí)行(比如根據(jù)當前的 id 抓取數(shù)據(jù)),3.0 的 watch() 默認行為可以直接表達這樣的需求。

觀察 props

上面提到了 setup() 接收到的 props 對象是一個可觀測的響應(yīng)式對象:

const MyComponent = {
  props: {
    id: Number
  },
  setup(props) {
    const data = ref(null)
    watch(() => props.id, async (id) => {
      data.value = await fetchData(id)
    })
    return {
      data
    }
  }
}

觀察包裝對象

watch()可以直接觀察一個包裝對象:

// double 是一個計算包裝對象
const double = computed(() => count.value * 2)
watch(double, value => {
  console.log('double the count is: ', value)
}) // -> double the count is: 0
count.value++ // -> double the count is: 2

觀察多個數(shù)據(jù)源

watch() 也可以觀察一個包含多個數(shù)據(jù)源的數(shù)組 - 這種情況下,任意一個數(shù)據(jù)源的變化都會觸發(fā)回調(diào),同時回調(diào)會接收到包含對應(yīng)值的數(shù)組作為參數(shù):

watch(
  [refA, () => refB.value],
  ([a, b], [prevA, prevB]) => {
    console.log(`a is: ${a}`)
    console.log(`b is: $`)
  }
)

停止觀察

watch() 返回一個停止觀察的函數(shù):

const stop = watch(...)
// stop watching
stop()

如果 watch() 是在一個組件的 setup() 或是生命周期函數(shù)中被調(diào)用的,那么該 watcher 會在當前組件被銷毀時也一同被自動停止:

export default {
  setup() {
    // 組件銷毀時也會被自動停止
    watch(/* ... */)
  }
}

清理副作用

有時候當觀察的數(shù)據(jù)源變化后,我們可能需要對之前所執(zhí)行的副作用進行清理。舉例來說,一個異步操作在完成之前數(shù)據(jù)就產(chǎn)生了變化,我們可能要撤銷還在等待的前一個操作。為了處理這種情況,watcher 的回調(diào)會接收到的第三個參數(shù)是一個用來注冊清理操作的函數(shù)。調(diào)用這個函數(shù)可以注冊一個清理函數(shù)。清理函數(shù)會在下屬情況下被調(diào)用:

  • 在回調(diào)被下一次調(diào)用前
  • 在 watcher 被停止前
watch(idValue, (id, oldId, onCleanup) => {
  const token = performAsyncOperation(id)
  onCleanup(() => {
    // id 發(fā)生了變化,或是 watcher 即將被停止.
    // 取消還未完成的異步操作。
    token.cancel()
  })
})

之所以要用傳入的注冊函數(shù)來注冊清理函數(shù),而不是像 React 的 useEffect 那樣直接返回一個清理函數(shù),是因為 watcher 回調(diào)的返回值在異步場景下有特殊作用。我們經(jīng)常需要在 watcher 的回調(diào)中用 async function 來執(zhí)行異步操作:

const data = ref(null)
watch(getId, async (id) => {
  data.value = await fetchData(id)
})

我們知道 async function 隱性地返回一個 Promise - 這樣的情況下,我們是無法返回一個需要被立刻注冊的清理函數(shù)的。除此之外,回調(diào)返回的 Promise 還會被 Vue 用于內(nèi)部的異步錯誤處理。

Watcher 回調(diào)的調(diào)用時機

默認情況下,所有的 watcher 回調(diào)都會在當前的 renderer flush 之后被調(diào)用。這確保了在回調(diào)中 DOM 永遠都已經(jīng)被更新完畢。如果你想要讓回調(diào)在 DOM 更新之前或是被同步觸發(fā),可以使用 flush 選項:

watch(
  () => count.value + 1,
  () => console.log(`count changed`),
  {
    flush: 'post', // default, fire after renderer flush
    flush: 'pre', // fire right before renderer flush
    flush: 'sync' // fire synchronously
  }
)

全部的 watch 選項(TS 類型聲明)

interface WatchOptions {
  lazy?: boolean
  deep?: boolean
  flush?: 'pre' | 'post' | 'sync'
  onTrack?: (e: DebuggerEvent) => void
  onTrigger?: (e: DebuggerEvent) => void
}
interface DebuggerEvent {
  effect: ReactiveEffect
  target: any
  key: string | symbol | undefined
  type: 'set' | 'add' | 'delete' | 'clear' | 'get' | 'has' | 'iterate'
}
  • lazy與 2.x 的 immediate 正好相反
  • deep與 2.x 行為一致
  • onTrack 和 onTrigger 是兩個用于 debug 的鉤子,分別在 watcher 追蹤到依賴和依賴發(fā)生變化的時候被調(diào)用,獲得的參數(shù)是一個包含了依賴細節(jié)的 debugger event。

生命周期函數(shù)

所有現(xiàn)有的生命周期鉤子都會有對應(yīng)的 onXXX 函數(shù)(只能在 setup() 中使用):

import { onMounted, onUpdated, onUnmounted } from 'vue'
const MyComponent = {
  setup() {
    onMounted(() => {
      console.log('mounted!')
    })
    onUpdated(() => {
      console.log('updated!')
    })
    // destroyed 調(diào)整為 unmounted
    onUnmounted(() => {
      console.log('unmounted!')
    })
  }
}

依賴注入

import { provide, inject } from 'vue'
const CountSymbol = Symbol()
const Ancestor = {
  setup() {
    // providing a ref can make it reactive
    const count = ref(0)
    provide(CountSymbol, count)
  }
}
const Descendent = {
  setup() {
    const count = inject(CountSymbol)
    return {
      count
    }
  }
}

如果注入的是一個包裝對象,則該注入綁定會是響應(yīng)式的(也就是說,如果 Ancestor 修改了 count,會觸發(fā) Descendent 的更新)。

類型推導(dǎo)

為了能夠在 TypeScript 中提供正確的類型推導(dǎo),我們需要通過一個函數(shù)來定義組件:

import { defineComponent, ref } from 'vue'
const MyComponent = defineComponent({
  // props declarations are used to infer prop types
  props: {
    msg: String
  },
  setup(props) {
    props.msg // string | undefined
    // bindings returned from setup() can be used for type inference
    // in templates
    const count = ref(0)
    return {
      count
    }
  }
})

defineComponent 從概念上來說和 2.x 的 Vue.extend 是一樣的,但在 3.0 中它其實是單純?yōu)榱祟愋屯茖?dǎo)而存在的,內(nèi)部實現(xiàn)是個 noop(直接返回參數(shù)本身)。它的返回類型可以用于 TSX 和 Vetur 的模版自動補全。如果你使用單文件組件,則 Vetur 可以自動隱式地幫你添加這個調(diào)用。

如果你使用手寫 render 函數(shù)或是 TSX,那么你可以在 setup() 當中直接返回一個渲染函數(shù)(注意這里不需要任何手動的類型聲明):

import { defineComponent, ref, h } from 'vue'
const MyComponent = defineComponent({
  props: {
    msg: String
  },
  setup(props) {
    const count = ref(0)
    return () => h('div', [
      h('p', `msg is ${props.msg}`),
      h('p', `count is ${count.value}`)
    ])
  }
}) 

純 TypeScript 的 Props 類型聲明

3.0 的 props 選項不是必須的,如果你不需要運行時的 props 類型檢查,你也可以選擇完全在 TypeScript 的類型層面聲明 props 的類型:

import { defineComponent, h } from 'vue'
interface Props {
  msg: string
}
const MyComponent = defineComponent({
  setup(props: Props) {
    return () => h('div', props.msg)
  }
})

如果不需要除了 setup 之外的選項,甚至可以直接傳一個函數(shù)給 defineComponent

const MyComponent = createComponent((props: { msg: string }) => {
  return () => h('div', props.msg)
}) 

這里返回的 MyComponent 也可以在 TSX 中提供正確的 props 補全和推導(dǎo)。

Required Props

Props 默認都是可選的,也就是說它們的類型都可能是 undefined。非可選的 props 需要聲明 required: true :

import { defineComponent } from 'vue'
defineComponent({
  props: {
    foo: {
      type: String,
      required: true
    },
    bar: {
      type: String
    }
  },
  setup(props) {
    props.foo // string
    props.bar // string | undefined
  }
})

復(fù)雜 Props 類型

Vue 提供的 PropType 類型可以用來聲明任意復(fù)雜度的 props 類型,但需要用 as any 進行一次強制類型轉(zhuǎn)換:

import { defineComponent, PropType } from 'vue'
defineComponent({
  props: {
    options: (null as any) as PropType<{ msg: string }>
  },
  setup(props) {
    props.options // { msg: string } | undefined
  }
}) 

依賴注入類型

依賴注入的 inject 方法是唯一必須手動聲明類型的 API:

import { defineComponent, inject, Ref } from 'vue'
defineComponent({
  setup() {
    const count: Ref<number> = inject(CountSymbol)
    return {
      count
    }
  }
})

這里的 Ref 類型即是包裝對象的類型 ,通過泛型參數(shù)來聲明其內(nèi)部包裝的值的類型。

缺點/潛在問題

新的 API 使得動態(tài)地檢視/修改一個組件的選項變得更困難(原來是一個對象,現(xiàn)在是一段無法被檢視的函數(shù)體)。

這可能是一件好事,因為通常在用戶代碼中動態(tài)地檢視/修改組件是一類比較危險的操作,對于運行時也增加了許多潛在的邊緣情況(特別是組件繼承和使用 mixin 的情況下)。新 API 的靈活性應(yīng)該在絕大部分情況下都可以用更顯式的代碼達成同樣的結(jié)果。

缺乏經(jīng)驗的用戶可能會寫出 “面條代碼”,因為新 API 不像舊 API 那樣強制將組件代碼基于選項切分開來。

我們在 Class API RFC 和內(nèi)部討論中聽到過好幾次這樣的聲音,但我認為這是一種沒有必要的擔(dān)憂。雖然理論上新的 API 確實制約更少,但我認為 “面條代碼” 的情況不太可能發(fā)生,這里詳細解釋一下。

基于函數(shù)的新 API 和基于選項的舊 API 之間的最大區(qū)別,就是新 API 讓抽取邏輯變得非常簡單 —— 就跟在普通的代碼中抽取函數(shù)一樣。也就是說,我們不必只在需要復(fù)用邏輯的時候才抽取函數(shù),也可以單純?yōu)榱烁玫亟M織代碼去抽取函數(shù)。

基于選項的代碼只是看上去更整潔。一個復(fù)雜的組件往往需要同時處理多個不同的邏輯任務(wù),每個邏輯任務(wù)所涉及的代碼在選項 API 下是被分散在多個選項之中的。舉例來說,從服務(wù)端抓取一份數(shù)據(jù),可能需要用到 propsdata()mounted 和 watch。極端情況下,如果我們把一個應(yīng)用中所有的邏輯任務(wù)都放在一個組件里,這個組件必然會變得龐大而難以維護,因為每個邏輯任務(wù)的代碼都被選項切成了多個碎片分散在各處。

對比之下,基于函數(shù)的 API 讓我們可以把每個邏輯任務(wù)的代碼都整理到一個對應(yīng)的函數(shù)中。當我們發(fā)現(xiàn)一個組件變得過大時,我們會將它切分成多個更小的組件;同樣地,如果一個組件的 setup() 函數(shù)變得很復(fù)雜,我們可以將它切分成多個更小的函數(shù)。而如果是基于選項,則無法做到這樣的切分,因為用 mixin 只會讓事情變得更糟糕。

從這個角度看,基于選項 vs. 基于函數(shù)就好像基于 HTML/CSS/JS 組織代碼 vs. 基于單文件組件來組織代碼。

升級策略

新的 API 和 2.x 的 API 完全兼容(只是多了一個 setup()選項) ,并且可以一起使用。新 API 適合在組件復(fù)雜度明顯的情況下用來更好的組織和復(fù)用邏輯,或是用來提供可高度復(fù)用的插件。

理論上,新 API 可以完全提供 2.x API 的全部能力,因此我們可能會提供一個可選的編譯時選項,啟用后可以去掉所有僅為 2.x API 支持而存在的代碼,減少一部分體積和性能開銷。

附錄

與 React Hooks 的對比

這里提出的 API 和 React Hooks 有一定的相似性,具有同等的基于函數(shù)抽取和復(fù)用邏輯的能力,但也有很本質(zhì)的區(qū)別。React Hooks 在每次組件渲染時都會調(diào)用,通過隱式地將狀態(tài)掛載在當前的內(nèi)部組件節(jié)點上,在下一次渲染時根據(jù)調(diào)用順序取出。而 Vue 的 setup() 每個組件實例只會在初始化時調(diào)用一次 ,狀態(tài)通過引用儲存在 setup() 的閉包內(nèi)。這意味著基于 Vue 的函數(shù) API 的代碼:

  • 整體上更符合 JavaScript 的直覺;
  • 不受調(diào)用順序的限制,可以有條件地被調(diào)用;
  • 不會在后續(xù)更新時不斷產(chǎn)生大量的內(nèi)聯(lián)函數(shù)而影響引擎優(yōu)化或是導(dǎo)致 GC 壓力;
  • 不需要總是使用 useCallback 來緩存?zhèn)鹘o子組件的回調(diào)以防止過度更新;
  • 不需要擔(dān)心傳了錯誤的依賴數(shù)組給 useEffect/useMemo/useCallback 從而導(dǎo)致回調(diào)中使用了過期的值 —— Vue 的依賴追蹤是全自動的。

注:React Hooks 的開創(chuàng)性毋庸置疑,也是本提案的靈感來源。Hooks 代碼和 JSX 并置使得對值的使用更簡潔也是其優(yōu)點,但其設(shè)計確實存在上述問題,而 Vue 的響應(yīng)式系統(tǒng)恰巧能夠讓我們繞過這些問題。

Class API 的類型問題

Class API 提案的主要目的是尋找一個能夠提供更好的 TypeScript 支持的組件聲明方式。但是由于 Vue 需要將來自多個選項的屬性混合到同一個渲染上下文上,這使得即使用了 Class,要得到良好的類型推導(dǎo)也不是很容易。

以 props 的類型推導(dǎo)為例。要將 props 的類型 merge 到 class 的 this 上,我們有兩個選擇:用 class 的泛型參數(shù),或是用 decorator。

這是用泛型參數(shù)的例子:

interface Props {
  message: string
}
class App extends Component<Props> {
  static props = {
    message: String
  }
} 

由于泛型參數(shù)是純類型層面的,所以我們還需要額外地進行一次運行時的 props 選項聲明來獲得正確的行為。這就導(dǎo)致需要進行雙重聲明。

使用 decorator 的例子如下:

class App extends Component<Props> {
  @prop message: string
}

Decorators 存在如下問題:

  • ES 的 decorator 提案仍然在 stage-2 且極其不穩(wěn)定。過去一年內(nèi)已經(jīng)經(jīng)歷了兩次徹底大改,且和 TS 現(xiàn)有的實現(xiàn)已經(jīng)完全脫節(jié)?,F(xiàn)在引入一個基于 TS decorator 實現(xiàn)的 API 風(fēng)險太大。
  • Decorator 只能聲明 class this 上的屬性,卻無法將某一類 decorator 聲明的屬性歸并到一個對象上(比如 $props),這就導(dǎo)致 this.$props 無法被推導(dǎo),且影響 TSX 的使用。
  • 用戶很可能會覺得可以用 @prop message: string = 'foo'這樣的寫法去聲明默認值,但事實上技術(shù)層面無法做到符合語義的實現(xiàn)。

最后,class 還有一個問題,那就是目前 class method 不支持參數(shù)的 contextual typing,也就是說我們無法基于 class 本身的 fields 來推導(dǎo)某個 method 的參數(shù)類型,需要用戶自己去聲明。

以上就是Vue開發(fā)手冊Function-based API RFC的詳細內(nèi)容,更多關(guān)于Vue Function-based API RFC的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Vue狀態(tài)管理工具Pinia的安裝與使用教程

    Vue狀態(tài)管理工具Pinia的安裝與使用教程

    這篇文章主要介紹了Vue狀態(tài)管理工具Pinia的安裝與使用,一步一步學(xué)習(xí)如何將pinia運用到項目實戰(zhàn)中去,文中有詳細的安裝教程和使用方法,并通過代碼示例講解的非常詳細,需要的朋友可以參考下
    2024-03-03
  • vue實現(xiàn)購物車列表

    vue實現(xiàn)購物車列表

    這篇文章主要為大家詳細介紹了vue實現(xiàn)購物車列表,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-06-06
  • Element控件Tree實現(xiàn)數(shù)據(jù)樹形結(jié)構(gòu)的示例代碼

    Element控件Tree實現(xiàn)數(shù)據(jù)樹形結(jié)構(gòu)的示例代碼

    我們在開發(fā)中肯定會遇到用樹形展示數(shù)據(jù)的需求,本文主要介紹了Element控件Tree實現(xiàn)數(shù)據(jù)樹形結(jié)構(gòu)的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-08-08
  • 關(guān)于Vue-cli3煩人的eslint問題

    關(guān)于Vue-cli3煩人的eslint問題

    這篇文章主要介紹了關(guān)于Vue-cli3煩人的eslint問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-09-09
  • Vue 列表上下過渡效果的實例代碼

    Vue 列表上下過渡效果的實例代碼

    最近有個需求,一個列表上下移動要有簡單過渡效果。本文通過實例代碼給大家介紹Vue 列表上下過渡效果,需要的朋友可以參考下
    2019-06-06
  • Vue+Element樹形表格實現(xiàn)拖拽排序示例

    Vue+Element樹形表格實現(xiàn)拖拽排序示例

    本文主要介紹了Vue+Element樹形表格實現(xiàn)拖拽排序示例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-08-08
  • 詳解vue如何使用rules對表單字段進行校驗

    詳解vue如何使用rules對表單字段進行校驗

    這篇文章主要介紹了詳解vue如何使用rules對表單字段進行校驗,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-10-10
  • Vue3?Reactive響應(yīng)式原理邏輯詳解

    Vue3?Reactive響應(yīng)式原理邏輯詳解

    這篇文章主要介紹了Vue3?Reactive響應(yīng)式原理邏輯詳解,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下
    2022-07-07
  • 使用vue3+ts打開echarts的正確方式

    使用vue3+ts打開echarts的正確方式

    這篇文章主要給大家介紹了關(guān)于使用vue3+ts打開echarts的正確方式,在Vue3中使用ECharts組件可以方便地創(chuàng)建各種數(shù)據(jù)可視化圖表,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2023-12-12
  • vue3中vite的@路徑別名與path中resolve實例詳解

    vue3中vite的@路徑別名與path中resolve實例詳解

    這篇文章主要給大家介紹了關(guān)于vue3中vite的@路徑別名與path中resolve的相關(guān)資料,文中通過實例代碼介紹的非常詳細,對大家學(xué)習(xí)或者使用vue具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2023-02-02

最新評論