آیا فکر میکنید مدیریت استیت و استیت بخشهای جداییناپذیر برنامه React شما هستند؟ آیا تا به حال با مدیریت استیت مبارزه کرده اید و به این فکر کرده اید که کجا به دنبال یک کتابخانه مدیریت استیت ساده بگردید؟ پس این مقاله برای شماست، به شما نشان می دهد که چگونه با استفاده از یک کتابخانه خارجی ساده به نام Zustand، استیت React را مدیریت کنید.
در واقع! مدیریت استیت و استیت همیشه جنبه های مهم یک برنامه React بوده است که تنها زمانی که استیت تغییر می کند دوباره رندر می شود. یک استیت می تواند شامل داده ها یا اطلاعات مربوط به یک کامپوننت باشد. همانطور که برنامه شما رشد می کند، مدیریت استیت و جریان داده در اجزای سازنده بسیار مهم است.
مدیریت استیت در React
شما می توانید استیت یک برنامه React را به روش های مختلفی مدیریت کنید.
مدیریت استیت به صورت بومی در ری اکت. هوک هایی مانند useState
، useReducer
، useRef
، useContext
و هوک های سفارشی از مدیریت استیت بومی پشتیبانی می کنند.
مدیریت غیر مستقیم استیت. اینها کتابخانه های خارجی مانند React Router و React Query هستند. آنها در درجه اول برای مدیریت استیت استفاده نمی شوند. با این حال، هنگامی که با یک هوک بومی ترکیب شوند، می توانند استیت را به خوبی اداره کنند.
مدیریت استیت مستقیم همچنین کتابخانه های شخص ثالثی وجود دارند که فقط برای مدیریت استیت استفاده می شوند. Redux، Zustand، Jotai و Valtio در این سبک قرار می گیرند. Recoil نیز یک مدیریت استیت بسیار ساده هست برای آشنایی با آن مقاله مدیریت استیت با Recoil را بررسی کنید.
Zustand چیست؟
Zustand یک کتابخانه مدیریت استیت است. جمع و جور، سریع و مقیاس پذیر است. این یک سیستم نسبتا ساده با کد دیگ بخار کمی است. شما می توانید آن را با کد کمتری نسبت به Redux و کتابخانه های مشابه برای مدیریت استیت های React استفاده کنید. به یک provider متکی نیست. در نتیجه، مجبور نیستید به اندازه منطق React کدنویسی کنید، که ممکن است چیزهایی را که ما تمایل به فراموشی داریم کاهش دهد. این بر اساس اصول flux ساده کار می کند و در درجه اول از هوک استفاده می کند.
چرا Zustand؟
سریعتر از کانکتست است. این به شما این امکان را می دهد که یک استیت خاص را انتخاب کنید. به طور پیش فرض استیت ادغام را دارد. به روز رسانی یک ویژگی منفرد از یک استیت شی، {x:1، y:2}
را در نظر بگیرید. می توانید مستقیماً {y:3}
را تنظیم کنید. Zustand داده ها را برای شما ادغام می کند. شما مجبور نیستید استیت قدیمی را توزیع کنید و ویژگیهایی مانند {state, y:3…}
را بهروزرسانی کنید.
به طور پیش فرض قابل تمدید است. بنابراین، می توانید از انواع میدلور های مختلف استفاده کنید.
کمتر سخت گیر است. شما مجبور نیستید به یک راه برای انجام کارها پایبند باشید. حتی اگر یک رویکرد توصیه شده وجود دارد، شما ملزم به اتخاذ آن نیستید.
Zustand در مقابل Redux
همانطور که می دانید، Redux یک کتابخانه مدیریت استیت کلاسیک است که با React کار می کند. این کتابخانه محبوب ترین کتابخانه برای مدیریت استیت های React در نظر گرفته می شود. از این رو، مقایسه طرح های معماری آنها کامپوننت مناسب این مقاله است. به معماری زیر نگاه کنید تا ببینید Redux چگونه کار می کند.
ابتدا، همانطور که در معماری بالا مشاهده می شود، رابط کاربری front-end دارید. سازندگان اقدام اطمینان حاصل می کنند که برای هر درخواست کاربر، اقدام صحیح انجام می شود. شما می توانید یک عمل را به عنوان رویدادی در نظر بگیرید که آنچه را در برنامه اتفاق افتاده است توصیف می کند. ممکن است مانند کلیک کردن روی یک دکمه یا انجام جستجو باشد. توزیع کنندگان در ارسال آن اقدامات به استور کمک خواهند کرد. بعداً، کاهش دهنده ها تصمیم خواهند گرفت که چگونه با استیت رفتار کنند. تابع کاهنده با گرفتن استیت فعلی و شیء اقدام، استیت را تغییر می دهد. در صورت لزوم استیت جدید را برمی گرداند و تغییرات استیت به روز شده رابط کاربری را ارائه می دهد.
حالا قسمت هیجان انگیز فرا می رسد! به نحوه کار Zustand با معماری ارائه شده در زیر نگاه کنید. ممکن است از نمودار ساده شده آن هیجان زده شوید.
شما همچنین کامپوننت UI را در اینجا دارید. هنگامی که یک درخواست تغییر وارد می شود، به استور هدایت می شود. استور تصمیم خواهد گرفت که استیت چگونه باید تغییر کند. هنگامی که استور استیت جدیدی را بازگرداند، رابط کاربری با تغییرات بهروزرسانی شده ارائه میشود. هیچ سازنده کنشی، توزیعکننده یا کاهشدهندهای را در اینجا نمیبینید. در عوض، Zustand دارای ویژگی است که به شما امکان می دهد در تغییرات استیت مشترک شوید. این کمک می کند تا رابط کاربری شما با داده های شما هماهنگ باشد.
برای توسعه دهندگانی که به دنبال یک راه حل مدیریت استیت ساده و سبک وزن بدون پیچیدگی یک جعبه ابزار بزرگتر مانند Redux هستند، Zustand یک گزینه عالی است.
برای آشنایی با ویژگی های ری اکت 19 این مقاله را بررسی کنید و همچنین برای آشنایی با تفاوت های بین ری اکت 19 و 18 این مقاله را بررسی کنید.
چگونه از Zustand با ReactJs استفاده کنیم؟
بیایید یک پروژه React ایجاد کنیم تا نحوه مدیریت استیت ها با استفاده از Zustand را نشان دهیم. یک برنامه کتابخانه استوری را در نظر بگیرید که مشکلات و بازگرداندن کتاب ها را پیگیری می کند. مراحل در زیر ذکر شده است.
1. یک برنامه React ایجاد کنید
برنامه React خود را با استفاده از دستور زیر ایجاد کنید. همچنین می توانید از Vite.js برای ایجاد پروژه ری اکت استفاده کنید.
npx create-react-app project_name
برای آشنایی با تفاوت های بین CRA و vite.js این مقاله را بررسی کنید.
2. Zustand Dependency را نصب کنید
به دایرکتوری پروژه بروید و وابستگی zustand را برای مدیریت استیت React نصب کنید.
npm i zustand
3. یک استور ایجاد کنید
یک bookStore ایجاد کنید. برای ایجاد فایل bookStore.js می توانید به کد زیر مراجعه کنید.
import { create } from "zustand";
const bookStore = (set, get) => ({
books: [],
noOfAvailable: 0,
noOfIssued: 0,
addBook: (book) => {
set((state) => ({
books: [...state.books, { ...book, status: "available" }],
noOfAvailable: state.noOfAvailable + 1,
}));
},
issueBook: (id) => {
const books = get().books;
const updatedBooks = books?.map((book) => {
if (book.id === id) {
return {
...book,
status: "issued",
};
} else {
return book;
}
});
set((state) => ({
books: updatedBooks,
noOfAvailable: state.noOfAvailable - 1,
noOfIssued: state.noOfIssued + 1,
}));
},
returnBook: (id) => {
const books = get().books;
const updatedBooks = books?.map((book) => {
if (book.id === id) {
return {
...book,
status: "available",
};
} else {
return book;
}
});
set((state) => ({
books: updatedBooks,
noOfAvailable: state.noOfAvailable + 1,
noOfIssued: state.noOfIssued - 1,
}));
},
reset: () => {
set({
books: [],
noOfAvailable: 0,
noOfIssued: 0,
});
},
});
const useBookStore = create(bookStore);
export default useBookStore;
استور Zustand یک هوک است، به همین دلیل نام کامپوننت useBookStore
است. create متدی است که برای ایجاد استور استفاده می شود. استور تنها منبع حقیقتی است که هر کامپوننت به اشتراک می گذارد. مجموعه تابع برای تغییر استیت یک متغیر یا شی استفاده می شود. تابع get برای دسترسی به استیت درون اکشن ها استفاده می شود.
شی استیت استور کتابخانه در مثال شامل سه فیلد است: books که شامل آرایه ای از جزئیات کتاب مانند id، نام و نویسنده است. تعداد کلی کتابهای موجود در کتابخانه در noOfAvailable
ذخیره میشود، در حالی که تعداد کل کتابهایی که برای کاربران صادر شدهاند در noOfIssued
ذخیره میشوند.
استور کتابخانه چهار متد ارائه میدهد: تابع addBook
یک کتاب جدید را به مجموعه کتابها اضافه میکند، تعداد کتابهایی را که در حال حاضر در دسترس هستند افزایش میدهد و استیت هر کتاب تازه اضافه شده را در دسترس قرار میدهد. تابع issueBook
یک کتاب برای کاربر صادر می کند. کتاب مرتبط اکنون استیت صادر شده را خواهد داشت. تعداد موارد افزایش یافته و تعداد موارد موجود کاهش خواهد یافت. تابع returnBook
برای برگرداندن کتاب صادر شده به کتابخانه استفاده می شود. استیت کتاب برگشتی به موجود تغییر می کند و تعداد کتاب های صادر شده کاهش می یابد و تعداد کتاب های موجود افزایش می یابد. در نهایت، متد reset
تمام فیلدهای استیت را پاک می کند.
4. کامپوننت را با استور خود متصل کنید
اجازه دهید ابتدا فایل App.js
نقطه ورودی ایجاد کنیم. به کد زیر مراجعه کنید
//App.js
import { useEffect } from "react";
import BookForm from "./components/BookForm";
import BookList from "./components/BookList";
import useBookStore from "./bookStore";
import "./App.css";
function App() {
const reset = useBookStore((state) => state.reset);
useEffect(() => {
reset();
}, [reset]);
return (
<div className="App">
<h2>My Library Store</h2>
<BookForm />
<BookList />
</div>
);
}
export default App;
دو کامپوننت در فایل App.js
وجود دارد: BookForm
و BookList. هر بار که کامپوننت App مانت میشود، از تابع بازنشانی نیز برای حذف هرگونه داده استیت استفاده میکنیم.
کامپوننت BookForm.js
را ایجاد کنید. به کد زیر مراجعه کنید
//BookForm.js
import { useState } from "react";
import useBookStore from "../bookStore";
function BookFom() {
const addBook = useBookStore((state) => state.addBook);
const [bookDetails, setBookDetails] = useState({});
const handleOnChange = (event) => {
const { name, value } = event.target;
setBookDetails({ ...bookDetails, [name]: value });
};
const handleAddBook = () => {
if (!Object.keys(bookDetails).length)
return alert("Please enter book details!");
addBook(bookDetails);
};
return (
<div className="input-div">
<div className="input-grp">
<label>Book ID</label>
<input type="text" name="id" size={50} onChange={handleOnChange} />
</div>
<div className="input-grp">
<label>Book Name</label>
<input type="text" name="name" size={50} onChange={handleOnChange} />
</div>
<div className="input-grp">
<label>Author</label>
<input type="text" name="author" size={50} onChange={handleOnChange} />
</div>
<button onClick={handleAddBook} className="add-btn">
{" "}
Add{" "}
</button>
</div>
);
}
export default BookFom;
کامپوننت BookForm
دارای فیلدهای فرم برای وارد کردن جزئیات کتاب، مانند شناسه، نام و نویسنده است. علاوه بر این، یک دکمه افزودن دارد که از روش addBook کتابفروشی برای وارد کردن جزئیات این کتاب استفاده می کند. رابط کاربری با جزئیات کتاب نمونه در زیر نشان داده شده است.
کامپوننت BookList.js را ایجاد کنید. به کد زیر مراجعه کنید
//BookList.js
import { Fragment } from "react";
import useBookStore from "../bookStore";
function BookList() {
const { books, noOfAvailable, noOfIssued, issueBook, returnBook } =
useBookStore((state) => ({
books: state.books,
noOfAvailable: state.noOfAvailable,
noOfIssued: state.noOfIssued,
issueBook: state.issueBook,
returnBook: state.returnBook,
}));
return (
<ul className="book-list">
{!!books?.length && (
<span className="books-count">
<h4>Available: {noOfAvailable}</h4>
<h4>Issued: {noOfIssued}</h4>
</span>
)}
{books?.map((book) => {
return (
<Fragment key={book.id}>
<li className="list-item">
<span className="list-item-book">
<span>{book.id}</span>
<span>{book.name}</span>
<span>{book.author}</span>
</span>
<div className="btn-grp">
<button
onClick={() => issueBook(book.id)}
className={`issue-btn ${
book.status === "issued" ? "disabled" : ""
}`}
disabled={book.status === "issued"}
>
{" "}
Issue{" "}
</button>
<button
onClick={() => returnBook(book.id)}
className={`return-btn ${
book.status === "available" ? "disabled" : ""
}`}
disabled={book.status === "available"}
>
{" "}
Return{" "}
</button>
</div>
</li>
</Fragment>
);
})}
</ul>
);
}
export default BookList;
کامپوننت BookList همه کتابهای تازه اضافه شده را به کتابخانه نمایش میدهد. همچنین تعداد کتاب های موجود و منتشر شده را نشان می دهد. هر رکورد کتاب در فهرست دارای دو دکمه است: Issue و Return.
دو کتاب در رابط کاربری ما موجود است و دکمه Issue برای هر رکورد کتاب فعال است. دکمههای بازگشت غیرفعال هستند و فقط برای کتابهای صادر شده فعال میشوند.
هنگامی که روی دکمه Issue کلیک می کنید، متد issueBook
استور فراخوانی می شود. شناسه کتاب مربوطه ارسال میشود و استیت کتاب منطبق روی صادر شده تنظیم میشود. سپس، دکمه Issue مرتبط غیرفعال می شود، در حالی که دکمه Return فعال است. همچنین می توانید کاهش تعداد موجود و افزایش تعداد صادر شده را مشاهده کنید.
وقتی روی دکمه Return کلیک می کنید، متد returnBook استور فراخوانی می شود. شناسه کتاب مربوطه ارسال میشود و استیت کتاب منطبق به استیت موجود برمیگردد. دکمه بازگشت مرتبط غیرفعال است، در حالی که دکمه Issue دوباره فعال است. همچنین می توانید افزایش تعداد موجود و کاهش تعداد صادر شده را مشاهده کنید.
از آنجایی که استور Zustand یک هوک است، می توانید از آن در هر جایی استفاده کنید. برخلاف Redux یا Redux Toolkit، به هیچ provider لازم نیست. به سادگی استیت خود را انتخاب کنید، و با تغییر استیت، کامپوننت دوباره ارائه می شود. ما باید یک selector به useBookStore
بدهیم تا فقط slice مورد نظر را دریافت کنیم.
با ویژگی های پایه Zustand آشنا شدیم ولی یک ویژگی بزرگ دارد: Middlewares.
Zustand Middlewares
Zustand را می توان با میدلور برای افزودن ویژگی های بیشتر به برنامه خود استفاده کرد. پرکاربردترین میدلور Zustand در زیر فهرست شده اند.
1. Redux DevTools
Redux DevTools برای دیباگ تغییرات استیت در برنامه استفاده می شود. می توان از آن با Zustand نیز استفاده کرد. مطمئن شوید که Redux DevTools را به عنوان افزونه Chrome نصب کرده اید. از کد زیر برای ادغام آن استفاده کنید.
import { create } from "zustand";
import { devtools } from "zustand/middleware";
const bookStore = (set, get) => ({
books: [],
noOfAvailable: 0,
noOfIssued: 0,
addBook: (book) => {
set((state) => ({
books: [...state.books, { ...book, status: "available" }],
noOfAvailable: state.noOfAvailable + 1,
}));
}
});
const useBookStore = create(devtools(bookStore));
export default useBookStore;
برای استفاده از ابزارهای توسعه، آن را از zustand/middleware
وارد کنید. سپس، با استفاده از متد ایجاد، استور خود را با ابزارهای توسعهیافته بپیچید.
Redux DevTools را در مرورگر وب باز کنید و استیت را مشاهده می کنید.
2. Persist Middleware
Persist Middleware به شما امکان میدهد با استفاده از هر نوع ذخیرهسازی کلاینت، استیت تداوم داشته باشید. حتی اگر برنامه را مجدداً بارگیری کنید، اطلاعات استور در فضای ذخیره سازی باقی می ماند. برای افزودن این میدلور به کد زیر مراجعه کنید.
import { create } from "zustand";
import { persist, createJSONStorage } from "zustand/middleware";
const bookStore = (set, get) => ({
books: [],
noOfAvailable: 0,
noOfIssued: 0,
addBook: (book) => {
set((state) => ({
books: [...state.books, { ...book, status: "available" }],
noOfAvailable: state.noOfAvailable + 1,
}));
}
});
const useBookStore = create(
persist(bookStore, {
name: "books",
storage: createJSONStorage(() => sessionStorage),
})
);
export default useBookStore;
واردات از zustand/middleware
ادامه دارد. استور را با persist
در داخل متد create
قرار دهید. مورد موجود در فضای ذخیرهسازی میتواند نامی داشته باشد، اما باید منحصربهفرد باشد. علاوه بر این، نوع ذخیره سازی را می توان مشخص کرد. sessionStorage
در این کد ارجاع داده شده است. اگر چیزی مشخص نشده باشد، localStorage
گزینه پیش فرض است.
مرورگر وب را باز کنید و به فضای ذخیره سشن نگاه کنید تا استیت ذخیره شده را ببینید.
بیاید یک مثال کاربردی با هم دیگر با zustand بزنیم.
با استفاده از Zustand یک برنامه To-do بسازید
اکنون، ما اصول اولیه zustand و API آن را یاد گرفتیم. ما با استفاده از Zustand یک برنامه To-Do
ایجاد خواهیم کرد.
برنامه To-do یک برنامه React خواهد بود، در حالی که Zustand مدیریت استیت ما را تقویت خواهد کرد.
بیایید شروع کنیم، با استفاده از ابزار create-react-app
، یک پروژه React را خواهیم داشت:
create-react-app todo-app
cd todo-app
بعد، ما zustand را نصب می کنیم:
npm install zustand
اولین چیزی که ما استور هوک خود را ایجاد می کنیم:
import create from "zustand";
const useStore = create((set) => ({
todos: [],
addTodo: (text) =>
set((state) => ({
todos: [
...state.todos,
{
id: Date.now(),
text,
completed: false,
},
],
})),
toggleTodo: (id) =>
set((state) => ({
todos: state.todos.map((todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo,
),
})),
deleteTodo: (id) =>
set((state) => ({
todos: state.todos.filter((todo) => todo.id !== id),
})),
}));
export default useStore;
ببینید، ما یک استیت todos
داریم. این آرایه ای از کارهای ما را نگه می دارد. ما سه اکشن داریم: addTodo
، toggleTodo
و deleteTodo
. اکشن addTodo
کار جدیدی را به استیت todos
اضافه می کند. ، اکشن toggleTodo
استیت کامل یک todo
را تغییر می دهد. deleteTodo
یک todo
را از استیت آرایه حذف می کند. حالا بیایید اجزا را بسازیم.
DisplayTodos این کامپوننت یک کار خواهد داشت. استیت todos
را نشان می دهد:
const DisplayTodos = () => {
const { todos, deleteTodo } = useStore((state) => {
return { todos: state.todos, deleteTodo: state.deleteTodo };
});
return (
<ul>
{todos.map((todo) => (
<li
key={todo.id}
style={{
textDecoration: todo.completed ? "line-through" : "none",
}}
onClick={() => toggleTodo(todo.id)}
>
{todo.text}
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</li>
))}
</ul>
);
};
export default DisplayTodos;
ما آرایه todos
را از استیت جدا کردیم و از متد Array#map
برای رندر کردن todos
استفاده کردیم، همچنین deleteAction
را نیز اسلایس دادیم. دکمه Delete
هر کار را از لیست حذف می کند، این کار را با فراخوانی اکشن deleteAction
با کلیک روی id کار انجام می دهد. حالا بیایید کامپوننتی را بسازیم که بتوانیم یک todo را به لیست اضافه کنیم.
TodosControl
const TodosControl = () => {
const addTodo = useStore((state) => state.addTodo);
const [text, setText] = useState("");
function handleSubmit(e) {
e.preventDefault();
addTodo(text);
setText("");
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
/>
<button type="submit">Add</button>
</form>
);
};
export default TodosControl;
این کامپوننت فرمی را در اختیار ما قرار می دهد که در آن یک todo را وارد کرده و آن را به استور state اضافه می کنیم. ما یک متن استیت داریم که متنی را که تایپ می کنیم در عنصر ورودی نگه می دارد. سپس، هنگام ارسال فرم از طریق دکمه Add، تابع handleSubmit
فراخوانی می شود. در داخل تابع handleSubmit
، addTodo
در استیت متن به عنوان آرگومان ارسال می شود. با این کار یک todo جدید به استیت todos ایجاد و اضافه می شود.
جمع کردن همه آنها:
const App = () => {
return (
<>
<DisplayTodos />
<TodosControl />
</>
);
};
export default App;
برای آشنایی با معماری میکروفرانت اند در ری اکت این مقاله را بررسی کنید.
نتیجه
همانطور که در این مقاله اشاره کردیم، مدیریت استیت در برنامه های React بسیار مهم است. همانطور که برنامه شما رشد می کند، باید یک راه قوی برای مدیریت استیت آن انتخاب کنید. کتابخانه Zustand یک راه حل ایده آل برای مدیریت استیت React شما است. این بسیار ساده تر است و دارای دیگ بخار کمتری نسبت به Redux است. همچنین میتوانید کتابخانههای مدیریت استیتی مختلف را کاوش کنید تا کتابخانه مناسب برای برنامه خود را پیدا کنید.