防止浏览器进行与服务器相同的异步调用
Prevent browser from making the same async calls as the server
我正在学习本教程:https://crypt.codemancers.com/posts/2017-06-03-reactjs-server-side-rendering-with-router-v4-and-redux/ 我认为这是 'standard' 在 React 中进行服务器端渲染的方式 (?)。
基本上发生的事情是我使用 React Router (v4) 制作一棵包含所有即将渲染的组件的树:
const promises = branch.map(({ route }) => {
return route.component.fetchInitialData
? route.component.fetchInitialData(store.dispatch)
: Promise.resolve();
});
等待所有这些承诺解决,然后调用 renderToString
。
在我的组件中,我有一个名为 fetchInitialData
的静态函数,它看起来像这样:
class Users extends React.Component {
static fetchInitialData(dispatch) {
return dispatch(getUsers());
}
componentDidMount() {
this.props.getUsers();
}
render() {
...
}
}
export default connect((state) => {
return { users: state.users };
}, (dispatch) => {
return bindActionCreators({ getUsers }, dispatch);
})(Users);
除了在服务器和客户端都调用 getUsers
之外,所有这些都很好用。
我当然可以检查是否有任何用户加载,而不是在 componentDidMount
中调用 getUsers
,但必须有更好的、明确的方法来避免两次异步调用。
在越来越熟悉 React 之后,我相当有信心找到解决方案。
我沿着所有呈现的路线传递一个 browserContext
对象,很像服务器上的 staticContext
。在 browserContext
我设置了两个值; isFirstRender
和 usingDevServer
。 isFirstRender
仅在应用首次呈现时为真,usingDevServer
仅在使用 webpack-dev-server 时为真。
const store = createStore(reducers, initialReduxState, middleware);
浏览器端入口文件:
const browserContext = {
isFirstRender: true,
usingDevServer: !!process.env.USING_DEV_SERVER
};
const BrowserApp = () => {
return (
<Provider store={store}>
<BrowserRouter>
{renderRoutes(routes, { store, browserContext })}
</BrowserRouter>
</Provider>
);
};
hydrate(
<BrowserApp />,
document.getElementById('root')
);
browserContext.isFirstRender = false;
USING_DEV_SERVER
在 webpack 配置文件中使用 webpack.DefinePlugin
定义
然后我编写了一个 HOC 组件,它仅在需要的情况下使用此信息来获取初始数据:
function wrapInitialDataComponent(Component) {
class InitialDatacomponent extends React.Component {
componentDidMount() {
const { store, browserContext, match } = this.props;
const fetchRequired = browserContext.usingDevServer || !browserContext.isFirstRender;
if (fetchRequired && Component.fetchInitialData) {
Component.fetchInitialData(store.dispatch, match);
}
}
render() {
return <Component {...this.props} />;
}
}
// Copy any static methods.
hoistNonReactStatics(InitialDatacomponent, Component);
// Set display name for debugging.
InitialDatacomponent.displayName = `InitialDatacomponent(${getDisplayName(Component)})`;
return InitialDatacomponent;
}
最后要做的是用这个 HOC 组件包装任何用 React router 渲染的组件。我通过简单地递归迭代路由来做到这一点:
function wrapRoutes(routes) {
routes.forEach((route) => {
route.component = wrapInitialDataComponent(route.component);
if (route.routes) {
wrapRoutes(route.routes);
}
});
}
const routes = [ ... ];
wrapRoutes(routes);
这似乎可以解决问题:)
我正在学习本教程:https://crypt.codemancers.com/posts/2017-06-03-reactjs-server-side-rendering-with-router-v4-and-redux/ 我认为这是 'standard' 在 React 中进行服务器端渲染的方式 (?)。
基本上发生的事情是我使用 React Router (v4) 制作一棵包含所有即将渲染的组件的树:
const promises = branch.map(({ route }) => {
return route.component.fetchInitialData
? route.component.fetchInitialData(store.dispatch)
: Promise.resolve();
});
等待所有这些承诺解决,然后调用 renderToString
。
在我的组件中,我有一个名为 fetchInitialData
的静态函数,它看起来像这样:
class Users extends React.Component {
static fetchInitialData(dispatch) {
return dispatch(getUsers());
}
componentDidMount() {
this.props.getUsers();
}
render() {
...
}
}
export default connect((state) => {
return { users: state.users };
}, (dispatch) => {
return bindActionCreators({ getUsers }, dispatch);
})(Users);
除了在服务器和客户端都调用 getUsers
之外,所有这些都很好用。
我当然可以检查是否有任何用户加载,而不是在 componentDidMount
中调用 getUsers
,但必须有更好的、明确的方法来避免两次异步调用。
在越来越熟悉 React 之后,我相当有信心找到解决方案。
我沿着所有呈现的路线传递一个 browserContext
对象,很像服务器上的 staticContext
。在 browserContext
我设置了两个值; isFirstRender
和 usingDevServer
。 isFirstRender
仅在应用首次呈现时为真,usingDevServer
仅在使用 webpack-dev-server 时为真。
const store = createStore(reducers, initialReduxState, middleware);
浏览器端入口文件:
const browserContext = {
isFirstRender: true,
usingDevServer: !!process.env.USING_DEV_SERVER
};
const BrowserApp = () => {
return (
<Provider store={store}>
<BrowserRouter>
{renderRoutes(routes, { store, browserContext })}
</BrowserRouter>
</Provider>
);
};
hydrate(
<BrowserApp />,
document.getElementById('root')
);
browserContext.isFirstRender = false;
USING_DEV_SERVER
在 webpack 配置文件中使用 webpack.DefinePlugin
然后我编写了一个 HOC 组件,它仅在需要的情况下使用此信息来获取初始数据:
function wrapInitialDataComponent(Component) {
class InitialDatacomponent extends React.Component {
componentDidMount() {
const { store, browserContext, match } = this.props;
const fetchRequired = browserContext.usingDevServer || !browserContext.isFirstRender;
if (fetchRequired && Component.fetchInitialData) {
Component.fetchInitialData(store.dispatch, match);
}
}
render() {
return <Component {...this.props} />;
}
}
// Copy any static methods.
hoistNonReactStatics(InitialDatacomponent, Component);
// Set display name for debugging.
InitialDatacomponent.displayName = `InitialDatacomponent(${getDisplayName(Component)})`;
return InitialDatacomponent;
}
最后要做的是用这个 HOC 组件包装任何用 React router 渲染的组件。我通过简单地递归迭代路由来做到这一点:
function wrapRoutes(routes) {
routes.forEach((route) => {
route.component = wrapInitialDataComponent(route.component);
if (route.routes) {
wrapRoutes(route.routes);
}
});
}
const routes = [ ... ];
wrapRoutes(routes);
这似乎可以解决问题:)