ریاکتجیاس - بهروزرسانی نشدن useState در React
من در حال یادگیری React هستم و میدانم که این موضوع توسط سوالات زیادی پوشش داده شده است، اما همه آنها بر طبیعت غیرهمزمان useState تمرکز دارند. من مطمئن نیستم که این همان چیزی است که در اینجا اتفاق میافتد. من همچنین نسخهای را در ترکیب با useEffect امتحان کردم و نتیجه همان بود.
من یک کامپوننت دارم که در آن به فشردن کلیدها گوش میدهم - کاربر در حال تلاش برای حدس زدن یک کلمه است. وقتی کلمه حدس زده شد، شیء word قرار است با یک شیء دیگر جایگزین شود و یک پازل جدید آغاز میشود.
کامپوننت با استفاده از حالت درست (حروف کلمه جدید) رندر میشود، اما هنگام تلاش برای اولین حدس از دومین معما، شیء word هنوز شیء حالت اصلی است.
چگونه میتوانم این شیء word را به درستی بهروزرسانی کنم؟
CodeSandbox
مراحل بازتولید در فایل readme.md:
const WordFrame = () => {
const [word, setWord] = useState(() => new Word 'apple');
const [renderedCharacters, setRenderedCharacters] = useState(
word.renderedCharacters
);
const keyDownHandler = (e: KeyboardEvent): void => {
console.log(e.key);
if (letters.includes(e.key)) {
const correctGuess = word.processGuess(e.key);
if (correctGuess) {
setRenderedCharacters([...word.renderedCharacters]);
// moving the following if block into useEffect with dependency on word.isGuessed and renderedCharacters doesn't help
if (word.isGuessed) {
const newWord: Word = new Word('banana');
setWord(newWord);
setRenderedCharacters(newWord.renderedCharacters);
}
}
}
};
useEffect(() => {
document.addEventListener('keydown', keyDownHandler);
return () => document.removeEventListener('keydown', keyDownHandler);
}, []);
// useEffect(() => {
// if (word.isGuessed) {
// const newWord = new Word('banana);
// setWord(newWord);
// setRenderedCharacters(newWord.renderedCharacters);
// }
// }, [word.isGuessed, renderedCharacters]);
return (
<div className='word-frame'>
{renderedCharacters.map((c) => (
<LetterFrame characterValue={c.value} key={c.id} />
))}
</div>
);
};
export default WordFrame;
Word.ts
class Word {
private readonly _unguessedCharacter: string = ' ';
private readonly _characters: string[] = [];
private _guessingIndex = 0;
private _renderedCharacters: Character[] = [];
public get renderedCharacters() {
return this._renderedCharacters;
}
private _isGuessed: boolean = false;
public get isGuessed(): boolean {
return this._isGuessed;
}
constructor(wordString: string) {
console.log(`Word.constructor called with parameter: ${wordString}`);
this._characters = wordString.split('');
this.setRenderedCharacters();
}
public processGuess(letter: string): boolean {
const isSuccessfulGuess = (): boolean =>
this._characters[this._guessingIndex].toLowerCase() ===
letter.toLowerCase();
const successful = isSuccessfulGuess();
if (successful) {
this._guessingIndex++;
this.setRenderedCharacters();
}
if (this._guessingIndex > this._characters.length - 1) {
this._isGuessed = true;
}
return successful;
}
private setRenderedCharacters(): void {
this._renderedCharacters = [];
this._characters.forEach((c, i): void => {
if (i >= this._guessingIndex) {
this._renderedCharacters.push(
new Character(this._unguessedCharacter)
);
} else {
this._renderedCharacters.push(new Character(c));
}
});
}
}
export default Word;
پاسخ شماره ۱
مشکل این است که keyDownHandler word را از رندر اولیه میگیرد و سپس تنها یک بار به keydown متصل میشود. شما نیاز دارید که هر بار که word تغییر میکند، keyDownHandler را حذف و دوباره متصل کنید (به خاطر داشته باشید که هر زمان setWord را فراخوانی کنید، React یک رندر مجدد ایجاد میکند که در آن word شامل مقدار جدید کلمه است).
این هم راهحل:
const WordFrame = () => {
const [word, setWord] = useState(() => new Word('apple'));
// ...
const keyDownHandler = (e: KeyboardEvent): void => { /* ... */ };
useEffect(() => {
document.addEventListener('keydown', keyDownHandler);
return () => document.removeEventListener('keydown', keyDownHandler);
}, [word]); // <-- add `word` to the dependencies for this effect
// ...
};
حالا، هر بار که WordFrame با یک word جدید دوباره رندر میشود، یک نسخه تازه از بسته keyDownHandler را ضمیمه میکند که آخرین مقدار word را ذخیره میکند.
اگر میخواهید بیشتر درباره این مکانیک بخوانید، من به شدت راهنمای (جامع) دن آبراموف را اینجا توصیه میکنم. شما همچنین میتوانید عبارت «stale closure React useEffect» را در گوگل جستجو کنید و چند مقاله خوب دیگر پیدا کنید.
پاسخ شماره ۲
اشکالزدایی بدون یک کدسندباکس سخت است اما حدس میزنم چون شما میخواهید useEffect زمانی که انجام میدهید فعال شود
setRenderedCharacters([...word.renderedCharacters]);شما باید renderedCharacters را به عنوان وابستگی اضافه کنید
useEffect(() => {
if (word.isGuessed) {
const newWord = getWord();
setWord(newWord);
setRenderedCharacters(newWord.renderedCharacters);
}
}, [word.isGuessed, renderedCharacters]);
