iT邦幫忙

2021 iThome 鐵人賽

DAY 5
4
Modern Web

[ 重構倒數30天,你的網站不Vue白不Vue ] 系列 第 5

[重構倒數第26天] - 你可能不需要Vuex (You might not need Vuex)

  • 分享至 

  • xImage
  •  

前言

該系列是為了讓看過Vue官方文件或學過Vue但是卻不知道怎麼下手去重構現在有的網站而去規畫的系列文章,在這邊整理了許多我自己使用Vue重構很多網站的經驗分享給讀者們。

You might not need Vuex

我們在Vue要使用跨多個 component 做資料傳遞溝通的時候除了 Vuex 以外,在 Vue3 的時候我們有多了其他選擇,那就是 Provide / inject

vue mike

我們可以看到官網上面的這張圖就完整的表達 Provide / inject 的使用概念,你可以在父層使用provide來提供資料,然後再子 component 透過 inject 來使用這些資料,無論你的 component 結構有多深,你的父層都可以為你所有底下的所有的 component 去直接 inject。

我們來看看如何實作,先來看看資料夾結構,其他資料夾等我就先不放上來了

|-- src
    |-- App.vue         // 最上層的component
    |-- main.js         // 進入點
    |-- components
    |   |-- Content.vue // 一般的UI component
    |-- store
    |   |-- index.js    // 不是Vuex,是要使用 Composition API 存放資料的地方
    |-- views
        |-- Home.vue    // 首頁的頁面component

首先我們先來定義 store/index.js

import { reactive, readonly } from "vue";
const state = reactive({
  count: 0,
});
export default {
  state: readonly(state),
};

在這邊你會看到我透過 vue3 的reactive去定義我的資料,透過 vue 的方法可以確保我的資料被更動的時候可以被更新,當然你可以用 ref,就依照你的需求來決定,最後我在 export 的時候用了 readonly 函數包起來,就是要避免外面 inject 的時候不小心改到 state 的資料,至於要怎麼改資料我們等等來說,我們先來看要怎麼在component 去使用。

App.vue

<script>
import { provide } from "vue";
import store from "@/store";
import Home from "@/views/Home.vue";
export default {
  components: {
    Home,
  },
  setup() {
    provide("mapStore", store);
  },
};
</script>

我在 setup 裡面將我們 provide 命名為 mapStore ( 借鏡一下 Vuex 的命名,這樣從 Vuex 轉換過來也會比較親切 ) ,然後把我剛剛定義好的資料給載入進來,就這麼簡單。

Content.vue

<script>
import { inject, toRefs } from "vue";
export default {
  setup() {
    const mapStore = inject("mapStore");
    const { state } = mapStore;
    return {
      ...toRefs(state),
    };
  },
};
</script>

<template>
  <div class="content">
    <h1>count: {{ count }}</h1>
    <button>add</button>
    <button>remove</button>
  </div>
</template>

這邊透過 inject 的方式把我的 mapStore 給注入進來,接下來我就可以直接取得我定義好的state 資料,然後丟到我的template上,不過這邊要注意我使用了toRefs 去做包我的 state,為什麼不直接把 state 丟出去呢?

原因是因為透過 reactive 包裝的資料,是透過解構的方式取出的話,會失去它的連動特性,所以這個時候在需要透過 toRefs 的包裝,來讓它維持資料的連動,最後把我定義好的 count 這個資料放到畫面上

toRefs 詳情可以參考文件 https://v3.cn.vuejs.org/api/refs-api.html#torefs

現在的畫面長這樣

vue mike

假設今天我直接增加一個 function去修改我拿到的count的話

<script>
import { inject, toRefs } from "vue";
export default {
  setup() {
    const mapStore = inject("mapStore");
    const { state } = mapStore;

    const headleClick = () => {
      state.count++;
    };

    return {
      ...toRefs(state),
      headleClick,
    };
  },
};
</script>
<template>
  <div class="content">
    <h1>count: {{ count }}</h1>
    <button @click="headleClick">add</button>
    <button>remove</button>
  </div>
</template>

你 click 它 log 會跳出說這個資料它是唯獨的,不可以修改

mike vue

