Начальное состояние Redx-саги на стороне сервера
Я использую React-шаблон для моего приложения (с помощью ветки SSR). По какой-то причине нам нужен рендеринг на стороне сервера. Также у нас есть некоторый API покоя, и нам нужно вызвать один API перед всем API (для регистрации чего-либо). Я думаю, что для начального состояния мне нужно вызвать этот API (первый API, который нуждается в данных для регистрации) на сервере и сохранить данные ответов в хранилище и вернуть хранилище клиенту. В реакторе для создания магазина:
/**
* Create the store with asynchronously loaded reducers
*/
import { createStore, applyMiddleware, compose } from 'redux';
import { fromJS } from 'immutable';
import { routerMiddleware } from 'react-router-redux';
import createSagaMiddleware from 'redux-saga';
import createReducer from './reducers';
const sagaMiddleware = createSagaMiddleware();
export default function configureStore(initialState = {}, history) {
// Create the store with two middlewares
// 1. sagaMiddleware: Makes redux-sagas work
// 2. routerMiddleware: Syncs the location/URL path to the state
const middlewares = [
sagaMiddleware,
routerMiddleware(history),
];
const enhancers = [
applyMiddleware(...middlewares),
];
// If Redux DevTools Extension is installed use it, otherwise use Redux compose
/* eslint-disable no-underscore-dangle */
const composeEnhancers =
process.env.NODE_ENV !== 'production' &&
typeof window === 'object' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ : compose;
/* eslint-enable */
const store = createStore(
createReducer(),
fromJS(initialState),
composeEnhancers(...enhancers)
);
// Extensions
store.runSaga = sagaMiddleware.run;
store.asyncReducers = {}; // Async reducer registry
// Make reducers hot reloadable, see http://mxs.is/googmo
/* istanbul ignore next */
if (module.hot) {
module.hot.accept('./reducers', () => {
import('./reducers').then((reducerModule) => {
const createReducers = reducerModule.default;
const nextReducers = createReducers(store.asyncReducers);
store.replaceReducer(nextReducers);
});
});
}
return store;
}
а также для определения начального магазина:
function renderAppToStringAtLocation(url, { webpackDllNames = [], assets, lang }, callback) {
const memHistory = createMemoryHistory(url);
const store = createStore({}, memHistory);
syncHistoryWithStore(memHistory, store);
const routes = createRoutes(store);
const sagasDone = monitorSagas(store);
store.dispatch(changeLocale(lang));
match({ routes, location: url }, (error, redirectLocation, renderProps) => {
if (error) {
callback({ error });
} else if (redirectLocation) {
callback({ redirectLocation: redirectLocation.pathname + redirectLocation.search });
} else if (renderProps) {
renderHtmlDocument({ store, renderProps, sagasDone, assets, webpackDllNames })
.then((html) => {
const notFound = is404(renderProps.routes);
callback({ html, notFound });
})
.catch((e) => callback({ error: e }));
} else {
callback({ error: new Error('Unknown error') });
}
});
}
и для заполнения начального состояния я делаю некоторые изменения:
async function fetches (hostname) {
const domain = hostname.replace('.myExample.com', '').replace('www.', '');
const options = {
method: 'GET',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/example.api.v1.0+json',
}
};
const uri ='https://api.example.com/x/' + domain + '/details';
const shopDetail = await fetch(uri, options);
return shopDetail.json();
}
function renderAppToStringAtLocation(hostname ,url, { webpackDllNames = [], assets, lang }, callback) {
const memHistory = createMemoryHistory(url);
console.log('url :', hostname);
fetches(hostname).then( data => {
const store = createStore(data, memHistory);
syncHistoryWithStore(memHistory, store);
const routes = createRoutes(store);
const sagasDone = monitorSagas(store);
store.dispatch(changeLocale(lang));
match({ routes, location: url }, (error, redirectLocation, renderProps) => {
if (error) {
callback({ error });
} else if (redirectLocation) {
callback({ redirectLocation: redirectLocation.pathname + redirectLocation.search });
} else if (renderProps) {
renderHtmlDocument({ store, renderProps, sagasDone, assets, webpackDllNames })
.then((html) => {
const notFound = is404(renderProps.routes);
callback({ html, notFound });
})
.catch((e) => callback({ error: e }));
} else {
callback({ error: new Error('Unknown error') });
}
});
});
и тогда в консоли я получаю эту ошибку:
Unexpected properties "code", "data" found in initialState argument
passed to createStore. Expected to find one of the known reducer
property names instead: "route", "global", "language". Unexpected
properties will be ignored.
как это исправить?
1 ответ
Я думаю, для начального состояния мне нужно вызвать этот (первый API, который нуждается в данных для регистрации) API на сервере и сохранить данные ответа в хранилище и вернуть хранилище клиенту
Существует два разных решения, в зависимости от стороны, на которых должен выполняться вызов API.
Если это просто вызов на стороне сервера, ответ HTTP и последующая фаза SSR должны быть отложены до fetch
готово. Это может быть решено в express
оборачивая в функцию промежуточного программного обеспечения. Обычно такая схема используется при интеграции с внешними сервисами авторизации (Auth0, Passport и т. Д.), Но лучше обернуть информацию об авторизации в JWT
а не в INITIAL_STATE
,
Если вызов API можно выполнить со стороны клиента, просто используйте redux-saga
, Он может порождать выделенный процесс, который перехватит все избыточные действия перед вызовом API, а затем воспроизведет их соответственно. В этом случае initialState
объект должен содержать структурные поля без данных, которые будут заполнены позже после вызова API.