本項(xiàng)目Github地址,歡迎star
目錄
由于服務(wù)端是無狀態(tài)的绞灼,所以服務(wù)端渲染和客戶端渲染并不相同. 最基本的就是我們封裝app時(shí), 使用無狀態(tài)的 < StaticRouter >來代替< BrowserRouter >, 使用來自于服務(wù)端的請(qǐng)求url來匹配路由懦趋。接下來我們會(huì)討論 context 屬性
// client
<BrowserRouter>
<App/>
</BrowserRouter>
// server (not the complete story)
<StaticRouter
location={req.url}
context={context}
>
<App/>
</StaticRouter>
當(dāng)你在客戶端渲染 < Redirect >, 瀏覽器地址欄會(huì)改變狀態(tài)使我們能看到新的頁(yè)面,然而在一個(gè)靜態(tài)的服務(wù)環(huán)境下, 我們不能夠改變app的狀態(tài)。代替的是, 我們將渲染的結(jié)果賦給context屬性. 如果我們找到了 context.url, 那么我們知道這個(gè)app重定向了. 這允許我們向服務(wù)端發(fā)送一個(gè)重定向請(qǐng)求
const context = {}
const markup = ReactDOMServer.renderToString(
<StaticRouter
location={req.url}
context={context}
>
<App/>
</StaticRouter>
)
if (context.url) {
// Somewhere a `<Redirect>` was rendered
redirect(301, context.url)
} else {
// we're good, send the response
}
添加明確的應(yīng)用內(nèi)容信息
路由只能添加 context.url. 但是你可能想要發(fā)送重定向的301或302的響應(yīng)。或許你在某些特殊的UI渲染后需要發(fā)送一個(gè)404響應(yīng), 又或者在客戶端沒有認(rèn)證的情況下發(fā)送401。 context屬性是屬于你的, 所以你可以任意改變她. 下面是分辨301與302重定向的方法凸舵。
const RedirectWithStatus = ({ from, to, status }) => (
<Route render={({ staticContext }) => {
// there is no `staticContext` on the client, so
// we need to guard against that here
if (staticContext)
staticContext.status = status
return <Redirect from={from} to={to}/>
}}/>
)
// somewhere in your app
const App = () => (
<Switch>
{/* some other routes */}
<RedirectWithStatus
status={301}
from="/users"
to="/profiles"
/>
<RedirectWithStatus
status={302}
from="/courses"
to="/dashboard"
/>
</Switch>
)
// on the server
const context = {}
const markup = ReactDOMServer.renderToString(
<StaticRouter context={context}>
<App/>
</StaticRouter>
)
if (context.url) {
// can use the `context.status` that
// we added in RedirectWithStatus
redirect(context.status, context.url)
}
404, 401, 或其他狀態(tài)
我們現(xiàn)在可以做到和上面一樣的事,創(chuàng)建一個(gè)包含想要內(nèi)容的組件失尖,當(dāng)收到不同的的狀態(tài)碼時(shí)可以在應(yīng)用的任何地方渲染該組件啊奄。
function Status({ code, children }) {
return (
<Route
render={({ staticContext }) => {
if (staticContext) staticContext.status = code;
return children;
}}
/>
);
}
現(xiàn)在,當(dāng)你想要給靜態(tài)內(nèi)容添加一個(gè)狀態(tài)碼時(shí)雹仿,你可以在應(yīng)用的任何地方渲染一種狀態(tài)增热。
function NotFound() {
return (
<Status code={404}>
<div>
<h1>Sorry, can’t find that.</h1>
</div>
</Status>
);
}
// somewhere else
<Switch>
<Route path="/about" component={About} />
<Route path="/dashboard" component={Dashboard} />
<Route component={NotFound} />
</Switch>;
組合所有內(nèi)容
雖然這不是一個(gè)真正的應(yīng)用,但是她展現(xiàn)了將所有內(nèi)容組合在一起所需的常規(guī)部分
import { createServer } from 'http'
import React from 'react'
import ReactDOMServer from 'react-dom/server'
import { StaticRouter } from 'react-router'
import App from './App'
createServer((req, res) => {
const context = {}
const html = ReactDOMServer.renderToString(
<StaticRouter
location={req.url}
context={context}
>
<App/>
</StaticRouter>
)
if (context.url) {
res.writeHead(301, {
Location: context.url
})
res.end()
} else {
res.write(`
<!doctype html>
<div id="app">${html}</div>
`)
res.end()
}
}).listen(3000)
然后是客戶端
import ReactDOM from 'react-dom'
import { BrowserRouter } from 'react-router-dom'
import App from './App'
ReactDOM.render((
<BrowserRouter>
<App/>
</BrowserRouter>
), document.getElementById('app'))
數(shù)據(jù)加載
要做到這一點(diǎn)有很多不同的方法胧辽,對(duì)此并沒有最佳的實(shí)踐。所以我們尋求多種方法的不同組合方式公黑,而不是規(guī)定或傾向某一種邑商。我們相信React Router可以在你的應(yīng)用的規(guī)則限制下找到一種合理的方式。
最主要的約束是你希望在頁(yè)面渲染前加載完數(shù)據(jù)凡蚜。React Router暴露了一個(gè)matchPath靜態(tài)函數(shù)人断,你可以用她來進(jìn)行路由匹配。你可以在服務(wù)端用這個(gè)函數(shù)來確定哪些依賴的數(shù)據(jù)是要在渲染前完成的朝蜘。
這種方法的特點(diǎn)是在進(jìn)行實(shí)際跳轉(zhuǎn)前設(shè)定好靜態(tài)匹配規(guī)則恶迈,在實(shí)際跳轉(zhuǎn)前就已經(jīng)知道要使用哪些數(shù)據(jù)。
const routes = [
{ path: '/',
component: Root,
loadData: () => getSomeData(),
},
// etc.
]
然后使用這些規(guī)則在應(yīng)用中渲染你的路由
import { routes } from './routes'
const App = () => (
<Switch>
{routes.map(route => (
<Route {...route}/>
))}
</Switch>
)
在服務(wù)端你可能會(huì)做這些谱醇;
import { matchPath } from 'react-router-dom'
// inside a request
const promises = []
// use `some` to imitate `<Switch>` behavior of selecting only
// the first to match
routes.some(route => {
// use `matchPath` here
const match = matchPath(req.url, route)
if (match)
promises.push(route.loadData(match))
return match
})
Promise.all(promises).then(data => {
// do something w/ the data so the client
// can access it then render the app
})
最后暇仲,客戶端需要獲取數(shù)據(jù)。我們并不是給你的應(yīng)用規(guī)定數(shù)據(jù)加載的模式副渴,但這些是在開發(fā)中常用的形式奈附。
你可能會(huì)對(duì)我們的進(jìn)行數(shù)據(jù)加載和靜態(tài)路由配置的React Router Config包感興趣。