防止浏览器与服务器进行相同的异步调用

时间:2018-05-21 22:05:26

标签: javascript reactjs react-router react-redux react-ssr

我正在关注本教程:https://crypt.codemancers.com/posts/2017-06-03-reactjs-server-side-rendering-with-router-v4-and-redux/我认为这是'标准'在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之外,所有这些都很有效。

我当然可以检查是否已加载任何用户而不是getUsers中的componentDidMount,但必须有一种更好,更明确的方式来不进行两次异步调用。

1 个答案:

答案 0 :(得分:0)

在越来越熟悉反应后,我觉得我有一个解决方案。

我在所有渲染路径上传递browserContext对象,就像服务器上的staticContext一样。在browserContext我设置了两个值; isFirstRenderusingDevServerisFirstRender仅在第一次呈现应用时才为true,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;
}

然后最后要做的事情是将使用react路由器呈现的任何组件与此HOC组件一起包装。我通过简单地递归遍历路径来做到这一点:

function wrapRoutes(routes) {
    routes.forEach((route) => {
        route.component = wrapInitialDataComponent(route.component);

        if (route.routes) {
            wrapRoutes(route.routes);
        }
    });
}

const routes = [ ... ];

wrapRoutes(routes);

这似乎可以解决问题:)