آیا باید از State یا useReducer استفاده کنم؟
«When to useState به جای useReducer» را در egghead.io تماشا کنید
«When to useReducer به جای useState» را در egghead.io تماشا کنید
هرگاه دو کار برای انجام یک کار وجود داشته باشد، مردم ناگزیر می پرسند: «چه زمانی
از یکی به جای دیگری استفاده کنید؟” دو دلیل احتمالی برای تعدد وجود دارد
راه های انجام همین کار:
- یکی «روش قدیم» و دیگری «راه جدید (و بهبود یافته)». معمولی
روش قدیمی به دلایل سازگاری با عقب و روش جدید حفظ می شود
راه رو به جلو برای کد جدید است. به عنوان مثال: اجزای کلاس (روش قدیمی) در مقابل
اجزای عملکردی (راه جدید). - آنها با مبادلات مختلفی همراه هستند که باید در نظر گرفته شوند و از این رو
باید در موقعیت هایی به کار رود که مناسب تر است (گاهی اوقات به این معناست
در یک برنامه خاص از بیش از یک مورد استفاده خواهید کرد). به عنوان مثال:
useEffectدر مقابلuseLayoutEffectیا
استاتیک در مقابل واحد در مقابل ادغام در مقابل آزمون های E2E
یا
“Control Props” در مقابل “Status Reducers”
useState و useReducer در اینجا در دسته دوم قرار می گیرند. پس بیایید صحبت کنیم
در مورد مصالحه
(و نه، اینطور نیست
سوال ترفند
😂).
من فکر می کنم بهترین راه برای بحث در مورد این مبادلات از طریق لنز است
نمونه ها ما به دو نمونه نگاه خواهیم کرد. یکی که مناسب است useState بهتر و یکی
که مناسب است useReducer بهتر این برای پوشش همه افراد کافی نخواهد بود
سازش، اما امیدواریم که این می تواند نقطه شروع خوبی برای ما باشد.
برای سفارش useDarkMode قلاب
من اخیراً این را برای پروژه های کارگاهی خود نوشتم
(مثلا). خوب است
جالبه پس بیایید مقایسه کنیم useState و useReducer تحقق برای
که:
useState عملکرد
من حوزه هایی را که در آنها با دولت تعامل داریم برجسته می کنم:
function useDarkMode() {
const preferDarkQuery = '(prefers-color-scheme: dark)'
const [mode, setMode] = React.useState(
() =>
window.localStorage.getItem('colorMode') ||
(window.matchMedia(preferDarkQuery).matches ? 'dark' : 'light'),
)
React.useEffect(() => {
const mediaQuery = window.matchMedia(preferDarkQuery)
const handleChange = () => setMode(mediaQuery.matches ? 'dark' : 'light')
mediaQuery.addListener(handleChange)
return () => mediaQuery.removeListener(handleChange)
}, [])
React.useEffect(() => {
window.localStorage.setItem('colorMode', mode)
}, [mode])
return [mode, setMode]
}
امیدوارم که منطقی باشد. اساساً می گوییم که اگر کاربر آنها را تنظیم کرده باشد
حالت تاریک را ترجیح می دهیم، سپس حالت خود را به آن مقداردهی اولیه می کنیم dark، در غیر این صورت
به مقداردهی اولیه می کنیم light و سپس بازگشت mode و setMode و به عنوان
حالت تغییر می کند (چه با تماس setMode به طور مستقیم یا زمانی که کاربر مال خود را تغییر می دهد
تنظیمات سیستم) مقدار تنظیم شده را حفظ می کنیم localStorage برای استفاده در آینده
useReducer اجرا:
چندین روش وجود دارد که می توانید این را بنویسید useReducer. من شروع می کنم
با روش معمولی که اکثر مردم کاهنده می نویسند:
const preferDarkQuery = '(prefers-color-scheme: dark)'
function darkModeReducer(state, action) {
switch (action.type) {
case 'MEDIA_CHANGE': {
return { ...state, mode: action.mode }
}
case 'SET_MODE': {
// make sure to spread that state just in case!
return { ...state, mode: action.mode }
}
default: {
// helps us avoid typos!
throw new Error(`Unhandled action type: ${action.type}`)
}
}
}
// use the init function to lazily initialize state so we don't read into
// localstorage or call matchMedia every render
function init() {
return {
mode:
window.localStorage.getItem('colorMode') ||
(window.matchMedia(preferDarkQuery).matches ? 'dark' : 'light'),
}
}
function useDarkMode() {
const [state, dispatch] = React.useReducer(
darkModeReducer,
{ mode: 'light' },
init,
)
const { mode } = state
React.useEffect(() => {
const mediaQuery = window.matchMedia(preferDarkQuery)
const handleChange = () =>
dispatch({
type: 'MEDIA_CHANGE',
mode: mediaQuery.matches ? 'dark' : 'light',
})
mediaQuery.addListener(handleChange)
return () => mediaQuery.removeListener(handleChange)
}, [])
React.useEffect(() => {
window.localStorage.setItem('colorMode', mode)
}, [mode])
// We like the API the way it is, so instead of returning the state object
// and the dispatch function, we'll return the `mode` property and we'll
// create a setMode helper (which we have to memoize in case someone wants
// to use it in a dependency list):
const setMode = React.useCallback(
(newMode) => dispatch({ type: 'SET_MODE', mode: newMode }),
[],
)
return [mode, setMode]
}
وای، من فکر می کنم ما هر دو می توانیم با آن موافق باشیم useState نسخه بسیار ساده تر بود! اما
صبر کن ما می توانیم به شدت ساده کنیم useReducer نسخه با مخالفت با
“دانه”، نه نوشتن کاهنده معمولی سبک ردوکس. بیایید این را امتحان کنیم:
function useDarkMode() {
const preferDarkQuery = '(prefers-color-scheme: dark)'
const [mode, setMode] = React.useReducer(
(prevMode, nextMode) =>
typeof nextMode === 'function' ? nextMode(prevMode) : nextMode,
'light',
() =>
window.localStorage.getItem('colorMode') ||
(window.matchMedia(preferDarkQuery).matches ? 'dark' : 'light'),
)
React.useEffect(() => {
const mediaQuery = window.matchMedia(preferDarkQuery)
const handleChange = () => setMode(mediaQuery.matches ? 'dark' : 'light')
mediaQuery.addListener(handleChange)
return () => mediaQuery.removeListener(handleChange)
}, [])
React.useEffect(() => {
window.localStorage.setItem('colorMode', mode)
}, [mode])
return [mode, setMode]
}
این بسیار بهتر از تجربیات دیگر ما است useReducer. اما ما عمدتا
برآورده شد useState با useReducer.
و حتی پس از آن هنوز هم کمتر از ما روشن است useState نسخه بنابراین در پایان
روز useState در این مورد راه حل بسیار بهتری بود.
وقتی فقط یک عنصر مستقل از ایالتی است که شما مدیریت می کنید: useState
برای سفارش useUndo قلاب
عالی وجود دارد
useUndo به GitHub متعهد شوید. من گرفتم
الهام از آن برای این مثال. متشکرم
هومر چن!
(برای آنها، تقریباً همه چیز با حالت تعامل دارد، بنابراین … بدون برجسته …)
useState عملکرد
function useUndo(initialPresent) {
const [past, setPast] = React.useState([])
const [present, setPresent] = React.useState(initialPresent)
const [future, setFuture] = React.useState([])
const canUndo = past.length !== 0
const canRedo = future.length !== 0
const undo = React.useCallback(() => {
if (!canUndo) return
const previous = past[past.length - 1]
const newPast = past.slice(0, past.length - 1)
setPast(newPast)
setPresent(previous)
setFuture([present, ...future])
}, [canUndo, future, past, present])
const redo = React.useCallback(() => {
if (!canRedo) return
const next = future[0]
const newFuture = future.slice(1)
setPast([...past, present])
setPresent(next)
setFuture(newFuture)
}, [canRedo, future, past, present])
const set = React.useCallback(
(newPresent) => {
if (newPresent === present) {
return
}
setPast([...past, present])
setPresent(newPresent)
setFuture([])
},
[past, present],
)
const reset = React.useCallback((newPresent) => {
setPast([])
setPresent(newPresent)
setFuture([])
}, [])
return [
{ past, present, future },
{ set, reset, undo, redo, canUndo, canRedo },
]
}
خیلی خوب به نظر می رسد، اینطور نیست؟ احتمالاً اینطور است، اما در واقع شرایطی وجود دارد که در آن
این می تواند کاملا حشره دار باشد. اما ابتدا می خواهم به یک تصور غلط رایج بپردازم
مردم باید چندین برنامه را به ترتیب فراخوانی کنند تا وضعیت را به روز کنند (همانطور که ما انجام می دهیم
در هر یک از این توابع).
اغلب مردم فکر می کنند که این بدان معناست که شما برای هر تماس یک رندر مجدد راه اندازی می کنید (بنابراین،
آنها این تماس را ارائه می دهند reset باعث سه رندر مجدد خواهد شد). اول،
به یاد داشته باشید که روی آن تمرکز کنید
قبل از رفع رندرینگ، رندر آهسته را برطرف کنید،
اما دوم، به یاد داشته باشید که React یک سیستم دسته ای دارد، بنابراین اگر نیاز دارید تماس بگیرید
reset از یک کنترل کننده رویداد یا در یک useEffect تماس پاسخ فعال خواهد شد
فقط یک رندر مجدد
گفت، اگر مجبور بودیم تماس بگیریم reset در یک تابع همگام (مانند پس از ایجاد a
درخواست HTTP)، سپس این من منجر به سه رندر مجدد شد. با این حال، در
در آینده با حالت موازی آنها نیز گروه بندی خواهند شد. پس دغدغه اصلی من
ارائه مجدد نیست.
نگرانی من در مورد خطاهای بسته شدن موذیانه در کد ما است! آیا می توانید تشخیص دهید
آنها؟ سه تا هستند! من به شما یک اشاره می کنم، در هر یک از آنها یکی وجود دارد undo،
redo، و set، اما وجود ندارد reset.
در اینجا یک مثال ساختگی آورده شده است که این خطا را آشکار می کند:
function Example() {
const [state, { set }] = useUndo('first')
React.useEffect(() => {
set('second')
}, [])
React.useEffect(() => {
set('third')
}, [])
return pre>{JSON.stringify(state, null, 2)}pre>
}
خروجی چاپ شده در اینجا خواهد بود:
{
"past": ["first"],
"present": "third",
"future": []
}
باید باشد:
{
"past": ["first", "second"],
"present": "third",
"future": []
}
پس چه شد "second" در شرایط ما؟ آه! معلوم شد دلمون تنگ شده
بسته به set در آرایه وابستگی های اثر. غاز احمق. اضافه کنیم
اینها:
function Example() {
const [state, { set }] = useUndo('first')
React.useEffect(() => {
set('second')
}, [set])
React.useEffect(() => {
set('third')
}, [set])
return pre>{JSON.stringify(state, null, 2)}pre>
}
عالی، ذخیره، بارگذاری مجدد… منتظر بمانید؟ اوه نه! وقتی آنها را اضافه کردم این اتفاق افتاد:
{
"past": [
"first",
"second",
"third",
"second",
"third",
"second",
"third",
"second",
"third",
"second",
"third",
"second",
"third",
"second",
"third",
"second",
"third",
"second",
"third",
"second",
"third",
"second",
"third",
"second",
"third",
"second",
"third",
"... this goes on forever..."
],
"present": "third",
"future": []
}
اما صبر کن! یادمون نمیاد؟ set عملکرد؟ نباید تغییر کند مگر اینکه وابستگی ها تغییر کنند… صبر کنید… این شامل past و present ارزش های …
و اوه! وقتی زنگ میزنیم set این ارزش ها تغییر می کنند و در نتیجه ارزش ما ایجاد می شود
حلقه بی پایان!
اکنون این ممکن است ساختگی به نظر برسد، اما اگر چنین بود ممکن است یک اشکال مشابه ظاهر شود
وضعیت به روز رسانی بر اساس رویدادهای شبکه و آنها به شکل دیگری بازگشتند
از لحظه ارسال سفارش دهید در هر صورت، شما فقط نمی خواهید مجبور شوید
به چنین چیزهایی فکر کن، درست است؟ درست است
بنابراین ما می توانیم این مشکل را با useReducer، اما ما در واقع می توانیم مال خود را تغییر دهیم
useState اجرا و گام جانبی در این موضوع و فکر کردم از آن لذت ببرید
که، پس اینجاست:
function useUndo(initialPresent) {
const [state, setState] = React.useState({
past: [],
present: initialPresent,
future: [],
})
const canUndo = state.past.length !== 0
const canRedo = state.future.length !== 0
const undo = React.useCallback(() => {
setState((currentState) => {
const { past, present, future } = currentState
if (past.length === 0) return currentState
const previous = past[past.length - 1]
const newPast = past.slice(0, past.length - 1)
return {
past: newPast,
present: previous,
future: [present, ...future],
}
})
}, [])
const redo = React.useCallback(() => {
setState((currentState) => {
const { past, present, future } = currentState
if (future.length === 0) return currentState
const next = future[0]
const newFuture = future.slice(1)
return {
past: [...past, present],
present: next,
future: newFuture,
}
})
}, [])
const set = React.useCallback((newPresent) => {
setState((currentState) => {
const { present, past } = currentState
if (newPresent === present) return currentState
return {
past: [...past, present],
present: newPresent,
future: [],
}
})
}, [])
const reset = React.useCallback((newPresent) => {
setState(() => ({
past: [],
present: newPresent,
future: [],
}))
}, [])
return [state, { set, reset, undo, redo, canUndo, canRedo }]
}
برای رفع این مشکل چند کار انجام دادم:
- هنگام فراخوانی توابع بهروزرسانی وضعیت، از تماسهای بهروزرسانیکننده وضعیت استفاده کردم
میتونستم بگیرمcurrentStateبه عنوان یک استدلال یعنی من این کار را نکردم
شما باید کشور را به عنوان وابستگی مشخص کنید. - من تمام حالت ها را در یک شی ادغام کردم. من مجبور شدم این کار را انجام دهم زیرا آنجا بود
موقعیت هایی هستند که برای تعیین مقدار دیگر به یک مقدار نیاز دارید. به عنوان مثال در
redo، من به ارزش نیاز دارمpresentبرای به روز رسانیpastو ارزش
futureبرای به روز رسانیpresent. - من محاسبات را انجام دادم
canUndoوcanRedoدر برنامه به روز رسانی وضعیت
تماس های برگشتی بر اساسcurrentStateمن از استدلال ها دریافت می کنم، بنابراین من
نیازی به فهرست کردن آنهایی که در آرایه وابستگیها وجود دارد نیست.
این مشکلی را که ما داشتیم حل می کند، و آن را به خوبی انجام می دهد، اما بیایید آن را امتحان کنیم
همین مورد با useReducer برای مقایسه
useReducer عملکرد
const UNDO = 'UNDO'
const REDO = 'REDO'
const SET = 'SET'
const RESET = 'RESET'
function undoReducer(state, action) {
const { past, present, future } = state
const { type, newPresent } = action
switch (type) {
case UNDO: {
if (past.length === 0) return state
const previous = past[past.length - 1]
const newPast = past.slice(0, past.length - 1)
return {
past: newPast,
present: previous,
future: [present, ...future],
}
}
case REDO: {
if (future.length === 0) return state
const next = future[0]
const newFuture = future.slice(1)
return {
past: [...past, present],
present: next,
future: newFuture,
}
}
case SET: {
if (newPresent === present) return state
return {
past: [...past, present],
present: newPresent,
future: [],
}
}
case RESET: {
return {
past: [],
present: newPresent,
future: [],
}
}
}
}
function useUndo(initialPresent) {
const [state, dispatch] = React.useReducer(undoReducer, {
past: [],
present: initialPresent,
future: [],
})
const canUndo = state.past.length !== 0
const canRedo = state.future.length !== 0
const undo = React.useCallback(() => dispatch({ type: UNDO }), [])
const redo = React.useCallback(() => dispatch({ type: REDO }), [])
const set = React.useCallback(
(newPresent) => dispatch({ type: SET, newPresent }),
[],
)
const reset = React.useCallback(
(newPresent) => dispatch({ type: RESET, newPresent }),
[],
)
return [state, { set, reset, undo, redo, canUndo, canRedo }]
}
عجب useUndo خود موضوع در حال حاضر بسیار ساده است. اگر شروع کرده بودیم
با useReducer از ابتدا ما حتی به اضافه کردن فکر نمی کردیم
چیزی برای آرایه وابستگی ما زیرا این توابع بسیار ساده هستندمن به چیزی نیاز ندارم تمام منطق در کاهنده ما زندگی می کند. این به ما کمک می کند که از آن اجتناب کنیم
به طور طبیعی موضوع
شاید برای شما جالب باشد که محفظه های سوئیچ در گیربکس ما اساساً هستند
دقیقاً محتویات توابع ما قبل از ایجاد تغییر چیست.
هنگامی که یک عنصر از حالت شما به مقدار عنصر دیگر تکیه می کند
وضعیت شما برای به روز رسانی: useReducer
بنابراین اگر می خواهید برخی از “قوانین” (NOT ESLINT RULES) در اینجا وجود دارد:
- وقتی فقط یک عنصر مستقل از ایالتی است که شما مدیریت می کنید:
useState - هنگامی که یک عنصر از حالت شما به مقدار عنصر دیگر تکیه می کند
وضعیت شما برای به روز رسانی:useReducer
خارج از این «قوانین» همه چیز دیگر واقعاً ذهنی است. انصافا حتی
“قوانین” ذهنی هستند زیرا همانطور که نشان دادم شما می توانید هر کاری که بخواهید انجام دهید
من با یکی از این دو می خواهم.
همچنین به خاطر داشته باشید که این مورد به صورت موردی است. شما کاملا می توانید
استفاده کنید useState در همان جزء یا قلابی که استفاده می کند useReducer. و تو
می تواند متعدد باشد useStates و چندگانه useReducers در یک قلاب بافی یا
جزء این مشکلی نیست. حالت را به صورت منطقی بر اساس دامنه تقسیم کنید. اگر تغییر کند
با هم، احتمالا بهتر است در کنار هم در یک کاهنده نگهداری شوند. اگر چیزی هست
بنابراین کاملاً مستقل از سایر عناصر حالت در آن قلاب / جزء
قرار دادن آن با سایر عناصر حالت فقط پیچیدگی غیر ضروری را اضافه می کند
و سر و صدا به این گیربکس و بهتر است آن را به حال خود رها کنید.
بنابراین این فقط زمانی نیست که من بیش از X شماره داشته باشم useStateتغییر به
useReducer. ” این بسیار ظریف تر از این است. اما امیدواریم این پست به شما کمک کند
این تفاوت های ظریف را درک کنید و به ابزاری برسید که معاوضه هایی دارد
برای موقعیت خود بهترین کار را انجام دهید به طور کلی، من پیشنهاد می کنم با شروع useState،
و حرکت می کند به useReducer وقتی متوجه شدید که آیتم های حالت باید تغییر کنند
با هم
موفق باشید!



