React useState钩子(和useEffect)在回调函数中不工作



已经尝试了useState,它的功能变化和尝试使用useEffect(不允许在回调函数中使用)。

我被卡住了。
我有一个父组件'Table',它呈现一个TableHead子组件和一个TableBody子组件。TableHead子组件有一个复选框,当单击该复选框时,将执行父组件上的回调函数。此时,布尔值selectAll(来自useState的setter和value)应该切换(更改值)。但它保持在初始状态。结果是,第一次为selectall的头部复选框,触发和重新渲染确实显示所有行在主体被选中,但然后取消选中' selectall '确实触发回调,但' selectall '仍然是假的,所有的行仍然被选中。

父组件代码:

function OTable(props) {
const [selectAll, setSelectAll] = useState(false);
const onAllRowsSelected = useCallback(() => {
if (selectAll === false)
{
setSelectAll(selectAll => selectAll = true);
}
else
{
setSelectAll(selectAll => selectAll = false);
}
}
return (
<TableContainer>
<Table>
<OTableHead
onSelectAllClick={onAllRowsSelected}
setSelectAll={setSelectAll}
/>
<OTableBody
selectAll={selectAll}
/>
</Table>
</TableContainer>

怎么做?由于

如果我做了一些合理的假设(例如,您在useCallback调用上关闭了)),那么切换selectAll的代码可以工作,尽管从您使用useCallback开始,我怀疑它并不完全按照您想要的方式工作。下面是带有这些假设的代码:

const {useState, useCallback} = React;
const TableContainer = ({children}) => {
return <div>{children}</div>;
};
const Table = ({children}) => {
return <div>{children}</div>;
};
const OTableHead = ({onSelectAllClick, children}) => {
console.log(`OTableHead is rendering`);
return <div>
<input type="button" onClick={onSelectAllClick} value="Select All" />
<div>{children}</div>
</div>;
};
const OTableBody = ({selectAll}) => {
return <div>selectAll = {String(selectAll)}</div>;
};
function OTable(props) {
const [selectAll, setSelectAll] = useState(false);
const onAllRowsSelected = useCallback(() => {
if (selectAll === false)
{
setSelectAll(selectAll => selectAll = true);
}
else
{
setSelectAll(selectAll => selectAll = false);
}
});
return (
<TableContainer>
<Table>
<OTableHead
onSelectAllClick={onAllRowsSelected}
setSelectAll={setSelectAll}
/>
<OTableBody
selectAll={selectAll}
/>
</Table>
</TableContainer>
);
}
ReactDOM.render(
<OTable />,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>

但有些事情很突出:

  1. 你没有传递任何依赖数组给useCallback。这没有做任何有用的事情,因为useCallback将始终返回您传递给它的新函数。我怀疑你的意思是对它有一个空的依赖数组,以便它总是重用第一个函数(以避免不必要的重新渲染OTableHead)。

  2. 您正在使用setSelectAll的回调形式,但您正在使用硬编码值(truefalse)。这段代码:

    const onAllRowsSelected = useCallback(() => {
    if (selectAll === false)
    {
    setSelectAll(selectAll => selectAll = true);
    }
    else
    {
    setSelectAll(selectAll => selectAll = false);
    }
    });
    

    所做的正是这段代码要做的事情(假设我们知道selectAll是从一个布尔值开始的,如果我们不知道这一点,它将会非常微妙地不同):

    const onAllRowsSelected = useCallback(() => {
    setSelectAll(!selectAll);
    });
    

    ,因为if使用的是函数关闭的selectAll的版本,而不是回调接收到的参数。(setSelectAll(selectAll => selectAll = false);在功能上与setSelectAll(() => false)相同,对参数赋值没有任何影响。)这段代码和下面的代码一样:

    const onAllRowsSelected = () => {
    setSelectAll(!selectAll);
    };
    

    但我怀疑你使用回调版本的原因与使用useCallback相同。

代码没有成功地避免重新渲染,正如您可以从上面我添加到OTableHeadconsole.log中看到的。

通过记忆回调,useCallback对于避免在回调没有真正改变的情况下重新呈现子元素很有用。下面是在代码

中正确使用它的方法
  1. 传递一个空的依赖数组给useCallback,所以它只返回你定义的第一个回调。

  2. 使用setSelectAll函数版本传递给回调的参数值

  3. 确保如果回调没有改变,你想要的组件不会重新渲染,实现检查它的属性,当它们没有改变时不会重新渲染。对于像OTableHead这样的函数组件,您只需将其传递给React.memo即可。

下面是上面的修改后的例子:

const {useState, useCallback} = React;
const TableContainer = ({children}) => {
return <div>{children}</div>;
};
const Table = ({children}) => {
return <div>{children}</div>;
};
// *** Use `React.memo`:
const OTableHead = React.memo(({onSelectAllClick, children}) => {
console.log(`OTableHead is rendering`);
return <div>
<input type="button" onClick={onSelectAllClick} value="Select All" />
<div>{children}</div>
</div>;
});
const OTableBody = ({selectAll}) => {
return <div>selectAll = {String(selectAll)}</div>;
};
function OTable(props) {
const [selectAll, setSelectAll] = useState(false);
const onAllRowsSelected = useCallback(() => {
// Callback version, using the parameter value
setSelectAll(selectAll => !selectAll);
}, []); // <=== Empty dependency array
return (
<TableContainer>
<Table>
<OTableHead
onSelectAllClick={onAllRowsSelected}
setSelectAll={setSelectAll}
/>
<OTableBody
selectAll={selectAll}
/>
</Table>
</TableContainer>
);
}
ReactDOM.render(
<OTable />,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>

如果不是担心不必要的重新渲染,那么你可以完全摆脱useCallback,只是这样做:

const onAllRowsSelected = () => {
setSelectAll(!selectAll);
};

在这种情况下不需要useCalback。像这样更新setState:

const onAllRowsSelected = () => {
setSelectAll((preState) => !preState);
};

是的,保持初始值,因为useCallback是一个记忆,如果你不添加状态依赖,它保持初始值(由于记忆本身)。要解决这个问题,只需将selectAll作为useCallback依赖项:

const onAllRowsSelected = useCallback(() => {
setSelectAll((prev) => !prev)
}, [selectAll])

不需要记住状态更新函数,因为React保证它是一个稳定的引用。

useState

注意

React保证setState函数标识是稳定的在重新渲染时更改。这就是为什么从useEffect中省略它是安全的或useCallback依赖项列表。

你可以简化你的回调,只使用函数更新状态。

const onAllRowsSelected = () => setSelectAll(all => !all);

如果OTableHead要求onSelectAllClickprop是一个稳定的引用,那么useCallback可以与一个空的依赖数组一起使用,以提供一个稳定的onAllRowsSelected回调引用。注意:这并不影响onAllRowsSelected从前一个状态值正确切换的能力,它只是为子组件提供一个稳定的回调引用。

useCallback

useCallback将返回一个记忆版本的回调函数如果其中一个依赖项发生更改,则更改。这在以下情况下很有用向依赖引用的优化子组件传递回调=防止不必要的渲染(如。shouldComponentUpdate)。

const onAllRowsSelected = useCallback(
() => setSelectAll(all => !all),
[],
);