如何避免React服务端渲染时浏览器重复发起相同异步请求
我之前在做React SSR的时候也碰到过一模一样的问题——服务端已经通过fetchInitialData获取了数据,结果客户端挂载组件时又调用了一次接口,白白浪费请求资源。你说的检查数据是否存在确实可行,但确实有更显式、更优雅的方案,下面分享几个常用的解决思路:
1. 在Action Creator中加入条件判断(最推荐)
把“是否需要发起请求”的逻辑封装到Redux的action creator里,让它自己判断当前state中是否已有数据,避免重复请求。这样组件里不管是服务端的fetchInitialData还是客户端的componentDidMount,都可以放心调用,不用额外判断:
// 你的getUsers action creator修改成这样 export const getUsers = () => (dispatch, getState) => { // 先从state里拿已有数据 const { users } = getState(); // 如果数据已经存在,直接返回resolved的Promise,不发起请求 if (users && users.length > 0) { return Promise.resolve(); } // 否则正常发起异步请求 return fetch('/api/users') .then(res => res.json()) .then(data => { dispatch({ type: 'GET_USERS_SUCCESS', payload: data }); }) .catch(err => { dispatch({ type: 'GET_USERS_FAILURE', payload: err }); }); };
这种方式把逻辑收敛到了action层,符合Redux的单一职责原则,组件代码也保持简洁,完全不用关心重复请求的问题。
2. 用高阶组件(HOC)封装数据获取逻辑
如果不想把判断逻辑放在action里,可以写一个专门处理SSR数据的HOC,把“是否在客户端重新获取数据”的逻辑抽离出来,让组件更干净:
const withSSRData = (Component, fetchInitialData) => { return class SSRDataWrapper extends React.Component { componentDidMount() { const { store } = this.props; // 根据你的state结构,检查对应数据是否存在 const hasData = store.getState().users && store.getState().users.length > 0; if (!hasData) { fetchInitialData(store.dispatch); } } render() { return <Component {...this.props} />; } }; }; // 然后修改组件的导出 export default connect( state => ({ users: state.users }), dispatch => bindActionCreators({ getUsers }, dispatch) )(withSSRData(Users, Users.fetchInitialData));
这个HOC可以复用在所有需要SSR数据的组件上,统一处理重复请求的问题,代码结构更清晰。
3. 利用服务端注入的初始状态标记
其实本质上和检查数据存在性类似,但可以更显式:服务端渲染完成后,会把Redux的初始状态注入到全局变量(比如window.__INITIAL_STATE__),客户端初始化store时会用这个状态。你可以在全局变量里加一个标记,记录哪些请求已经在服务端完成,比如:
// 服务端渲染时,在注入INITIAL_STATE的地方加个标记 const initialState = store.getState(); initialState._fetched = { users: true }; // 标记users数据已获取 // 客户端组件里 componentDidMount() { if (!window.__INITIAL_STATE__._fetched.users) { this.props.getUsers(); } }
不过这种方式需要维护额外的标记,不如前两种方案简洁,一般不推荐,但如果有特殊场景需要追踪哪些请求已执行,可以考虑。
为什么会出现重复请求?
简单说就是服务端和客户端是两个独立的执行环境:服务端通过fetchInitialData获取数据并渲染HTML,客户端接管页面后,组件挂载会触发componentDidMount,如果没有判断逻辑,就会再次调用接口。上面的方案都是通过不同方式让客户端“知道”数据已经存在,从而跳过重复请求。
内容的提问来源于stack exchange,提问作者Shikyo

