1. Введение
Инструментальные статьи следует пропускать, а литературную классику следует перечитывать повторно. Если вы скажете реагировать0.14
Различные жизненные циклы версии можно сравнить со статьями об инструментах, а затем16.7
Принесенные крючки нужно перечитывать снова и снова, как литературную классику.
Hooks API намного лучше предыдущего API жизненного цикла с точки зрения простоты и глубины использования, поэтому его нужно постоянно понимать и практиковать, иначе он останется только на поверхности.
по сравнению сuseState
Или для кастомных хуков, самое сложное для понимания этоuseEffect
Надеемся, что с помощью этого инструментаa-complete-guide-to-useeffectОдна статья, глубокое пониманиеuseEffect
.
Первоначальный текст очень длинный, поэтому краткое изложение сделано автором. авторDan Abramov, React основной разработчик.
2. Обзор
unLearning, то есть научиться забывать.Ваш предыдущий опыт обучения будет мешать вашему дальнейшему обучению.
хочу понятьuseEffect
Сначала вы должны глубоко понять механизм рендеринга функционального компонента.Разница в функции между функциональным компонентом и классовым компонентом была подробно прочитана в предыдущем выпуске.Интенсивное чтение «Компоненты функций и классов»был введен, и у них все еще есть различия в мышлении:
Функциональный компонент — это более совершенная абстракция, управляемая состоянием, в нем даже нет концепции жизненного цикла компонента класса, есть только одно состояние, а React отвечает за синхронизацию с DOM.Это для понимания функционального компонента иuseEffect
Ключ, который будет подробно описан позже.
Поскольку исходный текст очень и очень длинный, автор упростил содержание и снова переставил его. Другая причина, по которой исходный текст очень длинный, заключается в том, что в нем используется эвристическое мышление и послойное написание, и автор максимально сохраняет эту структуру мышления.
Начните с нескольких вопросов
Предполагается, что читатель имеет относительно богатый опыт разработки интерфейса и React и написал несколькоHooks. Тогда вы можете подумать, что функциональный компонент очень полезен, но ложка дегтя в том, что у вас всегда есть некоторые сомнения, такие как:
- 🤔 Как пользоваться
useEffect
заменятьcomponentDidMount
? - 🤔 Как пользоваться
useEffect
Доступ? параметр[]
представляет что? - 🤔
useEffect
Может ли зависимость быть функцией? Каковы функции? - 🤔 Почему выборка иногда вызывает бесконечный цикл?
- 🤔 Почему иногда
useEffect
Состояние или пропс получается в старом?
Первый вопрос, возможно, задавали и на него отвечали бесчисленное количество раз, но вы забудете его в следующий раз, когда будете писать код. Автор тот же, и представил этот вопрос в трех разных интенсивных чтениях:
- Интенсивное чтение «React Hooks»
- Интенсивное чтение "Как использовать React Hooks для сборки колеса"
- Интенсивное чтение «Компоненты функций и классов»
Но на следующий день я забыл, потому чтоРеализация жизненных циклов с помощью хуков действительно неудобна.Если быть честным,Если вы хотите полностью решить эту проблему, пожалуйста, забудьте о React, забудьте о жизненном цикле и переосмыслите образ мышления функционального компонента!
Ответы на 5 вышеперечисленных вопросов повторяться не будут, если у читателей есть сомнения, они могут перейти наИсходный TLDRПроверять.
чтобы было ясноuseEffect
, лучше всего начать с концепции рендеринга, чтобы понять.
Каждый рендер имеет свои реквизиты и состояние
Можно считать, что содержимое каждого рендеринга будет формировать снимок и сохраняться, поэтому при изменении состояния и рендеринга формируется N состояний рендеринга, и каждое состояние рендеринга имеет свои собственные фиксированные свойства и состояние.
увидеть нижеcount
:
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
При каждом щелчкеcount
Это просто константа, которая не меняется, и здесь нет эксплойта.Proxy
Двусторонняя привязка — это просто константа, которая существует в каждом рендере.
в исходном состоянииcount
значение0
, и при нажатии кнопки в каждом процессе рендерингаcount
значение будет зафиксировано на1
,2
,3
:
// During first render
function Counter() {
const count = 0; // Returned by useState()
// ...
<p>You clicked {count} times</p>;
// ...
}
// After a click, our function is called again
function Counter() {
const count = 1; // Returned by useState()
// ...
<p>You clicked {count} times</p>;
// ...
}
// After another click, our function is called again
function Counter() {
const count = 2; // Returned by useState()
// ...
<p>You clicked {count} times</p>;
// ...
}
На самом деле не только объекты, но и функции также независимы каждый раз, когда они рендерятся. ЭтоCapture ValueФункции, которые в этом случае не будут расширяться одна за другой, описываются только как «Здесь есть функция Capture Value».
Каждый рендер имеет свою собственную обработку событий
объясняет, почему следующий код выводит5
вместо3
:
const App = () => {
const [temp, setTemp] = React.useState(5);
const log = () => {
setTimeout(() => {
console.log("3 秒前 temp = 5,现在 temp =", temp);
}, 3000);
};
return (
<div
onClick={() => {
log();
setTemp(3);
// 3 秒前 temp = 5,现在 temp = 5
}}
>
xyz
</div>
);
};
существуетlog
В процессе рендеринга, где выполняется функция,temp
Значение можно рассматривать как константу5
,воплощать в жизньsetTemp(3)
будет визуализирован новым Render, поэтому не будет выполнятьсяlog
функция.И то, что выполняется через 3 секунды, выполняетсяtemp
за5
Визуализация, так что результат естественно5
.
Причина в том,temp
,log
Оба имеют свойство Capture Value.
Каждый рендер имеет свои собственные эффекты
useEffect
Он также имеет характеристики Capture Value.
useEffect
Выполняется после рендеринга фактического DOM, чтоuseEffect
Полученное значение также соответствует характеристикам Capture Value:
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
вышеuseEffect
В каждом процессе рендерингаcount
Все это твердые константы.
Как обойти Capture Value
использоватьuseRef
Вы можете обойти функцию Capture Value.Это можно считатьref
Уникальная ссылка сохраняется во всех процессах рендеринга, поэтому все ссылки наref
Присваивание или значение , все получают только одно конечное состояние, без изоляции между каждым Render.
function Example() {
const [count, setCount] = useState(0);
const latestCount = useRef(count);
useEffect(() => {
// Set the mutable latest value
latestCount.current = count;
setTimeout(() => {
// Read the mutable latest value
console.log(`You clicked ${latestCount.current} times`);
}, 3000);
});
// ...
}
Можно также кратко считать, что,ref
является изменчивым, иstate
неизменяемый.
механизм рециркуляции
Когда компонент уничтожается,useEffect
Зарегистрированный слушатель должен быть уничтожен, что можно сделать с помощьюuseEffect
Возвращаемое значение:
useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.id, handleStatusChange);
};
});
Когда компонент уничтожается, выполняется функция обратного вызова в функции возвращаемого значения. Кроме того, благодаря функции Capture Value каждая «регистрация» и «переработка» получают пару фиксированных значений.
Замена «жизненного цикла» на синхронизацию
Функциональный компонент не имеет жизненного цикла, поэтому не пытайтесь переместить концепцию жизненного цикла компонента класса и подогнать ее. Функциональный компонент описывает только состояние пользовательского интерфейса, React синхронизирует его с DOM и все.
Поскольку это синхронизация состояний, состояние каждого рендеринга будет закреплено, включаяstate
props
useEffect
И все функции написаны на функциональном компоненте.
Однако отказ от синхронизации жизненного цикла принесет некоторые проблемы с производительностью, поэтому нам нужно указать React, как сравнивать эффект.
Расскажите React, как сравнивать Эффекты
Хотя React будет различать содержимое во время рендеринга DOM и изменять только измененную часть, а не заменять его целиком, он не может распознавать инкрементную модификацию Эффекта. Поэтому разработчикам необходимо пройтиuseEffect
Второй аргумент сообщает React, какие внешние переменные использовать:
useEffect(() => {
document.title = "Hello, " + name;
}, [name]); // Our deps
до того какname
Рендер при смене,useEffect
будет выполняться снова.
Однако ручное обслуживание обременительно и может быть упущено, поэтому вы можете использоватьeslintПлагин Auto Prompt + FIX:
Не лгите зависимостям
Если вы явно используете переменную, но не объявляете ее в зависимости, вы лжете React, и в результате при изменении зависимой переменнойuseEffect
Также не выполняется снова:
useEffect(() => {
document.title = "Hello, " + name;
}, []); // Wrong: name is missing in dep
Это выглядит глупо, но как насчет другого примера?
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(id);
}, []);
return <h1>{count}</h1>;
}
setInterval
Мы хотим выполнить его только один раз, поэтому мы мудро солгали React и написали зависимость как[]
.
"Инициализация компонента выполняется один разsetInterval
, выполняется один раз при уничтоженииclearInterval
Этот код работает как ожидалось. «Вы можете думать так.
Но вы не правы, ибо useeffect соответствует характеристикам Capture Value, получите егоcount
значение всегда инициализируется0
.эквивалентноsetInterval
навсегдаcount
за0
выполняется в рамках вашего последующегоsetCount
Действия не имеют никакого эффекта.
цена честности
Я немного изменил заголовок, потому что честность имеет свою цену:
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(id);
}, [count]);
Вы честно говорите React: «Эй, подождиcount
Выполнить после изменения», то вы получите одну хорошую новость и две плохие новости.
Хорошей новостью является то, что код работает нормально, получил последнюю версиюcount
.
Плохая новость:
- Таймер выключен, потому что каждый раз
count
Любые изменения уничтожаются и пересчитываются. - Частые таймеры возрождения/уничтожения накладывают определенную нагрузку на производительность.
Как это может быть одновременно честным и эффективным?
В приведенном выше примере используетсяcount
, однако такой код неудобен,Потому что вы полагаетесь на внешние переменные в Эффекте, который хотите выполнить только один раз.
Если хочешь быть честным, тоНайдите способ не зависеть от внешних переменных:
useEffect(() => {
const id = setInterval(() => {
setCount(c => c + 1);
}, 1000);
return () => clearInterval(id);
}, []);
setCount
Существует также режим обратного вызова функции, вам не нужно заботиться о текущем значении, просто измените «старое значение». Таким образом, хотя код всегда выполняется в первом рендере, он всегда может получить доступ к последнему.state
.
Отделяйте обновления от действий
Возможно, вы обнаружили, что описанный выше оппортунистический метод не решает полностью проблемы всех сценариев, таких как опора на дваstate
Случай:
useEffect(() => {
const id = setInterval(() => {
setCount(c => c + step);
}, 1000);
return () => clearInterval(id);
}, [step]);
вы обнаружите, что вам нужно полагаться наstep
С этой переменной мы вернулись в главу «Цена честности». Конечно, Дэн обязательно даст нам решение.
использоватьuseEffect
БратьяuseReducer
функции достаточно отделить обновление от действия:
const [state, dispatch] = useReducer(reducer, initialState);
const { count, step } = state;
useEffect(() => {
const id = setInterval(() => {
dispatch({ type: "tick" }); // Instead of setCount(c => c + step);
}, 1000);
return () => clearInterval(id);
}, [dispatch]);
Это частичный «Redux», так как обновление становитсяdispatch({ type: "tick" })
Таким образом, независимо от того, сколько переменных вам нужно использовать при обновлении, вам не нужно полагаться ни на какие переменные в действии, вызывающем обновление.Конкретная операция обновления находится вreducer
Это можно написать в функции.Онлайн-демонстрация.
Дэн также
useReducer
Сравнимо с чит-режимом Hooks, потому что он полностью обходит механизм Diff, но решает болевые точки!
Переместите функцию в эффект
Важность зависимостей и честности с React рассматривается в главе «Расскажите React, как сравнивать различия». то, если определение функции неuseEffect
В теле функции не только могут быть пропущены зависимости, но иeslintПлагины также не помогают автоматически собирать зависимости.
Ваша интуиция подскажет, что это принесет больше проблем, например, как повторно использовать функции? Да, функции, которые не зависят от переменных в функциональном компоненте, могут быть безопасно извлечены:
// ✅ Not affected by the data flow
function getFetchUrl(query) {
return "https://hn.algolia.com/api/v1/search?query=" + query;
}
А как насчет функций, зависящих от переменных?
Что делать, если вам нужно написать функцию вне эффекта?
Если вам нужно сделать это, используйтеuseCallback
Бар!
function Parent() {
const [query, setQuery] = useState("react");
// ✅ Preserves identity until query changes
const fetchData = useCallback(() => {
const url = "https://hn.algolia.com/api/v1/search?query=" + query;
// ... Fetch data and return it ...
}, [query]); // ✅ Callback deps are OK
return <Child fetchData={fetchData} />;
}
function Child({ fetchData }) {
let [data, setData] = useState(null);
useEffect(() => {
fetchData().then(setData);
}, [fetchData]); // ✅ Effect deps are OK
// ...
}
Так как функция также имеет свойство Capture Value, послеuseCallback
Обернутые функции можно рассматривать как обычные переменные, какuseEffect
зависимость.useCallback
Что он делает, так это возвращает ссылку на новую функцию при изменении ее зависимостей, запускаяuseEffect
Зависимость изменяется, и активируйте ее повторное выполнение.
Преимущества использования обратного звонка
В коде компонента класса, если вы хотите повторно получить число при изменении параметра, вы не можете напрямую сравнивать Diff функции выборки:
componentDidUpdate(prevProps) {
// 🔴 This condition will never be true
if (this.props.fetchData !== prevProps.fetchData) {
this.props.fetchData();
}
}
Напротив, сравнение заключается в том, изменяется ли параметр выборки:
componentDidUpdate(prevProps) {
if (this.props.query !== prevProps.query) {
this.props.fetchData();
}
}
Однако такой код не является связным, и как только параметры выборки изменятся, это вызовет многочисленные кризисы обслуживания кода.
Напротив, использование функционального компонентаuseCallback
Инкапсулированная функция выборки может быть передана напрямую как зависимостьuseEffect
,useEffect
Просто позаботьтесь о том, изменится ли функция выборки, а изменение параметров выборки находится вuseCallback
заботиться и сотрудничатьeslintСканирование подключаемых модулей может гарантировать, что зависимости не будут потеряны, а логика будет связной, что упрощает ее обслуживание.
более сплоченный
В дополнение к логической связности функциональной зависимости, давайте посмотрим на весь процесс получения чисел:
Обычная выборка компонента класса должна учитывать следующие моменты:
- существует
didMount
Инициализируйте запрос. - существует
didUpdate
Оценивается, изменился ли параметр выборки, и вызывается функция выборки для повторной выборки значения, если оно изменилось. - существует
unmount
Добавьте флаг в жизненный цикл, вdidMount
didUpdate
Они совместимы, и выборка отменяется, когда компонент уничтожается.
Вы почувствуете, что код скачет, не только одновременно заботясь о функции выборки и параметрах выборки, но и поддерживая несколько наборов логики в разных жизненных циклах. Итак, что вы думаете о переходе на функциональный компонент?
Автор использует useCallback для преобразования исходного демо.
function Article({ id }) {
const [article, setArticle] = useState(null);
// 副作用,只关心依赖了取数函数
useEffect(() => {
// didCancel 赋值与变化的位置更内聚
let didCancel = false;
async function fetchData() {
const article = await API.fetchArticle(id);
if (!didCancel) {
setArticle(article);
}
}
fetchData();
return () => {
didCancel = true;
};
}, [fetchArticle]);
// ...
}
Когда вы действительно понимаете концепцию функционального компонента, вы можете понять предложение Дэна: хотяuseEffect
Изучение заранее обходится дороже, но как только вы все сделаете правильно, он будет обрабатывать пограничные случаи лучше, чем Class Component.
useEffect
Это только базовый API. В будущем бизнес будет подвергаться воздействию более инкапсулированных API верхнего уровня, таких какuseFetch
илиuseTheme
, они будут работать лучше.
3. Интенсивное чтение
оригинальныйТам 9000+ слов, очень долго. Но в то же время она также наглядно объясняет принцип выполнения Render с некоторыми GIF-анимациями.Если вы хотите хорошо использовать Function Component или Hooks, эта статья почти обязательна к прочтению, потому что никто не может догадаться, что такое Capture Value, но не могу понять эту концепцию. Функциональный компонент не может использоваться плавно.
Переосмыслите идею этой статьи:
- Из введения Render ведет к функции Capture Value.
- Расширение до функционального компонента Все может быть захвачено, кроме Ref.
- Представьте API useEffect с точки зрения Capture Value.
- Представляет тот факт, что функциональный компонент заботится только о состоянии рендеринга.
- Натолкнуло на размышления о том, как повысить производительность useEffect.
- Вводит основные принципы недопущения лжи о зависимостях.
- Как решить эти проблемы с помощью функционального компонента, думая о частном случае необходимости лгать.
- Когда вы научитесь мыслить с точки зрения функционального компонента, вы постепенно откроете для себя некоторые из его преимуществ.
- Наконец, указываются две характеристики логической связности и высокоуровневой инкапсуляции, чтобы вы могли одновременно реализовать мощь и элегантность хуков.
Можно увидеть, что более высокая сфера, чем написание фреймворка, - это открыть для себя красоту кода.Например, хуки изначально были созданы для расширения возможностей функционального компонента, но в процессе создания проблем и решения проблем вы можете постоянно видеть правила и ограничения, и изменить другой угол ломает его, и, наконец, осознает красоту общей логики.
Также прочитайте, как улучшить свое обучение из этой статьи. Автор говорит нам, что умение забывать приводит к лучшему пониманию. Давайте не будем применять фиксированное мышление о жизненном цикле к хукам, потому что это помешает нам понять концепцию хуков.
Также добавьте некоторые кусочки.
В чем преимущества использованияEffect
useEffect
Выполняется в конце рендеринга, поэтому он не блокирует процесс рендеринга в браузере, поэтому проекты, написанные с использованием функционального компонента, обычно имеют лучшую производительность.
Это, естественно, соответствует концепции React Fiber, потому что Fiber будет приостанавливать или обрезать очередь для выполнения рендеринга различных компонентов в зависимости от ситуации.Если код соответствует характеристикам Capture Value, безопасный доступ к значению будет гарантируется в среде Fiber, а ослабление жизненного цикла также может решить прерванное выполнение проблем, вызванных временем.
useEffect
Не будет выполняться во время рендеринга на стороне сервера.
Поскольку он выполняется после выполнения DOM, он гарантированно получит атрибуты DOM после того, как состояние вступит в силу.
4. Резюме
Наконец, упомяните два наиболее важных момента, чтобы проверить, читали ли вы эту статью:
- Свойство Capture Value.
- последовательность.Обратите внимание на зависимости (
useEffect
второй параметр[]
) вместо того, чтобы сосредоточиться на том, когда он срабатывает.
Каковы ваши более глубокие интерпретации «постоянства»? Добро пожаловать, чтобы оставить сообщение, чтобы ответить.
Адрес обсуждения:Интенсивное чтение Полное руководство по использованию эффекта · Выпуск №138 · dt-fe/weekly
Если вы хотите принять участие в обсуждении, пожалуйста,кликните сюда, с новыми темами каждую неделю, выходящими по выходным или понедельникам. Интерфейс интенсивного чтения — поможет вам отфильтровать надежный контент.
обрати внимание наАккаунт WeChat для интенсивного чтения в интерфейсе
special Sponsors
Заявление об авторских правах: Бесплатная перепечатка - некоммерческая - не производная - сохранить авторство (Лицензия Creative Commons 3.0)