کتابخانه Zustand در ری اکت ! یک State Management قدرتمند و ساده !
کتابخانه Zustand یک State Management قدرتمند ولی ساده و باحاله که یادگیریش خیلی خیلی آسونه! بر خلاف State Management هایی مثل ریداکس که خیلی پیچیده هستن و یادگیری سختی دارن، کتابخانه Zustand تو کمتر از چند دقیقه کانفیگ میشه و خیلی خیلی سادس 🙂
پس اگه شما هم مثل من با Redux حال نمیکنید و دمبال یک State Management قدرتمند ولی ساده و دوست داشتنی میگردید، کتابخانه Zustand همون چیزیه که دمبالش میگردیم 🙂
بیاید بریم ببینیم کتابخانه Zustand در ری اکت چیست و به چه دردی میخوره …
کتابخانه Zustand چیست ؟
کتابخانه Zustand یک State Management سبک، قدرتمند و ساده برای مدیریت State در کامپوننت های ری اکتی هست.
کار کردن با کتابخانه Zustand در ری اکت خیلی خیلی آسونه و همین باعث شده که با ریداکس و امثال اون خداحافظی کنیم و بریم سراغ Zustand در ریکت 🙂
کتابخانه Zustand یه عالمه هوک کاربردی در اختیار ما گذاشته که باعث شده خیلی بهینه تر و ساده تر بتونیم State های اپیکیشن خودمون رو مدیریت کنیم.
صادقانه بخوام بگم، کتابخانه Zustand ارزش یادگیری داره، اما بیاید یه چندتا آمار باحال درمورد این کتابخانه باهمدیگه بببنیم :
2.5 میلیون بار در هفته دانلود میشه و این آمار به سرعت درحال رشده !
مجموعا 118 میلیون بار دانلود شده !
بشدت سبکه ( حجم کل باندل این کتابخانه فقط 1.2 کیلوبایت هست ! )
40 هزار ستاره تو گیتهاب دریافت کرده !
بریم یکم کد بزنیم و ببینیم چطور میشه از کتابخانه Zustand در ری اکت برای مدیریت State استفاده کرد ..
آموزش کتابخانه Zustand در ری اکت
ما میخوایم یک اپیکیشن ری اکتی بسازیم و کتابخانه Zustand رو در اون نصب کنیم، سپس مباحث اولیه Zustand رو باهمدیگه یاد بگیریم و تمرین کنیم.
برای اینکار من یک اپیکیشن ری اکتی به کمک دستور زیر میسازم :
npx create-react-app my-app
اگه با create-react-app حال نمیکنید، پیشنهاد میکنم نصب ری اکت با Vite رو امتحان کنید چون ابزار Vite بشدت سریعه !
حالا که ری اکت رو نصب کردیم، باید کتابخانه Zustand رو نصب کنیم ( یا با npm این کار رو میکنیم یا با yarn ) :
yarn add zustand
npm install zustand
خب عالیه 🙂
ما تونستیم کتابخانه Zustand در ری اکت رو نصب کنیم، حالا برای بررسی قابلیت ها و ویژگی های کتابخانه Zustand در React من یک Counter ( شمارنده ) میسازم. این Counter شامل 2 دکمه + و – برای کم و زیاد کردن عدد ذخیره شده هست.
میخوام خروجی اینطوری باشه :
برای اینکه کامپوننت بالارو بسازم، از کد زیر استفاده میکنم :
export const Counter = () => {
return (
{123}
)
}
حالا برای اینکه کامپوننت خودمون رو بتونیم ببینیم، باید تو کامپوننت App فراخوانیش کنیم :
function App() {
return (
);
}
ساخت Store در کتابخانه Zustand برای ذخیره کردن State ها !
اگه با Redux آشنا باشیم، میدونیم که تو State Management ها یک مفهومی داریم به اسم Store که محل ذخیره کردن State های ماست!
در حقیقت Store مثل یک انبار هست که داده های مارو ذخیره میکنه.
برای اینکه تو کتابخانه Zustand یک Store بسازیم باید یک فایل با نام دلخواه ( مثلا Store.js ) ایجاد کنیم و از تیکه کد زیر برای ساخت Store استفاده کنیم :
import {create} from "zustand";
export const useCounterStore = create(() => (
{number: 123}
))
تو تیکه کد بالا من تابع create رو از کتابخانه Zustand فراخوانی کردم که عملیات ساخت Store رو برای من انجام میده.
اسم Store خودم رو useCounterStore گذاشتم و هرچیزی که داخل این Store میخوام ذخیره کنم رو مشخص کردم ( در خط 4 )
من یک number با مقدار پیشفرض 123 داخلش قرار دادم.
حالا چطور میتونیم از این Store استفاده کنیم و مقدار ذخیره شده داخلش رو به کاربر نمایش بدیم ؟
تو تیکه کد زیر من Store خودم رو فراخوانی کردم و خیلی ساده number رو به کاربر نمایش دادم :
import {useCounterStore} from "./store";
export const Counter = () => {
const counterNumber = useCounterStore(state => state.number)
return (
{counterNumber}
)
}
کد بالا انقدر واضحه که اصلا نیازی به توضیح نداره 🙂 کتابخانه Zustand واقعا ساده و دوست داشتنیه 🙂
تو خط 1 من Store خودم رو فراخوانی کردم. در خط 4 مقدار number رو از داخل Store بیرون کشیدم و داخل یک متغیر با نام counterNumber ریختم.
در نهایت این متغیر رو در خط 8 به کاربر نمایش دادم.
تا به اینجای کار تونستیم یک Store بسازیم و یک State ( که عدد بود ) داخلش ذخیره کنیم و به کاربر نمایش بدیم.
اما ما نیاز داریم این State رو تغییر بدیم و مقدارش رو کم و زیاد کنیم ( زمانیکه روی دکمه + و – کلیک میکنیم )
افزایش شمارنده با کلیک روی دکمه +
برای اینکه بتونیم عملیات افزایش شمارنده رو هندل کنیم، باید وارد Store خودمون بشیم و یک تابع با نام دلخواه ( مثل increaseCounterNumber ) بسازیم. ما میخوایم هر زمان این تابع رو صدا زدیم عدد شمارنده یکی زیاد بشه.
پس Store ما بصورت زیر تغییر پیدا میکنه :
import {create} from "zustand";
export const useCounterStore = create((set) => (
{
number: 123,
increaseCounterNumber: () => set((state) => ({number: state.number + 1}))
}
))
تو تیکه کد بالا ما یک تابع به اسم increaseCounterNumber ساختیم که قراره وظیفه افزایش عدد ذخیره شده رو ایفا کنه.
داخل این تابع ما از set استفاده کردیم که set از خود تابع create ( در خط 3 ) داره میاد.
به همین سادگی میتونیم پروسه ویرایش مقدار ذخیره شده در Store رو انجام بدیم.
پس الان اگه تابع increaseCounterNumber رو صدا بزنیم، مقدار number ما یک عدد اضافه خواهد شد.
حالا برگردیم سراغ کامپوننت شمارنده خودمون و به دکمه افزایش عدد، تابع increaseCounterNumber رو پاس بدیم تا با کلیک روی این دکمه، عدد شمارنده ما افزایش پیدا کنه:
import {useCounterStore} from "./store";
export const Counter = () => {
const counterNumber = useCounterStore(state => state.number)
const increaseNumber = useCounterStore(state => state.increaseCounterNumber)
return (
{counterNumber}
)
}
حالا اگه اپیکیشن خودمون رو اجرا کنیم و روی دکمه + کلیک کنیم، میبینیم که عدد شمارنده ما یک عدد افزایش خواهد یافت.
کاهش شمارنده با کلیک روی دکمه –
دقیقا مثل پروسه افزایش شمارنده، کاهش شمارنده رو هم باید انجام بدیم.
کاهش شمارنده به این صورت هست که با کلیک روی دکمه – عدد شمارنده باید کاهش پیدا کنه.
پس به Store میریم و خط زیر رو به Store خودمون اضافه میکنیم :
decreaseCounterNumber: () => set((state) => ({number: state.number - 1}))
سپس به تابع onClick دکمه – ، تابع decreaseCounterNumber رو پاس میدیم:
export const Counter = () => {
const counterNumber = useCounterStore(state => state.number)
const increaseNumber = useCounterStore(state => state.increaseCounterNumber)
const decreaseNumber = useCounterStore(state => state.decreaseCounterNumber)
return (
{counterNumber}
)
}
و تمام 🙂
به همین سادگی تونستیم یک اپیکیشن کامل بسازیم که شامل یک Store و 2 اکشن برای کم و زیاد کردن مقدار ذخیره شده در Store هست.
البته بیاید عجله نکنیم و چند نکته مهم درمورد کتابخانه Zustand رو بررسی کنیم ..
تو پروسه بروزرسانی State بالا، نیازی به Spread Operator نداشتیم !
زمانیکه تو ری اکت یک State به کمک useState میساختیم و میخواستیم فقط یکی از اعضای اون State رو بروزرسانی کنیم، مجبور بودیم از Spread Operator استفاده کنیم، یعنی اینجوری :
setState({...state, number: state.number + 1})
اما تو کتابخانه Zustand دیگه نیازی به استفاده از Spread Operator نیست چون این کتابخانه خودش در پشت صحنه این قضیه رو هندل میکنه.
اما در جریان باشیم که این قضیه فقط برای Object هایی با یک عمق کاربرد داره، یعنی اگه Object ما تو در تو هست، همچنان باید از Spread Operator استفاده کنیم. یعنی اینجوری :
export const useCounterStore = create((set) => (
{
counterValues: {
number: 123
},
increaseCounterNumber: () => set((state) => ({
counterValues: {
...state.counterValues,
number: state.counterValues.number + 1
}
})),
decreaseCounterNumber: () => set((state) => ({
counterValues: {
...state.counterValues,
number: state.counterValues.number - 1
}
})),
}
تابع Get در کتابخانه Zustand چیه ؟
یادتونه درمورد تابع set صحبت کردیم؟ ( تو تیکه کد بالا هم ازش استفاده کردیم ) تابع set مقدار State مارو بروزرسانی میکرد.
خود Zustand در ری اکت یک تابع به اسم Get در اختیار ما میزاره که به کمک این تابع میتونیم به مقدار ذخیره شده در Store دسترسی داشته باشیم.
تو مثال زیر از تابع get برای دسترسی به مقدار ذخیره شده در خود Store استفاده کردیم:
export const useCounterStore = create((set, get) => (
{
number: 123,
increaseCounterNumber: () => set((state) => ({number: state.number + 1})),
decreaseCounterNumber: () => set((state) => ({number: state.number - 1})),
logNumber: () => {
console.log(` Current number value equals ${get().number}`)
}
}
))
در حقیقت تو خط 6 یک اکشن به اسم logNumber ساختیم و به کمک get تونستیم به number دسترسی پیدا کنیم.
ساخت Async action در کتابخانه Zustand !
ما بدون نیاز به هیچ پکیج جانبی، میتونیم action های Async در Zustand بسازیم 🙂
بیاید این مورد رو بصورت عملی و با ذکر یک مثال باهمدیگه بررسی کنیم.
فرض کنید میخوایم یکسری اطلاعات رو از سرور به کمک API دریافت کنیم و نمایش بدیم. ( در اینجا باید از Async Action استفاده کنیم )
من یک کامپوننت به اسم PokemonList میسازم که قراره داخل این کامپوننت اطلاعات رو از سرور دریافت کنیم و نمایش بدیم.
تو قدم اول باید Store خودمون رو آپدیت کنیم و یک Async Action جدید بسازیم که وظیفه دریافت اطلاعات از سرور رو بر عهده داره :
import {create} from "zustand";
export const useCounterStore = create((set, get) => (
{
pokemon: [],
number: 123,
increaseCounterNumber: () => set((state) => ({number: state.number + 1})),
decreaseCounterNumber: () => set((state) => ({number: state.number - 1})),
logNumber: () => {
console.log(` Current number value equals ${get().number}`)
},
fetchPokemon: async () => {
await fetch('https://pokeapi.co/api/v2/pokemon')
.then(response => response.json())
.then(data => set({pokemon: data.results}))
}
}
))
تو خط 12 تیکه کد بالا من یک Async Action ساختم که اطلاعات مدنظرم رو از سرور دریافت میکنه. این اکشن Async هست.
پس از دریافت اطلاعات از سرور، خط 14 و سپس خط 15 به ترتیب اجرا میشن.
اگه به خط 5 هم دقت کنیم، میبینیم که یک آرایه جدید به اسم pokemon برای ذخیره اطلاعات دریافتی از سرور ایجاد کردیم.
تو خط 14 اطلاعات دریافتی رو به JSON تبدیل میکنیم و در خط 15 این اطلاعات رو به کمک تابع set، داخل Store ذخیره میکنیم.
حالا که یک Async Action ساختیم که میتونه اطلاعات رو از سرور دریافت کنه و در Store ذخیره کنه، باید از این اطلاعات استفاده کنیم و به کاربر نمایششون بدیم.
من یک کامپوننت به اسم PokemonList ایجاد میکنم و چند پروسه مهم داخلش پیاده سازی میکنم :
import {useCounterStore} from "./store";
import {useEffect} from "react";
export const PokemonList = () => {
const getPokemons = useCounterStore(state => state.fetchPokemon)
const pokemonList = useCounterStore(state => state.pokemon)
useEffect(() => {
getPokemons()
}, [getPokemons])
if (!pokemonList.length) {
return "Loading"
}
return (
{pokemonList.map(item => {
return - {item.name}
})}
)
}
تو خط 5 و 6 ما 2 اکشن داریم:
اکشن getPokemons برای ما اطلاعات رو از سرور دریافت میکنه. اما این اکشن چه زمانی اجرا میشه؟ اگه به خط 8 دقت کنیم میبینیم که این اکشن در داخل useEffect صدا زده شده ( یکبار در ابتدا اجرا میشه )
اکشن pokemonList نیز اطلاعات ذخیره شده در Store رو برای ما دریافت میکنه. ما تو خط 18 روی این اطلاعات که در قالب آرایه هست map زدیم و اطلاعات رو به کاربر نمایش دادیم.
حالا اگه کامپوننت بالا ( PokemonList ) رو در فایل App.js فراخوانی کنیم، میبینیم که به محض اجرای اپیکیشن اطلاعات از سرور دریافت میشه و به کاربر نمایش داده میشه.
تقسیم کردن State های مرتبط باهم به فایلهای جدا !
اگه یه نگاهی به Store خودمون که ساختیم بندازیم، میبینیم که خیلی کثیفه !
شاید بپرسیم چرا کثیفه ؟
ما باید State ها و Action های مرتبط با هم رو در یک فایل جدا نگهداری کنیم. یعنی نباید State ها و Action هایی که مرتبط با همدیگه نیستن رو درکنار همدیگه نگهداری کنیم.
فرض کنید اپیکیشن ما خیلی بزرگ بشه، چنین موردی بشدت آزاردهندس!
حالا راه حل چیه ؟
ما باید State و Action های مرتبط باهمدیگه رو در یکجا نگهداری کنیم. یعنی دو Store بسازیم:
- یک Store برای Pokemon ( اطلاعات دریافتی از سرور )
- یک Store برای Number ( شمارنده )
من یک Store جدید میسازم و Action و State مرتبط با pokemon رو به داخل این Store منتقل میکنم.. داخل این Store دقیقا همون Action و State رو قرار میدم :
const createPokemonStore = ((set) => (
{
pokemon: [],
fetchPokemon: async () => {
await fetch('https://pokeapi.co/api/v2/pokemon')
.then(response => response.json())
.then(data => set({pokemon: data.results}))
}
}
))
و یک Store جدید برای State و Action مرتبط با شمارنده:
const createCounterStore = ((set, get) => (
{
number: 123,
increaseCounterNumber: () => set((state) => ({number: state.number + 1})),
decreaseCounterNumber: () => set((state) => ({number: state.number - 1})),
logNumber: () => {
console.log(` Current number value equals ${get().number}`)
},
}
))
حالا ما دو Store داریم اما کتابخانه Zustand فقط یک Store از ما میگیره. پس چاره چیه ؟
ما باید این دو Store خودمون رو ترکیب ( Combine ) کنیم.
پس یک تابع جدید به اسم useCombineStore میسازم و دو Store خودم رو ترکیب میکنم :
export const useCombinedStore = create((...params) => ({
...createPokemonStore(...params),
...createCounterStore(...params)
}))
این یعنی ما میتونیم ده ها Store مختلف داشته باشیم و به کمک این تکنیک، ترکیب کنیمشون!
اینجوری Store ها، State ها و Action های ما خیلی مرتب تر و تمیزتر خواهند بود.
تو تیکه کد بالا از params… استفاده کردیم، اینکار باعث میشه تمام ویژگی ها مثل get و set کپی بشه.
فقط باید بخاطر داشته باشم که State های ما از این به بعد داخل useCombineStore ذخیره میشن.
در حقیقت فایل Sote.js ما بصورت زیر خواهد بود :
const createPokemonStore = ((set) => (
{
pokemon: [],
fetchPokemon: async () => {
await fetch('https://pokeapi.co/api/v2/pokemon')
.then(response => response.json())
.then(data => set({pokemon: data.results}))
}
}
))
const createCounterStore = ((set, get) => (
{
number: 123,
increaseCounterNumber: () => set((state) => ({number: state.number + 1})),
decreaseCounterNumber: () => set((state) => ({number: state.number - 1})),
logNumber: () => {
console.log(` Current number value equals ${get().number}`)
},
}
))
export const useCombinedStore = create((...params) => ({
...createPokemonStore(...params),
...createCounterStore(...params)
}))
مزایای کتابخانه Zustand چیست ؟
از مهمترین مزایای Zustand میشه به موارد زیر اشاره کرد :
خیلی سبکه!
اجازه میده Store های مختلفی بسازیم و سپس ترکیبشون کنیم.
خیلی انعطاف پذیره
توسط تیم سازنده خیلی خوب پشتیبانی و بروزرسانی میشه
نیازی به Provider نداره ( مثل Context )
یکی از ساده ترین State Manegment های ری اکت به حساب میاد
معایب کتابخانه Zustand چیست ؟
کتابخانه Zustand در ری اکت معایب خیلی بزرگ و زیادی نداره اما میشه به دو عیب کوچیک زیر اشاره کرد :
میتونست داکیومنت بهتری داشته باشه.
چون نسبتا جدیده شاید پیدا کردن بعضی راه حل ها کمی دشوار باشه.
جمع بندی
کتابخانه Zustand در ری اکت یک State Manegment دوست داشتنی، سبک، قدرتمند و ساده به حساب میاد که میتونیم بهش به چشم یک جایگزین عالی برای ریداکس نگاه کنیم!
تو Zustand دیگه خبری از پیچیدگی و شلوغی های همیشگی نیست و خیلی ساده میتونیم Global State های خودمون رو ذخیره و مدیریت کنیم.
پس اگه شماهم دمبال یک State Management همه چی تموم هستین، مثل من کتابخانه Zustand رو انتخاب کنین 🙂
کتابخانه Zustand یک State Management برای ذخیره و مدیریت State های گلوبال در اپیکیشن های جاوااسکریپتی به خصوص ری اکت هست.
مهمترین مزایای کتابخانه Zustand سادگی یادگیری و استفاده، سبک بودن و قدرتمند بودنش هست.
هدف Zustand در ری اکت این بود که ساده تر و سبک تر باشه. صادقانه میشه گفت به این هدف دست پیدا کرده چون حجم 1 کیلوبایتی داره و خیلی ساده میشه ازش استفاده کرد.
درباره احمد احمدنژاد
من یه برنامه نویس و توسعه دهنده وب هستم که عاشق دنیای صفر و یکم❤️
نوشتههای بیشتر از احمد احمدنژاد8 دیدگاه
به گفتگوی ما بپیوندید و دیدگاه خود را با ما در میان بگذارید.
عالی
خوشحالم که برات مفید واقع شده 🙂
عالی توضیح دادین عالی
سلام و درود
ممنونم نظر لطف شماست 🙂
خیلی خوشحالم که براتون مفید واقع شده
موفق باشین
سلام سپاس فراوان
سلام خواهش میکنم موفق باشی 🙂
درود بر آقا احمد عزیز
سپاس از توضیحات آموزشی منظم خیلی خوب و انرژی بخشتون ، براتون موفقیت و تندرستی رو خواستارم.
سلام الهام جان خیلی ممنونم از انرژی مثبتی که دادی، منم برای آرزوی موفقیت و سلامتی دارم 🌱🙏