想無縫鏈接 Vue3.0,先體驗一下 composition-api

閱讀本文你能夠了解到html

  1. 提早感覺一下 Vue3.x(以 composition-api 爲例)
  2. composition-api 解決了什麼問題
  3. composition-api 配合 TypeScript 的使用
  4. 對 Composition-api 理解的最佳實踐

Vue3.x 的改動(介紹一些比較常見的)

Vue3.0 的 RFC 提出來也有一段時間了。從剛開始的 function-base-api 到如今的 composition-api。這二者都有一個共同的目標就是將 2.x 中與邏輯相關的選項都以函數形式抽離出來vue

  1. ref 和 reactive

這兩個都能定義響應式數據,在這裏其實有那麼點區別,先舉個例子node

const x = ref<number>(0)
const y = ref<string>("Hello World")
// x y的類型是 Ref<number> | Ref<string>

const state = reactive({
    text:"Hello World"
})
複製代碼

這裏的 Ref 類型是包裝類型的意思。之前的 Vue2.x 直接監聽的是字符串,還有數字的變化。由於這些值都是做爲 data 的屬性值存在,監聽的實際上是 data 對象的屬性值。可是如今單獨在函數中返回的字符串,和數字等基本數字類型,不存在引用關係,是沒法追蹤原始變量後續的變化的。因此包裝對象的意義就在於提供一個讓咱們可以在函數之間以引用的方式傳遞任意類型值的容器。容器的存在能夠經過引用的方式傳回給組件。react

state 跟原來的 Vue2.x 的就很很相似,存在於 state 對象上,監聽的是 state 屬性的變化。ajax

怎麼去選擇定義數據的方法?其實很簡單,均可以,看我的習慣問題。api

const state = reactive({
    user:{
        name:"A",
        age:10,
        where:"china"
    }
})
// 至關於
const state = {
    name :"A",
    age:10,
    where:"china"
}
const userName = ref<string>("A")
const userAge = ref<number>(10)
const userWhere = ref<string>("china")
// 至關於
const name = "A";
const age = 10;
const where = 'china'
複製代碼

⚠️ 注意在使用 Ref 的時候,在模板會自動拆箱,可是在函數裏面,傳遞的是對象。因此要取值的時候記得要加 ref.value 才能取值數組

2.watchpromise

watch 用過的同窗都知道是用來觀察數據變化進行對應的操做的。此次的改動是將原來的 Options 的寫法換成函數的形式進行調用。async

watch 的第一個參數能夠是返回任意值的函數,一個包裝對象,還有是包含兩種數據的數組函數

...
const state = reactive<{count:number}>({
    count:1,
})
watch(
  () => state.count,
  (cur,pre) => {
      // 觀察的是 state對象裏面的一個值在這裏進行操做
  },
);
...
...
const name = ref<string>("Hello World")
watch(
  () => name,
  (cur,pre) => {
      // do other things
  },
);
...
複製代碼

⚠️ 注意的是包裝對象不能用函數返回,由於是兩種不一樣類型的觀察值,所對應的操做也是不同的。若是用函數返回,則沒有監聽的效果。(雖然你看到的值是改變了,可是你.value 事後仍然是初始的值)

⚠️ 注意 watch 的回調會在建立的時候就使用,相似於 2.x 設置了immediate: true,watch 的觸發老是會在 DOM 更新完的狀況下。因此說若是想要在 DOM 更新前就得設置flush選項

✏️ watch 還可以中止監聽,這是 2.x 沒有的

const stop = watch(()=>state.count)

// watch函數返回的是一個 中止觀察數據的函數。 調用一下就能中止觀察了
複製代碼

❤️ 特別說明一下:若是 watch 實在setup或者是生命週期函數裏面調用。watch會在銷燬組件的時候自動中止

3.setup 函數和生命週期

Vue3.x 的生命週期發生了些變化,廢棄了原來的createdbeforeCreated的生命週期,在這二者之間新增了一個組件選項setup函數。

// setup是在初始化props後面調用的,因此會接受props爲參數。context是整個組件的上下文。之前都是用this類指向Vue組件,如今換成函數就用參數的形式傳遞上下文。
...
setup(props:Props,context:SetupContext){
    onMounted()

    onUpdate();

    onUnMounted();
}
...
複製代碼

生命週期都改了名字。而且以函數 Api 的形式,更重要的是隻能在 setup 函數裏面調用~!

⚠️ 注意的 props 不能進行結構賦值,也不能進行擴展運算符等破壞監聽值的操做。由於這些操做都會有中間量的生成致使破壞原有存在的監聽系統。要用的話就得在state上面使用toRefs方法。這個方法可讓對象不會破壞原來的監聽。同時 Props 也是不可修改的。

Composition-api 比較 2.x 解決了什麼痛點

組件間的邏輯複用

