Запретить браузеру выполнять те же асинхронные вызовы, что и сервер - PullRequest
0 голосов
/ 22 мая 2018

Я придерживаюсь этого урока: 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 Ответ

0 голосов
/ 28 мая 2018

После того, как я все больше и больше узнаю о реакции, я чувствую себя довольно уверенно, у меня есть решение.

Я передаю объект browserContext по всем отображаемым маршрутам, очень похоже на staticContext на сервере.В browserContext я установил два значения;isFirstRender и usingDevServer.isFirstRender имеет значение true только тогда, когда приложение отображается в первый раз, а usingDevServer - только true при использовании webpack-dev-server.

const store = createStore (redurs, 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.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);

И это, похоже, делает свое дело:)

...