我能够在服务器端预加载所有需要的状态,并将这些初始状态传递给客户端应用程序的redux存储。
我已经启用了redux-logger来查看我的应用程序中发生了什么,并且正在从商店中重新获取状态。那是。因为我的组件在componentDidMount
期间调用必要的操作。这是一个示例reducer,action和component:
// action:
import axios from 'axios';
export function fetchNewsTop() {
return {
type: 'FETCH_NEWS_TOP',
payload: axios.get('/news/top')
};
}
// reducer:
export function newsArchive(state = {
pending: false,
response: { data: [] },
error: null
}, action) {
switch (action.type) {
case 'FETCH_NEWS_TOP_PENDING':
return { ...state, pending: true, response: { data: [] }, error: null };
case 'FETCH_NEWS_TOP_FULFILLED':
return { ...state, pending: false, response: action.payload.data, error: null };
case 'FETCH_NEWS_TOP_REJECTED':
return { ...state, pending: false, response: { data: [] }, error: action.payload };
default:
return state;
}
}
// component:
export class NewsArchiveFactory extends React.Component {
componentDidMount() {
this.props.fetchNewsTop();
}
render() {
if (this.props.news) {
return (
<NewsGrid items={this.props.news} />
);
}
return null;
}
}
我正在使用redux-promise-middleware
,它会创建承诺操作(已完成,已拒绝,待处理)。
安装组件时会调用该操作。我的理解是,即使在服务器端呈现组件以通知JS它存在,也会调用componentDidMount。组件本身不会被重新安装。但是,我还读到,在componentDidMount
中运行我的操作是一个更好的选择,我个人认为如果我从componentDidUpdate
调用它,它将无法工作(我们将进入无限循环)
我想抑制这些初始操作被调用,因为状态已经来自服务器端。我怎样才能做到这一点?
答案 0 :(得分:0)
我希望有更好的方法,但到目前为止,我所提出的是在收到数据时根据是否收到数据在reducer状态中设置ssr
标志在服务器或客户端上。
首先,ComponentDidMount
不会在服务器上触发,仅在客户端上触发。因此,您将需要另一种机制,它使用类似react-router
匹配函数的内容以及组件中的静态数据获取函数。有关具有易于理解的服务器端数据获取机制的良好样板,请参阅universal-react。但是,您仍然需要在ComponentDidMount
方法中获取数据,或者如果Component仅在客户端上呈现或在初始服务器呈现之后安装,则会中断。这就是引入潜在问题的原因&#34; double fetches&#34;当数据在服务器上预先加载但在ComponentDidMount
在客户端上触发时再次请求。
下面是一个解决此问题的示例实现(请注意,我使用的是redux格式,其中actions和reducers在一个文件中):
终极版/模块/ app.js:
import { basepath, fetcher, fetchNeeded, checkStatus } from '../../lib/api'
const isClient = typeof document !== 'undefined'
const SET_FLAG_SSR = 'app/SET_FLAG_SSR'
const REQUEST_DATA = 'app/REQUEST_DATA'
const RECEIVE_DATA = 'app/RECEIVE_DATA'
const DATA_FAIL = 'app/DATA_FAIL'
const INVALIDATE_DATA = 'app/INVALIDATE_DATA'
const initialState = {
ssr: false,
initialized: false,
isFetching: false,
didInvalidate: true,
conditionFail: false,
data: {}
}
export default function reducer(state = initialState, action = {}) {
switch (action.type) {
case SET_FLAG_SSR:
return Object.assign({}, state, {
ssr: action.flag
})
case INVALIDATE_DATA:
return Object.assign({}, state, {
didInvalidate: true
})
case REQUEST_DATA:
return Object.assign({}, state, {
isFetching: true,
conditionFail: false,
})
case RECEIVE_DATA:
return Object.assign({}, state, {
ssr: !isClient,
initialized: true,
isFetching: false,
conditionFail: false,
didInvalidate: false,
lastUpdated: action.receivedAt,
data: action.data,
})
case DATA_FAIL:
return Object.assign({}, state, {
isFetching: false,
conditionFail: true,
})
default:
return state
}
}
function setFlagSSR(flag) {
return {
type: SET_FLAG_SSR,
flag: flag,
}
}
function requestData() {
return {
type: REQUEST_DATA
}
}
function receiveData(json) {
return {
type: RECEIVE_DATA,
data: json,
receivedAt: Date.now()
}
}
function receiveFail(err) {
return {
type: DATA_FAIL,
err: err,
}
}
function fetchData(url) {
return (dispatch, getState) => {
dispatch(requestData())
return fetcher(url, { credentials: 'include' }, getState().cookie)
.then(response => response.json())
.then((json) => { return dispatch(receiveData(json)) })
.catch(err => {
dispatch(receiveFail(err))
})
}
}
export function getData() {
return (dispatch, getState) => {
const state = getState()
if (fetchNeeded(state.app, () => dispatch(setFlagSSR(false)))) {
return dispatch(fetchData(basepath + '/api/app'))
}
}
}
LIB / api.js:
import fetchPonyfill from 'fetch-ponyfill'
import Promise from 'bluebird'
const { fetch, Request, Response, Headers } = fetchPonyfill(Promise)
const isClient = typeof document !== 'undefined'
export const basepath = isClient ? '': `${__APIROOT__}`
// check HTTP response status and throw error if not valid
export function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response
} else {
let error = new Error(response.statusText)
error.response = response
throw error
}
}
// Generic Redux check to triage fetch requests and SSR flags
export function fetchNeeded(statebranch, clearFlagSSR) {
if (statebranch.isFetching) return false
if (statebranch.ssr && isClient) {
clearFlagSSR()
return false
}
return true
}
// Fetch wrapper to set defaults and inject cookie data on server (if present)
export function fetcher(url, config={}, cookie={}) {
let options = {}
let headers = new Headers();
// Base Headers
headers.set('Accept', 'application/json');
headers.set('Content-Type', 'application/json');
if (config.headers) {
Object.getOwnPropertyNames(config.headers).forEach(function(key, idx, array) {
headers.append(key, config.headers[key])
})
}
if (cookie.value && !isClient) {
//inject cookie data into options header for server-to-server requests
headers.set('Cookie', cookie.value)
}
options = {
credentials: config.credentials || 'same-origin',
method: config.method || 'get',
headers: headers,
}
if (config.body) options.body = config.body
return fetch(url, options)
}
这是如何工作的基础是如果服务器上发生ssr
操作,则redux状态中的true
标志设置为RECEIVE_DATA
。否则设置为false
。
如果在客户端上调用getData
函数,它首先调用fetchNeeded
函数(将状态树的这个特定片段与可以调度操作的函数一起传递给ssr
函数以清除{ {1}}旗帜)。如果ssr
标记为true
(当然我们在客户端),fetchNeeded
函数将在调用函数清除false
后返回ssr
标志。
所以最后,如果服务器调用我们的getData
函数和数据已经存在,那么客户端第一次就不会从服务器重新请求数据(而是清除{{ 1}}旗帜)。标志清除后(或者如果它从未在第一个位置设置),客户端将像正常一样从服务器请求数据。这允许我们避免对数据进行初始冗余调用,但如果组件仅安装在客户端上或在客户端上刷新,则仍然有效。
希望这很有帮助。