การจัดการสถานะที่ใช้ร่วมกันใน Vue 3

เผยแพร่แล้ว: 2022-03-10
สรุปอย่างรวดเร็ว ↬ การเขียนแอปพลิเคชั่น Vue ขนาดใหญ่อาจเป็นสิ่งที่ท้าทาย การใช้สถานะที่ใช้ร่วมกันในแอปพลิเคชัน Vue 3 ของคุณอาจเป็นวิธีแก้ปัญหาเพื่อลดความซับซ้อนนี้ มีวิธีแก้ปัญหาทั่วไปหลายประการในการแก้ปัญหาสถานะ ในบทความนี้ ผมจะเจาะลึกถึงข้อดีและข้อเสียของแนวทางต่างๆ เช่น โรงงาน วัตถุที่ใช้ร่วมกัน และการใช้ Vuex ฉันจะแสดงให้คุณเห็นด้วยว่ามีอะไรใหม่ใน Vuex 5 ที่อาจเปลี่ยนวิธีที่เราทุกคนใช้สถานะแชร์ใน Vue 3

รัฐอาจเป็นเรื่องยาก เมื่อเราเริ่มโปรเจ็กต์ 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 }; },

เมื่อโปรเจ็กต์ของคุณเป็นหน้าเดียวในการแสดงข้อมูล (อาจต้องจัดเรียงหรือกรองข้อมูล) อาจเป็นเรื่องที่น่าสนใจ แต่ในกรณีนี้ ส่วนประกอบนี้จะได้รับข้อมูลในทุกคำขอ จะทำอย่างไรถ้าคุณต้องการเก็บไว้ นั่นคือสิ่งที่การจัดการของรัฐเข้ามาเล่น เนื่องจากการเชื่อมต่อเครือข่ายมักจะมีราคาแพงและไม่น่าเชื่อถือในบางครั้ง จึงควรรักษาสถานะนี้ไว้ขณะที่คุณไปยังส่วนต่างๆ ของแอปพลิเคชัน

อีกปัญหาหนึ่งคือการสื่อสารระหว่างส่วนประกอบต่างๆ แม้ว่าคุณจะสามารถใช้เหตุการณ์และอุปกรณ์ประกอบในการสื่อสารกับผู้ปกครองที่เป็นเด็กได้โดยตรง การจัดการกับสถานการณ์ง่ายๆ เช่น การจัดการข้อผิดพลาดและการแจ้งว่ายุ่งอาจเป็นเรื่องยากเมื่อการดู/เพจแต่ละรายการแยกจากกัน ตัวอย่างเช่น สมมติว่าคุณมีการควบคุมระดับบนสุดที่เชื่อมต่อเพื่อแสดงข้อผิดพลาดและการโหลดภาพเคลื่อนไหว:

 // 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>

หากไม่มีวิธีที่มีประสิทธิภาพในการจัดการกับสถานะนี้ ระบบอาจแนะนำระบบการเผยแพร่/สมัครสมาชิก แต่ในความเป็นจริง การแบ่งปันข้อมูลจะตรงไปตรงมามากกว่าในหลายกรณี อยากได้แชร์สเตทต้องทำยังไง? ลองดูวิธีทั่วไปในการทำเช่นนี้

หมายเหตุ : คุณจะพบรหัสสำหรับส่วนนี้ในสาขา "หลัก" ของโครงการตัวอย่างบน GitHub

แชร์สถานะใน Vue 3

ตั้งแต่ย้ายมาที่ Vue 3 ฉันได้ย้ายไปใช้ Composition API อย่างสมบูรณ์แล้ว สำหรับบทความนี้ ฉันยังใช้ TypeScript อยู่แม้ว่าจะไม่ได้จำเป็นสำหรับตัวอย่างที่ฉันแสดงให้คุณดูก็ตาม ในขณะที่คุณสามารถแชร์สถานะได้ตามที่คุณต้องการ ฉันจะแสดงให้คุณเห็นถึงเทคนิคต่างๆ ที่ฉันพบรูปแบบที่ใช้บ่อยที่สุด แต่ละคนมีข้อดีและข้อเสียของตัวเอง ดังนั้นอย่าถือเอาสิ่งที่ฉันพูดถึงนี้เป็นความเชื่อ

