当前位置: 首页 > 知识库问答 >
问题:

React Redux SSR如何抑制从componentDidMount调用的初始操作?

怀晋
2023-03-14

我能够在服务器端预加载所有需要的状态,并将这些初始状态传递给客户端应用程序的redux存储

我已启用redux logger以查看我的应用程序中发生了什么,并且正在从存储中重新提取状态。这是因为我的组件在componentDidMount期间调用了必要的操作。以下是减速器、动作和组件的示例:

// 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-中间件,它创建promise操作(实现,拒绝,挂起)。

在安装组件时调用该操作。我的理解是,即使在服务器端呈现组件以通知JS它存在,也会调用componentDidMount。组件本身不会重新安装。然而,我也读到,在componentDidMount中运行我的操作是一个更好的选择,我个人认为,如果我从componentdiddupdate调用它,它甚至不会工作(我们将进入一个无限循环)。

我想禁止调用这些初始操作,因为状态已经来自服务器端。我怎样才能做到这一点?

共有1个答案

洪鸿
2023-03-14

我希望有更好的解决方法,但到目前为止,我想到的是在接收数据时,根据服务器还是客户端接收数据,在reducer状态下设置ssr标志。

首先,ComponentDidMount不会在服务器上启动,只在客户端启动。因此,您需要另一种机制,在组件中使用react-routermatch函数以及静态数据获取函数。有关具有易于理解的服务器端数据获取html" target="_blank">机制的良好样板文件,请参阅universal react。但是,您仍然需要在ComponentDidMount方法中获取数据,否则,如果组件仅在客户端上呈现,或者在初始服务器呈现之后装载,则会出现问题。这就是在服务器上预加载数据,但在客户机上触发ComponentDidMount时再次请求数据时可能出现的“双重获取”问题。

下面是一个解决此问题的示例实现(请注意,我使用的是redux格式,其中操作和还原程序位于一个文件中):

Redux/模块/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)
}

其工作原理的基础是,如果服务器上发生了RECEIVE_DATA操作,则redux状态下的ssr标志设置为true。否则设置为false

如果在客户机上调用了getData函数,它首先调用fetchNeeded函数(将状态树的这个特定部分与一个函数一起传递给它,该函数可以分派一个操作来清除ssr标志)。如果ssr标志为true(当然我们是在客户机上),调用函数清除ssr标志后,fetchNeeded函数将返回false

因此,最后,如果服务器调用了我们的getData函数,并且数据已经存在,那么客户端将不会第一次从服务器重新请求数据(而是清除ssr标志)。清除标志后(或者如果从未设置标志),客户端将像正常情况一样从服务器请求数据。这允许我们避免对数据的初始冗余调用,但如果组件仅安装在客户机上或在客户机上刷新,则仍然有效。

希望这是有帮助的。

 类似资料:
  • 我是一名It专业的一年级学生。我们的任务是为最终项目的支付系统创建一个概念方案。我们决定做一个工资项目。下面是我使用JOptionFrame和JOptionPane的代码片段 由于分配给工资变量的数字取决于工作职位,为了计算准确,我必须从if语句中调用它。有人有解决方案吗?

  • 假设我有一个名为的扩展,my_extension一个名为的插件。 然后,我的控制器中有一个名为的函数。 如何通过通常的http GET请求从外部调用此?

  • 我是一名It专业的一年级学生。我们的任务是为最终项目的支付系统创建一个概念方案。我们决定做一个工资项目。下面是我使用JOptionFrame和JOptionPane的代码片段 由于分配给工资变量的数字取决于工作岗位,我必须从if语句中调用它,以便计算准确。有人有办法吗?

  • 问题内容: 每次我都会收到警告: 抑制它的最佳方法是什么?所有软件包都是最新的。 Conf: OSX带有Brew Python 2.7.10(默认,2015年7月13日,12:05:58),pandas == 0.17.0和matplotlib == 1.5.0 问题答案: 您可以禁止所有警告:

  • 我需要从JavaScript调用操作过程。我的操作接受2个输入参数和1个输出参数。下面是我的行动截图 有时还说 请求头字段access-control-allog-headers不允许access-control-allog-headers

  • 我目前在JUnit测试中遇到了障碍,需要一些帮助。所以我用静态方法得到了这个类,它将重构一些对象。为了简化起见,我举了一个小例子。这是我的工厂课程: 这是我的测试类: 基本上,我试图实现的是私有方法check String()应该被抑制(因此不会抛出异常),并且还需要验证方法check String()实际上是在方法factorObject()中调用的。 更新:抑制正常工作,代码如下: ... 但