在用 Composition-api 的同時,也能夠用原來基於選項的 options 的作法。composition-api 是爲了解決一些 2.x 存在的問題。

vue 在應對簡單的業務的時候確實很遊刃有餘。可是在大型項目,涉及不少組件,或者是組件之間的邏輯複用的問題的時候,可能就有點棘手了。 在這以前可能都知道有 Mixins,HOC 等解決邏輯複用的手段。可是他們都或多或少有點問題。譬如:來源不清。命名空間衝突。須要額外的開銷等等。

composition-api 能夠將組件的邏輯封裝到一個函數裏面進行復用。

// 舉一個封裝的例子 判斷是否下拉到底

// useIsEnd.ts
...
const isEnd = ref<boolean>(false) // 初始化未到最底下

export function getEnd(node:Ref<HTMLElement>){
    // 已經封裝好的三個函數 判斷是否到底的 -10 是爲了有些狀況下會出現小數(在移動端內嵌的時候)
    if (
      getClientHeight(node) + (getScrollTop(node) as number) >=
      getScrollHeight(node) - 10
    ) {
      isEnd.value = true;
    } else {
      isEnd.value = false;
    }
    return {
      isEnd
    }
}
// 這裏將isEnd放在外面是爲了在觸發觸摸事件的時候(touch),不會每次都從新建立一個新的對象致使判斷失誤


// demo.vue
...
import { getEnd } from "useIsEnd";
export default defineComponent({
    ...
    setup(){
        const node = ref<HTMLElement>('node') // 獲取一個node
        ...
        // 這是@touchmove 事件
        function handleGetIsEnd(){
            const { isEnd } = getEnd(node) // 判斷當前是否到底
            // do other things like ajax
        }
        ...
        return {
            // 記得return
            node,
            handleGetIsEnd
        }
    }
    ...
})

...
複製代碼

✏️ 不存在來源不清晰,返回值還能夠從新定義,沒有命名空間的衝突。把全部的邏輯封裝成一個函數沒有額外的組件的開銷。還有能夠更好的組織代碼

更好的類型推導

Vue3.x 用 TypeScript 重寫以後,對的類型推導自然支持。再加上是函數的 APi 的緣故,類型推導更上一層樓。

更加小的尺寸

也是由於函數的緣由。對 tree shakeing 友好。每一個函數均可以單獨引入,而且也由於函數的 API 的緣由能夠壓縮函數名字達到極致的最小尺寸

TypeScript 配合 Composition-api

之前 Vue 項目假如要用 TypeScript,就得引入對應的插件(例如 vue-class-component or vue-property-decorator)。可是經過 TypeScript 重寫之後,就能夠直接使用 TypeScript。

用 TypeScript 也是想要 TypeScript 的類型推導,這裏很少贅述 TypeScript 的概念

// 這裏的interface可讓Ts提供提示
interface Props{
    name:string;
    age:number;
}

interface State{
    title:string;
    head:string;
}
...
// 類型推導須要用defineCompoent or createComponent
export default defineComponent({
...
    props:{
        name:String;
        age:Number;
    }
    ...
    setup(props:Props,context:SetupContext){
        ...
        const value = ref<string>(""); // ref 的類型是Ref<T>
        ...
        const state = reactive<State>({
            title:"",
            head:""
        })
        ...
        const { demo } = useXXX(...)
        demo();
        ...
        onMounted(async ()=>{
            const res = await fetchData(...);
            state.XXX = res.data.XXX
        })
        ...
        return {
            value,
            // 這樣的話 就能夠直接 使用 title / head
            ...toRefs(state)
        }
    }
...
})
複製代碼

TypeScript 和 Composition-api 最佳實踐

在使用 Composition-api 的時候咱們須要知道咱們用的目的是什麼。當咱們的邏輯很複雜的時候,能夠考慮使用它來幫咱們抽離邏輯複用。當咱們想更好的組織代碼的時候,咱們可使用它讓咱們的代碼更有條理性。

💪 實踐 1: 將 data(state)狀態跟方法抽離到一個文件中

// 之前多是會這樣寫
export default {
    ...
    data(){
        // 當數據很大的時候 這裏可能會很長 對應的每一條也須要寫長長的註釋
        return {
            ...
            key1:value1,
            ...
        }
    }
    ...
    methods:{
      // 方法這裏 可能會這樣寫, 假如方法過多 也會出現很冗餘的狀況,註釋雖然能夠幫助咱們很快的找到對應的代碼。可是 可能會出現幾百行的狀況
      handleXXX(){
        ... do otherthings
      }
    }
}
複製代碼

👍 如今咱們可能會這樣寫(一個函數包裹該函數的自身的狀態,僅僅維護本函數自身狀態)

