不多廢話,全篇干貨。
說(shuō)SSR之前,先說(shuō)react component的一些注意點(diǎn)(這能說(shuō)明為啥我要這么預(yù)?。?/p>
- 盡量不要用
componentWillMount - 有Side Effect的操作應(yīng)該放在
componentDidMount中(比如, 調(diào)用api訪問(wèn)服務(wù)器取數(shù)據(jù)就是這樣的操作) - 不要再
constructor里面調(diào)用setState
前兩點(diǎn)都是官方說(shuō)法,所以一定有道理。這里并不去深究,如果有興趣,可以參考這篇文章。
然而,我又不希望server端太復(fù)雜,還是盡量走react的lifecycle,所以,在組件(準(zhǔn)確地說(shuō)是container組件)的constructor中,加入了服務(wù)器端預(yù)取的邏輯(這可能不是個(gè)好的做法),然后componentDidMount放入客戶(hù)端預(yù)取的邏輯。 所以,一個(gè)component大概是這樣子的(示例代碼,僅供參考):
class SomeContainer extends Component {
constructor(props) {
if (typeof document === 'undefined') {
props.dispatch(requestUserName()) // 這會(huì)觸發(fā)對(duì)服務(wù)器端的調(diào)用, 獲取數(shù)據(jù)
}
}
componentDidMount() {
if (this.props.username === "") {
this.setState({isLoading: true})
this.props.dispatch(requestUserName({
callabck: () => { this.setState({isLoading: false}) } // 異步執(zhí)行, 無(wú)法放在constructor中
}))
}
}
}
constructor部分做了判斷,因此只會(huì)在服務(wù)器端執(zhí)行, 而componentDidMount只會(huì)在客戶(hù)端執(zhí)行。
如果取數(shù)據(jù)的時(shí)候,不考慮setState, 那么這兩個(gè),可以合并起來(lái)放到constructor中, 從而省去componentDidMount,變成簡(jiǎn)化版:
class SomeContainer extends Component {
constructor(props) {
// 當(dāng)客戶(hù)端(browser)不需要setState的時(shí)候,可以合并成這個(gè)簡(jiǎn)寫(xiě)的方式
// 如果是在客戶(hù)端其他頁(yè)面導(dǎo)航過(guò)來(lái),會(huì)執(zhí)行并取數(shù)據(jù)。
// 如果是直接訪問(wèn)該頁(yè),服務(wù)端渲染,那么服務(wù)器端會(huì)執(zhí)行,取好數(shù)據(jù)。
// 此時(shí)客戶(hù)端不會(huì)重新取數(shù)據(jù)了
if (props.username === "") {
props.dispatch(requestUserName())
}
}
}
代碼里面的的注釋說(shuō)得很清楚這兩個(gè)寫(xiě)法不同的用意了。
OK, 組件本身就是這樣的。不管用的是什么庫(kù)(redux-thunk, redux-saga等),我個(gè)人理解的組件的設(shè)計(jì)道理就是這樣(目的是在服務(wù)器端也幾乎完全符合于類(lèi)似于客戶(hù)端的執(zhí)行流程和邏輯, 簡(jiǎn)單點(diǎn)說(shuō),就是加個(gè)服務(wù)端渲染不用額外干太多事)。
讓我詫異的是,現(xiàn)在居然沒(méi)有一個(gè)非常官方的SSR的做法(或者是有,只是我孤陋寡聞了。。。create-react-app或者next.js在這方面有效嗎?)。
另外,吐槽一下Javascript各個(gè)庫(kù)的升級(jí),webpack、react-router這些非常流行的庫(kù)升級(jí)時(shí)居然都是向后不兼容的,本來(lái)預(yù)估的一個(gè)“簡(jiǎn)單的”升級(jí)變成了一場(chǎng)浩浩蕩蕩的代碼重構(gòu),十分操蛋。
下一篇講我如何用選定的庫(kù)在整體流程上進(jìn)行SSR實(shí)現(xiàn)的。