原文地址:iming.work/detail/5d7f…javascript
根據提案衍生的體驗包 @vue/composition-api
咱們能夠對 v3.0
的思想加以實踐。html
實踐內容:實現一個 todo list,且列表可拖拽。demo:示例詳情vue
type TodoItem = {
id: number
content: string
status: 0 | 1
}
type TodoList = Array<TodoItem>
複製代碼
文件目錄java
.
├── App.vue
├── assets
│ ├── drag.ts
├── components
│ ├── item-input.vue
│ └── item-list.vue
├── main.ts
└── views
└── home.vue
複製代碼
咱們將輸入框和列表分爲2個組件去處理,同時寫了一個 useDrag()
方法來處理拖拽。react
// 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 的本質。
在大概使用這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 的屬性」。
歡迎你們一塊兒交流