如何在 useState 中同步改变状态
How to synchronously change state within useState
大家好,我是 React Hooks 的新手,但无法直接在 google 上找到它。我试图嵌套 setState 回调,以便状态可以同步更新,但没有成功,因为我得到了未定义的值。状态中的值依赖于我状态中的其他值,因此理想情况下我希望它同步 运行 。我尝试将它嵌套在下面,当它是一个 class 组件并使用 this.setState 并使用回调时它可以工作,但是当我尝试在功能性 class 中使用反应钩子时它不起作用.
这是我的代码:
const [state, setState] = useState({numCols: 0, numRows: 0, cardWidth: 0, cardHeight: 0, containerWidth: 0});
const {
sheetList,
sheetsTotalCount,
sheetsMetadata,
id,
projectId,
onEditButtonClick,
handleInfiniteLoad,
sheetsAreLoading,
classes,
permissions,
onItemClick,
} = props;
const setCardSize = ({ width }) {
setState({
containerWidth: width > 0 ? width : defaultWidth
},() => {
setState({numCols: Math.max(Math.floor(state.containerWidth / baseThumbnailWidth), 1) }, () => {
setState({numRows: Math.ceil(sheetList.size / state.numCols)}, () => {
setState({cardWidth: Math.floor(state.containerWidth / state.numCols - 2 * marginBetweenCards)}, () => {
setState({cardHeight: Math.round(state.cardWidth * thumbnailProportion)});
});
});
});
});
}
理想情况下,我希望首先更新 containerWidth 变量,然后是 numCols 变量,然后是 cardWidth,然后是 cardHeight。有什么方法可以同步执行此操作,这样我就不会得到未定义的值吗?
我对你想要达到的目标有点困惑。但不要忘记,与 class 不同,您每次都必须将所有属性设置为 in state。
setState({numCols: Math.max(Math.floor(state.containerWidth / baseThumbnailWidth), 1) }
此代码将仅用 numCols 替换您的所有状态。你想要像这样的其他状态,现在只有 numCols
会改变,其他一切都一样。
setState({...state, numCols: Math.max(Math.floor(state.containerWidth / baseThumbnailWidth), 1) }
接下来要记住的是,如果你想在一次渲染中多次更改状态,请使用这种形式:
setState(oldState => {...oldState, newValue: 'newValue'});
这将允许在一个渲染中使用最后一个值集而不是在最后一个渲染中对状态进行多次更新。例如:
const [state, setState] = useState(0); // -> closed on this state's value!
setState(state + 1);
setState(state + 1);
setState(state + 1); //State is still 1 on next render
// because it is using the state which happened on the last render.
// When this function was made it "closed" around the value 0
// (or the last render's number) hence the term closure.
对比这个:
const [state, setState] = useState(0);
setState(state => state + 1);
setState(state => state + 1);
setState(state => state + 1); //State is 3 on next render.
但为什么不同步计算值呢?
const setCardSize = (width) => {
const containerWidth = width > 0 ? width : defaultWidth;
const numCols = Math.max(Math.floor(containerWidth / baseThumbnailWidth), 1);
const numRows = Math.ceil(sheetList.size / numCols);
const cardWidth = Math.floor(containerWidth / numCols - 2 * marginBetweenCards);
const cardHeight = Math.round(cardWidth * thumbnailProportion);
setState({containerWidth, numCols, numRows, cardWidth, cardHeight});
}
查看docs它讨论的内容
Unlike the setState method found in class components, useState does
not automatically merge update objects.
If the new state is computed using the previous state, you can pass a
function to setState. The function will receive the previous value,
and return an updated value. Here’s an example of a counter component
that uses both forms of setState:
鉴于您正在计算大量相互依赖的变量,并且您希望所有状态同时更新,为什么不拆分计算并在最后设置一次状态?更具可读性,只需要一个 setState 调用。
const setCardSize = ({ width }) => {
const containerWidth = width > 0 ? width : defaultWidth;
const numCols = Math.max(Math.floor(containerWidth / baseThumbnailWidth), 1,);
const numRows = Math.ceil(sheetList.size / numCols);
const cardWidth = Math.floor(containerWidth / numCols - 2 * marginBetweenCards);
const cardHeight = Math.round(cardWidth * thumbnailProportion);
setState({ containerWidth, numCols, numRows, cardWidth, cardHeight });
};
不过,要回答实际问题,如果您想使一个变量的状态更新立即(或如您所说的“同步”)更新另一个状态变量,则使用 useEffect
.
您只需给 useEffect
两个参数:每次因变量变化时 运行 的函数,然后是要关注的这些变量的数组。
每个状态变量都有自己的useState
,而不是只有一个大对象,这样更干净(更快,更不容易出错,通常推荐用于功能组件),我也这样做了这里。
const [containerWidth, setContainerWidth] = useState(0);
const [numCols, setNumCols] = useState(0);
const [numRows, setNumRows] = useState(0);
const [cardWidth, setCardWidth] = useState(0);
const [cardHeight, setCardHeight] = useState(0);
const setCardSize = ({ width }) => setContainerWidth(width > 0 ? width : defaultWidth)
useEffect(() => setNumCols(Math.max(Math.floor(containerWidth / baseThumbnailWidth), 1)) , [containerWidth])
useEffect(() => setNumRows(Math.ceil(sheetList.size / numCols)), [numCols])
useEffect(() => setCardWidth(Math.floor(containerWidth / numCols - 2 * marginBetweenCards)), [containerWidth])
useEffect(() => setCardHeight(Math.round(cardWidth * thumbnailProportion)), [cardWidth])
大家好,我是 React Hooks 的新手,但无法直接在 google 上找到它。我试图嵌套 setState 回调,以便状态可以同步更新,但没有成功,因为我得到了未定义的值。状态中的值依赖于我状态中的其他值,因此理想情况下我希望它同步 运行 。我尝试将它嵌套在下面,当它是一个 class 组件并使用 this.setState 并使用回调时它可以工作,但是当我尝试在功能性 class 中使用反应钩子时它不起作用.
这是我的代码:
const [state, setState] = useState({numCols: 0, numRows: 0, cardWidth: 0, cardHeight: 0, containerWidth: 0});
const {
sheetList,
sheetsTotalCount,
sheetsMetadata,
id,
projectId,
onEditButtonClick,
handleInfiniteLoad,
sheetsAreLoading,
classes,
permissions,
onItemClick,
} = props;
const setCardSize = ({ width }) {
setState({
containerWidth: width > 0 ? width : defaultWidth
},() => {
setState({numCols: Math.max(Math.floor(state.containerWidth / baseThumbnailWidth), 1) }, () => {
setState({numRows: Math.ceil(sheetList.size / state.numCols)}, () => {
setState({cardWidth: Math.floor(state.containerWidth / state.numCols - 2 * marginBetweenCards)}, () => {
setState({cardHeight: Math.round(state.cardWidth * thumbnailProportion)});
});
});
});
});
}
理想情况下,我希望首先更新 containerWidth 变量,然后是 numCols 变量,然后是 cardWidth,然后是 cardHeight。有什么方法可以同步执行此操作,这样我就不会得到未定义的值吗?
我对你想要达到的目标有点困惑。但不要忘记,与 class 不同,您每次都必须将所有属性设置为 in state。
setState({numCols: Math.max(Math.floor(state.containerWidth / baseThumbnailWidth), 1) }
此代码将仅用 numCols 替换您的所有状态。你想要像这样的其他状态,现在只有 numCols
会改变,其他一切都一样。
setState({...state, numCols: Math.max(Math.floor(state.containerWidth / baseThumbnailWidth), 1) }
接下来要记住的是,如果你想在一次渲染中多次更改状态,请使用这种形式:
setState(oldState => {...oldState, newValue: 'newValue'});
这将允许在一个渲染中使用最后一个值集而不是在最后一个渲染中对状态进行多次更新。例如:
const [state, setState] = useState(0); // -> closed on this state's value!
setState(state + 1);
setState(state + 1);
setState(state + 1); //State is still 1 on next render
// because it is using the state which happened on the last render.
// When this function was made it "closed" around the value 0
// (or the last render's number) hence the term closure.
对比这个:
const [state, setState] = useState(0);
setState(state => state + 1);
setState(state => state + 1);
setState(state => state + 1); //State is 3 on next render.
但为什么不同步计算值呢?
const setCardSize = (width) => {
const containerWidth = width > 0 ? width : defaultWidth;
const numCols = Math.max(Math.floor(containerWidth / baseThumbnailWidth), 1);
const numRows = Math.ceil(sheetList.size / numCols);
const cardWidth = Math.floor(containerWidth / numCols - 2 * marginBetweenCards);
const cardHeight = Math.round(cardWidth * thumbnailProportion);
setState({containerWidth, numCols, numRows, cardWidth, cardHeight});
}
查看docs它讨论的内容
Unlike the setState method found in class components, useState does not automatically merge update objects.
If the new state is computed using the previous state, you can pass a function to setState. The function will receive the previous value, and return an updated value. Here’s an example of a counter component that uses both forms of setState:
鉴于您正在计算大量相互依赖的变量,并且您希望所有状态同时更新,为什么不拆分计算并在最后设置一次状态?更具可读性,只需要一个 setState 调用。
const setCardSize = ({ width }) => {
const containerWidth = width > 0 ? width : defaultWidth;
const numCols = Math.max(Math.floor(containerWidth / baseThumbnailWidth), 1,);
const numRows = Math.ceil(sheetList.size / numCols);
const cardWidth = Math.floor(containerWidth / numCols - 2 * marginBetweenCards);
const cardHeight = Math.round(cardWidth * thumbnailProportion);
setState({ containerWidth, numCols, numRows, cardWidth, cardHeight });
};
不过,要回答实际问题,如果您想使一个变量的状态更新立即(或如您所说的“同步”)更新另一个状态变量,则使用 useEffect
.
您只需给 useEffect
两个参数:每次因变量变化时 运行 的函数,然后是要关注的这些变量的数组。
每个状态变量都有自己的useState
,而不是只有一个大对象,这样更干净(更快,更不容易出错,通常推荐用于功能组件),我也这样做了这里。
const [containerWidth, setContainerWidth] = useState(0);
const [numCols, setNumCols] = useState(0);
const [numRows, setNumRows] = useState(0);
const [cardWidth, setCardWidth] = useState(0);
const [cardHeight, setCardHeight] = useState(0);
const setCardSize = ({ width }) => setContainerWidth(width > 0 ? width : defaultWidth)
useEffect(() => setNumCols(Math.max(Math.floor(containerWidth / baseThumbnailWidth), 1)) , [containerWidth])
useEffect(() => setNumRows(Math.ceil(sheetList.size / numCols)), [numCols])
useEffect(() => setCardWidth(Math.floor(containerWidth / numCols - 2 * marginBetweenCards)), [containerWidth])
useEffect(() => setCardHeight(Math.round(cardWidth * thumbnailProportion)), [cardWidth])