React:渲染前获取数据的最优解决方案
我之前在做SSR项目的时候也碰到过一模一样的问题!当时也是在componentWillMount里尽早发请求,但偶尔还是会有页面闪烁,后来摸索出几个靠谱的方案,按优先级给你说说:
最优解决方案推荐
1. 服务端预获取配置,注入到页面(最推荐)
这是从根源解决闪烁的方案——既然是服务端渲染,那就在服务端渲染组件树之前先调用/settings接口拿到配置,把配置作为全局变量注入到生成的HTML中。这样客户端hydrate时直接用这个全局变量,不用再发请求,服务端渲染的HTML和客户端最终渲染的内容完全一致,彻底避免闪烁。
服务端代码示例(以Express为例):
app.get('*', async (req, res) => { try { // 提前获取用户配置 const settings = await fetch(`${process.env.API_URL}/settings`, { headers: { Cookie: req.headers.cookie } // 如果配置和用户登录状态关联,带上Cookie }).then(res => res.json()); // 渲染组件时传入配置 const appHtml = ReactDOMServer.renderToString(<App initialSettings={settings} />); // 将配置注入到HTML的全局变量中 res.send(` <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Your App</title> </head> <body> <div id="root">${appHtml}</div> <script> window.__APP_INITIAL_SETTINGS__ = ${JSON.stringify(settings)}; </script> <script src="/client-bundle.js"></script> </body> </html> `); } catch (err) { // 处理接口异常,用默认配置兜底 const defaultSettings = { numberFormat: 'en-US', defaultCurrency: 'USD' }; const appHtml = ReactDOMServer.renderToString(<App initialSettings={defaultSettings} />); res.send(` <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Your App</title> </head> <body> <div id="root">${appHtml}</div> <script> window.__APP_INITIAL_SETTINGS__ = ${JSON.stringify(defaultSettings)}; </script> <script src="/client-bundle.js"></script> </body> </html> `); } });
客户端组件代码示例:
class App extends React.Component { constructor(props) { super(props); // 直接用服务端传入的配置,无需额外请求 this.state = { settings: props.initialSettings || window.__APP_INITIAL_SETTINGS__ }; } render() { const { settings } = this.state; return ( <div> <NumberInput format={settings.numberFormat} /> <PriceDisplay currency={settings.defaultCurrency} /> </div> ); } }
2. 客户端预加载配置,再启动Hydrate
如果因为某些限制(比如配置依赖客户端专属信息,服务端无法获取)不能在服务端预取,那可以在客户端入口文件中先请求配置,等配置返回后再启动React的hydrate流程。这样页面会先显示你提前设置的加载态,不会出现内容跳变。
客户端入口代码示例:
// client-entry.js // 先请求配置 fetch('/settings') .then(res => res.json()) .then(settings => { // 配置获取完成后,再渲染/ hydrate组件 ReactDOM.hydrate( <App settings={settings} />, document.getElementById('root') ); }) .catch(err => { // 请求失败用默认配置兜底 const defaultSettings = { numberFormat: 'en-US', defaultCurrency: 'USD' }; ReactDOM.hydrate( <App settings={defaultSettings} />, document.getElementById('root') ); });
补充:可以在静态HTML中先加入骨架屏或加载动画,提升用户等待体验,避免页面长时间空白。
3. 优化现有组件的状态管理(兜底方案)
如果上面两种方案都无法实施,那只能优化当前的componentWillMount逻辑,尽量减少闪烁:
- 维护一个
isLoading状态,在配置未返回时,使用和服务端渲染完全一致的默认配置渲染内容,确保客户端初始渲染的内容和服务端生成的HTML一致,hydrate后不会出现视觉跳变。 - 避免在配置返回前显示空白或和服务端不一致的内容。
组件代码示例:
// 定义和服务端一致的默认配置 const DEFAULT_SETTINGS = { numberFormat: 'en-US', defaultCurrency: 'USD' }; class App extends React.Component { state = { settings: null, isLoading: true }; async componentWillMount() { try { const settings = await fetch('/settings').then(res => res.json()); this.setState({ settings, isLoading: false }); } catch (err) { this.setState({ settings: DEFAULT_SETTINGS, isLoading: false }); } } render() { // 优先用已获取的配置,否则用默认配置 const currentSettings = this.state.settings || DEFAULT_SETTINGS; return ( <div> {/* 用currentSettings渲染,确保和服务端初始渲染一致 */} <NumberFormatter format={currentSettings.numberFormat} /> <CurrencySelector default={currentSettings.defaultCurrency} /> </div> ); } }
总结
优先选择方案1,这是SSR场景下最合理的解决方案,彻底解决闪烁问题;如果有技术限制则选择方案2;方案3作为兜底优化,能最大程度降低闪烁的影响。
内容的提问来源于stack exchange,提问作者mdmb
相关产品推荐
相关产品推荐

