手把手从头开始教你,彻底理解服务端渲染原理( 四 )


part4: 异步数据的服务端渲染方案(数据注水与脱水)一、问题引入在平常客户端的React开发中,我们一般在组件的componentDidMount生命周期函数进行异步数据的获取 。但是,在服务端渲染中却出现了问题 。
现在我在componentDidMount钩子函数中进行Ajax请求:
import { getHomeList } from './store/actions'//......componentDidMount() {this.props.getList();}//......const mapDispatchToProps = dispatch => ({getList() {dispatch(getHomeList());}})复制代码//actions.jsimport { CHANGE_LIST } from "./constants";import axios from 'axios'const changeList = list => ({type: CHANGE_LIST,list})export const getHomeList = () => {return dispatch => {//另外起的本地的后端服务return axiosInstance.get('localhost:4000/api/news.json').then((res) => {const list = res.data.data;dispatch(changeList(list))})}}//reducer.jsimport { CHANGE_LIST } from "./constants";const defaultState = {name: 'sanyuan',list: []}export default (state = defaultState, action) => {switch(action.type) {case CHANGE_LIST:const newState = {...state,list: action.list}return newStatedefault:return state;}}复制代码好,现在启动服务 。
 

手把手从头开始教你,彻底理解服务端渲染原理

文章插图
 
现在页面能够正常渲染,但是打开网页源代码 。
 
手把手从头开始教你,彻底理解服务端渲染原理

文章插图
 
源代码里面并没有这些列表数据啊!那这是为什么呢?
 
让我们来分析一下客户端和服务端的运行流程,当浏览器发送请求时,服务器接受到请求,这时候服务器和客户端的store都是空的,紧接着客户端执行componentDidMount生命周期中的函数,获取到数据并渲染到页面,然而服务器端始终不会执行componentDidMount,因此不会拿到数据,这也导致服务器端的store始终是空的 。换而言之,关于异步数据的操作始终只是客户端渲染 。
现在的工作就是让服务端将获得数据的操作执行一遍,以达到真正的服务端渲染的效果 。
二、改造路由在完成这个方案之前需要改造一下原有的路由,也就是routes.js
import Home from './containers/Home';import Login from './containers/Login';export default [{path: "/",component: Home,exact: true,loadData: Home.loadData,//服务端获取异步数据的函数key: 'home'},{path: '/login',component: Login,exact: true,key: 'login'}}];复制代码此时客户端和服务端中编写的JSX代码也发生了相应变化
//客户端//以下的routes变量均指routes.js导出的数组<Provider store={store}><BrowserRouter><div>{routers.map(route => {<Route {...route} />})}</div></BrowserRouter></Provider>复制代码//服务端<Provider store={store}><StaticRouter><div>{routers.map(route => {<Route {...route} />})}</div></StaticRouter></Provider>复制代码其中配置了一个loadData参数,这个参数代表了服务端获取数据的函数 。每次渲染一个组件获取异步数据时,都会调用相应组件的这个函数 。因此,在编写这个函数具体的代码之前,我们有必要想清楚如何来针对不同的路由来匹配不同的loadData函数 。
在server/utils.js中加入以下逻辑
import { matchRoutes } from 'react-router-config';//调用matchRoutes用来匹配当前路由(支持多级路由)const matchedRoutes = matchRoutes(routes, req.path)//promise对象数组const promises = [];matchedRoutes.forEach(item => {//如果这个路由对应的组件有loadData方法if (item.route.loadData) {//那么就执行一次,并将store传进去//注意loadData函数调用后需要返回Promise对象promises.push(item.route.loadData(store))}})Promise.all(promises).then(() => {//此时该有的数据都已经到store里面去了//执行渲染的过程(res.send操作)})复制代码现在就可以安心的写我们的loadData函数,其实前面的铺垫工作做好后,这个函数是相当容易的 。
import { getHomeList } from './store/actions'Home.loadData = https://www.isolves.com/it/cxkf/bk/2020-05-12/(store) => {return store.dispatch(getHomeList())}复制代码//actions.jsexport const getHomeList = () => {return dispatch => {return axios.get('xxxx').then((res) => {const list = res.data.data;dispatch(changeList(list))})}}复制代码根据这个思路,服务端渲染中异步数据的获取功能就完成啦 。
三、数据的注水和脱水其实目前做了这里还是存在一些细节问题的 。比如当我将生命周期钩子里面的异步请求函数注释,现在页面中不会有任何的数据,但是打开网页源代码,却发现:


推荐阅读