มีอะไรมาสู่ VueX?
เผยแพร่แล้ว: 2022-03-10Vuex เป็น โซลูชันสำหรับการจัดการสถานะในแอปพลิเคชัน Vue เวอร์ชันถัดไป — Vuex 4 — กำลังดำเนินการตามขั้นตอนสุดท้ายก่อนที่จะเปิดตัวอย่างเป็นทางการ รุ่นนี้จะนำความเข้ากันได้อย่างสมบูรณ์กับ Vue 3 แต่ไม่ได้เพิ่มคุณสมบัติใหม่ แม้ว่า Vuex จะเป็นโซลูชันที่ทรงพลังมาโดยตลอด และเป็นตัวเลือกแรกสำหรับนักพัฒนาหลายๆ คนสำหรับการจัดการสถานะใน Vue นักพัฒนาบางคนหวังว่าจะพบปัญหาเวิร์กโฟลว์เพิ่มเติม อย่างไรก็ตาม แม้ในขณะที่ Vuex 4 เพิ่งจะออกจากประตู Kia King Ishii (สมาชิกในทีมหลักของ Vue) ก็กำลังพูดถึงแผนการของเขาสำหรับ Vuex 5 และฉันรู้สึกตื่นเต้นมากกับสิ่งที่เห็นจนต้องแชร์กับคุณ ทั้งหมด. โปรดทราบว่าแผน Vuex 5 ยัง ไม่ สิ้นสุด ดังนั้นบางสิ่งอาจเปลี่ยนแปลงได้ก่อนที่ Vuex 5 จะออกวางจำหน่าย แต่ถ้ามันจบลงที่คล้ายกับที่คุณเห็นในบทความนี้เป็นส่วนใหญ่ ก็ควรปรับปรุงประสบการณ์ของนักพัฒนาอย่างมาก
ด้วยการถือกำเนิดของ Vue 3 และองค์ประกอบ API ผู้คนต่างมองหาทางเลือกง่ายๆ ที่สร้างขึ้นด้วยมือ ตัวอย่างเช่น คุณอาจไม่ต้องการ Vuex แสดงให้เห็นถึงรูปแบบที่ค่อนข้างง่าย แต่ยืดหยุ่นและแข็งแกร่งสำหรับการใช้ API องค์ประกอบควบคู่ไปกับ provide/inject
เพื่อสร้างที่เก็บสถานะที่ใช้ร่วมกัน ตามที่ Gabor ระบุไว้ในบทความของเขา ควรใช้สิ่งนี้ (และทางเลือกอื่น) ในแอปพลิเคชันขนาดเล็กเท่านั้น เพราะพวกเขาขาดทุกสิ่งที่ไม่ได้เกี่ยวกับโค้ดโดยตรง: การสนับสนุนชุมชน เอกสารประกอบ ข้อตกลง การผสานรวม Nuxt ที่ดีและนักพัฒนา เครื่องมือ
อันสุดท้ายเป็นปัญหาที่ใหญ่ที่สุดสำหรับฉันเสมอ ส่วนขยายเบราว์เซอร์ Vue devtools เป็นเครื่องมือที่ยอดเยี่ยมสำหรับการดีบักและการพัฒนาแอป Vue และการสูญเสียตัวตรวจสอบ Vuex ด้วย "การเดินทางข้ามเวลา" จะเป็นการสูญเสียครั้งใหญ่สำหรับการดีบักแอปพลิเคชันที่ไม่สำคัญ
โชคดีที่ Vuex 5 ทำให้เราสามารถกินเค้กของเราได้เช่นกัน มันจะทำงานเหมือนทางเลือก API องค์ประกอบเหล่านี้ แต่ยังคงประโยชน์ทั้งหมดของการใช้ไลบรารีการจัดการของรัฐอย่างเป็นทางการ ตอนนี้เรามาดูกันว่าจะมีอะไรเปลี่ยนแปลงบ้าง
การกำหนดร้านค้า
ก่อนที่เราจะสามารถทำอะไรกับร้านค้า Vuex ได้ เราต้องกำหนดเสียก่อน ใน Vuex 4 คำจำกัดความของร้านค้าจะมีลักษณะดังนี้:
import { createStore } from 'vuex' export const counterStore = createStore({ state: { count: 0 }, getters: { double (state) { return state.count * 2 } }, mutations: { increment (state) { state.count++ } }, actions: { increment (context) { context.commit('increment') } } })
แต่ละร้านมีสี่ส่วน: state
เก็บข้อมูล, getters
ให้สถานะที่คำนวณ, mutations
ใช้เพื่อเปลี่ยน state และ actions
คือวิธีที่ถูกเรียกจากภายนอกร้านค้าเพื่อทำสิ่งใดๆ ที่เกี่ยวข้องกับสโตร์ให้สำเร็จ โดยปกติ การกระทำไม่เพียงแต่สร้างการกลายพันธุ์ดังตัวอย่างนี้ แต่จะใช้เพื่อทำงานแบบอะซิงโครนัสเนื่องจากการกลายพันธุ์ ต้อง เป็นแบบซิงโครนัสหรือเพียงแค่ใช้ฟังก์ชันที่ซับซ้อนหรือหลายขั้นตอนเท่านั้น การกระทำไม่สามารถเปลี่ยนแปลงสถานะได้ด้วยตนเอง พวกเขาต้องใช้เครื่องกลายพันธุ์ Vuex 5 มีหน้าตาเป็นอย่างไร?
import { defineStore } from 'vuex' export const counterStore = defineStore({ name: 'counter', state() { return { count: 0 } }, getters: { double () { return this.count * 2 } }, actions: { increment () { this.count++ } } })
มีการเปลี่ยนแปลงเล็กน้อยที่ควรทราบที่นี่ อันดับแรก แทนที่จะ createStore
เราใช้ defineStore
ความแตกต่างนี้เล็กน้อยมาก แต่มีอยู่ด้วยเหตุผลเชิงความหมาย ซึ่งเราจะพูดถึงในภายหลัง ต่อไป เราต้องระบุ name
ร้านที่เราไม่ต้องการมาก่อน ในอดีต โมดูลมีชื่อเป็นของตัวเอง แต่โมดูลไม่ได้จัดเตรียมไว้ เป็นเพียงชื่อคุณสมบัติที่ได้รับมอบหมายจากร้านค้าหลักที่เพิ่มไว้ ตอนนี้ ไม่มีโมดูล แต่ละโมดูลจะเป็นร้านค้าแยกต่างหากและมีชื่อแทน ชื่อนี้ถูกใช้โดยรีจิสทรี Vuex ซึ่งเราจะพูดถึงในภายหลัง
หลังจากนั้น เราต้องทำให้ state
เป็นฟังก์ชันที่คืนค่าสถานะเริ่มต้น แทนที่จะตั้งค่าให้เป็นสถานะเริ่มต้น ซึ่งคล้ายกับตัวเลือก data
บนส่วนประกอบ เราเขียน getters
คล้ายกับวิธีที่เราทำใน Vuex 4 แต่แทนที่จะใช้ state
เป็นพารามิเตอร์สำหรับตัวรับแต่ละตัว คุณสามารถใช้ this
เพื่อไปยังสถานะได้ ในทำนองเดียวกัน actions
ไม่จำเป็นต้องกังวลเกี่ยวกับวัตถุ context
ที่ถูกส่งผ่าน: พวกเขาสามารถใช้ this
เพื่อเข้าถึงทุกสิ่งได้ สุดท้าย ไม่มี mutations
แต่การกลายพันธุ์จะรวมกับ actions
แทน Kia สังเกตว่าบ่อยครั้งเกินไปที่การกลายพันธุ์กลายเป็นตัวกำหนดง่ายๆ เขาไม่ได้พูดถึงว่ามัน "โอเค" หรือไม่ที่จะเปลี่ยนสถานะโดยตรงจากภายนอกร้าน แต่เราได้รับอนุญาตอย่างแน่นอนและสนับสนุนให้เปลี่ยนสถานะโดยตรงจากการกระทำ และรูปแบบ Flux ขมวดคิ้วกับการกลายพันธุ์โดยตรงของรัฐ
หมายเหตุ : สำหรับผู้ที่ชอบ API องค์ประกอบมากกว่าตัวเลือก API สำหรับการสร้างส่วนประกอบ คุณจะยินดีที่จะเรียนรู้ว่ายังมีวิธีสร้างร้านค้าในลักษณะเดียวกันกับการใช้ API องค์ประกอบ
import { ref, computed } from 'vue' import { defineStore } from 'vuex' export const counterStore = defineStore('counter', { const count = ref(0) const double = computed(() => count.value * 2) function increment () { count.value++ } return { count, double, increment } })
ดังที่แสดงไว้ด้านบน ชื่อจะถูกส่งต่อเป็นอาร์กิวเมนต์แรกสำหรับ defineStore
ส่วนที่เหลือดูเหมือนฟังก์ชันการจัดองค์ประกอบสำหรับส่วนประกอบ ซึ่งจะให้ผลลัพธ์เหมือนกับตัวอย่างก่อนหน้าที่ใช้ตัวเลือก API ทุกประการ
รับอินสแตนซ์ของร้านค้า
ใน Vuex 4 สิ่งต่าง ๆ เปลี่ยนไปจาก Vuex 3 แต่ฉันจะดูที่ v4 เพื่อไม่ให้สิ่งต่าง ๆ หลุดมือ ใน v4 เมื่อคุณเรียก createStore
คุณได้สร้างอินสแตนซ์ไว้แล้ว จากนั้นคุณสามารถใช้มันในแอปของคุณไม่ว่าจะผ่าน app.use
หรือโดยตรง:
import { createApp } from 'vue' import App from './App.vue' // Your root component import store from './store' // The store definition from earlier const app = createApp(App) app.use(store) app.mount('#app') // Now all your components can access it via `this.$store` // Or you can use in composition components with `useStore()` // ----------------------------------------------- // Or use directly... this is generally discouraged import store from './store' store.state.count // -> 0 store.commit('increment') store.dispatch('increment') store.getters.double // -> 4
นี่เป็นสิ่งหนึ่งที่ Vuex 5 ทำให้ซับซ้อนกว่าใน v4 เล็กน้อย ตอนนี้แต่ละแอพสามารถรับ Vuex แยกกัน ซึ่งทำให้แน่ใจว่าแต่ละแอพสามารถมีอินสแตนซ์ที่แยกจากกันของร้านค้าเดียวกันโดยไม่ต้องแชร์ข้อมูลระหว่างกัน คุณสามารถแชร์อินสแตนซ์ของ Vuex ได้หากต้องการแชร์อินสแตนซ์ของร้านค้าระหว่างแอป
import { createApp } from 'vue' import { createVuex } from 'vuex' import App from './App.vue' // Your root component const app = createApp(App) const vuex = createVuex() // create instance of Vuex app.use(vuex) // use the instance app.mount('#app')
ตอนนี้ส่วนประกอบทั้งหมดของคุณสามารถเข้าถึงอินสแตนซ์ Vuex ได้แล้ว แทนที่จะให้คำจำกัดความร้านค้าของคุณโดยตรง คุณต้องนำเข้าส่วนประกอบเหล่านั้นไปยังส่วนประกอบที่คุณต้องการใช้ในและใช้อินสแตนซ์ Vuex เพื่อสร้างอินสแตนซ์และลงทะเบียน:
import { defineComponent } from 'vue' import store from './store' export default defineComponent({ name: 'App', computed: { counter () { return this.$vuex.store(store) } } })
การเรียก $vuex.store
สร้างอินสแตนซ์และลงทะเบียนร้านค้าในอินสแตนซ์ Vuex จากจุดนั้น ทุกครั้งที่คุณใช้ $vuex.store
กับร้านค้านั้น มันจะคืนร้านค้าที่สร้างอินสแตนซ์ไปแล้วให้คุณ แทนที่จะสร้างอินสแตนซ์ใหม่อีกครั้ง คุณสามารถเรียกใช้เมธอด store
ได้โดยตรงบนอินสแตนซ์ของ Vuex ที่สร้างโดย createVuex()
ตอนนี้ร้านค้าของคุณสามารถเข้าถึงได้จากส่วนประกอบนั้นผ่านทาง this.counter
หากคุณกำลังใช้ API องค์ประกอบสำหรับองค์ประกอบของคุณ คุณสามารถใช้ useStore
แทน this.$vuex.store
:
import { defineComponent } from 'vue' import { useStore } from 'vuex' // import useStore import store from './store' export default defineComponent({ setup () { const counter = useStore(store) return { counter } } })
มีข้อดีและข้อเสียในการนำเข้าร้านค้าโดยตรงไปยังส่วนประกอบและสร้างอินสแตนซ์ที่นั่น ช่วยให้คุณสามารถแยกรหัสและโหลดร้านค้าอย่างเกียจคร้านได้เฉพาะเมื่อจำเป็น แต่ตอนนี้เป็นการพึ่งพาโดยตรงแทนที่จะถูกผู้ปกครองสั่งฉีด (ไม่ต้องพูดถึงคุณต้องนำเข้าทุกครั้งที่คุณต้องการใช้) หากคุณต้องการใช้การแทรกการพึ่งพาเพื่อให้ทั่วทั้งแอป โดยเฉพาะอย่างยิ่งถ้าคุณรู้ว่าจะใช้ที่รูทของแอปที่การแยกโค้ดไม่ช่วย คุณสามารถใช้ provide
:
import { createApp } from 'vue' import { createVuex } from 'vuex' import App from './App.vue' import store from './store' const app = createApp(App) const vuex = createVuex() app.use(vuex) app.provide('store', store) // provide the store to all components app.mount('#app')
และคุณสามารถฉีดเข้าไปในส่วนประกอบใดๆ ที่คุณจะใช้งาน:
import { defineComponent } from 'vue' export default defineComponent({ name: 'App', inject: ['store'] }) // Or with Composition API import { defineComponent, inject } from 'vue' export default defineComponent({ setup () { const store = inject('store') return { store } } })
ฉันไม่ตื่นเต้นกับการใช้คำฟุ่มเฟือยนี้ แต่มันชัดเจนและยืดหยุ่นมากกว่า ซึ่งฉันเป็นแฟนตัวยง โดยทั่วไปแล้วโค้ดประเภทนี้จะเขียนเพียงครั้งเดียวในตอนเริ่มต้นของโปรเจ็กต์ และจะไม่รบกวนคุณอีก แม้ว่าตอนนี้คุณจะต้องระบุร้านค้าใหม่แต่ละแห่งหรือนำเข้าทุกครั้งที่คุณต้องการใช้ การนำเข้าหรือฉีดโมดูลโค้ดเป็นวิธีที่โดยทั่วไปเราต้องทำงานกับอย่างอื่น ดังนั้นจึงทำให้ Vuex ทำงานสอดคล้องกับวิธีที่ผู้คนมักจะทำงานอยู่แล้ว
การใช้ร้านค้า
นอกเหนือจากการเป็นแฟนตัวยงของความยืดหยุ่นและวิธีใหม่ในการกำหนดร้านค้าแบบเดียวกับส่วนประกอบที่ใช้ API องค์ประกอบแล้ว ยังมีอีกสิ่งหนึ่งที่ทำให้ฉันตื่นเต้นมากกว่าสิ่งอื่นใด นั่นคือ วิธีการใช้ร้านค้า นี่คือลักษณะการใช้งานร้านค้าใน Vuex 4
store.state.count // Access State store.getters.double // Access Getters store.commit('increment') // Mutate State store.dispatch('increment') // Run Actions
State
, getters
, mutations
และ actions
ทั้งหมดได้รับการจัดการในรูปแบบที่แตกต่างกันผ่านคุณสมบัติหรือวิธีการที่แตกต่างกัน สิ่งนี้มีข้อดีคือความชัดเจน ซึ่งฉันยกย่องก่อนหน้านี้ แต่ความชัดเจนนี้ไม่ได้ประโยชน์อะไรจากเราเลย และ API นี้จะใช้งานยากขึ้นเมื่อคุณใช้โมดูลเนมสเปซเท่านั้น เมื่อเปรียบเทียบแล้ว Vuex 5 ดูเหมือนว่าจะทำงานได้อย่างที่คุณคาดหวังตามปกติ:
store.count // Access State store.double // Access Getters (transparent) store.increment() // Run actions // No Mutators
ทุกอย่าง — สถานะ ผู้ได้รับ และการกระทำ — มีให้โดยตรงที่รูทของร้านค้า ทำให้ง่ายต่อการใช้งานโดยใช้คำฟุ่มเฟือยน้อยลง และขจัดความจำเป็นในการใช้ mapState
, mapGetters
, mapActions
และ mapMutations
สำหรับตัวเลือก API หรือการเขียน คำสั่ง computed
พิเศษหรือฟังก์ชันอย่างง่ายสำหรับ API องค์ประกอบ สิ่งนี้ทำให้ร้านค้า Vuex มีรูปลักษณ์และดำเนินการเหมือนกับร้านค้าทั่วไปที่คุณสร้างขึ้นเอง แต่ได้รับประโยชน์ทั้งหมดจากปลั๊กอิน เครื่องมือดีบัก เอกสารอย่างเป็นทางการ ฯลฯ
ร้านขายของแต่ง
มุมมองสุดท้ายของ Vuex 5 ที่เราจะพิจารณาในวันนี้คือความสามารถในการเขียนได้ Vuex 5 ไม่มีโมดูลเนมสเปซที่สามารถเข้าถึงได้จากร้านค้าเดียว แต่ละโมดูลเหล่านั้นจะถูกแบ่งออกเป็นร้านค้าที่แยกจากกันโดยสิ้นเชิง ง่ายพอที่จะจัดการกับส่วนประกอบ: พวกเขาเพียงแค่นำเข้าร้านใดก็ได้ที่พวกเขาต้องการและเปิดไฟและใช้งาน แต่ถ้าร้านหนึ่งต้องการโต้ตอบกับร้านอื่นล่ะ ใน v4 การเนมสเปซจะทำให้เกิดความสับสนทั้งหมด ดังนั้นคุณต้องใช้เนมสเปซในการ dispatch
คำ rootGetters
commit
rootState
จากนั้นจึงเพิ่มเข้าไปในเนมสเปซที่คุณต้องการเข้าถึง getters และ state นี่คือวิธีการทำงานใน Vuex 5:
// store/greeter.js import { defineStore } from 'vuex' export default defineStore({ name: 'greeter', state () { return { greeting: 'Hello' } } }) // store/counter.js import { defineStore } from 'vuex' import greeterStore from './greeter' // Import the store you want to interact with export default defineStore({ name: 'counter', // Then `use` the store use () { return { greeter: greeterStore } }, state () { return { count: 0 } }, getters: { greetingCount () { return `${this.greeter.greeting} ${this.count}' // access it from this.greeter } } })
ด้วย v5 เรานำเข้าร้านค้าที่เราต้องการใช้ จากนั้นลงทะเบียนด้วย use
และตอนนี้สามารถเข้าถึงได้ทั่วทั้งร้านค้าไม่ว่าจะชื่อคุณสมบัติใดก็ตามที่คุณให้ไว้ สิ่งต่าง ๆ จะง่ายยิ่งขึ้นหากคุณใช้รูปแบบ API องค์ประกอบของคำจำกัดความร้านค้า:
// store/counter.js import { ref, computed } from 'vue' import { defineStore } from 'vuex' import greeterStore from './greeter' // Import the store you want to interact with export default defineStore('counter', ({use}) => { // `use` is passed in to function const greeter = use(greeterStore) // use `use` and now you have full access const count = 0 const greetingCount = computed(() => { return `${greeter.greeting} ${this.count}` // access it like any other variable }) return { count, greetingCount } })
ไม่มีโมดูลเนมสเปซอีกต่อไป แต่ละร้านแยกกันและใช้แยกกัน คุณสามารถใช้ use
เพื่อทำให้ร้านค้าพร้อมใช้งานภายในร้านอื่นเพื่อสร้างมันขึ้นมา ในทั้งสองตัวอย่าง โดยทั่วไป use
เป็นเพียงกลไกเดียวกับ vuex.store
จากรุ่นก่อน และช่วยให้มั่นใจว่าเราสร้างอินสแตนซ์ร้านค้าด้วยอินสแตนซ์ Vuex ที่ถูกต้อง
รองรับ TypeScript
สำหรับผู้ใช้ TypeScript หนึ่งในแง่มุมที่ยอดเยี่ยมที่สุดของ Vuex 5 ก็คือการทำให้เข้าใจง่ายขึ้นทำให้เพิ่มประเภทลงในทุกสิ่งได้ง่ายขึ้น เลเยอร์ของนามธรรมที่ Vuex เวอร์ชันเก่าทำให้แทบเป็นไปไม่ได้ และตอนนี้ด้วย Vuex 4 ได้เพิ่มความสามารถในการใช้ประเภทของเรา แต่ก็ยังมีงานที่ต้องดำเนินการด้วยตนเองมากเกินไปที่จะได้รับการสนับสนุนประเภทที่เหมาะสม ในขณะที่ใน v5 คุณสามารถใส่ประเภทของคุณไว้ในบรรทัดได้เช่นเดียวกับที่คุณคาดหวังและคาดหวัง
บทสรุป
Vuex 5 ดูเหมือนจะเกือบจะเหมือนกับที่ฉัน — และคนอื่นๆ อีกหลายคน — หวังว่าจะเป็นแบบนั้น และฉันก็รู้สึกว่ามันมาเร็วไม่พอ มันช่วยลดความซับซ้อนของ Vuex ส่วนใหญ่ ลบค่าใช้จ่ายทางจิตที่เกี่ยวข้องออกไป และทำให้ซับซ้อนหรือละเอียดมากขึ้นเท่านั้นซึ่งจะเพิ่มความยืดหยุ่น แสดงความคิดเห็นด้านล่างเกี่ยวกับสิ่งที่คุณคิดเกี่ยวกับการเปลี่ยนแปลงเหล่านี้และสิ่งที่คุณอาจทำการเปลี่ยนแปลงแทนหรือเพิ่มเติม หรือไปที่แหล่งที่มาโดยตรงและเพิ่ม RFC (ขอความคิดเห็น) ในรายการเพื่อดูว่าทีมหลักคิดอย่างไร