@vue/composition-api 實踐

原文地址:iming.work/detail/5d7f…javascript

根據提案衍生的體驗包 @vue/composition-api 咱們能夠對 v3.0 的思想加以實踐。html

實踐內容:實現一個 todo list,且列表可拖拽。demo:示例詳情vue

前期準備

  1. 定義列表結構
type TodoItem = {
  id: number
  content: string
  status: 0 | 1
}

type TodoList = Array<TodoItem>
複製代碼
  1. 定義佈局

文件目錄java

.
├── App.vue
├── assets
│   ├── drag.ts
├── components
│   ├── item-input.vue
│   └── item-list.vue
├── main.ts
└── views
    └── home.vue
複製代碼

咱們將輸入框和列表分爲2個組件去處理,同時寫了一個 useDrag() 方法來處理拖拽。react

  1. 源碼帶來的一個問題和思考
// home.vue
setup (props, context) {
  // 咱們選擇將 state 初始化時用一個大對象包裹起來
  // 而不是零散的使用 ref、reactive
  const data = reactive({
    selected: null,
    list: [
      {
        id: 1,
        content: '計劃內容、幹什麼事情',
        status: 1
      }
    ]
  })
  const delItem = (item: TodoItem) => {}
  const changeIndex = (nI: number, oI: number) => {}
  const addItem = (item: TodoItem) => {}

  watch(() => data.list, () => {
    console.log('====變化了')
  }, { lazy: true })

  return {
    // 選擇使用大對象包裹,在解構以後是會丟失響應式的,可使用 toRefs 將大對象裏的屬性添加引用包裹
    ...toRefs(data),
    delItem,
    changeIndex,
    addItem
  }
}
複製代碼

其實,這裏也是我在實踐時發現的一個問題,即:數組不能被 reactive ?git

對於 data 的包裹徹底是沒有必要的,由於能夠:github

const selected = ref(null)
const list = reactive([
  // { ... }
])
複製代碼

但在實際的操做中,爲 list push 一條記錄是不會觸發 watch 的api

setup () {
  const arr = reactive([1, 2])
  watch(() => arr, () => console.log('arr change'), { lazy: true })
  setTimeout(() => { arr.push(3) }, 1000)
}
複製代碼

另外,在 setup 中即便你沒有爲對象或屬性添加響應式,將其 return 後,響應式也會被自動添加。數組

例如bash

<template>
  <p @click="a = { b: 2 }">{{ a.b }}</p>
</template>

<script> export default { setup () { const a = { b: 1 } return { a } } } </script>
複製代碼

這個例子中,對象 a 就被自動添加了響應式,模版也會被更新。vue 源碼以下:

var binding = setup(props, ctx);

if (isPlainObject(binding)) {
    var bindingObj_1 = binding;
    vmStateManager.set(vm, 'rawBindings', binding);
    // 遍歷返回值
    Object.keys(binding).forEach(function (name) {
      var bindingValue = bindingObj_1[name];
      // only make primitive value reactive
      if (!isRef(bindingValue)) {
          if (isReactive(bindingValue)) {
            bindingValue = ref(bindingValue);
          }
          else {
            // a non-reactive should not don't get reactivity
            bindingValue = ref(nonReactive(bindingValue));
          }
      }
      asVmProperty(vm, name, bindingValue);
    });
    return;
}
複製代碼

因此在 todo list 這個 demo 中,即便你的 list 不是 reactive 的,在點擊 addItem push 後,template依舊會更新,但不理解的是這樣作 watch 無效。由於我發現,使用 2.0 的 api watch 這個 list 是沒問題的。

這不得不讓我去思考 reactive 和 ref 的本質。

reactive 和 ref

在大概使用這2個 api 後不難發現,對於簡單的數據類型咱們要使用 ref,而複雜數據類型要使用 reactive。

ref 的做用是爲簡單數據類型包裹一層對象,這樣就能夠爲對象設置 proxy,達到值的變化監聽。

