React Hooks FAQ:getderivedstatefromprops 的实现导致渲染状态不连贯

React Hooks FAQ: implementation of getderivedstatefromprops leads to rendering with incoherent state

恕我直言 来自 React Hooks FAQ #getDerivedStateFromProps 的建议 导致第一次呈现的值 rowisScrollingDown 的值不对应。由于调用 setIsScrollingDown 只会安排新的渲染,不会影响当前渲染,因此后者将使用新值 row 和旧值 isScrollingDown 执行。

此行为不等同于允许 rowisScrollingDown 之间连贯的组件 class 的静态 getderivedstatefromprops 方法。

是否应该使用类似以下代码的内容更新示例以保证连贯的渲染?还是我错过了什么?

谢谢!

function ScrollView({row}) {
    let [isScrollingDown, setIsScrollingDown] = useState(false);
    let [prevRow, setPrevRow] = useState(null);

    if (row !== prevRow) {
        // Row changed since last render. Update isScrollingDown.
        isScrollingDown = prevRow !== null && row > prevRow
        setIsScrollingDown(isScrollingDown);
        setPrevRow(row);
    }

    return `Scrolling down: ${isScrollingDown}`;
}

文档中的重要部分无需您进行更改:

React will re-run the component with updated state immediately after exiting the first render so it wouldn’t be expensive.

不同步的渲染将永远不会提交给浏览器。事实上,如果它从渲染返回一个子组件,子组件的渲染直到状态更新后才会执行(从更新状态的渲染返回的子组件将被忽略)。

下面是一个示例,其中添加了控制台日志来说明这一点。请注意,当您增加行时,ScrollView 会呈现两次,但 ScrollingDown 只会呈现一次,仅接收到 ScrollView 状态的最新版本。

import React, { useState } from "react";
import ReactDOM from "react-dom";

function ScrollingDown({ isScrollingDown, prevRow, row }) {
  console.log("ScrollingDown", isScrollingDown, prevRow, row);
  return (
    <div>
      {`Scrolling down: ${isScrollingDown}`}
      <br />
      {`prevRow: ${prevRow}`}
      <br />
      {`row: ${row}`}
    </div>
  );
}

function ScrollView({ row }) {
  let [isScrollingDown, setIsScrollingDown] = useState(false);
  let [prevRow, setPrevRow] = useState(null);

  if (row !== prevRow) {
    // Row changed since last render. Update isScrollingDown.
    setIsScrollingDown(prevRow !== null && row > prevRow);
    setPrevRow(row);
  }
  console.log("ScrollView", isScrollingDown, prevRow, row);
  return (
    <ScrollingDown
      isScrollingDown={isScrollingDown}
      prevRow={prevRow}
      row={row}
    />
  );
}

function App() {
  const [row, setRow] = useState(1);
  return (
    <div className="App">
      <ScrollView row={row} />
      <button onClick={() => setRow(prev => prev + 1)}>Increment Row</button>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);