در قسمتهای قبلی (^ ,^ ,^ ) نحوهی ارتباط بین کامپوننتها بررسی شد؛ روش دیگری هم برای به اشتراک گذاری دادهها بین کامپوننتها وجود دارد که با استفاده از کتابخانهای بنام Vuex پیاده سازی میشود. وقتی برنامهی شما وسعت پیدا میکند و ارتباط بین کامپوننتها بیشتر و پیچیدهتر میشود، روشهای قبلی (^ ,^ ,^ ) کارایی لازم را ندارند و یا اینکه به سختی میشود دادههای به اشتراک گذاشته شدهی بین کامپوننتها را مدیریت نمود. در اینجا میتوان از Vuex استفاده کرد و بهراحتی ارتباطهای پیچیدهی بین کامپوننتها را مدیریت کرد.
درون کامپوننت اصلی برنامه App.vue، هر دو کامپوننت را فراخوانی میکنیم:
در Terminal دستور زیر را تایپ و اجرا کنید تا نتیجه رویت گردد:
در این برنامه از دو کامپوننت مجزا با دادهی واحد، استفاده میکنیم و دیگر خبری از emit$ و on$ و EventBus و تزریق وابستگی نخواهد بود.
سپس برنامه را با دستور زیر اجرا کنید:
کد زیر را در نظر بگیرید:
new Vue({ // state // دادهها در اینجا قرار میگیرند data () { return { count: 0 } }, // view // ویوها برای نمایش دادهها مورد استفاده قرار میگیرند template: `<div>{{ count }}</div>`, // actions // برای تغییر دادهها از متدها استفاده میکنیم methods: { increment () { this.count++ } } })
در یک کامپوننت ساده، از طریق Actionها، دادهها (State) تغییر داده میشوند و سپس این تغییر در view مشاهده میشود. اما فرض کنید بیش از صد کامپوننت در برنامه دارید که بسیاری از آنها از دادههای واحدی استفاده میکنند. روشهای قبلی (^ ,^ ,^) برای چنین سناریویی جوابگو نخواهند بود (آنرا به سختی میتوان مدیریت کرد و بسیار طاقت فرسا خواهد بود).
راه حل Vuex:
با استفاده از Vuex میتوان برای دادهها (State)، یک منبع در نظر گرفت تا کامپوننتها قادر باشند از دادههای واحدی استفاده کنند و اشتراک گذاری دادهها ساده شود.
یک برنامه ساده با استفاده از Vuex:
یک پروژه Vuejs را ایجاد کنید و مطابق تصویر زیر، گزینه دوم را انتخاب و Enter را فشار دهید:
سپس گزینه Vuex را طبق تصویر زیر با دکمهی space انتخاب کنید و برای مابقی گزینههای بعدی با زدن Enter، پیش فرضها را بپذیرید تا پروژه ساخته شود:
دو کامپوننت را به برنامه اضافه میکنیم.
کامپوننت اول با نام increase-counter-component.vue
<template> <div> <!-- نمایش شمارشگر --> <h1>{{count}}</h1> <!-- افزودن یک واحد به شمارشگر --> <button @click="add">Add 1</button> <!-- افزودن مقداری دلخواه به شمارشگر --> <button @click="add2">Add 2</button> </div> </template> <script> // یا همان منبع ذخیره دادهها store کردن import import store from "../store"; export default { // You can consider computed properties another view into your data. // https://css-tricks.com/methods-computed-and-watchers-in-vue-js/ computed: { count: () => store.state.count }, // به دو طریق فراخوانی شده add تابع methods: { // بدون پارامتر add: () => store.commit("add"), // با پارامتر // برای مقدار مورد نظر استفاده کنیم input میتوانیم بجای مقدار ثابت از یک add2: () => store.commit("add", 2) } }; </script>
کامپوننت دوم با نام decrease-counter-component.vue
<template> <div> <h1>{{count}}</h1> <button @click="subtract">Subtract 1</button> <button @click="subtract(3)">Subtract 3</button> </div> </template> <script> import store from "../store"; export default { computed: { count: () => store.state.count }, methods: { subtract: payload => store.commit("subtract", +payload) } }; </script>
<template> <div id="app"> <img alt="Vue logo" src="./assets/logo.png" /> <counter-plus></counter-plus> <hr /> <hr /> <counter-minus></counter-minus> </div> </template> <script> import counterPlus from "./components/increase-counter-component"; import counterMinus from "./components/decrease-counter-component"; export default { name: "app", components: { "counter-plus": counterPlus, "counter-minus": counterMinus } }; </script>
محتویات فایل store.js که تنظیمات Vuex در آن لحاظ شدهاست به شکل زیر میباشد:
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ // دادههای به اشتراک گذاشته شده state: { count: 0 }, // تعریف متدها mutations: { add(state, payload) { // If we get a payload, add it to count // Else, just add one to count payload ? (state.count += payload) : state.count++; }, subtract(state, payload) { payload ? (state.count -= payload) : state.count--; } } });
npm run serve
چگونه کار میکند؟
در Vuex، متدها در قسمت mutation در فایل store.js نوشته میشوند و در methods درون کامپوننتها فراخوانی میشوند. اگر با سی شارپ آشنا باشید، این فراخوانی تقریبا شبیه delegate میباشد. دادهها در store.js تعریف میشوند و در سراسر برنامه در تمام کامپوننتها قابل دسترس میباشند. بدین ترتیب اشتراک گذاری دادهها بین کامپوننتها بسیار ساده میباشد.
npm install
npm run serve
مطالب
Dependency Injection
در ادامه مباحث بهتر کد بنویسیم و الگوهایی که در این رابطه معرفی شدند، اخیرا کتابی از انتشارات manning منتشر شده تحت عنوان Dependency Injection . هر چند به ظاهر این کتاب برای جاوا کارها تهیه شده اما قسمت عمدهای از آن برای سایر زبانهای برنامه نویسی دیگر نیز قابل استفاده است.
DESCRIPTION
In object-oriented programming, a central program normally controls other objects in a module, library, or framework. With dependency injection, this pattern is inverted—a reference to a service is placed directly into the object which eases testing and modularity. Spring or Google Guice use dependency injection so you can focus on your core application and let the framework handle infrastructural concerns.
Dependency Injection explores the DI idiom in fine detail, with numerous practical examples that show you the payoffs. You'll apply key techniques in Spring and Guice and learn important pitfalls, corner-cases, and design patterns. Readers need a working knowledge of Java but no prior experience with DI is assumed.
WHAT'S INSIDE:
◊ How to apply it (Understand it first!)
◊ Design patterns and nuances
◊ Spring, Google Guice, PicoContainer, and more
◊ How to integrate DI with Java frameworks
In object-oriented programming, a central program normally controls other objects in a module, library, or framework. With dependency injection, this pattern is inverted—a reference to a service is placed directly into the object which eases testing and modularity. Spring or Google Guice use dependency injection so you can focus on your core application and let the framework handle infrastructural concerns.
Dependency Injection explores the DI idiom in fine detail, with numerous practical examples that show you the payoffs. You'll apply key techniques in Spring and Guice and learn important pitfalls, corner-cases, and design patterns. Readers need a working knowledge of Java but no prior experience with DI is assumed.
WHAT'S INSIDE:
◊ How to apply it (Understand it first!)
◊ Design patterns and nuances
◊ Spring, Google Guice, PicoContainer, and more
◊ How to integrate DI with Java frameworks
راستی، این کتاب تر و تازه رو میتونید از همین کتاب فروشیهای دور و اطراف نیز تهیه کنید! در سایت booktraining دات ارگ در قسمت graphics-and-design به تاریخ 4 آگوست.
- در Angular یک کامپوننت فقط یکبار در قسمت declarations فقط یک ماژول قابل اضافه شدن است. برای اینکار در نزدیکترین ماژول به محل استفاده آنرا یکبار import و سپس به قسمت declarations اضافهاش کنید:
- اما اگر قرار است در چندین ماژول استفاده شود، یکی از روشها ایجاد یک shared module (اضافه کردن آن فقط به یک تک ماژول اشتراکی) و سپس تعریف این کامپوننت در قسمت exports آن است:
در آخر از این shared module به صورت زیر میتوان استفاده کرد:
import { MyComponent } from './../path/my.component'; @NgModule({ imports: [...], declarations: [ MyComponent], providers: [...] }) export class UsersLoginModule {}
- اما اگر قرار است در چندین ماژول استفاده شود، یکی از روشها ایجاد یک shared module (اضافه کردن آن فقط به یک تک ماژول اشتراکی) و سپس تعریف این کامپوننت در قسمت exports آن است:
import {SharedComponentA} from "./SharedComponentA"; import {SharedComponentB} from "./SharedComponentA"; @NgModule({ imports: [ ], declarations: [ SharedComponentA, SharedComponentB ], providers: [ ], exports: [ SharedComponentA, SharedComponentB ] }) export class SharedModule {}
import {SharedModule } from './SharedModule'; @NgModule({ imports: [ //... SharedModule //... ], declarations: [ // .... ], providers: [ // .... ] }) export class PersonModule{}
اشتراکها
شروع به کار با Kendo UI for Angular
استفاده از Redux درون پروژههای React، به روشهای مختلفی قابل انجام است؛ یعنی محدودیتی از لحاظ نحوه چیدمان فایلها، تغییر state و نحوهی dispatch کردن action وجود ندارد. به عبارتی این آزادی عمل را خواهیم داشت تا خودمان سیمکشی پروژه را انجام دهیم؛ ولی مشکل اصلی اینجاست که نمیتوانیم مطمئن شویم روشی که پروژه را با آن ستاپ کردهایم آیا به عنوان یک best-practice محسوب میشود یا خیر. در نهایت خروجی را که خواهیم داشت، حجم انبوهی از کدهای boilerplate و پکیجهای زیادی است که در حین توسعهی پروژه، به همراه Redux اضافه شدهاند. همچنین در نهایت یک store پیچیده را خواهیم داشت که مدیریت آن به مراتب سخت خواهد شد. یک مشکل دیگر این است که روال گفته شده را باید به ازای هر پروژهی جدید تکرار کنیم. برای حل این مشکل، یکی از maintainerهای اصلی تیم ریداکس، یک پروژه را تحت عنوان Redux Toolkit، مدتها قبل برای حل مشکلات عنوان شده شروع کرده است و این پکیج، جدیداً به قالب رسمی create-react-app اضافه شده است. که در واقع یک روش استاندارد و به اصطلاح opinionated برای ایجاد پروژههای ریداکسی میباشد و شامل تمامی وابستگیهای موردنیاز برای کار با Redux از قبیل redux-thunk و همچنین Redux DevTools است.
در کد فوق، نحوهی ایجاد store، نسبت به حالت معمول، خیلی تمیزتر است. نکتهی جالب این است که به همراه کد فوق، Redux DevTools و همچنین redux-thunk هم از قبل تنظیم شدهاند و در نتیجه، نیازی به تنظیم و نصب آنها نیست. تغییر مهم دیگر، پوشهی features میباشد که یک روش رایج برای گروهبندی کامپوننتها، همراه با فایلهای وابستهی آنها است. درون این پوشه، یک پوشه جدید را تحت عنوان counter داریم که حاوی فایلهای زیر میباشد:
Counter.js، کامپوننتی است که در نهایت درون صفحه رندر خواهد شد. درون این فایل با استفاده از Redux Hooks کار اتصال به store و همچنین dispatch کردن اکشنها انجام گرفته است:
کاری که immer انجام میدهد، تغییر یک شیء، به صورت مستقیم نیست؛ در واقع یک draftState را ایجاد خواهد کرد که در عمل یک proxy برای state فعلی میباشد. یعنی با mutate کردن state، یک شیء جدید را در نهایت clone خواهد کرد و به عنوان state نهایی برمیگرداند.
ایجاد یک برنامهی خالی React با قالب redux
در ادامه برای بررسی این قالب جدید، یک پروژهی جدید React را ایجاد خواهیم کرد:
> npx create-react-app redux-template --template redux > cd redux-template > yarn start
بررسی ساختار پروژهی ایجاد شده
ساختار پروژهی ایجاد شده به صورت زیر است:
این ساختار خیلی شبیه به قالب پیشفرض create-react-app میباشد. همانطور که در تصویر فوق نیز مشاهده میکنید، پروژهی ایجاد شدهی با قالب redux (تصویر سمت چپ)، یک فایل با نام store و همچنین یک دایرکتوری را به نام features دارد. اگر به فایل store.js مراجعه کنید، خواهید دید که تنظیمات اولیهی ایجاد store را در قالب یک مثال Counter ایجاد کردهاست:
import { configureStore } from '@reduxjs/toolkit'; import counterReducer from './features/counter/counterSlice'; export default configureStore({ reducer: { counter: counterReducer, }, });
Counter Counter.module.css counterSlice.js
import React, { useState } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { decrement, increment, incrementByAmount, selectCount, } from './counterSlice'; import styles from './Counter.module.css'; export function Counter() { const count = useSelector(selectCount); const dispatch = useDispatch(); const [incrementAmount, setIncrementAmount] = useState(2); return ( <div> <div className={styles.row}> <button className={styles.button} aria-label="Increment value" onClick={() => dispatch(increment())} > + </button> <span className={styles.value}>{count}</span> <button className={styles.button} aria-label="Decrement value" onClick={() => dispatch(decrement())} > - </button> </div> <div className={styles.row}> <input className={styles.textbox} value={incrementAmount} onChange={e => setIncrementAmount(e.target.value)} /> <button className={styles.button} onClick={() => dispatch( incrementByAmount({ amount: Number(incrementAmount) || 0 }) ) } > Add Amount </button> </div> </div> ); }
فایل Counter.module.css نیز در واقع استایلهای مربوط به کامپوننت فوق میباشد که به صورت CSS module اضافه شدهاست. در نهایت فایل counterSlice.js را داریم که کار همان reducer سابق را برایمان انجام خواهد داد؛ اما اینبار با یک ساختار جدید و تحت عنوان slice. اگر به فایل عنوان شده مراجعه کنید، کدهای زیر را خواهید دید:
import { createSlice } from '@reduxjs/toolkit'; export const slice = createSlice({ name: 'counter', initialState: { value: 0, }, reducers: { increment: state => { // Redux Toolkit allows us to 'mutate' the state. It doesn't actually // mutate the state because it uses the immer library, which detects // changes to a "draft state" and produces a brand new immutable state // based off those changes state.value += 1; }, decrement: state => { state.value -= 1; }, incrementByAmount: (state, action) => { state.value += action.payload.amount; }, }, }); export const selectCount = state => state.counter.value; export const { increment, decrement, incrementByAmount } = slice.actions; export default slice.reducer;
در این قالب جدید، ترکیب این قطعات هستند که شیء اصلی یا در واقع همان state کلی پروژه را تشکیل خواهند داد. همانطور که مشاهده میکنید، برای ایجاد یک قطعه جدید، از تابع createSlice استفاده شده است. این تابع، تعدادی پارامتر را از ورودی دریافت میکند:
- name: برای هر بخش از state، میتوانیم یک نام را تعیین کنیم و این همان عنوانی خواهد بود که میتوانید توسط Redux DevTools مشاهده کنید.
- initialValue: در اینجا میتوانیم مقادیر اولیهای را برای این بخش از state، تعیین کنیم که در مثال فوق، value به مقدار صفر تنظیم شدهاست.
- reducers: این قسمت محل تعریف actionهایی هستند که قرار است state را تغییر دهند. نکته جالب توجه این است که state در هر کدام از متدهای فوق، به ظاهر mutate شده است؛ اما همانطور که به صورت کامنت نیز نوشتهاست، در پشت صحنه از کتابخانهای با عنوان immer استفاده میکند که در عمل بجای تغییر state اصلی، یک کپی از state جدید را جایگزین state قبلی خواهد کرد.
توسط selectCount نیز کار انتخاب مقدار موردنظر از state انجام شدهاست که معادل همان mapPropsToState است و در اینجا امکان دسترسی به state ذخیره شده در مخزن redux فراهم شده است. همچنین در خطوط پایانی فایل نیز اکشنها برای دسترسی سادهتر به درون کامپوننت، به صورت Object Destructuring به بیرون export شدهاند. در نهایت reducer نهایی را از slice ایجاد شده استخراج کردهایم. این پراپرتی برای ایجاد store مورداستفاده قرار میگیرد.
چرا قالب Redux Toolkit از immer برای تغییر state استفاده میکند؟
همانطور که در این قسمت از سری Redux توضیح داده شد، اعمال تغییرات بر روی آرایهها و اشیاء، باعث ایجاد ناخالصی خواهد شد؛ بنابراین به جای تغییر شیء اصلی، باید توسط یکی از روشهای Object.assign و یا spread operator، یک clone از state اصلی را ایجاد کرده و آن را به عنوان state نهایی لحاظ کنیم. اما در حین کار با اشیاء nested، انجام اینکار سخت خواهد شد و همچنین خوانایی کد را نیز کاهش میدهد:
return { ...state, models: state.models.map(c => c.model === action.payload.model ? { ...c, on: action.payload.toggle } : c ) };
اما با کمک immer میتوانیم به صورت مستقیم state را تغییر دهیم:
state.models.forEach(item => { if (item.model === action.payload.model) { item.on = action.payload.toggle; } });
اشتراکها
معماری module based در TypeScript
نظرات مطالب
چک لیست تهیه یک برنامه ASP.NET MVC
- Request.IsAuthenticated یک IP را در روال رخداد گردان AuthenticateRequest و یا PostAuthenticateRequest بررسی کنید.
- از هر روشی که علاقمندید برای تایمر استفاده کنید. من به روشهای ساده و توکار برای انجام کارهای ساده در یک HTTP Module نصف صفحهای علاقمندم.
- از هر روشی که علاقمندید برای تایمر استفاده کنید. من به روشهای ساده و توکار برای انجام کارهای ساده در یک HTTP Module نصف صفحهای علاقمندم.