不多废话,全篇干货。
说SSR之前,先说react component的一些注意点(这能说明为啥我要这么预取):
- 尽量不要用
componentWillMount
- 有Side Effect的操作应该放在
componentDidMount
中(比如, 调用api访问服务器取数据就是这样的操作) - 不要再
constructor
里面调用setState
前两点都是官方说法,所以一定有道理。这里并不去深究,如果有兴趣,可以参考这篇文章。
然而,我又不希望server端太复杂,还是尽量走react的lifecycle,所以,在组件(准确地说是container组件)的constructor中,加入了服务器端预取的逻辑(这可能不是个好的做法),然后componentDidMount
放入客户端预取的逻辑。 所以,一个component大概是这样子的(示例代码,仅供参考):
class SomeContainer extends Component {
constructor(props) {
if (typeof document === 'undefined') {
props.dispatch(requestUserName()) // 这会触发对服务器端的调用, 获取数据
}
}
componentDidMount() {
if (this.props.username === "") {
this.setState({isLoading: true})
this.props.dispatch(requestUserName({
callabck: () => { this.setState({isLoading: false}) } // 异步执行, 无法放在constructor中
}))
}
}
}
constructor
部分做了判断,因此只会在服务器端执行, 而componentDidMount
只会在客户端执行。
如果取数据的时候,不考虑setState
, 那么这两个,可以合并起来放到constructor
中, 从而省去componentDidMount
,变成简化版:
class SomeContainer extends Component {
constructor(props) {
// 当客户端(browser)不需要setState的时候,可以合并成这个简写的方式
// 如果是在客户端其他页面导航过来,会执行并取数据。
// 如果是直接访问该页,服务端渲染,那么服务器端会执行,取好数据。
// 此时客户端不会重新取数据了
if (props.username === "") {
props.dispatch(requestUserName())
}
}
}
代码里面的的注释说得很清楚这两个写法不同的用意了。
OK, 组件本身就是这样的。不管用的是什么库(redux-thunk, redux-saga等),我个人理解的组件的设计道理就是这样(目的是在服务器端也几乎完全符合于类似于客户端的执行流程和逻辑, 简单点说,就是加个服务端渲染不用额外干太多事)。
让我诧异的是,现在居然没有一个非常官方的SSR的做法(或者是有,只是我孤陋寡闻了。。。create-react-app
或者next.js
在这方面有效吗?)。
另外,吐槽一下Javascript各个库的升级,webpack、react-router这些非常流行的库升级时居然都是向后不兼容的,本来预估的一个“简单的”升级变成了一场浩浩荡荡的代码重构,十分操蛋。
下一篇讲我如何用选定的库在整体流程上进行SSR实现的。