function ref(raw) {
    var _a;
    // 包裹的空對象
    var value = reactive((_a = {}, _a[RefKey] = raw, _a));
    return createRef({
        get: function () { return value[RefKey]; },
        set: function (v) { return (value[RefKey] = v); },
    });
}
複製代碼

reactive 的本質就是調用 2.0 api 裏的 observe()

function reactive(obj) {
  // ...
  var observed = Vue.observable(obj);
  return observed
}
複製代碼

這樣就會獲得一個被監聽對象的引用。上面咱們說過,即便這個對象沒有被 reactive,setup return 以後都會自動添加,那這樣作的意義?

很明顯,獲得這個引用,咱們能夠單獨爲這個引用作處理,例如添加 watch,或者 computed,從而實現業務邏輯上的抽象與解耦。

⚠️可是,仍是沒有解決數組被 reactive 後,watch 不到的問題。。。

解耦業務代碼

咱們思考一下 3.0 的目的,我以爲一方面是爲了迎合當下流行的思想,提高知名度與活躍度;另外一方面,主要仍是爲了能更好的抽象與組織代碼,充分利用 tree shaking 和 支持 ts。

利用函數的組合,移除了 mixins 中變量衝突與未知來源的問題,也拿掉了麪條式的還要記屬性順序的固定的選項,固然,我以爲對於更初級的開發者會更習慣於 2.0 的 api 中明確的界限,例如 state 就放 data 裏,method 就放 methods 裏等。對於組合 api 式的都在 setup 中寫更考驗開發者的邏輯組合能力。

例如 todo list 中的拖拽 useDrag(),咱們看看 /components/item-list.vue 文件中的 setup

// 接受了 props 列表數據
setup ({ list }: { [k: string]: unknown }, context: SetupContext) {
  const handleSelect = (item: TodoItem) => {
    context.emit('input', item)
  }
  const handleComplete = (item: TodoItem) => {
    // 我發現這裏能夠直接修改? props,而沒有任何報錯或警告
    item.status = item.status === 1 ? 0 : 1
  }
  const handleDelete = (item: TodoItem) => {
    context.emit('del-item', item)
  }
  const handleChange = (newIndex: number, oldIndex: number) => {
    context.emit('change-index', newIndex, oldIndex)
  }
  // 經過 useDrag 咱們能夠獲得 2 個有用的信息
  // handleDown 是監聽 mousedown 事件的回調
  // finalPosition 是在 mousemove 的過程當中,當前被拖拽對象的位置
  // handleChange 是 mouseup 後,若是位置有變化就觸發的回調,其實也能夠返回而不用傳參進去
  const { handleDown, targetIndex, finalPosition } = useDrag(handleChange)

  return {
    handleSelect,
    handleComplete,
    handleDelete,
    handleDown,
    targetIndex,
    finalPosition
  }
}
複製代碼

因此在 template 中,咱們只須要簡單的這樣作就能夠,是否是更清晰一些呢?關於 drag 的細節,可查看drag.ts

<template>
  <div class="todo-list__item" :style="finalPosition" @mousedown="handleDown" >
   ...
  </div>
<template>
複製代碼

因而,咱們彷佛發現,函數式組合的 api 能夠提供咱們一種能力,在任何地方的任何一個函數裏,均可以寫出與 vue 生命週期、鉤子實例等息息相關的代碼,就好像咱們寫在 2.0 時代的選項 componentOptions 中同樣。

總結

有人說,這愈來愈像 react 了,確實,對於開創業界潮流 react 一直都是領先者,但你以爲真的是同樣嗎?因爲機制本質的不同,在 vue 中不須要那麼多的原生 hooks 來幫助你實現或優化某些特性,在 vue 中,被稱爲 hooks 我以爲是不合適的,由於它並非像鉤子、或者守衛同樣等待着被調用,它只會在 setup 被中調用一次,時機在 beforeCreate 和 created 之間。另外 mutable 的數據能夠很優雅的處理不少 react 中沒必要要的麻煩,但也會帶來一些問題,例如上面提到的「能夠直接修改 props 的屬性」。

最後

歡迎你們一塊兒交流

相關文章
相關標籤/搜索