Запретить браузеру выполнять те же асинхронные вызовы, что и сервер
Я следую этому уроку: https://crypt.codemancers.com/posts/2017-06-03-reactjs-server-side-rendering-with-router-v4-and-redux/ который я считаю "стандартным" способ сделать рендеринг на стороне сервера в реакции (?).
По сути, я использую реагирующий маршрутизатор (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
вызывается как на сервере, так и на клиенте.
Я мог бы конечно проверить, загружены ли какие-либо пользователи и не звонить getUsers
в componentDidMount
но должен быть лучший, явный способ не выполнять асинхронный вызов дважды.
1 ответ
После того, как я все больше и больше узнаю о реакции, я чувствую себя уверенно, у меня есть решение.
Я передаю browserContext
Объект вдоль всех визуализированных маршрутов, очень похоже на staticContext
на сервере. в browserContext
я установил два значения; isFirstRender
а также usingDevServer
, isFirstRender
верно только тогда, когда приложение отображается в первый раз и usingDevServer
верно только при использовании webpack-dev-server.
const store = createStore (redurs, initialReduxState, промежуточное программное обеспечение);
Входной файл для браузера:
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.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;
}
И затем последнее, что нужно сделать, - это обернуть все компоненты, отображаемые с помощью router, с этим компонентом HOC. Я сделал это, просто рекурсивно перебирая маршруты:
function wrapRoutes(routes) {
routes.forEach((route) => {
route.component = wrapInitialDataComponent(route.component);
if (route.routes) {
wrapRoutes(route.routes);
}
});
}
const routes = [ ... ];
wrapRoutes(routes);
И это, кажется, делает свое дело:)