یک ترفند ساده برای جلوگیری از استفاده از Effect در React

مسئله
یک بار به مشکل عجیبی برخوردم. به خاطر این مقاله، بیایید با این مثال ساختگی برویم:
من یک جزء دارم MyDumbComponent.tsx
و یک شناسه با مقدار حالت اولیه دریافت می کند و از آن حالت برای واکشی برخی از داده ها استفاده می کند.
این حالت همچنین می تواند در داخل همان مؤلفه دستکاری شود:
import { useEffect, useState } from 'react'
import todos from '../data/todos.json'
type Unpacked<T> = T extends (infer U)[] ? U : T
export default function MyDumbComponent({ initialId }: { initialId: number }) {
const [id, setId] = useState(initialId)
const [todoData, setTodoData] = useState<Unpacked<typeof todos> | null>(null)
useEffect(() => {
const allTodos = todos
const selectedTodo = allTodos.find(todo => todo.id === id) ?? null
setTodoData(selectedTodo)
}, [id])
return (
<>
<div>
<code>
<pre>{JSON.stringify(todoData, null, 2)}</pre>
</code>
</div>
<small>Child id: {id}</small>
<br />
<br />
<button onClick={() => setId(prev => prev + 1)}>+</button>
<button onClick={() => id > 1 && setId(prev => prev - 1)}>-</button>
</>
)
}
وقتی روی آن کلیک کردم +
و -
را فشار دهید، شناسه را تغییر میدهد و یک جزئیات کار جدید را میآورد و به کاربر نشان میدهد.
نمونه نسخه ی نمایشی کد را ببینید
این کاملاً خوب و همانطور که انتظار می رفت کار کرد. موضوع زمانی پیش آمد که من می خواستم شناسه (props) را در والد به روز کنم.
عقل سلیم احمقانه من می گوید که باید دوباره رندر شود. اما در کمال تعجب من اینطور نشد. در اینجا من وضعیت را به روز کردم:
import { useState } from 'react'
import './App.css'
import MyDumbComponent from './components/MyDumbComponent'
function App() {
const [count, setCount] = useState(1)
return (
<div>
<p> Parent state variable: {count} </p>
<button onClick={() => setCount(c => c + 1)}>
Increment parent state
</button>
<br /> <br />
<br />
<MyDumbComponent initialId={count} />
</div>
)
}
export default App
در اینجا مشاهده میشود که اگر prop به عنوان یک مقدار اولیه برای وضعیت استفاده شود، حتی زمانی که prop تغییر میکند، React مؤلفه فرزند را «بارگذاری مجدد» نمیکند.
در ابتدا، ممکن است غیر قابل درک باشد. دلیل آن این است که React یک کامپوننت را زمانی که حالت تغییر می کند یا ویژگی های آن تغییر می کند، به روز می کند.
اگر React فقط دور انداخته و کودک را دور میاندازد و هر بار که یکی از اجزای آن تغییر میکند، یک مورد جدید ایجاد میکند، باید گرههای DOM جدید ایجاد کند، گرههای جدید ایجاد کند و آنها را تنظیم کند.
اینها می توانند گران باشند، به خصوص اگر پایه ها به طور مکرر تغییر کنند یا تعداد زیادی از چنین پایه های تغییر دهنده داشته باشند. همه اینها گران و کند است. بنابراین، React از کامپوننتی که آنجا بود دوباره استفاده میکند، زیرا از همان نوع و در همان موقعیت درخت است.
استفاده از useEffect به عنوان به روز رسانی حالت
من مقصرم که در این سناریو از افکت دوم استفاده کردم😅
می خواهد: هوم.. پس باید بر اساس زمان تعویض پروپ یه کاری انجام بدیم.. وقتی پروپ عوض میشه چی کار می کنه… با اون پروپ در وابستگی استفاده کنید!!
بنابراین، من این افکت را بعد از اولین مورد اضافه میکنم (imo اولین useEffect باید با react-query یا برخی موارد دیگر واکشی دادهها نیز مرتبط شود).
اما به هر حال، به این صورت است:
useEffect(() => {
// Changing children's state whenever our prop `initialId` changes
setId(initialId)
}, [initialId])
در اینجا می توان این رویکرد استفاده از useEffect را مشاهده کرد (زبان گردان، درست است؟) برای به روز رسانی Vale of State که با برخی از کارهای پروپ مقداردهی اولیه شده است
اما این راه حل می تواند بهتر باشد. useEffect مقدار حالت را در رندر دوم به روز کرد.
همچنین، جلوگیری از استفاده از useEffect تا زمانی که فرد می تواند یک قانون کلی خوب است. من متوجه شده ام که این امر خوانایی را افزایش می دهد و با استفاده نه چندان دقیق از useEffect از برخی اشکالات جلوگیری می کند.
این توصیه به من کمک کرد تا این را به خاطر بسپارم: useEffect فقط باید زمانی استفاده شود که یک سرویس خارجی / چیزی خارج از پارادایم React (مانند شنوندگان سفارشی) باید با React یکپارچه شود.
بنابراین useEffect
را می توان به عنوان useSyncronise
راه حل: استفاده از کلیدها برای “بارگذاری مجدد” یک React Component
پس راه چیست؟ کلیدهای نجات!!🔑 اگر کامپوننتی کلید داشته باشد و تغییر کند، React از مقایسه رد می شود و کامپوننت جدید جدیدی می سازد.
بنابراین، اگر بخواهید، می توانید Keys را به عنوان یک “هویت” یا منبع حقیقت برای یک جزء در نظر بگیرید. بنابراین، اگر کلید تغییر کند، کامپوننت باید از ابتدا بارگیری مجدد شود.
این همان دلیلی است که هنگام رندر کردن یک لیست به کلید نیاز دارید، به طوری که React بتواند آیتم های لیست را زمانی که (اگر) موقعیت / ترتیب آنها در آن لیست تغییر کند، متمایز کند.
بنابراین، در مورد ما، ما فقط می توانیم کلید را به مؤلفه فرزند منتقل کنیم و از ابتدا دوباره ایجاد می شود:
import { useState } from 'react'
import './App.css'
import MyDumbComponent from './components/MyDumbComponent'
function App() {
const [count, setCount] = useState(1)
return (
<div>
<p> Parent state variable: {count} </p>
<button onClick={() => setCount(c => c + 1)}>
Increment parent state
</button>
<br /> <br />
<br />
<MyDumbComponent key={count} initialId={count} />
</div>
)
}
export default App
به راحتی متوجه شدم که اسناد React جدید مقاله ای در مورد بازنشانی وضعیت با کلیدها دارد
در ادامه بخوانید: