ما الذي سيأتي إلى 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 يوضح نمطًا بسيطًا نسبيًا ، ولكنه مرن وقوي لاستخدام واجهة برمجة التطبيقات للتكوين جنبًا إلى جنب مع 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
تستخدم لتغيير الحالة ، 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
. لاحظت كيا أنه في كثير من الأحيان ، أصبحت الطفرات مجرد عوامل بسيطة ، مما يجعلها مطولة بلا جدوى ، لذلك قاموا بإزالتها. لم يذكر ما إذا كان من "المقبول" تغيير الحالة مباشرة من خارج المتجر ، لكننا بالتأكيد مسموح لنا ونشجع على تغيير الحالة مباشرة من إجراء ما ، كما أن نمط التدفق يتجاهل الطفرة المباشرة للحالة.
ملاحظة : بالنسبة لأولئك الذين يفضلون تكوين واجهة برمجة التطبيقات على خيارات واجهة برمجة التطبيقات لإنشاء المكونات ، سيسعدك معرفة أن هناك أيضًا طريقة لإنشاء متاجر بطريقة مماثلة لاستخدام واجهة برمجة التطبيقات للتكوين.
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 ، لكنني سألقي نظرة على الإصدار 4 فقط لمنع الأشياء من الخروج عن السيطرة. في الإصدار 4 ، عندما اتصلت بـ 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 أكثر تعقيدًا من الإصدار 4. يمكن لكل تطبيق الآن الحصول على مثيل منفصل من 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
. إذا كنت تستخدم واجهة برمجة تطبيقات التكوين للمكون الخاص بك ، فيمكنك استخدام 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 تعمل بشكل أكبر على غرار الطريقة التي يميل الناس إلى العمل بها بالفعل.
باستخدام متجر
بصرف النظر عن كوني معجبًا بالمرونة والطريقة الجديدة لتعريف المخازن بنفس طريقة المكون الذي يستخدم واجهة برمجة التطبيقات للتكوين ، هناك شيء آخر يجعلني أكثر حماسًا من أي شيء آخر: كيفية استخدام المتاجر. إليك كيف يبدو استخدام متجر في 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
بطرق مختلفة عبر خصائص أو طرق مختلفة. هذا له ميزة الصراحة ، التي مدحتها سابقًا ، لكن هذا الصراحة لا يكسبنا أي شيء حقًا. وتزداد صعوبة استخدام واجهة برمجة التطبيقات هذه فقط عند استخدام الوحدات النمطية ذات مساحات الأسماء. بالمقارنة ، يبدو أن 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 على وحدات ذات مساحة اسم يمكن الوصول إليها جميعًا من المتجر الفردي. سيتم تقسيم كل من هذه الوحدات إلى متجر منفصل تمامًا. يعد هذا أمرًا بسيطًا بما يكفي للتعامل مع المكونات: فهم يستوردون المتاجر التي يحتاجون إليها ويشغلونها ويستخدمونها. ولكن ماذا لو أراد أحد المتاجر التفاعل مع متجر آخر؟ في الإصدار 4 ، تعمل مساحة الاسم على تعديل كل شيء ، لذلك تحتاج إلى استخدام مساحة الاسم في مكالمات commit
dispatch
، واستخدام rootGetters
و rootState
، ثم شق طريقك إلى مساحات الأسماء التي تريد الوصول إلى حروفها وحالتها. وإليك كيفية عملها في 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 } } })
مع الإصدار 5 ، نستورد المتجر الذي نرغب في استخدامه ، ثم نسجله مع use
، والآن يمكن الوصول إليه في جميع أنحاء المتجر بأي اسم ملكية أعطيته له. تكون الأمور أكثر بساطة إذا كنت تستخدم تكوين واجهة برمجة التطبيقات لتعريف المتجر:
// 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 (طلب التعليقات) إلى القائمة لمعرفة رأي الفريق الأساسي.