useCallback 函数返回的闭包中的 setState() 不会更新状态



我有一个用例,我需要在父组件中创建一个回调函数并将其传递给子组件。由于此函数依赖于子组件中不存在的一些值,因此我决定创建一个包装器,它将接受这些值并使用闭包生成一个新函数。

我的父组件是这样的:

const updateValue = useCallback(() => {
return (value) => {
setState(prev => {
prev[currentIndex] = value;
return prev;
}
}
}, [currentIndex, setState]);

然后像这样传递

<Child updateFunction={updateValue()} />

然而,当我从子组件调用这个函数时,它被调用,但它永远不会更新状态。

我试着用一个更简单的例子来说明这个问题:

import { useState, useCallback } from 'react';
export default function App() {
const [state, setState] = useState([
{
id: 1,
cars: ['BMW', 'Tesla'],
},
{
id: 2,
cars: ['BMW', 'Tesla'],
},
{
id: 3,
cars: ['BMW', 'Tesla'],
},
]);
const changer = useCallback(() => {
return () => {
setState(prev => {
const ran = prev[1];
ran.cars = ['Ferrari'];
return prev;
});
}
}, [setState]);
console.log(state);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2 onClick={changer()}>Start editing to see some magic happen!</h2>
</div>
);
}

这段代码像预期的那样工作,直到我包装成一个闭包。有什么问题吗?

你是在直接改变状态,而不是在react中更新状态。直接改变状态不会触发重新渲染。

要在您的示例中正确更新状态,您可以使用map()方法,如下所示:

setState(prev => {
return prev.map((obj, idx) => {
if (idx === 1) {
return { ...obj, cars: [...obj.cars, "Ferrari"] }; 
}
return obj;
});
});

如果map()方法的回调函数中的条件为真,则不需要改变现有对象,而需要返回一个新的对象。

您可以使用扩展语法使用旧对象创建新对象,并覆盖您想要更新的属性。

演示

function App() {
const [state, setState] = React.useState([
{ id: 1, cars: ['BMW', 'Tesla'] },
{ id: 2, cars: ['BMW', 'Tesla'] },
{ id: 3, cars: ['BMW', 'Tesla'] }
]);
const changer = React.useCallback(() => {
return () => {
setState(prev => {
return prev.map((obj, idx) => {
if (idx === 1) {
return { ...obj, cars: [...obj.cars, "Ferrari"] }; 
}
return obj;
});
});
}
}, [setState]);
return (
<div>
<button onClick={changer()}>Click</button>
{ state.map(obj => (
<li key={obj.id}>{obj.cars.join(", ")}</li>
))}
</div>
);
}
ReactDOM.render(<App/>, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="root"></div>

不能直接改变状态。你应该返回新的数组在setState回调:

const updateValue = useCallback(
(value) => {
setState((prev) => {
return prev.map((item, i) => (i === currentIndex ? value : item));
});
},
[currentIndex, setState]
);

<Child updateFunction={updateValue} />

我怀疑你的回调不工作,因为它的依赖关系是在setStatefn而不是状态本身。我敢保证setter的标识不会改变,所以你的闭包将在状态更新后失效。

试试这个:

const updateValue = useCallback((value) => {
const newState = [...state]
newState[currentIndex] = value
setState(newState)
}, [currentIndex, state]);
//...
<Child updaterFunction={updateValue} />

最新更新