Добавить Автора Переводчик: Front-end Xiaozhi Источник: дмитрипавлутин
Скорее всего, вы много читали о том, как использовать React Hooks. Но иногда знать, когда его не использовать, так же важно, как и знать, как его использовать.
В этой статье я в основном расскажу о том, как неправильно используются хуки React и как их решить.
- Не создавайте устаревшие замыкания
- Не используйте состояние для данных инфраструктуры
- Не забудьте убрать побочные эффекты
1. Не меняйте порядок вызовов хуков
За несколько дней до написания этой статьи я написал компонент для получения информации об игре по id, ниже простой вариантFetchGame
:
function FetchGame({ id }) {
if (!id) {
return 'Please select a game to fetch';
}
const [game, setGame] = useState({
name: '',
description: ''
});
useEffect(() => {
const fetchGame = async () => {
const response = await fetch(`/api/game/${id}`);
const fetchedGame = await response.json();
setGame(fetchedGame);
};
fetchGame();
}, [id]);
return (
<div> <div>Name: {game.name}</div> <div>Description: {game.description}</div> </div>
);
компонентыFetchGame
перениматьid
(т.е. ID игры, которую нужно получить).useEffect()
существуетawait fetch(/game/${id})
Извлеките информацию об игре и сохраните ее в переменных состоянияgame
середина.
Открыть демо (код sandbox.io/is/hooks - о, горячие точки...).组件正确地执行获取操作,并使用获取的数据更新状态。但是看看tab Eslint警告: 有 Hook 执行顺序不正确的问题。
Проблемы в этом суждении:
function FetchGame({ id }) {
if (!id) { return 'Please select a game to fetch'; }
// ...
}
когдаid
Когда компонент пуст, он отображает'Please select a game to fetch'
и выйти, не вызывая никаких хуков.
но еслиid
не пуст (например, равен «1»), он вызоветuseState()
а такжеuseEffect()
.
Условное выполнение хуков может привести к неожиданным ошибкам, которые трудно отладить. Внутренняя работа React Hooks требует, чтобы компоненты всегда вызывали Hooks в одном и том же порядке между рендерами.
Это именно то, что крючок дляпервое правило: не вызывайте хуки внутри циклов, условий или вложенных функций.
Решение размещено за условным крюком:
function FetchGame({ id }) {
const [game, setGame] = useState({
name: '',
description: ''
});
useEffect(() => {
const fetchGame = async () => {
const response = await fetch(`/api/game/${id}`);
const fetchedGame = await response.json();
setGame(fetchedGame);
};
if (id) { fetchGame(); } }, [id]);
if (!id) { return 'Please select a game to fetch'; }
return (
<div>
<div>Name: {game.name}</div>
<div>Description: {game.description}</div>
</div>
);
}
Теперь, есть лиid
пусто,useState()
а такжеuseEffect()
MyIncreaser
count
:
function MyIncreaser() {
const [count, setCount] = useState(0);
const increase = useCallback(() => {
setCount(count + 1);
}, [count]);
const handleClick = () {
increase(); increase(); increase(); };
return (
<>
<button onClick={handleClick}>Increase</button>
<div>Counter: {count}</div>
</>
);
}
handleClick
Обновление статуса вызывается 3 раза.
Теперь, прежде чем открывать демонстрацию, задайте вопрос: Увеличивается ли счетчик, если кнопка нажата один раз?3
?
Откройте демо (код sandbox.io/is/stale-var…
жаль, даже вhandleClick()
3 звонкаincrease()
, Счет только увеличился1
.
Проблема вsetCount(count + 1)
Обновление статуса. Когда кнопка нажата, React callsetCount(count + 1)
три раза
const handleClick = () {
increase();
increase();
increase();
};
// 等价:
const handleClick = () {
setCount(count + 1);
// count variable is now stale
setCount(count + 1);
setCount(count + 1);
};
setCount(count + 1)
count + 1 = 0 + 1 = 1
.但是,接下来的两次setCount(count + 1)
1
, потому что они используют устаревшееstale
условие.
通过使用函数方式更新状态来解决过时的状态。 мы используемsetCount(count => count + 1)
заменятьsetCount(count + 1)
:
function MyIncreaser() {
const [count, setCount] = useState(0);
const increase = useCallback(() => {
setCount(count => count + 1); }, []);
const handleClick = () {
increase();
increase();
increase();
};
return (
<>
<button onClick={handleClick}>Increase</button>
<div>Counter: {count}</div>
</>
);
}
Вот хорошее правило, чтобы избежать встречи с устаревшими переменными:
Если вы используете текущее состояние для расчета следующего состояния, всегда используйте метод функции для обновления состояния:setValue(prevValue => prevValue + someResult)
.
3. Не создавайте устаревшие замыкания
React Hook в значительной степени опирается на концепцию замыканий. Замыкание зависимостей делает их такими выразительными.
Замыкания в JavaScript — это функции, которые захватывают переменные из их лексической области видимости. Независимо от того, где выполняется замыкание, оно всегда может получить доступ к переменной из того места, где оно было определено.
При использовании в качестве параметра обратного вызова принимает крючок (например.useEffect(callback, deps)
,useCallback(callback, deps))
useEffect(callback, deps)
в компоненте<WatchCount>
середина,useEffect()
Печатать каждые 2 секундыcount
значение
const [count, setCount] = useState(0);
useEffect(function() {
setInterval(function log() {
console.log(`Count is: ${count}`);
}, 2000);
}, []);
const handleClick = () => setCount(count => count + 1);
return (
<> <button onClick={handleClick}>Increase</button> <div>Counter: {count}</div> </>
);
}
Открыть демо (код sandbox.io/is/stale-var…даCount is: 0
,Независимо от тогоcount
Каково фактическое значение переменной состояния.
Почему это так?
При рендеринге в первый разlog
функция захваченаcount
ценность0
.
После этого при нажатии кнопки иcount
при увеличении,setInterval
взятыйcount
Значение по-прежнему захватывается из исходного рендера.count
является значением 0.log
функция является устаревшим замыканием, потому что она фиксирует устаревшую переменную состоянияcount
.
Решение должно позволитьuseEffect()
знать закрытиеlog
зависит отcount
, и правильно сбросить таймер
function WatchCount() {
const [count, setCount] = useState(0);
useEffect(function() {
const id = setInterval(function log() {
console.log(`Count is: ${count}`);
}, 2000);
return () => clearInterval(id); }, [count]);
const handleClick = () => setCount(count => count + 1);
return (
<>
<button onClick={handleClick}>Increase</button>
<div>Counter: {count}</div>
</>
);
}
Как только зависимости установлены правильно, как толькоcount
Изменения,useEffect()
Будет обновленоsetInterval()
закрытие.
4. Не используйте состояние для данных инфраструктуры
В какой-то момент мне нужно было вызвать побочный эффект при обновлении состояния, не вызывая побочный эффект при первом рендеринге. useEffect(callback, deps)
Функция обратного вызова всегда вызывается после монтирования компонента: поэтому я хочу этого избежать.
Я нашел следующее решение
function MyComponent() {
const [isFirst, setIsFirst] = useState(true);
const [count, setCount] = useState(0);
useEffect(() => {
if (isFirst) {
setIsFirst(false);
return;
}
console.log('The counter increased!');
}, [count]);
return (
<button onClick={() => setCount(count => count + 1)}> Increase </button>
);
}
Переменные состоянияisFirst
Используется для определения того, является ли это первой визуализацией. После обновленияsetIsFirst(false)
, Появится еще одна причина для повторного рендеринга.
Держатьcount
Состояние имеет смысл, потому что интерфейс должен отображать значение count . но,isFirst
Не может использоваться непосредственно для расчета выходных данных.
Независимо от того, был ли это первый рендеринг, не следует сохранять в этом состоянии. Данные инфраструктуры, например, о циклах рендеринга (т. е. первый рендеринг, количество рендеров), идентификаторы таймера (setTimeout()
,setInterval()
useRef()
const isFirstRef = useRef(true); const [count, setCount] = useState(0);
useEffect(() => {
if (isFirstRef.current) {
isFirstRef.current = false;
return;
}
console.log('The counter increased!');
}, [count]);
return (
<button onClick={() => setCounter(count => count + 1)}>
Increase
</button>
);
}
isFirstRef
isFirstRef.current
Свойства используются для доступа и обновления ссылочных значений.
ВАЖНОЕ ПРИМЕЧАНИЕ: ОБНОВЛЕНИЕ ССЫЛОКisFirstRef.current = false
Повторный рендеринг не запускается.
5. Не забудьте убрать побочные эффекты
Множество побочных эффектов, таких как получение запросов или использованиеsetTimeout()
Такие таймеры являются асинхронными.
Не забудьте очистить побочный эффект, если компонент выгружается или результат побочного эффекта больше не нужен.
Компонент ниже имеет кнопку. При нажатии кнопки счетчик увеличивается с задержкой каждую секунду1
:
function DelayedIncreaser() {
const [count, setCount] = useState(0);
const [increase, setShouldIncrease] = useState(false);
useEffect(() => {
if (increase) {
setInterval(() => {
setCount(count => count + 1)
}, 1000);
}
}, [increase]);
return (
<> <button onClick={() => setShouldIncrease(true)}> Start increasing </button> <div>Count: {count}</div> </>
);
}
Откройте демо (Код Sandbox.io/is/unmounted ..
Во время увеличения нажмитеumount
кнопку для удаления компонента. React предупредит в консоли обновить состояние размонтированного компонента.
ремонтDelayedIncreaser
Это просто: просто начнитеuseEffect()
Обратный вызов возвращает функцию очистки:
// ...
useEffect(() => {
if (increase) {
const id = setInterval(() => {
setCount(count => count + 1)
}, 1000);
return () => clearInterval(id); }
}, [increase]);
// ...
}
Тем не менее, каждый раз, когда вы пишете код с побочными эффектами, спрашивайте себя, нужно ли его очищать. Таймеры, частые запросы (типа закачки файлов), сокеты почти всегда нужно чистить.
6. Резюме
Лучший способ запустить хук из React — научиться им пользоваться.
Но у вас также бывают ситуации, когда вы не можете понять, почему они ведут себя не так, как вы ожидали. Знать, как использовать React Hooks, недостаточно: вы также должны знать, когда их не следует использовать.
Не делайте этого, вы условно рендерите хук или меняете порядок вызовов HOOK. Независимо от значения PROPS или статуса, ожидается, что React вызовет HOOK в том же порядке.
useEffect(callback, deps)
, useCallback(callback, deps
setTimeout()
илиsetInterval()
Наконец, не забудьте очистить свои побочные эффекты.
~ Готово, мне было мало мудрости, я иду мыть посуду.
Ошибки, которые могут существовать после развертывания кода, не могут быть известны в режиме реального времени.Чтобы решить эти ошибки впоследствии, много времени тратится на отладку журнала.Кстати, я рекомендую всем полезный инструмент мониторинга ошибок.Fundebug.
оригинал:Рисовое путешествие avlutin.com/react-hooks…
общаться с
Статья постоянно обновляется каждую неделю. Вы можете выполнить поиск «Big Move to the World» в WeChat, чтобы прочитать и обновить ее как можно скорее (на одну или две статьи раньше, чем в блоге). Эта статья находится на GitHub.GitHub.com/QQ449245884…Он был включен, и многие мои документы были разобраны. Добро пожаловать в Звезду и совершенство. Вы можете обратиться в тестовый центр для ознакомления во время собеседования. Кроме того, обратите внимание на паблик-аккаунт и ответьте в фоновом режиме.Благосостояние, вы можете увидеть преимущества, вы знаете.