简体   繁体   English

ReactJS服务器端渲染,setTimeout问题

[英]ReactJS server-side rendering, setTimeout issue

On my ReactJS App I used setTimeout to defer some Redux action: 在我的ReactJS应用程序上,我使用setTimeout推迟了一些Redux操作:

export const doLockSide = (lockObject) => (dispatch) => {
  const timerId = setTimeout(() => {
    dispatch({
      type: CONSTANTS.TOPICS_SET_CURRENT_TOPIC_LOCKED_SIDE,
      payload: { id: lockObject.topicId, side: lockObject.side, locked: false }
    });
  }, lockObject.unlockTimeout);

  dispatch({
    type: CONSTANTS.TOPICS_SET_CURRENT_TOPIC_LOCKED_SIDE,
    payload: { id: lockObject.topicId, side: lockObject.side, timerId, locked: true }
  });
};

The lockObject comes from the server, so this code is a part of async Redux actions chain. lockObject来自服务器,因此此代码是异步Redux操作链的一部分。 It worked fine, but when I tried to make this functionality to be a part of server side rendering process, it broke the App. 它工作正常,但是当我尝试使此功能成为服务器端渲染过程的一部分时,它破坏了该应用程序。 I understand the difference between Browser and NodeJS runtime environments and the difference between its implementations of setTimeout . 我了解Browser和NodeJS运行时环境之间的差异以及setTimeout实现之间的差异。 Specifically my timerId could not be processed by Node due to it's an object, while my Redux reducer treats it as an integer. 具体来说,由于Node是我的对象,因此我的timerId无法被Node处理,而我的Redux reducer将其视为整数。 But the main problem is that during server side rendering Node fires setTimeout callback on the server side... 但是主要问题是在服务器端渲染期间,Node在服务器端触发了setTimeout回调...

The question . 问题 I have some redux-based proccess that should be deferred in some cases including the App start. 我有一些基于Redux的过程,在某些情况下,包括App启动,应该推迟。 How can I do it satisfying the requirement of server-side rendering? 如何满足服务器端渲染的要求?

After some research I was able to apply the following approach. 经过研究,我能够采用以下方法。

1) Push the deferred action data into some special storage in case of server-side rendering, and run it "as is" in case of Browser: 1)如果是服务器端渲染,则将延迟的操作数据推入某些特殊的存储中,如果使用浏览器,则按原样运行它:

import { _postRender } from '../utils/misc';

const doLockSideUI = (dispatch, lockObject) => {
  // the body of previous version of doLockSide inner function
  const timerId = setTimeout(() => {/*...*/}, lockObject.unlockTimeout);
  dispatch(/*...*/);
};

export const doLockSide = (lockObject) => (dispatch) => {
  if(typeof window === 'undefined') { // server-side rendering case
    _postRender.actions.push({
      name: 'doLockSide',
      params: lockObject
    });
  }
  else { // Browser case
    doLockSideUI(dispatch, lockObject);
  }
};

Where utils/misc.js has the following entity: 其中utils/misc.js具有以下实体:

// to run actions on the Client after server-side rendering
export const _postRender = { actions: [] }; 

2) On the server I've imported that _postRender object form utils/misc.js and pushed it to render parameters when all redux-store data dependencies had been resolved: 2)在服务器上,我已将_postRender对象从utils/misc.js导入,并在解决了所有redux-store数据依赖关系后,将其推送到render参数:

const markup = renderToString(/*...*/);
const finalState = store.getState();
const params = { markup, finalState, postRender: { ..._postRender } };
_postRender.actions = []; // need to reset post-render actions
return res.status(status).render('index', params);

_postRender.actions has to be cleaned up, otherwise _postRender.actions.push from p.1 will populate it again and again each time the Client had been reloaded. _postRender.actions已被清理,否则_postRender.actions.push从第1页将每个客户端已经重新加载时间一次又一次地填充它。

3) Then I provided my post-render actions the same way as it is done for preloaded state. 3)然后,我提供了与预加载状态相同的渲染后操作。 In my case it is index.ejs template: 就我而言,它是index.ejs模板:

<div id="main"><%- markup %></div>
<script>
  var __PRELOADED_STATE__ = <%- JSON.stringify(finalState) %>;
  var __POST_RENDER__ = <%- JSON.stringify(postRender) %>;
</script>

4) Now I need to call my __POST_RENDER__ actions with given params. 4)现在,我需要使用给定的参数调用__POST_RENDER__动作。 For this purpose I updated my root component's did-mount hook and dispatch an additional action which handles the post-render action list: 为此,我更新了我的根组件的did-mount钩子,并调度了一个额外的操作来处理渲染后操作列表:

componentDidMount() {
  console.log('The App has been run successfully');
  if(window.__POST_RENDER__ && window.__POST_RENDER__.actions.length) {
    this.props.dispatch(runAfterRender(window.__POST_RENDER__.actions));
  }
}

Where runAfterRender is a new action that is being imported from ../actions/render : 其中runAfterRender是从../actions/render导入的新动作:

import { doLockSide } from './topic'

export const runAfterRender = (list) => (dispatch) => {
  list.forEach(action => {
    if(action.name === 'doLockSide') {
      dispatch(doLockSide(action.params));
    }
    // other actions?
  });
};

As you can see, it's just a draft and I was forced to import doLockSide action from p.1 and call it explicitly. 如您所见,这只是草稿,我被迫从p.1导入doLockSide操作并显式调用它。 I guess there may be a list of possible actions that could be called on the Client after server-side rendering, but this approach already works. 我猜可能在服务器端渲染后可能会在客户端上调用的可能动作列表,但是这种方法已经有效。 I wonder if there is a better way... 我想知道是否有更好的方法...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM