Vue 3.0 前瞻,體驗 Vue Function API

最近 Vue 官方公佈了 Vue 3.0 最重要的RFC:Function-based component API,併發布了兼容 Vue 2.0 版本的 plugin:vue-function-api,可用於提早體驗 Vue 3.0 版本的 Function-based component API。筆者出於學習的目的,提早在項目中嘗試了vue-function-apijavascript

筆者計劃寫兩篇文章,本文爲筆者計劃的第一篇,主要爲筆者在體驗 Vue Function API 的學習心得。第二篇計劃寫閱讀vue-function-api的核心部分代碼原理,包括setupobservablelifecyclehtml

本文閱讀時間約爲15~20分鐘。vue

概述

Vue 2.x 及之前的高階組件的組織形式或多或少都會面臨一些問題,特別是在須要處理重複邏輯的項目中,一旦開發者組織項目結構組織得很差,組件代碼極有可能被人詬病爲「膠水代碼」。而在 Vue 2.x 及以前的版本,解決此類問題的辦法大體是下面的方案:java

筆者維護的項目也須要處理大量複用邏輯,在這以前,筆者一直嘗試使用mixin的方式來實現組件的複用。有些問題也一直會對開發者和維護者形成困惑,如一個組件同時mixin多個組件,很難分清對應的屬性或方法寫在哪一個mixin裏。其次,mixin的命名空間衝突也可能形成問題。難以保證不一樣的mixin不用到同一個屬性名。爲此,官方團隊提出函數式寫法的意見徵求稿,也就是RFC:Function-based component API。使用函數式的寫法,能夠作到更靈活地複用組件,開發者在組織高階組件時,沒必要在組件組織上考慮複用,能夠更好地把精力集中在功能自己的開發上。react

注:本文只是筆者使用 vue-function-api提早體驗 Vue Function API ,而這個 API 只是 Vue 3.0 的 RFC,而並不是與最終 Vue 3.x API 一致。發佈後可能有不一致的地方。

在 Vue 2.x 中使用

要想提早在Vue 2.x中體驗 Vue Function API ,須要引入vue-function-api,基本引入方式以下:git

import Vue from 'vue';
import { plugin as VueFunctionApiPlugin } from 'vue-function-api';

Vue.use(VueFunctionApiPlugin);

基本組件示例

先來看一個基本的例子:github

<template>
    <div>
        <span>count is {{ count }}</span>
        <span>plusOne is {{ plusOne }}</span>
        <button @click="increment">count++</button>
    </div>
</template>

<script>
import Vue from 'vue';
import { value, computed, watch, onMounted } from 'vue-function-api';

export default {
    setup(props, context) {
        // reactive state
        const count = value(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,
        };
    },
};
</script>

詳解

setup

setup函數是Vue Function API 構建的函數式寫法的主邏輯,當組件被建立時,就會被調用,函數接受兩個參數,分別是父級組件傳入的props和當前組件的上下文context。看下面這個例子,能夠知道在context中能夠獲取到下列屬性值:api

const MyComponent = {
    props: {
        name: String
    },
    setup(props, context) {
        console.log(props.name);
        // context.attrs
        // context.slots
        // context.refs
        // context.emit
        // context.parent
        // context.root
    }
}

value & state

value函數建立一個包裝對象,它包含一個響應式屬性value數組

那麼爲什麼要使用value呢,由於在JavaScript中,基本類型並無引用,爲了保證屬性是響應式的,只能藉助包裝對象來實現,這樣作的好處是組件狀態會以引用的方式保存下來,從而能夠被在setup中調用的不一樣的模塊的函數以參數的形式傳遞,既能複用邏輯,又能方便地實現響應式。併發

直接獲取包裝對象的值必須使用.value,可是,若是包裝對象做爲另外一個響應式對象的屬性,Vue內部會經過proxy來自動展開包裝對象。同時,在模板渲染的上下文中,也會被自動展開。

import { state, value } from 'vue-function-api';
const MyComponent = {
    setup() {
        const count = value(0);
        const obj = state({
            count,
        });
        console.log(obj.count) // 做爲另外一個響應式對象的屬性,會被自動展開

        obj.count++ // 做爲另外一個響應式對象的屬性,會被自動展開
        count.value++ // 直接獲取響應式對象,必須使用.value

        return {
            count,
        };
    },
    template: `<button @click="count++">{{ count }}</button>`,
};

若是某一個狀態不須要在不一樣函數中被響應式修改,能夠經過state建立響應式對象,這個state建立的響應式對象並非包裝對象,不須要使用.value來取值。

watch & computed

watchcomputed的基本概念與 Vue 2.x 的watchcomputed一致,watch能夠用於追蹤狀態變化來執行一些後續操做,computed用於計算屬性,用於依賴屬性發生變化進行從新計算。

computed返回一個只讀的包裝對象,和普通包裝對象同樣能夠被setup函數返回,這樣就能夠在模板上下文中使用computed屬性。能夠接受兩個參數,第一個參數返回當前的計算屬性值,當傳遞第二個參數時,computed是可寫的。

