服务器端React初始渲染导致重复的异步调用。

6
我在服务器上使用 react-router 2.0。我的一些顶层路由组件需要获取异步数据,并使用 Redux 管理状态。为了解决这个问题,我使用一个静态的 fetchData() 方法来返回异步操作的 Promise.all,正如官方 Redux 文档所建议的那样。它运行得非常好 - 来自服务器的初始渲染带有 Redux 存储中的适当数据。
我的问题源自于如何编写这些异步调用的方式,使其在服务器的初始呈现和随后的客户端呈现(不同的路由被加载时)均可正常工作。目前,在我的顶层组件的componentDidMount()内调用静态的 fetchData()方法,但这将导致在客户端的组件挂载时调用fetchData()。当从其他路由导航到该路由时,这是我想要的,但在初始呈现时我们已经在服务器上进行了此调用,不需要再次调用。
我一直在苦苦思索如何以以下方式编写我的数据提取逻辑:
  1. 仅在服务器上初始呈现时调用一次,而不是在客户端初始呈现时调用
  2. 在用户从另一个路由导航时呈现。
我考虑将类似于{ serverRendered: true }的属性注入到初始组件中,但是只有在调用renderToString()时才能访问<RouterContext>。我查看了源代码,但似乎我也无法从 JSX 标记中传递该属性。
我的问题如下:
  1. 是否有一种方法可以在<RouterContext>中注入属性,使其向“父”顶层路由组件传递?如果不能在<RouterContext>中完成,是否存在其他注入属性的方法?
  2. 如果 #1 不可行,是否有更好的处理fetchData()的方法,使得初始呈现不会导致在服务器和客户端上都调用fetchData()
这里是我的 server.jsx、fetchData() 处理中间件和一个顶级组件的示例代码。

你是否遇到了同样的问题?你有找到任何解决方案吗?我也在考虑在页面加载时使用一个标志,例如 { serverRendered: true },然后在之后将其关闭。 - Mahdi Pedram
嘿 @mahdipedram。我从来没有找到一个很好的解决方案来解决这个问题。我仍然使用静态的 fetchData() 方法进行初始的服务器端加载,并在 componentDidMount() 中检查 Redux 数据是否存在,然后再发出所需数据的另一个调用。这感觉有些笨拙,但它能够工作。如果你碰巧找到了更好的方法,请在这里发布! - daleee
2个回答

1
我遇到了相同的问题。使用setTimeout清除某些全局值或检查Redux存储器中是否存在数据对我来说不太好。我决定检查加载初始数据的路径,将此数组作为全局值,并在 'componentDidMount'中检查路由匹配情况。我的服务器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);
        }
    })

我的渲染模板

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

一些componentDidMount函数。
componentDidMount () {
    if (!wasFetchedInitially(this.props.route.path)) {
        this.props.fetchUsers();
    }
}

和助手

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;
};

谢谢,这解决了我的问题。同时也帮助我将Lighthouse得分从可怜的24提升到了令人尊敬的70。 - Hisham Ahamad

0

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接