เทคนิคต่างๆ ได้แก่ :

  • โรงงาน
  • ซิงเกิลตันที่ใช้ร่วมกัน
  • Vuex 4,
  • เว็กซ์ 5

หมายเหตุ : Vuex 5 ตอนที่เขียนบทความนี้ มันอยู่ในขั้นตอน RFC (ขอความคิดเห็น) ดังนั้นฉันจึงต้องการให้คุณพร้อมสำหรับตำแหน่งที่ Vuex กำลังจะไป แต่ตอนนี้ไม่มีตัวเลือกเวอร์ชันนี้ที่ใช้งานได้

มาขุดกัน…

โรงงาน

หมายเหตุ : รหัสสำหรับส่วนนี้อยู่ในสาขา "โรงงาน" ของโครงการตัวอย่างบน GitHub

รูปแบบโรงงานเป็นเพียงการสร้างอินสแตนซ์ของรัฐที่คุณสนใจ ในรูปแบบนี้ คุณจะส่งคืนฟังก์ชันที่เหมือนกับฟังก์ชัน เริ่มต้น ใน Composition API คุณต้องสร้างขอบเขตและสร้างส่วนประกอบของสิ่งที่คุณกำลังมองหา ตัวอย่างเช่น:

 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() แทนการกำหนดหนังสือ)

อีกวิธีในการจัดการสิ่งนี้คือการใช้อินสแตนซ์ที่ใช้ร่วมกัน

เพิ่มเติมหลังกระโดด! อ่านต่อด้านล่าง↓

อินสแตนซ์ที่ใช้ร่วมกัน

รหัสสำหรับส่วนนี้อยู่ในสาขา "SharedState" ของโครงการตัวอย่างบน GitHub

หากคุณกำลังจะแบ่งปันสถานะ อาจจะมีความชัดเจนเกี่ยวกับข้อเท็จจริงที่ว่ารัฐเป็นซิงเกิลตัน ในกรณีนี้ คุณสามารถนำเข้าเป็นวัตถุแบบคงที่ได้ ตัวอย่างเช่น ฉันชอบสร้างวัตถุที่สามารถนำเข้าเป็นวัตถุปฏิกิริยาได้:

 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 สามารถเข้ามาช่วยเหลือได้

Vuex 4

รหัสสำหรับส่วนนี้อยู่ในสาขา "Vuex4" ของโครงการตัวอย่างบน GitHub

Vuex เป็นผู้จัดการของรัฐสำหรับ Vue มันถูกสร้างขึ้นโดยทีมงานหลักแม้ว่าจะได้รับการจัดการเป็นโครงการที่แยกจากกัน วัตถุประสงค์ของ Vuex คือการแยกสถานะออกจากการกระทำที่คุณต้องการทำกับสถานะ การเปลี่ยนแปลงของรัฐทั้งหมดต้องผ่าน Vuex ซึ่งหมายความว่ามันซับซ้อนกว่า แต่คุณจะได้รับการปกป้องจากการเปลี่ยนแปลงสถานะโดยไม่ได้ตั้งใจ

แนวคิดของ Vuex คือการจัดเตรียมโฟลว์การจัดการสถานะที่คาดการณ์ได้ มุมมองไหลไปสู่การกระทำซึ่งในทางกลับกันใช้การกลายพันธุ์เพื่อเปลี่ยนสถานะซึ่งในทางกลับกันจะอัปเดตมุมมอง การจำกัดการไหลของการเปลี่ยนแปลงสถานะ คุณควรมีผลข้างเคียงน้อยลงที่เปลี่ยนสถานะของแอปพลิเคชันของคุณ จึงง่ายต่อการสร้างแอปพลิเคชันขนาดใหญ่ขึ้น Vuex มีช่วงการเรียนรู้ แต่ด้วยความซับซ้อนนั้น คุณจะคาดเดาได้

นอกจากนี้ Vuex ยังสนับสนุนเครื่องมือเวลาในการพัฒนา (ผ่านเครื่องมือ Vue) เพื่อทำงานร่วมกับการจัดการสถานะรวมถึงคุณลักษณะที่เรียกว่าการเดินทางข้ามเวลา ซึ่งจะทำให้คุณสามารถดูประวัติของรัฐและย้อนกลับไปข้างหน้าเพื่อดูว่ามีผลกับแอปพลิเคชันอย่างไร

