javascript - 组件的状态源自它的 prop,而该 prop 来自异步 API 调用

标签 javascript reactjs

最近在开发一些业务逻辑的时候遇到了这种情况。这是它的一个简化和人为的例子

function App() {
  const [myState, setMyState] = useState([])

  useEffect(() => {
    setTimeout(() => {
      setMyState((prevState) =>
        prevState.concat([
          {
            type: 'foo',
            enabled: true,
          },
          {
            type: 'bar',
            enabled: true,
          },
          {
            type: 'baz',
            enabled: true,
          },
        ])
      )
    }, 500)
  }, [])

  return (
    <div className="App">
      <ChildComponent propFromApp={myState} />
    </div>
  )
}

我用了setTimeout模拟 API 调用并将状态传递给 ChildComponent .

function ChildComponent({ propFromApp }) {
  const allTypes = propFromApp.map((item) => item.type)
  const [checkedItems, setCheckedItems] = useState(allTypes)

  return (
    <div>
      {allTypes.map((type, index) => (
        <div key={index}>
          <input
            type="checkbox"
            name={type}
            checked={checkedItems.includes(type)}
            onChange={() => {
              if (checkedItems.includes(type)) {
                setCheckedItems((prevState) =>
                  prevState.filter((prevType) => type !== prevType)
                )
              } else {
                setCheckedItems((prevState) => prevState.concat(type))
              }
            }}
          />
          <label htmlFor={type}>{type}</label>
        </div>
      ))}
    </div>
  )
}

ChildComponent负责渲染一堆基于 prop 的复选框。它首先派生了一个数组 type来自 Prop ,称为 allTypes然后设置自己的状态checkedItems使用这个allTypes作为初始值。所以最初所有的项目都会被检查。我做的原因ChildComponent将它们保持为自己的状态是组件可以通过复选框控制它们。

但是因为 propFromApp最初是一个空数组,所以 ChildComponent的状态checkedItems也被初始化为空数组,并且在以下情况下不会更新 真正的数据从 API 调用返回,这是在 setTimeout 中的回调时 500 毫秒后被解雇了。

然后我使用 useEffect更新 checkedItems当 Prop 更新时。

所以现在ChildComponent看起来像这样


function ChildComponent({ propFromApp }) {
  const allTypes = propFromApp.map((item) => item.type)
  const [checkedItems, setCheckedItems] = useState(allTypes)

  useEffect(() => {
    setCheckedItems(allTypes)
  }, [allTypes])

  return (....)

然而这给出了一个错误

Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.

我想是因为allTypes不是基本类型之一,所以相等性检查总是确定 allTypes每次组件重新渲染时都是不同的。所以最后我不得不将其更改为使用 useMemo以避免问题。现在ChildComponent看起来像这样

function ChildComponent({ propFromApp }) {
  const allTypes = useMemo(() => propFromApp.map((item) => item.type), [
    propFromApp,
  ])
  const [checkedItems, setCheckedItems] = useState(allTypes)

  useEffect(() => {
    setCheckedItems(allTypes)
  }, [allTypes])

  return (....)

现在问题解决了。但我不确定这是正确的做法还是一种好的做法。谁能提出更好的替代方案或指出我的代码哪里不够好?

这里是现场直播 https://codesandbox.io/s/async-sky-3vl1r?file=/src/App.js

最佳答案

useMemo 在这种情况下通常可以解决问题,但也可能不会。 useMemo 可能会也可能不会根据比我聪明的人编写的一些内存管理逻辑重新计算它的值。因此,您需要在编写代码时牢记内存值的引用身份可能会发生变化。引用标识的变化意味着 deps 数组将发生变化。

可以使用 useMemo 修复死循环。在大多数情况下,重新计算只会导致额外的重新渲染。

useMemo 影响应用程序逻辑是不好的。在您的示例中,重新计算将重置 checkedItems 而不重新计算则不会。要解决此问题,请推迟派生值,直到您需要导数。

  const [checkedItems, setCheckedItems] = useState(() => propFromApp.map((item) => item.type))

  useEffect(() => {
    const allTypes = propFromApp.map((item) => item.type)
    setCheckedItems(allTypes)
  }, [propFromApp])

或者,如果您有很多部门,那么密切管理部门可能会变得异常繁重。然后,您可以显式地断开效果回调,除非您期望的更改已经发生。

  const prevPropFromApp = useRef(propFromApp)
  const propFromAppChanged = prevPropFromApp.current !== (prevPropFromApp.current = propFromApp)
  useEffect(() => {
    if (!propFromAppChanged) return;
    setCheckedItems(allTypes)
  }, [allTypes, propFromAppChanged])

关于javascript - 组件的状态源自它的 prop,而该 prop 来自异步 API 调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63543674/

相关文章:

javascript - 不同打印按钮的特殊打印样式,打印后去除

javascript:获取月/年/日/小时/分钟时间戳

reactjs - 使用react-router-dom时Netlify无法识别URL参数

reactjs - Apollo GraphQL 的动态 header (在中间件之外)

reactjs - 创建 React App - 无法获取本地颁发者证书

javascript - 如何用呈现的值构建与指数相对应的数学公式

javascript - angularjs拦截器:why is it not receiving the proper status code and message in $http interceptor

reactjs - JSX : <use> 不再支持小写组件名称(使用)

javascript - Jasmine:在测试中忽略页面重定向

reactjs - 如何使用 Typescript 将组件传递到 react 路由器路由中?