简体   繁体   English

服务器端 React Initial Render 导致重复的异步调用

[英]Server Side React Initial Render causes duplicated async calls

I'm using react-router 2.0 on the server.我在服务器上使用 react-router 2.0。 Some of my top-level route components depend on async data to be fetched and uses Redux to manage state.我的一些顶级路由组件依赖于要获取的异步数据并使用 Redux 来管理状态。 To deal with this, I'm using a static fetchData() method to return a Promise.all of async actions as suggested in the official Redux docs .为了解决这个问题,我使用了一个静态的fetchData()方法来返回一个Promise.all的异步操作,如官方 Redux 文档中所建议 It works pretty well - the initial render from the server comes with the appropriate data in the Redux store.它工作得很好——来自服务器的初始渲染带有 Redux 存储中的适当数据。

My issue comes from how to write these async calls in a way that will work on both the initial render on the server and subsequent renders on the client as different routes get loaded.我的问题来自如何编写这些异步调用,这种方式既适用于服务器上的初始渲染,又适用于客户端上的后续渲染,因为加载了不同的路由。 Currently, I make calls to the static fetchData() method in my top-level component's componentDidMount() , but this would cause the fetchData() to be called once the component mounts on the client.目前,我在顶级组件的componentDidMount()调用静态fetchData()方法,但是一旦组件安装在客户端上,这将导致fetchData() This is what I want when navigating to the route from another, but not on the initial render as we already made this call on the server.这是我从另一个导航到路由时想要的,但不是在初始渲染时,因为我们已经在服务器上进行了这个调用。

I've been struggling with writing my data fetching logic in a way that:我一直在努力以一种方式编写我的数据获取逻辑:

  1. Only gets called once on initial render on the server, not on the initial client render仅在服务器上的初始渲染上调用一次,而不是在客户端初始渲染上调用
  2. Will render when a user navigates in from another route.当用户从另一条路线导航时将呈现。

I was thinking of injecting a prop like { serverRendered: true } into the initial component but I only have access to the <RouterContext> when calling renderToString() .我正在考虑将像{ serverRendered: true }这样的道具注入初始组件,但在调用renderToString()时我只能访问<RouterContext> I took a peek at the source for but it doesn't seem like I can pass the property from that JSX tag either.我查看了源代码,但似乎我也无法从该 JSX 标记传递该属性。

I guess my questions would be:我想我的问题是:

  1. Is there a way to inject a property in <RouterContext> so that it passes down to the 'parent' top-level route component?有没有办法在<RouterContext>注入一个属性,以便它向下传递到“父”顶级路由组件? If not in <RouterContext> , is there another way to inject the property in?如果不在<RouterContext> ,是否有另一种方法可以注入该属性?
  2. If #1 isn't possible, is there a better way to handle fetchData() so the initial render won't cause fetchData() to be called on both the server and client?如果#1 不可能,是否有更好的方法来处理fetchData()以便初始渲染不会导致fetchData()在服务器和客户端上都被调用?

Here's a code sample of my server.jsx, the fetchData() handling middleware and an example top-level compnent.这是我的 server.jsx、fetchData() 处理中间件和示例顶级组件的代码示例。

I faced up with the same issue.我遇到了同样的问题。 Clearing some global value with setTimeout or checking Redux store for data existence didn't look good for me.使用setTimeout清除某些全局值或检查 Redux 存储中是否存在数据对我来说并不好。

I decided to check on which paths initial data was loaded, put this array as global value and check matches for route on componentDidMount .我决定检查加载了哪些路径初始数据,将此数组作为全局值并检查componentDidMount路由匹配。

My server index.js我的服务器 index.js

const pathsWithLoadedData= []; // here I store paths

const promises = matchRoutes(Routes, req.path)
    .map(({ route }) => {
        if (route.loadData) {
            // if route loads data and has path then save it
            route.path && pathsWithLoadedData.push(route.path);
            return route.loadData(store);
        } else {
            return Promise.resolve(null);
        }
    })

My render template我的渲染模板

<body>
   ...
   <script> 
      window.INITIALLY_LOADED_PATHS = ${JSON.stringify(pathsWithLoadedData)};
   </script>
   ...   
</body>

Some componentDidMount function一些 componentDidMount 函数

componentDidMount () {
    if (!wasFetchedInitially(this.props.route.path)) {
        this.props.fetchUsers();
    }
}

And helper和帮手

export const wasFetchedInitially = componentPath => {
    const pathIndex = window.INITIALLY_LOADED_PATHS.indexOf(componentPath);
    if (pathIndex < 0) return false;

    window.INITIALLY_LOADED_PATHS.splice(pathIndex, 1);
    return true;
};

我注入一个全局变量window.__INITAL_FROM_SERVER__ = true并从客户端使用setTimeout清除它。

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

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