มีบางครั้งที่ Vuex ก็มีความสำคัญเช่นกัน

หากต้องการเพิ่มลงในโปรเจ็กต์ Vue 3 คุณสามารถเพิ่มแพ็กเกจลงในโปรเจ็กต์ได้:

 > npm i vuex

หรือคุณสามารถเพิ่มโดยใช้ Vue CLI:

 > vue add vuex

การใช้ CLI จะสร้างจุดเริ่มต้นสำหรับร้านค้า Vuex ของคุณ มิฉะนั้น คุณจะต้องเชื่อมต่อไปยังโปรเจ็กต์ด้วยตนเอง มาดูกันว่ามันทำงานอย่างไร

ขั้นแรก คุณจะต้องมีอ็อบเจ็กต์สถานะที่สร้างด้วยฟังก์ชัน createStore ของ Vuex:

 import { createStore } from 'vuex' export default createStore({ state: {}, mutations: {}, actions: {}, getters: {} });

อย่างที่คุณเห็น ร้านค้าต้องการคุณสมบัติหลายอย่างเพื่อกำหนด สถานะเป็นเพียงรายการข้อมูลที่คุณต้องการให้แอปพลิเคชันของคุณเข้าถึง:

 import { createStore } from 'vuex' export default createStore({ state: { books: [], isBusy: false }, mutations: {}, actions: {} });

โปรดทราบว่ารัฐไม่ควรใช้ตัว อ้างอิง หรือตัวห่อหุ้มแบบ รี แอกทีฟ ข้อมูลนี้เป็นข้อมูลประเภทเดียวกับที่เราใช้กับอินสแตนซ์หรือโรงงานที่ใช้ร่วมกัน ร้านค้านี้จะเป็นซิงเกิลตันในแอปพลิเคชันของคุณ ดังนั้นข้อมูลในสถานะก็จะถูกแชร์ด้วย

