在server路由中代码大致是这样的,在服务端获取到get请求以后,匹配路径,如果路径path是有映射页面组件的,获取到此组件并渲染,这就是我们的第一步:后端拦截路由,根据路径找到需要渲染的react页面组件。
2)如上图,匹配到组件以后,执行了组件的getInitialProps方法(和nextjs的命名保持一致),此方法是一个封装的静态方法,主要用于获取初始化所需要的ajax数据,在服务端会同步获取,而后通过ssrData参数传入组件prorps并执行组件渲染。 此方法在客户端依然是异步请求。 这一步比较重要,为什么我们需要一个静态方法,而不是直接把请求写在willmount中呢。 因为在服务端使用renderToString渲染组件时,生命周期只会执行到willmount之后的第一次render,在willmount内部,请求是异步的,第一次render完成的时候,异步的数据都没有获取到,这个时候renderToString就已经返回了。 那我们页面的初始化数据就没有了,返回的html不是我们所期望的。 因此定义了一个静态方法,在组件实例化之前获取到这个方法,同步执行,数据获取完成后,通过props把数据传入给组件进行渲染。 那么这个方法是如何实现的呢? 我们根据代码截图来看base.js:
首先在client的pages里新建一个base组件,base继承React.Component,所有pages里的页面组件都需要继承这个base,base有一个静态方法getInitialProps,此方法主要是返回组件初始化需要的异步数据。 如果有初始化的ajax请求,就应该重写在此方法里,并且return数据对象。 constructor判断了页面组件是否有初始化定义的state静态方法,有的话传递给组件实例化的state对象,如果props有传入ssrData,把ssrData传递值给组件state对象。 base中的componentWillMount会判断是否还需要去执行getInitialProps方法,如果在服务端渲染的时候,数据已经在组件实例化之前同步获取并传入了props,所以忽略。 如果在客户端环境,分两种情况,第一种:用户第一次进到页面,这时候是服务端去请求的数据,服务端获取到数据后在服务端渲染组件,同时也会把数据存放在html的script代码中,定义一个全局变量ssrData,如下图,react在注册单页面应用并且同构的时候会把全局ssrData传递给页面组件,这个时候页面组件在客户端同构渲染的时候,就可以延续使用服务端之前的数据,这样也保持了同构的一致性,也避免了一次重复请求。 第二种情况:就是当前用户在单页面之中切换路由,这样就没有服务端渲染,那么就执行getInitialProps方法,把数据直接返回给state,几乎等同于在willmount中执行请求。 这样封装我们就可以用一套代码兼容服务端渲染和单页面渲染。
client/app.js
再看看如何写页面组件,下面是页面组件Index的截图,Index继承Base,定义了静态state,组件constructor方法会把此对象传递给组件实例化的state对象中,之所以用静态方法来写默认数据,是想保证定义的默认state先传递给实例对象的state,接口请求传递的props数据后传递给实例对象的state。 为什么不直接写state属性而要加static,因为state属性会执行在constructor之后,这样会覆盖constructor定义的state,也就是会覆盖我们getInitialProps返回的数据。