// index.ts
interface InitState{
    key1:string;
    key2:number;
    key3:{
        'key3-1':string;
        'key3-2':boolean;
    }
}
export function init(){
    ...
    const initState = reactive<InitState>({
      key1:"";
      key2:0;
      key3:{
          'key3-1':"";
          'key3-2':false;
      }
    })
    // 這裏也能夠不watch 直接 外部 async 或者是 promise的then繼續執行後續操做也行
    const stop = watch(async ()=>{
        const res = await initData(params);
        const { key1,key2,key3 } = res.data;
        state.key1 = key1;
        state.key2 = key2;
        state.key3 = key3;
    })
    stop(); // 假如不放在setup 裏面的 或者是生命週期的 不會自動回收
    ...
    return {
      // 記得加上toRefs就不會丟失響應式了
      ...toRefs(initState)
    }
}

// index.vue
template
...
<script lang="ts">
import { defineComponent, reactive, SetupContext, toRefs } from '@vue/composition-api';
import { init } from "./index"

export default defineComponent({
  ...
  // 在某些狀況下 context 能夠進行解構 拿出你想要的 或者只是一個ui組件沒有邏輯能夠不傳
  /** 常見的解構的類型 * {{ root }: { root: ComponentInstance } * { root: { $router } }: { root: { $router: VueRouter } } * { root: { $store } }: { root: { store: Store } } */
  setup(props:Props,context:SetupContext){
    const initState = init();
    return {
      // 這裏是爲了更方便的取值
      ...toRefs(initState)
    }
  }
  ...
})
<script>
...
style
複製代碼

✏️ 這裏只有一種狀況,就是僅僅是初始化的時候的 state。這裏能夠解構賦值,可是我我的認爲 直接用一個對象包裹以後,減小沒必要要的命名衝突問題。這裏表示的是 init 的時候的 state,後面可能會還有別的 state。

💪 實踐 2: 別的請求的狀況(value 值不會立刻拿到的狀況)

// index.ts
// 這裏的value不能直接拿到,須要等待的話 都須要用watch 等待改變以後再進行請求
...
function getSomeData(value) {
  const xxxState = reactive<XXXState>({
    key1:''
  })
  const stop = watch(
    () => value,
    async (cur, pre) => {
      const res = await fetchData(cur);
      xxxState.key1 = res.data
    },
  );
  // 這裏的假如value值會變的 不會只用一次的,後續可能會變化的 就不須要 stop了
  return {
    ...toRefs(xxxState)
  }
}
...
複製代碼

💪 實踐 3: 初始化後或者其餘方法初始化的 state 後續別的方法用到,該如何修改

// index.ts
/** * 這裏可能有三種方法。第一將初始化的state放到全局上 * 就好像操做全局的變量同樣操做全局的state,這樣的作法是簡單,這樣作的後果就是後面須要查 * 現的問題的時候就不知道哪些函數有操做對應的state,致使難以定位問題。 */
const initState = reactive({
  key1:1;
})
async function init(){
  const { key1 } = await fetchData();
  initState.key1 = key1;
}
// 這個方法我想改變初始化的state
function handleChangeInitState(){
  const { key1 } = await fetchData();
  initState.key1 = key1;
}
複製代碼
// index.ts
/** * 第二種 將初始化的state返回後,須要修改的將state做爲參數進行傳入修改 * 這樣作比第一種好一點。知道是哪些函數修改,就直接看對應的函數就行,可是假如後續有不少須要修改的狀況,就得傳不少次參數形成冗餘。 */
async function init(){
  const initState = reactive({
    key1:1;
  })
  const { key1 } = await fetchData();
  initState.key1 = key1;
  return {
    initState,
  }
}

function handleChangeInitState(initState){
  const { key1 } = await fetchData();
  initState.key1 = key1;
}
複製代碼
// index.ts
/** * 最後一種 借鑑react-hook的思想,對外暴露操做改state中某個屬性的方法 * 比較簡潔,沒有多餘的參數傳遞 */
async function init(){
  const initState = reactive({
    key1:1;
  })
  const { key1 } = await fetchData();
  initState.key1 = key1;

  function changeKey1(value){
    initState.key1 = value;
  }

  return {
    changeKey1,
    ...toRefs(initState),
  }
}

// index.vue
import { init } from "./index"
...
setup(props,context){
  const {changeKey1,initState} = init();
  ...
  // 這裏能夠作操做state的方法
  const { xxxState } = useXXX();
  changeKey1(xxxState.key1)
  ...
  return {
    ...toRefs(initState)
  }
}
...
複製代碼

總結

這是我的以爲 composition-api 的最佳實踐。組合函數就是能更好的組織代碼,將多層嵌套抽離出來分紅一個個組合函數,將其組合以後,就能讓代碼更加有條理了。

參考

  1. Vue Composition API(RPC)
  2. 探祕 Vue3.0 - Composition API 在真實業務中的嚐鮮姿式
相關文章
相關標籤/搜索