Хуки React вводят useState для установки состояния компонента. Но как я могу использовать хуки для замены обратного вызова, как показано ниже:
setState(
{ name: "Michael" },
() => console.info(this.state)
);
Я хочу что-то сделать после обновления состояния.
Я знаю, что могу использовать useEffect для выполнения дополнительных действий, но мне нужно проверить предыдущее значение состояния, для чего требуется битовый код. Я ищу простое решение, которое можно использовать с крючком useState.
Есть простой способ сделать это без useEffect stackoverflow.com/a/70405577/5823517





Для этого вам нужно использовать крючок useEffect.
const [counter, setCounter] = useState(0);
const doSomething = () => {
setCounter(123);
}
useEffect(() => {
console.info('Do something after counter has changed', counter);
}, [counter]);
Это вызовет console.info при первом рендеринге, а также при каждом изменении counter. Что, если вы хотите сделать что-то только после того, как состояние было обновлено, но не при начальном рендеринге, поскольку установлено начальное значение? Я думаю, вы могли бы проверить значение в useEffect и решить, хотите ли вы что-то сделать. Будет ли это считаться лучшей практикой?
Чтобы избежать запуска useEffect при начальном рендеринге, вы можете создать нестандартный useEffect крючок, который не запускается при начальном рендеринге. Чтобы создать такой хук, вы можете проверить этот вопрос: stackoverflow.com/questions/53253940/…
А как насчет случая, когда я хочу вызывать разные обратные вызовы в разных вызовах setState, которые изменят одно и то же значение состояния? Ваш ответ неверен, и его не следует помечать как правильный, чтобы не сбивать с толку новичков. Правда в том, что setState вызывает одну из самых сложных проблем при миграции на хуки из классов, у которых нет одного четкого метода решения. Иногда вам действительно будет достаточно некоторого эффекта, зависящего от значения, а иногда потребуются некоторые хакерские методы, такие как сохранение каких-то флагов в ссылках.
Судя по всему, сегодня я мог бы вызвать setCounter(value, callback), чтобы какая-то задача выполнялась после обновления состояния, избегая useEffect. Я не совсем уверен, является ли это предпочтением или особым преимуществом.
@KenIngram Я только что попробовал и получил очень конкретную ошибку: Warning: State updates from the useState() and useReducer() Hooks don't support the second callback argument. To execute a side effect after rendering, declare it in the component body with useEffect().
Ах. Хорошо. Это казалось простым.
useEffect нельзя использовать во всех сценариях. иногда состояние обновляется из нескольких мест, но вы хотите использовать вызов только из одного из этих мест. Как легко отличить? Обратный вызов идеален в таких ситуациях. Взломом будет использование другого уродливого useState только для того, чтобы можно было обнаружить конкретное изменение. очень страшный...
Согласитесь с @vsync , второй обратный вызов должен быть повторно введен обратно в useState хук. Это похоже на шаг назад от this.setState от компонента класса. :/
Это не лучшая альтернатива тому, что может быть достигнуто с помощью обратного вызова, нам нужен только обратный вызов для определенного вызова setState, а не для каждого изменения состояния, и нет никакого способа определить это с помощью this.
Если вы хотите обновить предыдущее состояние, вы можете сделать это в хуках:
const [count, setCount] = useState(0);
setCount(previousCount => previousCount + 1);
@BimalGrg Это действительно работает? Я не могу повторить это. Не компилируется с этой ошибкой: expected as assignment or function call and instead saw an expression
@tonitone120 tonitone120 внутри setCount есть функция стрелки.
@BimalGrg Вопрос заключается в возможности выполнения кода сразу после обновления состояния после. Действительно ли этот код помогает с этой задачей?
Да, это будет работать, как в this.setState в компонентах класса,
@tonitone120 tonitone120, если вы хотите выполнить код сразу после обновления состояния, вам нужно использовать хук useEffect. В useEffect вы можете проверить, обновлено ли состояние или нет, и выполнить какое-либо действие соответственно.
Почему я нигде не могу найти документацию по этому шаблону?
UseEffect является основным решением. Но, как упомянул Дэррил, использование useEffect и передача состояния в качестве второго параметра имеет один недостаток: компонент будет работать в процессе инициализации. Если вы просто хотите, чтобы функция обратного вызова выполнялась с использованием обновленного значения состояния, вы можете установить локальную константу и использовать ее как в setState, так и в обратном вызове.
const [counter, setCounter] = useState(0);
const doSomething = () => {
const updatedNumber = 123;
setCounter(updatedNumber);
// now you can "do something" with updatedNumber and don't have to worry about the async nature of setState!
console.info(updatedNumber);
}
setCounter является асинхронным, вы не знаете, будет ли вызываться console.info после setCounter или раньше.
Ваш вопрос очень актуален. Позвольте мне сказать вам, что useEffect запускается один раз по умолчанию и после каждого изменения массива зависимостей.
проверьте пример ниже::
import React,{ useEffect, useState } from "react";
const App = () => {
const [age, setAge] = useState(0);
const [ageFlag, setAgeFlag] = useState(false);
const updateAge = ()=>{
setAgeFlag(false);
setAge(age+1);
setAgeFlag(true);
};
useEffect(() => {
if (!ageFlag){
console.info('effect called without change - by default');
}
else{
console.info('effect called with change ');
}
}, [ageFlag,age]);
return (
<form>
<h2>hooks demo effect.....</h2>
{age}
<button onClick = {updateAge}>Text</button>
</form>
);
}
export default App;
Если вы хотите, чтобы обратный вызов setState выполнялся с хуками, используйте переменную флага и задайте блок IF ELSE OR IF внутри useEffect, чтобы при выполнении этих условий выполнялся только этот блок кода. В любом случае эффект запускается при изменении массива зависимостей, но этот внутренний код IF будет выполняться только при этих конкретных условиях.
Это не сработает. Вы не знаете, в каком порядке будут работать три оператора внутри updateAge. Все три асинхронны. Единственное, что гарантируется, это то, что первая строка выполняется до 3-й (поскольку они работают в одном и том же состоянии). Вы ничего не знаете о 2-й линии. Этот пример слишком прост, чтобы увидеть это.
Мой друг Мохит. Я применил эту технику в большом сложном проекте реагирования, когда переходил от классов реагирования к хукам, и он отлично работает. Просто попробуйте ту же логику где угодно в хуках для замены обратного вызова setState, и вы все поймете.
«работает в моем проекте — это не объяснение», читайте документы. Они вообще не синхронны. Нельзя сказать наверняка, что три строки в updateAge будут работать в таком порядке. Если это было синхронизировано, то зачем нужен флаг, напрямую вызывайте строку console.info() после setAge.
useRef — гораздо лучшее решение для «ageFlag».
Имитация обратного вызова setState с помощью useEffect, срабатывает только в состоянии обновления (не начальное состояние):
const [state, setState] = useState({ name: "Michael" })
const isFirstRender = useRef(true)
useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false // toggle flag after first render/mounting
return;
}
console.info(state) // do something after state has updated
}, [state])
useEffectUpdatefunction useEffectUpdate(callback) {
const isFirstRender = useRef(true);
useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false; // toggle flag after first render/mounting
return;
}
callback(); // performing action after state has updated
}, [callback]);
}
// client usage, given some state dep
const cb = useCallback(() => { console.info(state) }, [state]); // memoize callback
useEffectUpdate(cb);
Альтернатива: useStateможет быть реализован для получения обратного вызова типа setState в классах.
useEffect не является интуитивным способом.Я создал обертку для этого. В этом пользовательском хуке вы можете передать свой обратный вызов параметру setState вместо параметра useState.
Я только что создал версию Typescript. Поэтому, если вам нужно использовать это в Javascript, просто удалите некоторые обозначения типов из кода.
const [state, setState] = useStateCallback(1);
setState(2, (n) => {
console.info(n) // 2
});
import { SetStateAction, useCallback, useEffect, useRef, useState } from 'react';
type Callback<T> = (value?: T) => void;
type DispatchWithCallback<T> = (value: T, callback?: Callback<T>) => void;
function useStateCallback<T>(initialState: T | (() => T)): [T, DispatchWithCallback<SetStateAction<T>>] {
const [state, _setState] = useState(initialState);
const callbackRef = useRef<Callback<T>>();
const isFirstCallbackCall = useRef<boolean>(true);
const setState = useCallback((setStateAction: SetStateAction<T>, callback?: Callback<T>): void => {
callbackRef.current = callback;
_setState(setStateAction);
}, []);
useEffect(() => {
if (isFirstCallbackCall.current) {
isFirstCallbackCall.current = false;
return;
}
callbackRef.current?.(state);
}, [state]);
return [state, setState];
}
export default useStateCallback;
Если переданная стрелочная функция ссылается на переменную внешнюю функцию, то она будет захватывать текущее значение, а не значение после обновления состояния. В приведенном выше примере использования console.info(состояние) напечатает 1, а не 2.
Спасибо! классный подход. Создан образец кодыпесочница
@ValYouW Спасибо за создание образца. Дело только в том, что если переданная стрелочная функция ссылается на переменную внешнюю функцию, то она будет захватывать текущие значения, а не значения после обновления состояния.
Да, это броская часть с функциональными компонентами...
Как здесь получить предыдущее значение состояния?
@ Ankan-Zerob Я думаю, что в части «Использование» state ссылается на предыдущий, когда вызывается обратный вызов. Не так ли?
@MJStudio Привет, MJ, спасибо за ваш ответ, в основном он правильный, но я не могу указать конкретный ключ в вашем примере. Просто попробуйте setState({name: 'Bob'}, (n) => {console.info(n.name)}). Если быть более точным, я получу здесь ошибку n.name, как это можно исправить?
@Andrew Это ошибка TypeScript? В моем случае const [s, setS] = useStateCallback({ name: 'mj' }); useEffect(() => { setS({ name: 'Bob' }, (n) => { console.info(n.name); }); }, [setS]); хорошо работает как печать Bob не mj но TypeScript вызывает ошибку, которая может быть n undefined
const [state, setState] = useStateCallback(1);setState(2, (n) => {console.info(n, state) });почему в приведенном выше примере: n равно 2, но состояние по-прежнему равно 1, и в любом случае это состояние также может печатать 2?
setState() ставит в очередь изменения состояния компонента и сообщает React, что этот компонент и его дочерние элементы необходимо повторно отобразить с обновленным состоянием.
Метод setState является асинхронным и фактически не возвращает промис. Итак, в случаях, когда мы хотим обновить или вызвать функцию, функция может быть вызвана обратным вызовом в функции setState в качестве второго аргумента. Например, в приведенном выше случае вы вызвали функцию как обратный вызов setState.
setState(
{ name: "Michael" },
() => console.info(this.state)
);
Приведенный выше код отлично работает для компонента класса, но в случае функционального компонента мы не можем использовать метод setState, и мы можем использовать хук эффекта использования для достижения того же результата.
Очевидный метод, который приходит на ум, заключается в том, что ypu может использовать с useEffect, как показано ниже:
const [state, setState] = useState({ name: "Michael" })
useEffect(() => {
console.info(state) // do something after state has updated
}, [state])
Но это будет срабатывать и при первом рендеринге, поэтому мы можем изменить код следующим образом, где мы можем проверить первое событие рендеринга и избежать рендеринга состояния. Поэтому реализация может быть выполнена следующим образом:
Здесь мы можем использовать пользовательский хук, чтобы идентифицировать первый рендер.
Хук useRef позволяет нам создавать изменяемые переменные в функциональных компонентах. Это полезно для доступа к узлам DOM/элементам React и для хранения изменяемых переменных без запуска повторного рендеринга.
const [state, setState] = useState({ name: "Michael" });
const firstTimeRender = useRef(true);
useEffect(() => {
if (!firstTimeRender.current) {
console.info(state);
}
}, [state])
useEffect(() => {
firstTimeRender.current = false
}, [])
это мне очень помогает, спасибо. Я хотел бы добавить кое-что, потому что поначалу это давало мне сбои, порядок функций useEffect очень важен. обратите внимание, что вы должны сначала написать useEffect с зависимостями, а затем «componentDidMount» без зависимостей. то есть как в примере. Спасибо еще раз.
У меня был вариант использования, когда я хотел создать вызов API с некоторыми параметрами после установки состояния. Я не хотел устанавливать эти параметры в качестве своего состояния, поэтому я сделал собственный хук, и вот мое решение.
import { useState, useCallback, useRef, useEffect } from 'react';
import _isFunction from 'lodash/isFunction';
import _noop from 'lodash/noop';
export const useStateWithCallback = initialState => {
const [state, setState] = useState(initialState);
const callbackRef = useRef(_noop);
const handleStateChange = useCallback((updatedState, callback) => {
setState(updatedState);
if (_isFunction(callback)) callbackRef.current = callback;
}, []);
useEffect(() => {
callbackRef.current();
callbackRef.current = _noop; // to clear the callback after it is executed
}, [state]);
return [state, handleStateChange];
};
разве он не выдает ошибку, когда не предоставляется обратный вызов?
Я столкнулся с той же проблемой, использование useEffect в моей настройке не помогло (я обновляю родительское состояние из массива нескольких дочерних компонентов, и мне нужно знать, какой компонент обновил данные).
Обертывание setState в промис позволяет запускать произвольное действие после завершения:
import React, {useState} from 'react'
function App() {
const [count, setCount] = useState(0)
function handleClick(){
Promise.resolve()
.then(() => { setCount(count => count+1)})
.then(() => console.info(count))
}
return (
<button onClick= {handleClick}> Increase counter </button>
)
}
export default App;
Следующий вопрос поставил меня в правильном направлении: Функционирует ли пакетное обновление React при использовании хуков?
setCount асинхронный, верно? Если это так, возникнет состояние гонки, и console.info может напечатать старое значение.
Как насчет этого:
const [Name, setName] = useState("");
...
onClick = {()=>{
setName("Michael")
setName(prevName=>{...}) //prevName is Michael?
}}
prevName не будет "Майкл", поскольку useState является асинхронным, как и setState в компонентах класса. Вы не можете обновить состояние в одной строке и предположить, что оно уже изменилось в следующей. Скорее всего, вы будете использовать неизменное состояние.
Хорошо, спасибо, я только что проверил этот код, вы правы,
Это странно, prevName — это Michael, но если вы вызываете другую функцию в обратном вызове, которая использует имя, оно все равно не обновляется.
вы можете использовать следующие способы, которые я знал, чтобы получить последнее состояние после обновления:
const [state, setState] = useState({name: "Michael"});
const handleChangeEventName = () => {
setState({name: "Jack"});
}
useEffect(() => {
console.info(state.name); //"Jack"
//do something here
}, [state]);
const [state, setState] = useState({name: "Michael"});
const handleChangeEventName = () => {
setState({name: "Jack"})
setState(prevState => {
console.info(prevState.name);//"Jack"
//do something here
// return updated state
return prevState;
});
}
const [state, setState] = useState({name: "Michael"});
const stateRef = useRef(state);
stateRef.current = state;
const handleClick = () => {
setState({name: "Jack"});
setTimeout(() => {
//it refers to old state object
console.info(state.name);// "Michael";
//out of syntheticEvent and after batch update
console.info(stateRef.current.name);//"Jack"
//do something here
}, 0);
}
В обработчике react syntheticEvent setState — это процесс пакетного обновления, поэтому каждое изменение состояния будет ожидаться и возвращать новое состояние.
«setState() не всегда сразу обновляет компонент. Он может пакетно обновлять или откладывать обновление на потом»,
https://reactjs.org/docs/react-component.html#setstate
Вот полезная ссылка
Сохраняет ли React порядок обновления состояния?
Мы можем написать хук с именем useScheduleNextRenderCallback, который возвращает функцию «расписание». После вызова setState мы можем вызвать функцию «расписание», передав обратный вызов, который мы хотим запустить при следующем рендеринге.
import { useCallback, useEffect, useRef } from "react";
type ScheduledCallback = () => void;
export const useScheduleNextRenderCallback = () => {
const ref = useRef<ScheduledCallback>();
useEffect(() => {
if (ref.current !== undefined) {
ref.current();
ref.current = undefined;
}
});
const schedule = useCallback((fn: ScheduledCallback) => {
ref.current = fn;
}, []);
return schedule;
};
Пример использования:
const App = () => {
const scheduleNextRenderCallback = useScheduleNextRenderCallback();
const [state, setState] = useState(0);
const onClick = useCallback(() => {
setState(state => state + 1);
scheduleNextRenderCallback(() => {
console.info("next render");
});
}, []);
return <button onClick = {onClick}>click me to update state</button>;
};
Сокращенный тестовый пример: https://stackblitz.com/edit/react-ts-rjd9jk
Я написал кастомный хук с машинописным текстом, если он еще кому-то нужен.
import React, { useEffect, useRef, useState } from "react";
export const useStateWithCallback = <T>(initialState: T): [state: T, setState: (updatedState: React.SetStateAction<T>, callback?: (updatedState: T) => void) => void] => {
const [state, setState] = useState<T>(initialState);
const callbackRef = useRef<(updated: T) => void>();
const handleSetState = (updatedState: React.SetStateAction<T>, callback?: (updatedState: T) => void) => {
callbackRef.current = callback;
setState(updatedState);
};
useEffect(() => {
if (typeof callbackRef.current === "function") {
callbackRef.current(state);
callbackRef.current = undefined;
}
}, [state]);
return [state, handleSetState];
}
ИМХО правильный подход. В других примерах не учитывается, что у вас могут быть разные обратные вызовы в вашем коде в зависимости от того, где вы вызываете setState — иногда вам может понадобиться один обратный вызов, иногда другой, иногда ни одного.
С помощью всех вас я смог создать этот пользовательский хук:
Очень похоже на класс this.setState(state, callback)
const useStateWithCallback = (initialState) => {
const [state, setState] = useState(initialState);
const callbackRef = useRef(() => undefined);
const setStateCB = (newState, callback) => {
callbackRef.current = callback;
setState(newState);
};
useEffect(() => {
callbackRef.current?.();
}, [state]);
return [state, setStateCB];
};
Таким образом, мы можем использовать его как..
const [isVisible, setIsVisible] = useStateWithCallback(false);
...
setIsVisible(true, () => console.info('callback called now!! =)');
Сохраняйте спокойствие и счастливого кодирования!
Это не работает, если вам нужно использовать новое значение состояния в обратном вызове. Например, если вы измените свой обратный вызов на () => console.info('the new value of isVisible = ' + isVisible), он отобразит старое значение.
Вы уверены? Потому что обратный вызов вызывается только тогда, когда состояние действительно изменилось.
Да по крайней мере в моем сценарии. Я должен упомянуть, что я использую React в рамках Ionic, поэтому, возможно, из-за этого поведение немного отличается.
Имеет ли приведенный выше ответ @Geoman такой же эффект в вашей ионной среде? Должен отображать тот же эффект, что и этот ответ здесь. Почти то же самое, если вы удалите машинописную часть и другие незначительные отличия.
Между прочим: это было протестировано как в React Native, так и в стандартном React.js.
Я не думаю, что различать смонтированное или нет с помощью useRef - это хороший способ, не лучше ли определить значение, сгенерированное useState() в useEffect(), является ли оно начальным значением?
const [val, setVal] = useState(null)
useEffect(() => {
if (val === null) return
console.info('not mounted, val updated', val)
}, [val])
Пока у нас нет встроенной поддержки обратного вызова setState, мы можем сделать простой способ javascript... вызвать функцию и напрямую передать ей новые переменные.
const [counter, setCounter] = useState(0);
const doSomething = () => {
const newCounter = 123
setCounter(newCounter);
doSomethingWCounter(newCounter);
};
function doSomethingWCounter(newCounter) {
console.info(newCounter); // 123
}
Не гарантируется, что doSomethingWCounter будет вызван после завершения setState.
Простое решение, просто установите
npm i use-state-with-callback
import React from 'react';
import { useStateWithCallbackLazy } from "use-state-with-callback";
const initialFilters = {
smart_filter: "",
};
const MyCallBackComp = () => {
const [filters, setFilters] = useStateWithCallbackLazy(initialFilters);
const filterSearchHandle = (e) => {
setFilters(
{
...filters,
smart_filter: e,
},
(value) => console.info("smartFilters:>", value)
);
};
return (
<Input
type = "text"
onChange = {(e) => filterSearchHandle(e.target.value)}
name = "filter"
placeholder = "Search any thing..."
/>
);
};
зачислено: РЕАКТИВНЫЙ ОБРАТНЫЙ ВЫЗОВ USESTATE
Если вам не нужно обновлять состояние асинхронно, вы можете использовать ссылку для сохранения значения вместо useState.
const name = useRef("John");
name.current = "Michael";
console.info(name.current); // will print "Michael" since updating the ref is not async
useState и useCallback:import React, { useCallback, useState } from 'react';
const Test = () => {
const [name, setName] = useState("");
const testCallback = useCallback(() => console.info(name), [name]);
return (
<button onClick = {() => {
setName("Michael")
testCallback();
}}>Name</button>
)
};
export default Test;
Я изучил npm-библиотеку use-state-with-callback и другие подобные пользовательские хуки, но в конце концов понял, что могу просто сделать что-то вроде этого:
const [user, setUser] = React.useState(
{firstName: 'joe', lastName: 'schmo'}
)
const handleFirstNameChange=(val)=> {
const updatedUser = {
...user,
firstName: val
}
setUser(updatedUser)
updateDatabase(updatedUser)
}
в компоненте класса я использовал async и await для достижения того же результата, что и вы, чтобы добавить обратный вызов в setState. К сожалению, он не работает в хуке. Даже если я добавлю асинхронность и ожидание, реакция не будет ждать обновления состояния. Возможно, useEffect — единственный способ сделать это.