Я использую функциональный компонент с хуками. Мне нужно обновить состояние родителя от ребенка. Я использую функцию поддержки в Parent. Все работает нормально, за исключением того, что моя функция поддержки получает предыдущее состояние, а не текущее состояние. Моя функция поддержки выполняется до того, как хук использование состояния устанавливает текущее состояние. Как я могу дождаться выполнения моей функции обратного вызова после вызова useState. Я ищу что-то вроде setState (состояние, обратный вызов) из компонентов на основе классов.
Вот фрагмент кода:
function Parent() {
const [Name, setName] = useState("");
getChildChange = getChildChange.bind(this);
function getChildChange(value) {
setName(value);
}
return <div> {Name} :
<Child getChildChange = {getChildChange} ></Child>
</div>
}
function Child(props) {
const [Name, setName] = useState("");
handleChangeEvent = handleChangeEvent.bind(this);
function handleChangeEvent(ele) {
setName(ele.target.value);
props.getChildChange(collectState());
}
function collectState() {
return Name;
}
return (<div>
<input onChange = {handleChangeEvent} value = {Name}></input>
</div>);
}
Я надеюсь, что мы получим интересные комментарии в этой теме github.com/facebook/реагировать/вопросы/17969
Есть простой способ сделать это без useEffect stackoverflow.com/a/70405577/5823517





