您好,登錄后才能下訂單哦!
在Vue 3中怎么管理共享狀態,針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
編寫大型應用程序可能是一個挑戰。在Vue3應用程序中使用共享狀態可以解決項目復雜性。有很多常見的狀態解決方案,我將深入探討使用工廠、共享對象和Vuex等方法的優缺點。我還將向你展示Vuex5中可能改變我們在Vue3中使用共享狀態的一些內容。
state
可能很難,當我們開始一個簡單的Vue項目時,只需要我們保持特定組件的工作狀態就很簡單:
setup() { let books: Work[] = reactive([]); onMounted(async () => { // Call the API const response = await bookService.getScienceBooks(); if (response.status === 200) { books.splice(0, books.length, ...response.data.works); } }); return { books }; },
當你的項目是顯示數據的單一頁面(可能是為了排序或過濾數據)時,這可能很有吸引力。但是在本例中,該組件將獲得每個請求的數據。如果你想保留它呢?這時候狀態管理就可以發揮作用了。由于網絡連接通常代價很高并且偶爾不太可靠。因此在瀏覽應用程序時最好保持一些狀態。
另一個問題是組件之間的通信。雖然您可以使用events
和props
直接與child
-parents
通信, 但是當你的每個views/pages都是獨立的時候,處理諸如錯誤攔截和忙碌標志之類的簡單情況會變得很困難。舉個例子, 假設你使用了一個頂級控件來顯示error和加載動畫:
// App.vue <template> <div class="container mx-auto bg-gray-100 p-1"> <router-link to="/"><h2>Bookcase</h2></router-link> <div class="alert" v-if="error">{{ error }}</div> <div class="alert bg-gray-200 text-gray-900" v-if="isBusy"> Loading... </div> <router-view :key="$route.fullPath"></router-view> </div> </template>
如果沒有有效的方法來處理這種狀態,它可能會建議使用發布/訂閱系統,但實際上在許多情況下共享數據更為直接。如果你想使用共享狀態,你該怎么做呢?讓我們看看一些常見的方法。
注意:你將在 GitHub 上示例項目的“main”分支中找到此部分的代碼。
自從遷移到 Vue 3 后,我已經完全遷移到使用 Composition API,這篇文章我還使用了TypeScript
盡管在我展示的示例中這不是必需的。雖然你可以以任何方式共享狀態,但我將向你展示我發現的幾種最常用的技術模式,每一種都有自己的優點和缺點,所以不要把我在這里所說的任何東西當作唯一。
技術包括:
工廠模式,
共享單例,
Vuex 4,
Vuex 5.
注意: 在撰寫本文時,Vuex 5還處于RFC(征求意見)階段,所以我想讓你為Vuex5的到來做好準備,但目前還沒有該選項的工作版本。
讓我們深入了解……
注意: 此部分的代碼位于GitHub 上示例項目的“Factories”分支中。
工廠模式只關心你所創建的狀態的實例。在此模式中,你將返回一個與Composition API中的start函數非常相似的函數。你將創建一個作用域并構建你想尋找的組件。舉個例子:
export default function () { const books: Work[] = reactive([]); async function loadBooks(val: string) { const response = await bookService.getBooks(val, currentPage.value); if (response.status === 200) { books.splice(0, books.length, ...response.data.works); } } return { loadBooks, books }; }
你可以要求工廠創建你所需要的那部分對象:
// In Home.vue const { books, loadBooks } = BookFactory();
如果我們添加一個isBusy
標記來顯示網絡請求何時發生,上面的代碼不會改變,但你可以決定在哪里顯示isBusy
:
export default function () { const books: Work[] = reactive([]); const isBusy = ref(false); async function loadBooks(val: string) { isBusy.value = true; const response = await bookService.getBooks(val, currentPage.value); if (response.status === 200) { books.splice(0, books.length, ...response.data.works); } } return { loadBooks, books, isBusy }; }
在另一個視圖(vue?)中,你可以只要isBusy
標記,而不需要知道工廠其他部分是如何工作的:
// App.vue export default defineComponent({ setup() { const { isBusy } = BookFactory(); return { isBusy } }, })
但是你可能注意到了一個問題;每次我們調用工廠時,我們都會獲得所有對象的新實例。有時,你希望工廠返回新實例,但在我們的例子中,我們討論的是共享狀態,因此我們需要將創建移到工廠之外:
const books: Work[] = reactive([]); const isBusy = ref(false); async function loadBooks(val: string) { isBusy.value = true; const response = await bookService.getBooks(val, currentPage.value); if (response.status === 200) { books.splice(0, books.length, ...response.data.works); } } export default function () { return { loadBooks, books, isBusy }; }
現在,工廠給了我們一個共享實例,或者一個單例。雖然這種模式可以工作,但返回一個每次都不創建新實例的函數可能會令人困惑。
因為底層對象被標記為const
,所以不能替換它們(也不能破壞單例的性質)。所以這段代碼應該會報錯:
// In Home.vue const { books, loadBooks } = BookFactory(); books = []; // Error, books is defined as const
因此,確保可變狀態能夠被更新是很重要的(例如,使用books.splice()
而不是設置books)。另一種處理方法是使用共享實例
本節的代碼在GitHub上示例項目的“Sharedstate”分支中。
如果你使用共享狀態,最好清楚狀態是一個單例的事實。在本例中,它可以作為一個靜態對象導入。例如,我喜歡創建一個可以作為響應式對象導入的對象:
export default reactive({ books: new Array<Work>(), isBusy: false, async loadBooks() { this.isBusy = true; const response = await bookService.getBooks(this.currentTopic, this.currentPage); if (response.status === 200) { this.books.splice(0, this.books.length, ...response.data.works); } this.isBusy = false; } });
在這種情況下,你只需導入對象(在本例中我將其稱為商店):
// Home.vue import state from "@/state"; export default defineComponent({ setup() { // ... onMounted(async () => { if (state.books.length === 0) state.loadBooks(); }); return { state, bookTopics, }; }, });
然后很容易綁定到狀態:
<!-- Home.vue --> <div class="grid grid-cols-4"> <div v-for="book in state.books" :key="book.key" class="border bg-white border-grey-500 m-1 p-1" > <router-link :to="{ name: 'book', params: { id: book.key } }"> <BookInfo :book="book" /> </router-link> </div>
與其他模式一樣,你可以在視圖之間共享此實例:
// App.vue import state from "@/state"; export default defineComponent({ setup() { return { state }; }, })
然后它可以綁定到同一個對象(無論它是Home.Vue
的父頁面)或路由到其他頁面):
<!-- App.vue --> <div class="container mx-auto bg-gray-100 p-1"> <router-link to="/"><h2>Bookcase</h2></router-link> <div class="alert bg-gray-200 text-gray-900" v-if="state.isBusy">Loading...</div> <router-view :key="$route.fullPath"></router-view> </div>
無論使用工廠模式還是共享實例,它們都有一個共同的問題:可變狀態。當你不希望綁定或代碼更改狀態時,可能會產生意外的副作用。在本文中我使用簡單示例中,它還沒有那么復雜,因此不用擔心。但是當你構建的應用程序越大越大時,將需要更仔細地考慮狀態的變化。這時候Vuex就可以伸出援手了。
本節的代碼位于GitHub上示例項目的“Vuex4”分支中。
Vuex是vue的狀態管理器。它是由核心團隊構建的,但它作為一個單獨的項目進行管理。Vuex的目的是將狀態與狀態執行操作分離開來。所有狀態更改都必須通過 Vuex,這意味著它更復雜,但你可以防止意外狀態更改。
Vuex 的想法是提供可預測的狀態管理流程。視圖流向 Actions,Actions 反過來使用 Mutations 來改變狀態,從而更新視圖。通過限制狀態更改的流程,可以減少改變應用程序狀態的副作用;因此更容易構建更大的應用程序。Vuex 有一個學習曲線,但有了這種復雜性,你就可以獲得可預測性。
此外,Vuex 還支持程序調試(通過 Vue Tools)來處理狀態管理,包括一個稱為time-travel
的功能。這允許你查看狀態的歷史記錄并前后移動以查看它如何影響應用程序的。
有時,Vuex 也很重要。
要將其添加到你的 Vue 3 項目中,你可以將包添加到項目中:
> npm i vuex
或者,你也可以使用 Vue CLI 添加它:
> vue add vuex
通過使用CLI,它將為你的 Vuex Store 創建初始的起點, 否則你需要手動將其接入到項目。讓我們來看看這是如何工作的。
首先,你需要使用 Vuex 的 createStore 函數創建一個狀態對象:
import { createStore } from 'vuex' export default createStore({ state: {}, mutations: {}, actions: {}, getters: {} });
如你所見,Store需要定義多個屬性。State只是一個想要授予應用程序訪問權限的數據列表:
import { createStore } from 'vuex' export default createStore({ state: { books: [], isBusy: false }, mutations: {}, actions: {} });
注意state不應使用ref or reactive 包裝。這里的數據與我們在共享實例或工廠中使用的共享數據類型相同。這個store在你的應用程序中將作為一個單例,因此狀態中的數據也會被共享
然后,讓我們看看actions
。 Actions是可以授予你改變state中數據的一個操作臺,舉個例子:
actions: { async loadBooks(store) { const response = await bookService.getBooks(store.state.currentTopic, if (response.status === 200) { // ... } } },
Actions 會傳遞一個 store 的實例,以便你可以獲取狀態和其他操作。通常,我們只會解構我們需要的部分:
actions: { async loadBooks({ state }) { const response = await bookService.getBooks(state.currentTopic, if (response.status === 200) { // ... } } },
最后一個是Mutations
。Mutations是能夠改變狀態的函數。只有Mutations才能影響狀態。在這個例子中,我們需要Mutations來改變狀態:
mutations: { setBusy: (state) => state.isBusy = true, clearBusy: (state) => state.isBusy = false, setBooks(state, books) { state.books.splice(0, state.books.length, ...books); } },
Mutation函數總是傳入狀態對象,以便你可以改變狀態。在前兩個示例中,你可以看到我們顯式地設置了狀態。但在第三個例子中,我們傳入了set的狀態。調用mutation時,mutation總是接收兩個參數:state和實參。
要調用mutation, 你需要在store中使用commit函數。在我們的例子中,我只是將它添加到解構中:
actions: { async loadBooks({ state, commit }) { commit("setBusy"); const response = await bookService.getBooks(state.currentTopic, if (response.status === 200) { commit("setBooks", response.data); } commit("clearBusy"); } },
你將在此處看到commit如何要求action的命名。有一些技巧使它不僅僅是有魔力的字符串。但是我現在跳過它們。這種有魔力字符串是限制Vuex的使用方式之一。
雖然使用 commit 可能看起來像是一個不必要的包裝器,但請記住,Vuex 不會讓你改變狀態,除非在mutation內部,因此只有通過commit調用才會。
你還可以看到對setBooks的調用采用了第二個參數。這是調用mutation的第二個參數。如果你需要更多信息,則需要將其打包成一個參數(目前 Vuex 的另一個限制)。假設你需要將一本書插入到圖書列表中,你可以這樣稱呼它:
commit("insertBook", { book, place: 4 }); // object, tuple, etc.
然后你可以解構成你需要的部分:
mutations: { insertBook(state, { book, place }) => // ... }
這優雅嗎?并不是,但它有效。
現在我們的 action 已經處理了mutations,我們需要能夠在我們的代碼中使用 Vuex 存儲。有兩種方法可以得到store。首先,通過使用應用程序(例如 main.ts/js)注冊store,你將可以訪問一個集中式store, 你可以在應用程序的任何地方訪問它:
// main.ts import store from './store' createApp(App) .use(store) .use(router) .mount('#app')
注意,這并不是在添加Vuex,而是你實際上正在創建的商店。一旦添加后, 你只需要調用useStore
即可獲取store對象:
import { useStore } from "vuex"; export default defineComponent({ components: { BookInfo, }, setup() { const store = useStore(); const books = computed(() => store.state.books); // ...
這很好用,但我更喜歡直接導入store:
import store from "@/store"; export default defineComponent({ components: { BookInfo, }, setup() { const books = computed(() => store.state.books); // ...
既然你可以訪問 store 對象,那么如何使用它呢?對于state,你需要使用計算函數包裝它們,以便將它們更改并傳遞到你的綁定上:
export default defineComponent({ setup() { const books = computed(() => store.state.books); return { books }; }, });
要調用actions,你需要調用dispatch 方法:
export default defineComponent({ setup() { const books = computed(() => store.state.books); onMounted(async () => await store.dispatch("loadBooks")); return { books }; }, });
Actions可以在方法名稱后添加的參數。最后,要更改狀態,你需要像我們在 Actions 中所做的那樣去調用 commit。例如,我在 store 中有一個 分頁屬性,然后我可以使用commit更改狀態:
const incrementPage = () => store.commit("setPage", store.state.currentPage + 1); const decrementPage = () => store.commit("setPage", store.state.currentPage - 1);
注意, 這樣調用可能會拋出一個異常(因為你不能手動改變state):
const incrementPage = () => store.state.currentPage++; const decrementPage = () => store.state.currentPage--;
這是Vuex真正強大的地方,我們想要控制狀態改變的地方并且不會造成在開發過程中產生進一步錯誤。
你可能對 Vuex 中大量改變狀態的方式感到不知所措,但它確實可以幫助管理更大、更復雜的項目中的狀態。我不會說你在每種情況下都需要它,但是會有一些大型項目在總體上對你有幫助。
這就是 Vuex 5 旨在簡化 Vuex 在 TypeScript(以及一般的 JavaScript 項目)中的工作方式的地方。讓我們看看它在下一次發布后將如何運作。
注意: 此部分的代碼位于GitHub 上示例項目的“Vuex5”分支中。
在撰寫本文時,Vuex 5 還沒有出現。這是一個 RFC(征求意見)。這是計劃。是討論的起點。所以我在這里說明的很多東西可能會發生變化。但是為了讓你們對 Vuex 的變化有所準備,我想讓你們了解一下它的發展方向。因此,與此示例關聯的代碼不會生成。
Vuex工作原理的基本概念從一開始就沒有改變過。隨著Vue 3的引入,Vuex 4的創建主要允許Vuex在新項目中工作。但該團隊正試圖找到Vuex的真正痛點并解決它們。為此,他們正在計劃一些重要的改變:
沒有更多的mutations: actions能改變mutation (可能任意的也可以)。
更好的TypeScript支持。
更好的多store功能。
那么它是如何工作的呢?讓我們從創建store開始:
export default createStore({ key: 'bookStore', state: () => ({ isBusy: false, books: new Array<Work>() }), actions: { async loadBooks() { try { this.isBusy = true; const response = await bookService.getBooks(); if (response.status === 200) { this.books = response.data.works; } } finally { this.isBusy = false; } } }, getters: { findBook(key: string): Work | undefined { return this.books.find(b => b.key === key); } } });
首先要看到的是,每個store現在都需要有自己的key。這允許你檢索多個store。接下來,你會注意到state對象現在是一個工廠(例如,從一個函數返回,不是在解析時創建的)。沒有mutations部分了。最后,在actions內部,你可以看到我們訪問的狀態只是this
指向的屬性。不需要再通過傳遞state來commit Actions。這不僅有助于簡化開發,還使TypeScript更容易推斷類型。
要將 Vuex 注冊到你的應用程序中,你將注冊 Vuex 而不是你的全局store:
import { createVuex } from 'vuex' createApp(App) .use(createVuex()) .use(router) .mount('#app')
最后,要使用store,你將導入store然后創建它的一個實例:
import bookStore from "@/store"; export default defineComponent({ components: { BookInfo, }, setup() { const store = bookStore(); // Generate the wrapper // ...
請注意,從商店返回的是一個工廠對象,無論你調用工廠多少次,它都會返回該商店的實例。返回只是一個具有單一類行為的actions、state和getters對象(帶有類型信息):
onMounted(async () => await store.loadBooks()); const incrementPage = () => store.currentPage++; const decrementPage = () => store.currentPage--;
你將在此處看到 state(例如currentPage
)只是簡單的屬性。而actions(例如loadBooks
)只是函數。實際上你在這里使用的store是一個副作用。你可以將 Vuex 對象視為一個對象并繼續工作。這是 API 的重大改進。
另一個需要指出的重要變化是,你也可以使用類似于Composition API的語法來生成你的store:
export default defineStore("another", () => { // State const isBusy = ref(false); const books = reactive(new Array?Work>()); // Actions async function loadBooks() { try { this.isBusy = true; const response = await bookService.getBooks(this.currentTopic, this.currentPage); if (response.status === 200) { this.books = response.data.works; } } finally { this.isBusy = false; } } findBook(key: string): Work | undefined { return this.books.find(b => b.key === key); } // Getters const bookCount = computed(() => this.books.length); return { isBusy, books, loadBooks, findBook, bookCount } });
這允許你像使用 Composition API 構建視圖那樣去構建 Vuex 對象,并且可以說它更簡單。
這種新設計的一個主要缺點是失去了狀態的不可變性。圍繞能夠啟用此功能(僅用于開發,就像 Vuex 4 一樣)進行了討論,但對此的重要性尚未達成共識。我個人認為這是 Vuex 的主要有點,但我們必須看看它是如何發揮作用的。
關于在Vue 3中怎么管理共享狀態問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。