本項(xiàng)目Github地址,歡迎star
目錄
這篇指南是是用來解釋React Router使用時(shí)的心智模型。我們將其稱為“動態(tài)路由”,這與你可能更熟悉的“靜態(tài)路由”完全不同。
靜態(tài)路由
如果你使用過Rails,Express,Ember,Angular等框架,那么你已經(jīng)使用過了靜態(tài)路由。 在這些框架中,當(dāng)你在進(jìn)行任何渲染之前,會將路由聲明為應(yīng)用初始化的一部分。React Router pre-v4在大多數(shù)情況下也是靜態(tài)的。下面我們來看看如何在express中配置路由:
// Express Style routing:
app.get("/", handleIndex);
app.get("/invoices", handleInvoices);
app.get("/invoices/:id", handleInvoice);
app.get("/invoices/:id/edit", handleInvoiceEdit);
app.listen();
請注意在應(yīng)用監(jiān)聽請求之前是如何聲明路由的。我們使用的客戶端路由與其是相似當(dāng)。在Angular中,你可以預(yù)先聲明路由,然后在渲染之前將它們導(dǎo)入頂級AppModule中:
// Angular Style routing:
const appRoutes: Routes = [
{
path: "crisis-center",
component: CrisisListComponent
},
{
path: "hero/:id",
component: HeroDetailComponent
},
{
path: "heroes",
component: HeroListComponent,
data: { title: "Heroes List" }
},
{
path: "",
redirectTo: "/heroes",
pathMatch: "full"
},
{
path: "**",
component: PageNotFoundComponent
}
];
@NgModule({
imports: [RouterModule.forRoot(appRoutes)]
})
export class AppModule {}
Ember有一個(gè)常規(guī)的routes.js文件,在構(gòu)建時(shí)會讀取這個(gè)文件當(dāng)內(nèi)容并導(dǎo)入到應(yīng)用中。這會在應(yīng)用渲染前進(jìn)行。
// Ember Style Router:
Router.map(function() {
this.route("about");
this.route("contact");
this.route("rentals", function() {
this.route("show", { path: "/:rental_id" });
});
});
export default Router;
盡管API不同,但她們都遵循“靜態(tài)路由”模型。React Router遵循這個(gè)模型直到v4。但是要成功使用React Router,你需要忘記這一切。
背景
坦率地說,我們React Router直到v2版本所采取的開發(fā)方向感到非常沮喪。 我們(Michael和Ryan)感覺現(xiàn)在采用的這種路由方式受限于API,我們正在重新實(shí)現(xiàn)React(生命周期等)的部分,并且它與React給我們創(chuàng)造UI的心智模型不匹配。當(dāng)我們在研討會之前在一家酒店的走廊互相討論時(shí)。我們互相問道:“如果我們使用我們在之前討論的模式構(gòu)建路由會是什么樣子?”開發(fā)只需要幾個(gè)小時(shí),我們就有了一個(gè)驗(yàn)證的概念,我們知道她是未來我們想要路由。 我們最終意識到API并不應(yīng)該在React的“外部”,這是一個(gè)由React的其余部分組成并自然落實(shí)到位的API。我們認(rèn)為你會喜歡她。
動態(tài)路由
動態(tài)路由指的是在應(yīng)用渲染時(shí)發(fā)生的路由,而不是在正在運(yùn)行的應(yīng)用之外配置或規(guī)定好的路由。這意味著幾乎所有東西都是React Router中的一個(gè)組件。接下來看一下這個(gè)API是如何工作的:
首先,創(chuàng)建一個(gè)路由組件,將其渲染在應(yīng)用的頂部。
// react-native
import { NativeRouter } from "react-router-native";
// react-dom (what we'll use here)
import { BrowserRouter } from "react-router-dom";
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
el
);
接下來,創(chuàng)建一個(gè)鏈接組件將其鏈接到一個(gè)新的地址。
const App = () => (
<div>
<nav>
<Link to="/dashboard">Dashboard</Link>
</nav>
</div>
);
最后,當(dāng)用戶查看‘/dashboard'路徑時(shí),渲染該路由并展示對應(yīng)UI
const App = () => (
<div>
<nav>
<Link to="/dashboard">Dashboard</Link>
</nav>
<div>
<Route path="/dashboard" component={Dashboard} />
</div>
</div>
);
Route將渲染<Dashboard {... props} />,props是一些看起來像{match,location,history}這樣的特定的路由的屬性。如果用戶不在‘/dashboard'路徑下,那么Route將不渲染任何東西。這就是它的全部內(nèi)容。
嵌套路由
很多路由庫都有一些“嵌套路由”的概念。如果你使用過React Router V4以前的版本,那么你就會知道我們也是這樣做的。然而當(dāng)你從靜態(tài)路由配置移動到動態(tài)渲染路由時(shí),該如何“嵌套路由”呢?
const App = () => (
<BrowserRouter>
{/* here's a div */}
<div>
{/* here's a Route */}
<Route path="/tacos" component={Tacos} />
</div>
</BrowserRouter>
);
// when the url matches `/tacos` this component renders
const Tacos = ({ match }) => (
// here's a nested div
<div>
{/* here's a nested Route,
match.url helps us make a relative path */}
<Route path={match.url + "/carnitas"} component={Carnitas} />
</div>
);
如上代碼,React Router并沒有嵌套的API,Router僅僅是一個(gè)組件,就想一個(gè)普通的div一樣。
響應(yīng)路由
設(shè)想一下用戶導(dǎo)航到'/invoice'。你的應(yīng)用需要適用于不同的尺寸的屏幕,當(dāng)她們具有較窄的視口時(shí),你只需向其顯示發(fā)票列表和到發(fā)票詳情的鏈接。她們可以導(dǎo)航到更深入的層級。
Small Screen
url: /invoices
+----------------------+
| |
| Dashboard |
| |
+----------------------+
| |
| Invoice 01 |
| |
+----------------------+
| |
| Invoice 02 |
| |
+----------------------+
| |
| Invoice 03 |
| |
+----------------------+
| |
| Invoice 04 |
| |
+----------------------+
在更大的屏幕上,我們會想要顯示一個(gè)類別-詳細(xì)內(nèi)容的視圖,其中導(dǎo)航位于左側(cè),詳情或特定發(fā)票顯示在右側(cè)。
Large Screen
url: /invoices/dashboard
+----------------------+---------------------------+
| | |
| Dashboard | |
| | Unpaid: 5 |
+----------------------+ |
| | Balance: $53,543.00 |
| Invoice 01 | |
| | Past Due: 2 |
+----------------------+ |
| | |
| Invoice 02 | |
| | +-------------------+ |
+----------------------+ | | |
| | | + + + | |
| Invoice 03 | | | + | | | |
| | | | | | + | + | |
+----------------------+ | | | | | | | | |
| | +--+-+--+--+--+--+--+ |
| Invoice 04 | |
| | |
+----------------------+---------------------------+
現(xiàn)在暫??紤]一下兩種屏幕尺寸的/invoice URL,她是大尺寸屏幕的有效路由嗎?我們應(yīng)該把右邊的東西放在什么位置?
Large Screen
url: /invoices
+----------------------+---------------------------+
| | |
| Dashboard | |
| | |
+----------------------+ |
| | |
| Invoice 01 | |
| | |
+----------------------+ |
| | |
| Invoice 02 | ??? |
| | |
+----------------------+ |
| | |
| Invoice 03 | |
| | |
+----------------------+ |
| | |
| Invoice 04 | |
| | |
+----------------------+---------------------------+
在大屏幕上,'/invoice'不是有效的路線,但在小屏幕上她是有效的,更有趣的是,請考慮一個(gè)擁尺寸手機(jī)的人。她/他可以用橫屏和豎屏兩種方式來查看手機(jī)頁面。突然間,我們有足夠的空間來顯示類別-詳情這種UI,所以你應(yīng)該立即重定向!
React Router在先前版本的靜態(tài)路由并沒有真正成功的答案。但是,當(dāng)路由是動態(tài)的時(shí),你可以聲明性地重組這些功能。如果你開始考慮將路由作為UI而不是靜態(tài)配置,那么你會寫出以下代碼:
const App = () => (
<AppLayout>
<Route path="/invoices" component={Invoices} />
</AppLayout>
);
const Invoices = () => (
<Layout>
{/* always show the nav */}
<InvoicesNav />
<Media query={PRETTY_SMALL}>
{screenIsSmall =>
screenIsSmall ? (
// small screen has no redirect
<Switch>
<Route exact path="/invoices/dashboard" component={Dashboard} />
<Route path="/invoices/:id" component={Invoice} />
</Switch>
) : (
// large screen does!
<Switch>
<Route exact path="/invoices/dashboard" component={Dashboard} />
<Route path="/invoices/:id" component={Invoice} />
<Redirect from="/invoices" to="/invoices/dashboard" />
</Switch>
)
}
</Media>
</Layout>
);
當(dāng)用戶將手機(jī)從縱向旋轉(zhuǎn)到橫向時(shí),此代碼會自動將其重定向到詳情頁。該組有效路由將根據(jù)用戶手中的移動設(shè)備的動態(tài)特性而改變。
這只是一個(gè)例子。我們還有許多其他問題可以討論,我們總結(jié)一下規(guī)律:我們應(yīng)該考慮組件,而不是靜態(tài)路由??紤]如何使用React的聲明可組合性來解決問題,因?yàn)閹缀趺總€(gè)“React Router問題”都可能是“React問題”。