ต่อไปมาดูการกระทำกัน การดำเนินการคือการดำเนินการที่คุณต้องการเปิดใช้งานที่เกี่ยวข้องกับสถานะ ตัวอย่างเช่น:

 actions: { async loadBooks(store) { const response = await bookService.getBooks(store.state.currentTopic, if (response.status === 200) { // ... } } },

การดำเนินการจะถูกส่งผ่านอินสแตนซ์ของร้านค้าเพื่อให้คุณได้รับสถานะและการดำเนินการอื่นๆ โดยปกติ เราจะทำลายเฉพาะส่วนที่เราต้องการ:

 actions: { async loadBooks({ state }) { const response = await bookService.getBooks(state.currentTopic, if (response.status === 200) { // ... } } },

ชิ้นสุดท้ายของสิ่งนี้คือการกลายพันธุ์ การกลายพันธุ์เป็นฟังก์ชันที่สามารถเปลี่ยนสถานะได้ การกลายพันธุ์เท่านั้นที่จะส่งผลต่อสถานะ สำหรับตัวอย่างนี้ เราต้องการการกลายพันธุ์ที่เปลี่ยนสถานะ:

 mutations: { setBusy: (state) => state.isBusy = true, clearBusy: (state) => state.isBusy = false, setBooks(state, books) { state.books.splice(0, state.books.length, ...books); } },

ฟังก์ชันการกลายพันธุ์จะส่งผ่านในสถานะวัตถุเสมอ เพื่อให้คุณสามารถเปลี่ยนแปลงสถานะนั้นได้ ในสองตัวอย่างแรก คุณจะเห็นว่าเรากำลังตั้งค่าสถานะอย่างชัดเจน แต่ในตัวอย่างที่สาม เรากำลังส่งต่อสถานะเพื่อตั้งค่า การกลายพันธุ์มักใช้สองพารามิเตอร์: state และอาร์กิวเมนต์เมื่อเรียกการกลายพันธุ์

หากต้องการเรียกการกลายพันธุ์ คุณจะต้องใช้ฟังก์ชัน การคอมมิต บนสโตร์ ในกรณีของเรา ฉันจะเพิ่มมันเข้าไปในการทำลาย:

 actions: { async loadBooks({ state, commit }) { commit("setBusy"); const response = await bookService.getBooks(state.currentTopic, if (response.status === 200) { commit("setBooks", response.data); } commit("clearBusy"); } },

สิ่งที่คุณจะเห็นคือ การคอมมิตต้องใช้ชื่อของการ กระทำ อย่างไร มีเทคนิคที่จะทำให้สิ่งนี้ไม่เพียงแค่ใช้สตริงเวทย์มนตร์ แต่ฉันจะข้ามไปในตอนนี้ การใช้สายเวทย์มนตร์เป็นหนึ่งในข้อจำกัดของการใช้ Vuex

ในขณะที่ใช้คอมมิตอาจดูเหมือนเป็น wrapper ที่ไม่จำเป็น โปรดจำไว้ว่า Vuex จะไม่ให้คุณเปลี่ยนสถานะยกเว้นภายในการกลายพันธุ์ ดังนั้นการเรียกผ่านความ มุ่งมั่น เท่านั้น

คุณยังสามารถเห็นได้ว่าการเรียกไปยัง setBooks มีอาร์กิวเมนต์ที่สอง นี่เป็นอาร์กิวเมนต์ที่สองที่เรียกการกลายพันธุ์ หากคุณต้องการข้อมูลเพิ่มเติม คุณจะต้องรวมไว้ในอาร์กิวเมนต์เดียว (ขณะนี้มีข้อจำกัดอื่นของ Vuex) สมมติว่าคุณต้องการแทรกหนังสือลงในรายการหนังสือ คุณสามารถเรียกได้ดังนี้:

 commit("insertBook", { book, place: 4 }); // object, tuple, etc.

จากนั้นคุณสามารถทำลายเป็นชิ้น ๆ ที่คุณต้องการ:

 mutations: { insertBook(state, { book, place }) => // ... }

สง่างามหรือไม่? ไม่จริง แต่ใช้งานได้

ตอนนี้เราได้ดำเนินการกับการกลายพันธุ์แล้ว เราจำเป็นต้องสามารถใช้ Vuex store ในโค้ดของเราได้ มีสองวิธีที่จะได้รับที่ร้าน ขั้นแรก โดยการลงทะเบียนร้านค้าด้วยแอปพลิเคชัน (เช่น main.ts/js) คุณจะสามารถเข้าถึงร้านค้าส่วนกลางที่คุณสามารถเข้าถึงได้จากทุกที่ในแอปพลิเคชันของคุณ:

 // 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); // ...

ใช้งานได้ดี แต่ฉันต้องการนำเข้าร้านค้าโดยตรง:

 import store from "@/store"; export default defineComponent({ components: { BookInfo, }, setup() { const books = computed(() => store.state.books); // ...

เมื่อคุณเข้าถึงวัตถุของร้านค้าได้แล้ว คุณจะใช้งานมันอย่างไร? สำหรับสถานะ คุณจะต้องรวมฟังก์ชันที่คำนวณไว้เพื่อให้การเปลี่ยนแปลงมีผลกับการผูกของคุณ:

 export default defineComponent({ setup() { const books = computed(() => store.state.books); return { books }; }, });

ในการเรียกการดำเนินการ คุณจะต้องเรียกวิธีการ จัดส่ง :

 export default defineComponent({ setup() { const books = computed(() => store.state.books); onMounted(async () => await store.dispatch("loadBooks")); return { books }; }, });

การดำเนินการสามารถมีพารามิเตอร์ที่คุณเพิ่มหลังชื่อของเมธอด สุดท้ายนี้ หากต้องการเปลี่ยนสถานะ คุณจะต้องเรียกใช้การคอมมิต เช่นเดียวกับที่เราทำในการดำเนินการ ตัวอย่างเช่น ฉันมีคุณสมบัติการเพจในร้านค้า จากนั้นฉันสามารถเปลี่ยนสถานะด้วย commit :

 const incrementPage = () => store.commit("setPage", store.state.currentPage + 1); const decrementPage = () => store.commit("setPage", store.state.currentPage - 1);

โปรดทราบว่าการเรียกแบบนี้จะทำให้เกิดข้อผิดพลาด (เพราะคุณไม่สามารถเปลี่ยนสถานะได้ด้วยตนเอง):

 const incrementPage = () => store.state.currentPage++; const decrementPage = () => store.state.currentPage--;

นี่คือพลังที่แท้จริงในที่นี้ เราต้องการควบคุมตำแหน่งที่มีการเปลี่ยนแปลงของรัฐ และไม่มีผลข้างเคียงที่ก่อให้เกิดข้อผิดพลาดในการพัฒนาต่อไป

คุณอาจรู้สึกหนักใจกับจำนวนชิ้นส่วนที่เคลื่อนไหวใน Vuex แต่สามารถช่วยจัดการสถานะในโครงการขนาดใหญ่และซับซ้อนมากขึ้นได้ ฉันจะไม่บอกว่าคุณต้องการมันในทุกกรณี แต่จะมีโครงการขนาดใหญ่ที่จะช่วยคุณโดยรวม

ปัญหาใหญ่ของ Vuex 4 คือการทำงานกับมันในโปรเจ็กต์ TypeScript ทำให้เหลืออะไรอีกมากเป็นที่ต้องการ คุณสามารถสร้างประเภท TypeScript เพื่อช่วยในการพัฒนาและสร้างได้อย่างแน่นอน แต่ต้องใช้ชิ้นส่วนที่เคลื่อนไหวจำนวนมาก

นั่นคือจุดที่ Vuex 5 มีไว้เพื่อทำให้วิธีการทำงานของ Vuex ใน TypeScript ง่ายขึ้น (และในโครงการ JavaScript โดยทั่วไป) เรามาดูกันว่าจะทำงานอย่างไรเมื่อเปิดตัวในครั้งต่อไป

Vuex 5

หมายเหตุ : รหัสสำหรับส่วนนี้อยู่ในสาขา "Vuex5" ของโครงการตัวอย่างบน GitHub

ในช่วงเวลาของบทความนี้ Vuex 5 ไม่ใช่ของจริง เป็น RFC (ขอความคิดเห็น) มันเป็นแผน เป็นจุดเริ่มต้นของการสนทนา ดังนั้นสิ่งที่ฉันอาจอธิบายในที่นี้อาจเปลี่ยนแปลงบ้าง แต่เพื่อเตรียมคุณให้พร้อมสำหรับการเปลี่ยนแปลงใน Vuex ฉันต้องการให้คุณเห็นว่าจะไปถึงไหน ด้วยเหตุนี้ รหัสที่เกี่ยวข้องกับตัวอย่างนี้จึงไม่สร้าง

แนวคิดพื้นฐานเกี่ยวกับวิธีการทำงานของ Vuex ค่อนข้างไม่เปลี่ยนแปลงตั้งแต่เริ่มก่อตั้ง ด้วยการเปิดตัว Vue 3 ทำให้ Vuex 4 ถูกสร้างขึ้นเพื่อให้ Vuex ทำงานในโครงการใหม่เป็นส่วนใหญ่ แต่ทีมกำลังพยายามดูจุดปวดที่แท้จริงด้วย Vuex และแก้ไข ด้วยเหตุนี้ พวกเขากำลังวางแผนการเปลี่ยนแปลงที่สำคัญบางประการ:

  • ไม่มีการกลายพันธุ์อีกต่อไป: การกระทำสามารถเปลี่ยนสถานะได้ (และอาจเป็นใครก็ได้)
  • รองรับ TypeScript ที่ดีขึ้น
  • ฟังก์ชั่นร้านค้าหลายร้านที่ดีขึ้น

แล้วมันจะทำงานอย่างไร? เริ่มต้นด้วยการสร้างร้านค้า:

 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); } } });

การเปลี่ยนแปลงครั้งแรกที่เห็นคือตอนนี้ทุกร้านต้องการกุญแจของตัวเอง เพื่อให้คุณสามารถดึงข้อมูลร้านค้าได้หลายร้าน ถัดไป คุณจะสังเกตเห็นว่าสถานะอ็อบเจ็กต์ตอนนี้เป็นแฟคทอรี (เช่น ส่งคืนจากฟังก์ชัน ไม่ได้สร้างขึ้นเมื่อแยกวิเคราะห์) และไม่มีส่วนการกลายพันธุ์อีกต่อไป สุดท้ายนี้ ภายในการดำเนินการ คุณจะเห็นว่าเรากำลังเข้าถึงสถานะเป็นเพียงคุณสมบัติบนตัวชี้ this ไม่ต้องผ่านในสถานะและกระทำการอีกต่อไป ซึ่งไม่เพียงแต่ช่วยให้การพัฒนาง่ายขึ้น แต่ยังช่วยให้อนุมานประเภทสำหรับ TypeScript ได้ง่ายขึ้นอีกด้วย

ในการลงทะเบียน Vuex ในแอปพลิเคชันของคุณ คุณจะต้องลงทะเบียน Vuex แทนร้านค้าส่วนกลางของคุณ:

 import { createVuex } from 'vuex' createApp(App) .use(createVuex()) .use(router) .mount('#app')

สุดท้าย ในการใช้ร้านค้า คุณจะต้องนำเข้าร้านค้าแล้วสร้างตัวอย่าง:

 import bookStore from "@/store"; export default defineComponent({ components: { BookInfo, }, setup() { const store = bookStore(); // Generate the wrapper // ...

ขอให้สังเกตว่าสิ่งที่ส่งคืนจากร้านค้าเป็นวัตถุโรงงานที่ส่งคืนอินสแตนซ์ของร้านค้า ไม่ว่าคุณจะโทรหาโรงงานกี่ครั้ง อ็อบเจ็กต์ที่ส่งคืนเป็นเพียงอ็อบเจ็กต์ที่มีการกระทำ สถานะ และผู้รับเป็นพลเมืองชั้นหนึ่ง (พร้อมข้อมูลประเภท):

 onMounted(async () => await store.loadBooks()); const incrementPage = () => store.currentPage++; const decrementPage = () => store.currentPage--;

สิ่งที่คุณจะเห็นที่นี่คือสถานะนั้น (เช่น currentPage ) เป็นเพียงคุณสมบัติง่ายๆ และการกระทำ (เช่น loadBooks ) เป็นเพียงหน้าที่ ความจริงที่ว่าคุณกำลังใช้ร้านค้าที่นี่เป็นผลข้างเคียง คุณสามารถปฏิบัติต่อวัตถุ Vuex เป็นเพียงวัตถุและทำงานของคุณต่อไป นี่คือการปรับปรุงที่สำคัญใน API

การเปลี่ยนแปลงที่สำคัญอีกประการหนึ่งที่ต้องชี้ให้เห็นคือ คุณสามารถสร้างร้านค้าของคุณโดยใช้ไวยากรณ์ที่คล้ายกับ Composition API:

 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 } });

วิธีนี้ช่วยให้คุณสร้างวัตถุ Vuex ได้เหมือนกับที่คุณสร้างในมุมมองด้วย Composition API และน่าจะง่ายกว่า

ข้อเสียเปรียบหลักประการหนึ่งในการออกแบบใหม่นี้คือ คุณจะสูญเสียสถานะที่ไม่สามารถเปลี่ยนแปลงได้ มีการพูดคุยกันเกี่ยวกับความสามารถในการเปิดใช้งาน (สำหรับการพัฒนาเท่านั้น เช่นเดียวกับ Vuex 4) แต่ไม่มีฉันทามติว่าสิ่งนี้มีความสำคัญเพียงใด โดยส่วนตัวแล้วฉันคิดว่าเป็นประโยชน์หลักสำหรับ Vuex แต่เราจะต้องดูว่าสิ่งนี้จะมีผลอย่างไร

เราอยู่ที่ไหน

การจัดการสถานะที่ใช้ร่วมกันในแอปพลิเคชันหน้าเดียวเป็นส่วนสำคัญของการพัฒนาสำหรับแอปส่วนใหญ่ การมีแผนเกมเกี่ยวกับวิธีที่คุณต้องการดำเนินการใน Vue เป็นขั้นตอนสำคัญในการออกแบบโซลูชันของคุณ ในบทความนี้ ฉันได้แสดงรูปแบบต่างๆ ในการจัดการสถานะที่ใช้ร่วมกัน รวมถึงสิ่งที่จะเกิดขึ้นกับ Vuex 5 หวังว่าคุณจะมีความรู้ในการตัดสินใจที่ถูกต้องสำหรับโครงการของคุณ