React Suspense 未按预期工作

Mun*_*eed 5 async-await reactjs react-suspense

我想在Powers获取/未定义时渲染后备组件。React.Suspense我使用代码在我的逻辑中实现:

<Suspense fallback={<p>Loading</p>}>
  <RoutesPowers />
</Suspense>
Run Code Online (Sandbox Code Playgroud)

我的RoutesPowers

const Powers = [ ... ];

const RoutesPowers = () => {

  const [powers, setPowers] = useState(null);
  const fetchPowers = () => setTimeout(() => setPowers(Powers), 3000);

  useEffect(() => fetchPowers(), []);

  return ( powers.map(route => <RoutePowers route={route}/> )

};
            
Run Code Online (Sandbox Code Playgroud)

但它给了我Cannot read property "map" of null可能是因为powers是空的。这意味着它React.Suspense没有发挥应有的作用。有人能帮我解决这个问题吗?

Nic*_*wer 11

为了使悬念产生任何效果,树中更靠下的组件需要抛出一个承诺。当这种情况发生时,Suspense 将捕获它并显示回退,直到 Promise 解决为止,然后它继续渲染其正常的子项。你的代码没有抛出任何承诺,所以悬念没有任何作用。

因此,如果您想为此使用悬念,则需要让您的组件在渲染期间检测到没有数据时抛出一个承诺。然后进行提取,并保存数据,以便当组件再次安装时它将拥有数据(您无法设置状态,因为组件在此期间不存在,而回退则存在)。

const App = () => (
  <React.Suspense fallback={<p>Loading</p>}>
    <RoutesPowers />
  </React.Suspense>
)

let savedPowers = null;
const fetchPowers = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      savedPowers = ['first', 'second']
      resolve();
    }, 3000)
  });
}

const RoutesPowers = () => {
  const [powers, setPowers] = React.useState(savedPowers);
  if (!powers) {
    throw fetchPowers();
  }

  return powers.map(value => <div key={value}>{value}</div>);
}

ReactDOM.render(<App />, document.getElementById("root"));
Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.development.js"></script>
<div id="root"></div>
Run Code Online (Sandbox Code Playgroud)

请注意,使用此模式并不常见。更常见的是在一个组件中处理所有这些,如果没有数据则渲染一个占位符,然后在效果中启动获取,然后在完成时更新状态。您当前的代码已经差不多了,您只需删除<Suspense>并在 RoutesPowers 中添加代码即可返回加载段落。

const RoutesPowers = () => {
  const [powers, setPowers] = useState(null);
  const fetchPowers = () => setTimeout(() => setPowers(Powers), 3000);

  useEffect(() => fetchPowers(), []);

  if (!powers) {
    return <p>loading</p>
  }

  return ( powers.map(route => <RoutePowers route={route}/> )

};
Run Code Online (Sandbox Code Playgroud)

  • 谢谢你,尼克!您不仅解决了我的问题,还提出了更好的模式。 (3认同)