function Parent() {
const [Name, setName] = useState("");
getChildChange = getChildChange.bind(this);
function getChildChange(value) {
setName(value);
}
return <div> {Name} :
<Child getChildChange = {getChildChange} ></Child>
</div>
}
function Child(props) {
const [Name, setName] = useState("");
handleChangeEvent = handleChangeEvent.bind(this);
collectState = collectState.bind(this);
function handleChangeEvent(ele) {
setName(ele.target.value);
}
function collectState() {
return Name;
}
useEffect(() => {
props.getChildChange(collectState());
});
return (<div>
<input onChange = {handleChangeEvent} value = {Name}></input>
</div>);
} useEffect действовать как componentDidMount, componentDidUpdate, поэтому после обновления состояния он будет работать
На самом деле вам следует избегать использования this при использовании реагирующих хуков. Он вызывает побочные эффекты. Вот почему команда реагирования создает react hooks.
Если вы удалите коды, которые пытаются связать this, вы можете просто передать setName из Parent в Child и вызвать его handleChangeEvent. Более чистый код!
function Parent() {
const [Name, setName] = useState("");
return <div> {Name} :
<Child setName = {setName} ></Child>
</div>
}
function Child(props) {
const [Name, setName] = useState("");
function handleChangeEvent(ele) {
setName(ele.target.value);
props.setName(ele.target.value);
}
return (<div>
<input onChange = {handleChangeEvent} value = {Name}></input>
</div>);
}
Более того, вам не нужно создавать две копии Name (одну в Parent, а другую в Child). Придерживайтесь принципа «Единого источника правды», Child не должен владеть государством Name, но должен получать его от Parent. Узел чище!
function Parent() {
const [Name, setName] = useState("");
return <div> {Name} :
<Child setName = {setName} Name = {Name}></Child>
</div>
}
function Child(props) {
function handleChangeEvent(ele) {
props.setName(ele.target.value);
}
return (<div>
<input onChange = {handleChangeEvent} value = {props.Name}></input>
</div>);
}
Для этого вы можете использовать useEffect/useLayoutEffect:
const SomeComponent = () => {
const [count, setCount] = React.useState(0)
React.useEffect(() => {
if (count > 1) {
document.title = 'Threshold of over 1 reached.';
} else {
document.title = 'No threshold reached.';
}
}, [count]);
return (
<div>
<p>{count}</p>
<button type = "button" onClick = {() => setCount(count + 1)}>
Increase
</button>
</div>
);
};
Подробнее об этом в здесь.
Если вы ищете готовое решение, обратите внимание на этот пользовательский крючок, который работает как useState, но принимает в качестве второго параметра функцию обратного вызова:
// npm install use-state-with-callback
import useStateWithCallback from 'use-state-with-callback';
const SomeOtherComponent = () => {
const [count, setCount] = useStateWithCallback(0, count => {
if (count > 1) {
document.title = 'Threshold of over 1 reached.';
} else {
document.title = 'No threshold reached.';
}
});
return (
<div>
<p>{count}</p>
<button type = "button" onClick = {() => setCount(count + 1)}>
Increase
</button>
</div>
);
};
Я пытаюсь установить использование состояния с обратным вызовом, но это не работает. Это дает ошибку. Что я могу сделать?
действительно хороший подход. Я нашел это чрезвычайно полезным
В React16.x и более поздних версиях, если вы хотите вызвать функцию обратного вызова при изменении состояния с помощью хука useState, вы можете использовать хук useEffect, прикрепленный к изменению состояния.
import React, { useEffect } from "react";
useEffect(() => {
props.getChildChange(name); // using camelCase for variable name is recommended.
}, [name]); // this will call getChildChange when ever name changes.
Что делать, если функций больше одной и только одна из них должна работать в повторе?
@Gucal, вы можете использовать useEffect несколько раз, например useEffect(() => loadFunctionAOnce()). useEffect(() => loadFunctionBIfNameChange(), [name])
Хм супер. Спасибо @DAMIENJIANG :)
Это также запустит props.getChildChange при начальном рендеринге.
мы можем написать функцию настройки, которая будет вызывать функцию callBack, если какие-либо изменения в состоянии
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const useStateCallbackWrapper = (initilValue, callBack) => {
const [state, setState] = useState(initilValue);
useEffect(() => callBack(state), [state]);
return [state, setState];
};
const callBack = state => {
console.info("---------------", state);
};
function App() {
const [count, setCount] = useStateCallbackWrapper(0, callBack);
return (
<div className = "App">
<h1>{count}</h1>
<button onClick = {() => setCount(count + 1)}>+</button>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
`
Это решение не работает при производственной сборке с React Hook useEffect has a missing dependency: 'callBack'. Either include it or remove the dependency array. If 'callBack' changes too often, find the parent component that defines it and wrap that definition in useCallback react-hooks/exhaustive-deps
попробуйте сохранить строку useEffect, например useEffect(() => callBack?callBack(state):null, [state, callBack]);
Другой способ добиться этого:
const [Name, setName] = useState({val:"", callback: null});
React.useEffect(()=>{
console.info(Name)
const {callback} = Name;
callback && callback();
}, [Name]);
setName({val:'foo', callback: ()=>setName({val: 'then bar'})})это как-то аккуратно. Таким образом, последовательность выполнения будет зависеть от того, как вы установите значения ключей? сначала val, затем обратный вызов?
вы можете использовать хук использоватьОбратный звонок для этого.
function Parent() {
const [name, setName] = useState("");
const getChildChange = useCallback( (updatedName) => {
setName(updatedName);
}, []);
return <div> {name} :
<Child getChildChange = {getChildChange} ></Child>
</div>
}
function Child(props) {
const [name, setName] = useState("");
function handleChangeEvent(ele) {
setName(ele.target.value);
props.getChildChange(ele.target.value);
}
function collectState() {
return name;
}
return (<div>
<input onChange = {handleChangeEvent} value = {name}></input>
</div>);
}
Это был ответ, который я искал, спасибо!
Установка состояния в двух компонентах для одной и той же переменной не кажется мне хорошей идеей.
useState Hook doesn't support the second callback argument. To execute a side effect after rendering, declare it in the component body with useEffect(). Это то, что я получаю после ввода обратного вызова
Привет, @dishwasherWithProgrammingSkill, для чего используется этот код? что отличается от встроенного setState следующим образом: <Child getChildChange = {(value) => setValue(value)} ></Child>
setState(updater, callback) за useStateСледующая реализация очень близка к исходному обратному вызову классов setState.
Улучшения, внесенные в принятый ответ:
setState, как с классами.const App = () => {
const [state, setState] = useStateCallback(0); // same API as useState
const handleClick = () => {
setState(
prev => prev + 1,
// second argument is callback, `s` being the *updated* state
s => console.info("I am called after setState, state:", s)
);
};
return <button onClick = {handleClick}>Increment</button>;
}
useStateCallbackfunction useStateCallback(initialState) {
const [state, setState] = useState(initialState);
const cbRef = useRef(null); // init mutable ref container for callbacks
const setStateCallback = useCallback((state, cb) => {
cbRef.current = cb; // store current, passed callback in ref
setState(state);
}, []); // keep object reference stable, exactly like `useState`
useEffect(() => {
// cb.current is `null` on initial render,
// so we only invoke callback on state *updates*
if (cbRef.current) {
cbRef.current(state);
cbRef.current = null; // reset callback after execution
}
}, [state]);
return [state, setStateCallback];
}
Дополнительная информация: React Hooks FAQ: есть ли что-то вроде переменных экземпляра?
const App = () => {
const [state, setState] = useStateCallback(0);
const handleClick = () =>
setState(
prev => prev + 1,
// important: use `s`, not the stale/old closure value `state`
s => console.info("I am called after setState, state:", s)
);
return (
<div>
<p>Hello Comp. State: {state} </p>
<button onClick = {handleClick}>Click me</button>
</div>
);
}
function useStateCallback(initialState) {
const [state, setState] = useState(initialState);
const cbRef = useRef(null);
const setStateCallback = useCallback((state, cb) => {
cbRef.current = cb;
setState(state);
}, []);
useEffect(() => {
if (cbRef.current) {
cbRef.current(state);
cbRef.current = null;
}
}, [state]);
return [state, setStateCallback];
}
ReactDOM.render(<App />, document.getElementById("root"));<script src = "https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity = "sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4 = " crossorigin = "anonymous"></script>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity = "sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w = " crossorigin = "anonymous"></script>
<script>var { useReducer, useEffect, useState, useRef, useCallback } = React</script>
<div id = "root"></div>что делает cbRef.current(state); в этом коде внутри условного оператора в useEffect?
@ bot19 это фактический вызов обратного вызова, который ранее был установлен через setState(..,cb). cbRef.current хранит функцию. Затем эта функция вызывается — (state) — с текущим обновленным состоянием.
Проблема с этим фрагментом заключается в том, что он срабатывает не будет, если состояние идентично. Это никогда не проблема с компонентами класса, потому что вы всегда создаете новый объект состояния. codeandbox.io/s/thirsty-tamas-7cqbp?file=/src/App.js
Альтернативное решение: codeandbox.io/s/дерзкий-мендель-tqh58?file=/src/App.js
@dwjohnston спасение от обновлений состояния в случае идентичных значений — это новое значение React по умолчанию для хуков, поэтому в большинстве случаев я бы не стал менять это поведение. Если вам действительно нужно использовать старое сравнение на основе классов по устаревшим причинам (которое ведет себя таким образом из-за слияния объектов), подход codeandbox выглядит разумным! Вместо использования Symbol вы могли бы каждый раз помещать значение состояния в новый контейнер объекта.
@ford04 Это хорошее решение, но это не 100% замена оригинала this.setState. 1) вы вызываете обратный вызов только тогда, когда состояние действительно меняется, исходный вызывал его всегда this.setState(prevState => prevState, () => console.info('Changed')); 2) Вы поддерживаете только 1 обратный вызов в то время, последний. Должен быть массив обратных вызовов. Вы можете вызвать свой setState дважды с разными обратными вызовами. Самое простое решение — использовать массив обратных вызовов и, возможно, использовать только этот массив в качестве зависимостей в useEffect — useEffect(..., [cbRefs.current]
@PetrÚjezdský спасибо за ваши идеи! К 1: Думаю, этот комментарий подходит. Re 2: Если вы вызываете setState дважды во время цикла рендеринга такой же и одного и того же экземпляра хука, последнее значение выигрывает в React. Поэтому я ожидаю такого же поведения при установке обратного вызова и скорее запутаюсь, если будут вызваны оба старых а также нового обратного вызова. И то, и другое в любом случае кажется скорее крайним случаем - скорее всего, у вас будет обработчик событий, в котором состояние настройки выполняется в разных рендерах.
Подумайте о том, чтобы сделать это пакетом npm!
почему бы вам просто не передать
setNameи не назвать это от ребенка?