Rumah >hujung hadapan web >View.js >Analisis mendalam tentang cara mengurus keadaan kongsi dalam Vue 3? 【terjemah】
Bagaimana untuk mengurus keadaan kongsi dalam Vue 3? Artikel berikut akan membincangkannya dengan anda, saya harap ia akan membantu anda!
Ringkasan Pantas↬ Menulis aplikasi besar boleh menjadi satu cabaran. Kerumitan projek boleh diselesaikan menggunakan keadaan kongsi dalam aplikasi Vue3. Terdapat banyak penyelesaian keadaan biasa, dan dalam artikel ini saya akan menyelami kebaikan dan keburukan menggunakan kaedah seperti kilang, objek kongsi dan Vuex. Saya juga akan menunjukkan kepada anda beberapa perkara dalam Vuex5 yang mungkin mengubah cara kami menggunakan keadaan kongsi dalam Vue3. [Cadangan berkaitan: tutorial video vue.js]
state
Ia mungkin sukar Apabila kami memulakan projek Vue yang mudah, ia hanya memerlukan kami mengekalkan status kerja komponen tertentu . Mudah:
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 }; },
Ini boleh menjadi menarik apabila projek anda ialah satu halaman yang memaparkan data (mungkin untuk mengisih atau menapis data). Tetapi dalam kes ini, komponen akan mendapat data untuk setiap permintaan. Bagaimana jika anda ingin mengekalkannya? Di sinilah pengurusan negeri berperanan. Oleh kerana sambungan rangkaian selalunya mahal dan kadangkala tidak boleh dipercayai. Oleh itu, adalah lebih baik untuk mengekalkan keadaan semasa menyemak imbas apl.
Isu lain ialah komunikasi antara komponen. Walaupun anda boleh menggunakan events
dan props
untuk berkomunikasi secara langsung dengan child
-parents
, apabila setiap paparan/halaman anda adalah bebas, pengendalian kes mudah seperti pemintasan ralat dan bendera sibuk akan menjadi sangat sukar. Sebagai contoh, katakan anda menggunakan kawalan peringkat atas untuk memaparkan ralat dan memuatkan animasi:
// App.vue <template> <div class="container mx-auto bg-gray-100 p-1"> <router-link to="/"><h1>Bookcase</h1></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>
Jika tiada cara yang cekap untuk mengendalikan keadaan ini, ia mungkin mencadangkan menggunakan sistem terbitkan/langganan, tetapi dalam amalan Berkongsi data adalah lebih mudah dalam banyak kes. Jika anda ingin menggunakan keadaan kongsi, bagaimana anda melakukannya. Mari lihat beberapa kaedah biasa.
Nota: Anda akan mencari kod untuk bahagian ini dalam cawangan "utama" projek contoh di GitHub.
Sejak berhijrah ke Vue 3, saya telah berhijrah sepenuhnya kepada menggunakan API Komposisi, yang juga saya gunakan dalam artikel ini TypeScript
Walaupun ini tidak perlu dalam contoh yang saya tunjukkan. Walaupun anda boleh berkongsi status dalam apa jua cara, saya akan menunjukkan kepada anda beberapa corak teknikal yang paling biasa yang saya temui, masing-masing mempunyai kebaikan dan keburukan sendiri, jadi jangan ambil apa-apa yang saya katakan di sini sebagai satu-satunya.
Teknologi termasuk:
Nota: Pada masa penulisan, Vuex 5 Ia masih dalam peringkat RFC (permintaan untuk komen), jadi saya ingin menyediakan anda untuk ketibaan Vuex5, tetapi masih belum ada versi yang berfungsi untuk pilihan ini.
Mari kita lihat lebih dekat...
Nota: Kod untuk bahagian ini adalah dalam cawangan "Kilang" projek contoh di GitHub.
Corak kilang hanya mengambil berat tentang keadaan keadaan yang anda buat. Dalam mod ini, anda akan mengembalikan fungsi yang hampir sama dengan fungsi mulakan dalam API Komposisi. Anda akan membuat skop dan membina komponen yang anda cari. Contohnya:
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 }; }
Anda boleh meminta kilang mencipta bahagian objek yang anda perlukan:
// In Home.vue const { books, loadBooks } = BookFactory();
Jika kami menambah tag isBusy
untuk ditunjukkan apabila permintaan rangkaian berlaku , kod di atas tidak akan berubah, tetapi anda boleh memutuskan tempat untuk memaparkan 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 }; }
Dalam paparan lain (vue?), anda boleh isBusy
tag tanpa mengetahui Bagaimana yang lain kilang berfungsi:
// 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="/"><h1>Bookcase</h1></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的真正痛点并解决它们。为此,他们正在计划一些重要的改变:
那么它是如何工作的呢?让我们从创建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的策略是非常重要的一步。在本文中,我向你展示了几种管理共享状态的模式,包括即将在Vuex 5中推出的模式。希望你现在拥有为自己的项目做出正确决策的知识。
原文地址:https://www.smashingmagazine.com/2021/06/managing-shared-state-vue3/
原文作者:Shawn Wildermuth
更多编程相关知识,请访问:编程入门!!
Atas ialah kandungan terperinci Analisis mendalam tentang cara mengurus keadaan kongsi dalam Vue 3? 【terjemah】. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!