Введение: С текущим ритмом обновлений по требованию, который становится все быстрее и быстрее, мы используем функциональный компонент вместо написания класса по многим причинам.После запуска хуков мы также можем полностью использовать функциональный компонент вместо написания класса, но, как говорится, хорошо , нет ничего идеального В процессе разбора и обобщения библиотеки хуков я ощутил улучшение опыта, приносимое хуками, а также есть недостатки в сравнении написания жизненного цикла.
Идея хуков React
Прежде всего, для исходных компонентов класса лучше всего инкапсулировать, мы используемconstructor
,componentDidMount
Все они являются методами, унаследованными от React. Преимущество этого перед хуками в том, что каждый из наших компонентов предсказуем для нас, поэтому мы также придерживаемся этой идеи, когда пишем каждый компонент. Очевидно, что неудобство, вызванное таким методом, что стоимость разработки каждого компонента слишком высока, а если компонент имеет логику, связанную с определенным жизненным циклом, нам неудобно его извлекать и использовать повторно.
Использование хуков полностью меняет приведенный выше шаблон — превращая набор хуков жизненного цикла в шаблон «включай и работай» от начала до конца, в некотором смысле наш компонентный дизайн становится более гибким.
Основной принцип
1. Попробуйте создать простые крючки
Первоначальная цель дизайна хуков состоит в том, чтобы сделать разработку быстрее и проще, поэтому при использовании хуков мы не должны скупиться на использование большего количества хуков, например, когда мы имеем дело с разными состояниями, соответствующими разной логике, согласно логике написания классов. , мы часто пишем несколько логик в функции жизненного цикла и различаем их с помощью if; при написании хуков, потому что нетshouldComponentUpdate
Для таких функций жизненного цикла мы должны разделить их и написать в разныхuseEffect
или использовать другойuseCallback
Упакованные зависимые переменные также должны быть максимально связаны с логикой, чтобы максимально избежать потери производительности и вывода ошибок.
2. Обратите внимание на логику внутри хуков В основном два принципа, упомянутые на официальном сайтереагировать JS.org/docs/hooks-…, очень важной концепцией, связанной с хуками, является порядок.Каждый раз, когда мы определяем функцию хука, реакция будет хранить их в «стеке» по порядку, подобноЕсли в это время мы что-то сделаем и поместим одну из функций-ловушек в оператор if, например, установим firstName только в первом рендеринге, то это вызовет такую ситуацию: первый рендеринг нормальный, но в При рендеринге во второй раз выполняется первая функция ловушки.
const [lastName, setLastName] = useState('yeung');
В это время react будет выполнять метод в стеке верхнего уровня, то есть наши последующие операции сдвигаются вперед на единицу.
инициализация
Обычно мы используемuseState
чтобы создать переменную с состоянием, эта функция ловушки возвращает переменную состояния иsetter
, когда мы звонимsetter
функция,render
Функция будет выполняться повторно, здесь общая проблема, использование несколькихstate
или объединены в одинstate
?
Проблема возникает из-за написанияuseSetState
думая когда делаешь, как писалось ранееclass
опыта, очевидно, что удобнее и управляемее писать все состояния вместе, но очевидно, что хуки неclass
, собственно, здесьsetter
Механизм действия такжеsetState
Разные,setState
заключается в объединении обновленных полей вthis.state
в и в крючкахsetter
Это прямая замена, поэтому, если мы поместим все переменные состояния в однуstate
, очевидно, вопреки первоначальному замыслу более удобного обслуживания.
Но это не такstate
Чем тоньше разделение, тем лучше.Взгляните на этот пример:
const [left, setLeft] = useState(0);
const [right, setRight] = useState(0);
el.addEventListener('mousemove', e => {
setLeft(e.offsetX);
setRight(e.offsetY);
})
Нам нужно изменить положение при работе мыши.В это время нет необходимости иметь дело с левым и правым отдельно, потому что вероятность изменения двух значений одновременно намного больше, чем вероятность изменения только одного значение, так что на данный момент мы можем их сложить. Напишите вместе:
const [position, setPosition] = useState({
left: 0,
right: 0,
})
Подводя итог, если два или более значения часто изменяются одновременно, то нам удобнее записывать их вместе, иначе они разделены.
операция по очистке
Задействованная здесь функция ловушкиuseEffect
, согласно введению официальной документации, useEffect можно рассматривать какcomponentDidMount
, componentDidUpdate
, and componentWillUnmount
Коллекция , DidMount и DidUpdate очень часто используются, здесь в основном говорят о том, какcomponentWillUnmount
Применение.
существуетuseClickOut
, мы добавили событие для документа.Очевидно, нам нужно выгрузить компонент, когда компонент выгружается.Метод здесь заключается в том, чтобыuseEffect
Функция выгрузки выполняется при возврате , Для использования этой части на официальном сайте есть полное введение:
React будет выполняться при размонтировании компонента и до повторного выполнения обратного вызова при изменении состояния зависимости.useEffect
Функция, возвращаемая обратным вызовом, почему? Поскольку эффекты будут выполняться более одного раза за повторный рендеринг, предыдущие эффекты, конечно же, также будут очищены. Здесь следует отметить, что и операция удаления, и операция обратного вызова выполняются после возврата компонента.
Уменьшить дублирование рендеринга
React.memo
Функция этого метода аналогична функции в классеshouldComponentUpdate
, разницаshouldComponentUpdate
То же будет сравнивать разницу в состоянии, ноReact.memo
Он только сравнивает пропы, и правила сравнения тоже очень просты, он сравнивает пропсы до и после, чтобы решить, стоит ли перерисовывать, но на самом деле есть большие скрытые опасности, и некоторые блоггеры не рекомендуют его использовать.React.memo
, но я думаю, что при соблюдении нескольких принципов,React.memo
Это действительно может в значительной степени сэкономить время рендеринга, особенно сейчас, когда оно используется.redux
, часто необходимо избегать обновлений других состояний, вызывающих обновление текущего компонента.
Во время оптимизации производительности необходимо детально рассчитать условия обновления компонентов.Как правило, добавляемые условия включают базовые типы, типы объектов должным образом сравниваются по глубине, а типы функций могут быть изменены в зависимости от ситуации, чтобы вся функция состоит всего из нескольких параметров. , если вы не можете быть уверены, то лучше использоватьPureComponent
илиReact.memo
.
useMemo
useMemo обычно используется для записи некоторых значений.Для начала давайте разберемся со сценариями использования useMemo:
1. Храните некоторые дорогостоящие переменные, чтобы избежать пересчета каждый раз при рендеринге;
2. Специально запишите некоторые значения, которые вы не хотите менять;
Что касается 2, вы можете использовать его напрямую.Что касается 1, мы должны решить, использовать ли его в зависимости от ситуации.Взгляните на следующую сцену:
const value = useMemo(() => {
return massiveCompute(deps);
}, [deps]);
использовать или нетuseMemo
в зависимости от:
1, MassComoute действительно большая операция, чтобы повлиять на производительность;
2. Тип данных deps, если это объект или массив, использовать useMemo бессмысленно, да и добавление сравнения скажется на производительности;
Сравнение UseEffect и ComponentDidMount
В официальной документации упоминается, что useEffect может реализовывать моки различных жизненных циклов, но на самом деле хуки механически отличаются от различных функций жизненного цикла.Если вообще отождествлять с жизненным циклом, то в последующих могут быть отклонения понимание. Конкретные различия см.useEffect is not the new ComponentDidMount, далее кратко поясняются проблемы, возникающие в процессе разработки.
Продолжительность
Первый дляcomponentDidMount
, когда мы впервые вошли, если бы мы были вcomponentDidMount
Выполнение государственной операции вcomponentDidMount
до и после, после чего браузер будет отображать только последний разrender
Рендеринг, чтобы избежать заставки, т.е.componentDidMount
на самом деле выполняется до того, как браузер отрисует; но дляuseEffect
, хотя это также вызовет второй рендеринг, но второй рендеринг выполняется снова после отрисовки браузером, и этот эффект также вызовет заставку. УведомлениеuseEffect
выполняется один раз после возврата каждого компонента.
Различия в захвате времени состояния
думатьcomponentDidMount
сценарий примененияcomponentDidMount
Асинхронная операция выполняется в асинхронной операции.После разрешения асинхронной операции, если мы напечатаем состояние в это время, какой результат мы получим? Конкретный код можно посмотретьlongResolve with ComponentDidMount.
В приведенном выше примере, если мы изменим значение состояния во время асинхронной операции и, наконец, когда асинхронная операция будет завершена и соответствующее состояние будет напечатано, результат, который мы получим, фактически будет последним результатом после изменения.
Тот же пример, если вы используетеuseEffect
заменятьComponentDidMount
Как это будет? См. longResolve с использованием useEffect.
Мы можем обнаружить, что независимо от того, как мы меняем значение состояния во время асинхронной операции, последняя печать является исходным значением или значением состояния при определении асинхронной операции.
Почему это происходит?
Если мы изменим код в примере с хуками и добавим счетчик в зависимости от useEffect, мы сможем лучше понять причину.
useEffect(() => {
longResolve().then(() => {
console.log(count);
});
}, [count]);
В это время мы нажимаем n раз, и функция здесь также будет выполняться n раз, поэтому мы можем понять механизм useEffect, так как при изменении значения в deps мы useEffect поместим callback-функцию в очередь выполнения, Итак, значение, используемое в функции, также, очевидно, является значением во время сохранения.
setInterval
При написании useInterval мы столкнулись с такой проблемой, если обрабатывать как в классе, то что мы делаем, так это пишем логику interval прямо в useEffect:
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1)
}, 1000);
return () => clearInterval(id)
}, [])
Результатом этого является то, что count сначала изменяется от 0 -> 1, а затем остается прежним. Причина та же, что и выше. Решение состоит в том, чтобы добавить соответствующую зависимую переменную -> count к deps, что может привести к тому, что мы беспокойтесь о том, чтобы вызвать бесконечный цикл, потому что мы одновременно меняем зависимые переменные, но учитываяsetInterval
Изначально это операция бесконечного цикла, так что здесь нет проблем.В то же время мы должны понимать, что пока мы находимся вuseEffect
Если в deps используется переменная, необходимо добавить ее в deps.Если код имеет бесконечный цикл, то мы должны рассмотреть, нет ли проблемы с нашей внутренней логикой.
Стоит отметить, что есть еще один способ написания функции setter, нам не нужно добавлять переменные в deps.
useEffect(() => {
const id = setInterval(() => {
// When we pass a function, React calls that function with the current
// state and whatever we return becomes the new state.
setCount(count => count + 1)
}, 1000)
return () => clearInterval(id)
}, [])
Разбор принципа хуков React по умолчанию
useMemo
Сначала позвольте мне объяснитьuseMemo
роль,useMemo
Его можно использовать для сохранения сохраненного значения, которое будет пересчитываться только при изменении deps, и часто используется при сохранении некоторых вычислительно затратных значений; давайте посмотрим, как React реализует эту функцию.
function useMemo<T>(nextCreate: () => T, deps: Array<mixed> | void | null): T {
currentlyRenderingComponent = resolveCurrentlyRenderingComponent();
workInProgressHook = createWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
if (workInProgressHook !== null) {
const prevState = workInProgressHook.memoizedState;
if (prevState !== null) {
if (nextDeps !== null) {
const prevDeps = prevState[1];
if (areHookInputsEqual(nextDeps, prevDeps)) {
return prevState[0];
}
}
}
}
if (__DEV__) {
isInHookUserCodeInDev = true;
}
const nextValue = nextCreate();
if (__DEV__) {
isInHookUserCodeInDev = false;
}
workInProgressHook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
Сначала поймите значение некоторых глобальных переменных здесь, workInProgressHook указывает, являются ли текущие запущенные хуки хуками повторного рендеринга, первое суждение здесь означает, что если это не первый рендеринг, то useMemo получит две зависимости. Чтобы сделать сравнение, если равно, он сразу вернется к состоянию в кеше, если это первый рендеринг, или если два депа не хотят ждать, то useMemo повторно выполнит коллбэк и присвоит значение соответствующему кешу.
useReducer & useState
useReducer
а такжеuseState
По существу принцип, хотя мы обычно используемuseState
больше, но по фактуuseState
даuseReducer
пакет; внизу справаuseReducer
Разберем принцип реализации;useReducer
Можно разделить на первоначальный рендеринг иre-render
Во-вторых, сначала посмотрите на первоначальный рендеринг:
if (__DEV__) {
isInHookUserCodeInDev = true;
}
let initialState;
if (reducer === basicStateReducer) {
// Special case for `useState`.
initialState =
typeof initialArg === 'function'
? ((initialArg: any): () => S)()
: ((initialArg: any): S);
} else {
initialState =
init !== undefined ? init(initialArg) : ((initialArg: any): S);
}
if (__DEV__) {
isInHookUserCodeInDev = false;
}
workInProgressHook.memoizedState = initialState;
const queue: UpdateQueue<A> = (workInProgressHook.queue = {
last: null,
dispatch: null,
});
const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
null,
currentlyRenderingComponent,
queue,
): any));
return [workInProgressHook.memoizedState, dispatch];
Из наиболее знакомого возвращаемого значения мы все знаем, чтоuseState(useReducer)
Возвращает массив, индексы 0 и 1 соответственноstate
а такжеdispatch
, сначала посмотрите на состояние, где состояние прямо равно тому, что мы передали при первом рендеринге.useReducer
параметров (useReducer
можно пройти еще одинinit
функция для получения начального состояния в качестве параметра и возврата соответствующего состояния); здесь основное внимание уделяется обработке отправки, здесьdispatchAction
метод, функция этого метода состоит в том, чтобы сохранить метод обновления на карте с очередью в качестве ключа.dispatchAction
Исходный код и конкретная функция:
function dispatchAction<A>(
componentIdentity: Object,
queue: UpdateQueue<A>,
action: A,
) {
if (componentIdentity === currentlyRenderingComponent) {
didScheduleRenderPhaseUpdate = true;
const update: Update<A> = {
action,
next: null,
};
if (renderPhaseUpdates === null) {
renderPhaseUpdates = new Map();
}
const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
if (firstRenderPhaseUpdate === undefined) {
renderPhaseUpdates.set(queue, update);
} else {
// 这里的处理是当我们连续调用 dispatch 的时候,我们将 update 追加到已有的队列后面,而不是另起一个
// 队列,这里在下次执行的时候可以将同步执行的 dispatch 合并到一个队列中,到时候也可以统一更新
let lastRenderPhaseUpdate = firstRenderPhaseUpdate;
while (lastRenderPhaseUpdate.next !== null) {
lastRenderPhaseUpdate = lastRenderPhaseUpdate.next;
}
lastRenderPhaseUpdate.next = update;
}
} else {
}
}
dispatchAction
это обновление на этапе рендеринга, скрывающее его в лениво созданной очереди -> список обновлений (renderPhaseUpdates
). После завершения этого рендера мы перезапускаем и применяем скрытое обновление к незавершенному рабочему хуку (work-in-process
)начальство.dispatchAction
Получены три параметра, которыеcomponentIdentity
,queue
,action
Здесь bind используется для привязки, поэтому параметр действия — это параметр, передаваемый при вызове диспетчеризации. Пока что однаждыuseState
Инициализация завершена, фактически мы можем обнаружить, что когда мы вызываем диспетчеризацию, конкретная операция на самом деле не является модификацией.state
, но добавляет соответствующее действие (или измененное значение) в очередь, когда повторный рендеринг рассчитывается какuseState
, затем выполнить соответствующее обновление из этой глобальной очереди; давайте рассмотрим ситуацию, когда рендеринг повторяется, и покажем, что при повторении рендерингаuseReducer
Логика в:
// This is a re-render. Apply the new render phase updates to the previous
// current hook.
const queue: UpdateQueue<A> = (workInProgressHook.queue: any);
const dispatch: Dispatch<A> = (queue.dispatch: any);
if (renderPhaseUpdates !== null) {
// Render phase updates are stored in a map of queue -> linked list
const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
if (firstRenderPhaseUpdate !== undefined) {
renderPhaseUpdates.delete(queue);
let newState = workInProgressHook.memoizedState;
let update = firstRenderPhaseUpdate;
do {
// Process this render phase update. We don't have to check the
// priority because it will always be the same as the current
// render's.
const action = update.action;
if (__DEV__) {
isInHookUserCodeInDev = true;
}
newState = reducer(newState, action);
if (__DEV__) {
isInHookUserCodeInDev = false;
}
update = update.next;
} while (update !== null);
workInProgressHook.memoizedState = newState;
return [newState, dispatch];
}
}
return [workInProgressHook.memoizedState, dispatch];
Во-первых, еслиrenderPhaseUpdates
имеет значение null, что указывает на то, что это обновление не обновлялось ранееdispatch
вызов, затем возврат непосредственно по исходному значению; еслиrenderPhaseUpdates
Не нуль, что указывает на то, что ранееdispatch
call, но это обновление глобальное, поэтому на самом деле хуки не знают, что запускает обновление.queue
хранится доrenderPhaseUpdates
Возьмите соответствующий метод обновления, если вы его получите, значит, это обновление было вызвано ранееdispatch
, в это время операция обновления являетсяdo-while
цикл, логика здесь соответствуетdispatchAction
Логика создания очереди - объединит несколько апдейтеров в одну очередь, поэтому здесь цикл do-while выполняет все апдейтеры одновременно, обратите внимание на комментарии и логику здесь, то есть если мы находимся в серии диспетчеров напрямую Для изменение значения состояния, модификация здесь фактически сохраняет только последнюю модификацию, но если функция обратного вызова передается, напримерsetState((state) => state + 1)
Затем вы можете получить последнее значение состояния, потому чтоnewState
Он меняется каждый раз.