2021年React中页面渲染前获取外部数据的标准实现方案
React 中实现渲染前预加载数据的常用方案
AngularJS 中 resolve 的核心逻辑是路由跳转前完成所有依赖数据的拉取,再渲染对应的业务组件,React 生态有几种成熟的实现方式可以达到完全一致的效果,避免组件重复渲染:
1. 路由层 loader 方案(最贴近 AngularJS resolve 的标准实现)
目前 React 生态最常用的路由库 React Router v6 直接提供了 loader 特性,功能和 AngularJS 的 resolve 完全对齐:路由切换时先执行 loader 里的所有异步请求,全部完成后才会渲染对应的页面组件,天然不会出现两次渲染的问题。
示例代码:
// 路由定义处 import { createBrowserRouter, RouterProvider } from 'react-router-dom'; import Kpi from './Kpi'; const router = createBrowserRouter([ { path: '/kpi', // 这里的loader等价于AngularJS的resolve loader: async () => { // 可以按依赖顺序串行请求,和你原来的resolve执行逻辑一致 const user = await auth.getUser(); const posts = await posts.getAll(); const users = await users.getAll(); const logs = await kpiService.getLogs(); const subscribers = await kpiService.getSubscribers(); // 所有数据返回后传给组件 return { user, posts, users, logs, subscribers }; }, element: <Kpi />, }, ]); // Kpi组件内直接拿数据,不需要自己发请求 import { useLoaderData } from 'react-router-dom'; class Kpi extends React.Component { getChartOptions1() { // 直接取loader拿到的logs,不用判断是否为空 const { logs } = useLoaderData(); // 配置逻辑 } render() { return <div> <HighchartsReact highcharts={Highcharts} options={this.getChartOptions1()} {...this.props} /> <div>{JSON.stringify(useLoaderData().logs)}</div> </div> } }
这个方案下 Kpi 组件只会在所有数据加载完成后才初始化渲染,getChartOptions1 只会调用一次,完全符合需求。
2. 外层包装层加载方案
如果不用路由级别的 loader,可以自行实现一层加载包装组件,数据拉取完成前只显示加载状态,数据就绪后再渲染业务组件,也能避免业务组件重复渲染:
// 包装组件 class KpiLoader extends React.Component { state = { loading: true, logs: null }; componentDidMount() { axios.get(`${BACKEND_URL}/httpOnly/kpi/logs`).then( res => { this.setState({ loading: false, logs: res.data }); } ) } render() { if (this.state.loading) return <div>加载中...</div>; // 数据加载完成才渲染Kpi组件 return <Kpi logs={this.state.logs} /> } } // 业务Kpi组件不需要关心数据加载逻辑 class Kpi extends React.Component { getChartOptions1() { const { logs } = this.props; // 配置逻辑 } render() { return <div> <HighchartsReact highcharts={Highcharts} options={this.getChartOptions1()} {...this.props} /> <div>{JSON.stringify(this.props.logs)}</div> </div> } }
3. Suspense 数据拉取方案
React 18 及以上版本可以使用 Suspense 搭配支持 Suspense 的数据流库(比如 React Query、SWR、Relay)实现同样的效果:
import { Suspense } from 'react'; import { useQuery } from '@tanstack/react-query'; // 数据请求逻辑 const fetchLogs = () => axios.get(`${BACKEND_URL}/httpOnly/kpi/logs`).then(res => res.data); function Kpi() { const { data: logs } = useQuery({ queryKey: ['kpiLogs'], queryFn: fetchLogs, suspense: true }); const getChartOptions1 = () => { // 直接使用logs即可,不需要判断空值 }; return <div> <HighchartsReact highcharts={Highcharts} options={getChartOptions1()} /> <div>{JSON.stringify(logs)}</div> </div> } // 外层使用Suspense包裹,数据加载完成前显示fallback function App() { return <Suspense fallback={<div>加载中...</div>}> <Kpi /> </Suspense> }
这种方案下 Suspense 会捕获组件内部的异步请求,数据加载完成前渲染 fallback,加载完成后才渲染 Kpi 组件,也不会出现重复渲染的问题。
你原来的写法会触发两次渲染的核心原因是组件初始化渲染时还没有发起请求,数据为空,等请求完成 setState 后触发第二次渲染,只要让业务组件在数据就绪后再初始化渲染,就能完全避免这个问题。
内容的提问来源于stack exchange,提问作者SoftTimur
相关产品推荐
相关产品推荐