import { value, computed } from 'vue-function-api';

const count = value(0);
const countPlusOne = computed(() => count.value + 1);

console.log(countPlusOne.value); // 1

count.value++;
console.log(countPlusOne.value); // 2

// 可寫的計算屬性值
const writableComputed = computed(
    // read
    () => count.value + 1,
    // write
    val => {
        count.value = val - 1;
    },
);

watch第一個參數和computed相似,返回被監聽的包裝對象屬性值,不過另外須要傳遞兩個參數:第二個參數是回調函數,當數據源發生變化時觸發回調函數,第三個參數是options。其默認行爲與 Vue 2.x 有所不一樣:

  • lazy:是否會在組件建立時就調用一次回調函數,與 Vue 2.x 相反,lazy默認是false,默認會在組件建立時調用一次。
  • deep:與 Vue 2.x 的 deep 一致
  • flush:有三個可選值,分別爲 'post'(在渲染後,即nextTick後才調用回調函數),'pre'(在渲染前,即nextTick前調用回調函數),'sync'(同步觸發)。默認值爲'post'。
// 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

watch多個被包裝對象屬性時,參數都可以經過數組的方式進行傳遞,同時,與 Vue 2.x 的vm.$watch同樣,watch返回取消監聽的函數:

const stop = watch(
    [valueA, () => valueB.value],
    ([a, b], [prevA, prevB]) => {
        console.log(`a is: ${a}`);
        console.log(`b is: ${b}`);
    }
);

stop();
注意:在 RFC:Function-based component API初稿中,有提到 effect-cleanup,是用於清理一些特殊狀況的反作用的,目前已經在提案中被取消了。

生命週期

全部現有的生命週期都有對應的鉤子函數,經過onXXX的形式建立,但有一點不一樣的是,destoryed鉤子函數須要使用unmounted代替:

import { onMounted, onUpdated, onUnmounted } from 'vue-function-api';

const MyComponent = {
    setup() {
        onMounted(() => {
            console.log('mounted!');
        });
        onUpdated(() => {
            console.log('updated!');
        });
        // destroyed 調整爲 unmounted
        onUnmounted(() => {
            console.log('unmounted!');
        });
    },
};

一些思考

上面的詳解部分,主要抽取的是 Vue Function API 的常見部分,並不是RFC:Function-based component API的所有,例如其中的依賴注入,TypeScript類型推導等優點,在這裏,因爲篇幅有限,想要了解更多的朋友,能夠點開RFC:Function-based component API查看。我的也在Function-based component API討論區看到了更多地一些意見:

  • 因爲底層設計,在setup取不到組件實例this的問題,這個問題在筆者嘗試體驗時也遇到了,期待正式發佈的 Vue 3.x 可以改進這個問題。
  • 對於基本類型的值必須使用包裝對象的問題:在 RFC 討論區,爲了同時保證TypeScript類型推導、複用性和保留Vue的數據監聽,包裝屬性必須使用.value來取值是討論最激烈的
  • 關於包裝對象valuestate方法命名不清晰可能致使開發者誤導等問題,已經在Amendment proposal to Function-based Component API這個提議中展開了討論:
setup() {
    const state = reactive({
        count: 0,
    });

    const double = computed(() => state.count * 2);

    function increment() {
        state.count++;
    }

    return {
        ...toBindings(state), // retains reactivity on mutations made to `state`
        double,
        increment,
    };
}
  • 引入reactive API 和 binding API,其中reactive API 相似於 state API , binding API 相似於 value API。
  • 以前使用的方法名state在 Vue 2.x 中可能被用做組件狀態對象,致使變量命名空間的衝突問題,團隊認爲將state API 改名爲 reactive 更爲優雅。開發者可以寫出const state = ... ,而後經過state.xxxx這種方式來獲取組件狀態,這樣也相對而言天然一些。
  • value方法用於封裝基本類型時,確實會出現不夠優雅的.value的狀況,開發者可能會在直接對包裝對象取值時忘記使用.value,修正方案提出的 reactive API,其含義是建立響應式對象,初始化狀態state就使用reactive建立,可保留每項屬性的gettersetter,這麼作既知足類型推導,也能夠保留響應式引用,從而可在不一樣模塊中共享狀態值的引用。
  • reactive可能致使下面的問題,須要引入binding API。 解決,如使用reactive建立的響應式對象,對其使用拓展運算符...時,則會丟失對象的gettersetter,提供toBindings方法可以保留狀態的響應式。

下一篇文章中,筆者將閱讀vue-function-api的核心部分代碼原理,包括setupobservablelifecycle等,從內部探索 Vue Function API 可能帶給咱們的改變。

固然,目前 Vue Function API 還處在討論階段,Vue 3.0 還處在開發階段,仍是期待下半年 Vue 3.0 的第一版問世吧,但願能給咱們帶來更多的驚喜。

相關文章
相關標籤/搜索