這是因為我們在 store/index.js 裡面透過readonly回傳的,所以我們不能這樣修改,readonly的好處就是要避免開發者直接修改資料,所以我們應該另外去定義可以修改的資料的 function,讓資料歸資料,修改交給其他方法去做。

我們在 store/index.is 上面新增了add 跟 remove 的兩個 functino

import { reactive, readonly } from "vue";

const state = reactive({
  count: 0,
});

const addCount = () => {
  state.count++;
};

const removeCount = () => {
  state.count--;
};

export default {
  state: readonly(state),
  addCount,
  removeCount,
};

然後把 function 取出來就可以掛在我們的 button,就可以成功的修改資料

<script>
import { inject, toRefs } from "vue";
export default {
  setup() {
    const mapStore = inject("mapStore");
    const { state, addCount, removeCount } = mapStore;
    return {
      ...toRefs(state),
      addCount,
      removeCount,
    };
  },
};
</script>
<template>
  <div class="content">
    <h1>count: {{ count }}</h1>
    <button @click="addCount">add</button>
    <button @click="removeCount">remove</button>
  </div>
</template>

vue mike

透過這樣的方式我們就可以不需要透過 Vuex 的方式來共用資料,你可能會問說如果要像 actions 一樣處理非同步的 API 要怎麼辦 ?

一樣我們可以把非同步給放到store/index.js裡面,這邊我就寫一個大概的示範

const fetchUserData = async () => {
  try {
    const res = await axios.get("https://www.api.com/api/user");
    const { name, age, address } = res.data;
    state.name = name;
    state.age = age;
    state.address = address;
  } catch (error) {
      state.errorMessage = "This user is not found!";
  }
};

這邊其實我們就把 store/index.js 給當成 Vuex 使用那樣來使用,有 state、actions/mutations,如果你想要有getter 的功能的話也可以使用 computed 來替代達成。

import { computed, reactive, readonly } from "vue";

const userInfo = computed(()=> { 
  return `Info: ${state.name},  ${state.age},  ${state.address}`
})

export default {  
    state: readonly(state),  
    addCount,  
    removeCount,  
    userInfo
};

這也是很多人在說為什麼使用 Vue3 的時候可能不需要在使用 Vuex 的原因,因為我們就可以利用Provide / inject來達成使用Vuex管理資料的作法。

我自己的使用下來歸納幾個想法

  1. Provide / inject 的使用法很適合用在小專案或是小區塊的功能開發上使用。
  2. 在大型專案的時候還是主要會以Vuex作為管理狀態的主選擇,Provide / inject為輔助。
  3. Vuex之中有許多功能是Provide / inject 沒有的功能(詳請可以參考 https://next.vuex.vuejs.org/ )

最後殺雞焉用牛刀,我們不管用哪個都沒有對錯,就只有適不適合而已,今天在這邊介紹了如何使用 Provide / inject,下一篇我們來談談 Vue3 使用 Vuex 的一些進階的用法。

Mike Vue

那如果對於Vue3不夠熟的話呢?

Ps. 購買的時候請登入或註冊該平台的會員,然後再使用下面連結進入網站點擊「立即購課」,這樣才可以讓我獲得更多的課程分潤,還可以幫助我完成更多豐富的內容給各位。

我有開設了一堂專門針對Vue3從零開始教學的課程,如果你覺得不錯的話,可以購買我課程來學習
https://hiskio.com/bundles/9WwPNYRpz?s=tc

那如果對於JS基礎不熟的朋友,我也有開設JS的入門課程,可以參考這個課程
https://hiskio.com/bundles/b9Rovqy7z?s=tc

訂閱Mike的頻道享受精彩的教學與分享

Mike 的 Youtube 頻道
Mike的medium
MIke 的官方 line 帳號,好友搜尋 @mike_cheng


上一篇
[重構倒數第27天] - 在 Vue 各種 CSS 的引入使用
下一篇
[重構倒數第25天] - Vuex + Composition API 組合技
系列文
[ 重構倒數30天,你的網站不Vue白不Vue ] 32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
mihuartuanr
iT邦新手 4 級 ‧ 2021-12-02 08:06:44

您好,咨询一下,提取了store/index.js,那应该可以在使用到数据的地方直接引入该文件即可。

为什么要选择在上层(根)组件上使用provide,在应用组件使用inject引入呢?

我要留言

立即登入留言