這是我學習react-router-dom@5.1.2時,爲了加深本身對react-router-dom的理解和幫助一些英文很差的同窗,對官方文檔進行了翻譯,本人水平有限,若有理解和翻譯錯誤,歡迎你們指正。官網地址html
要在web應用中開始使用React Router,您將須要一個React Web應用程序.若是您須要建立一個,咱們建議您嘗試Create React App。這是一個很是流行的工具,可與React Router很好地配合使用。node
首先,安裝create-react-app並使用它建立一個新項目。react
您可使用npm或yarn從公共npm註冊表中安裝React Router。因爲咱們構建的是web app,所以在本指南中將使用react-router-dom。webpack
npm install -g create-react-app // 全局安裝 create-react-app create-react-app demo-app // 建立一個react項目 cd demo-app // 進入react項目目錄 npm install react-router-dom // 安裝react-router-dom
接下來,將如下兩個示例之一複製/粘貼到src/App.js中。git
在此示例中,路由器處理了3個「頁面」:主頁、關於頁面和用戶頁面。當您點擊不一樣的<Link>時,這個路由會渲染匹配的<Route>。github
注意:其實<Link>最後渲染出來是一個有真實href的標籤,所以使用鍵盤導航或屏幕閱讀器的人也可使用react-router-dom。web
import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter, Switch, Route, Link } from 'react-router-dom'; function Home(props) { console.log('Home=>', props); return <h2>Home</h2> } function About(props) { console.log('About=>', props); return <h2>About</h2>; } function Users(props) { console.log('Users=>', props); return <h2>Users</h2>; } function App() { return <BrowserRouter> <div> <nav> <ul> <li> <Link to={'/'}>Home</Link> </li> <li> <Link to={'/about'}>About</Link> </li> <li> <Link to={'/users'}>Users</Link> </li> </ul> </nav> {/* <Switch>經過查找全部的子<Route>並渲染與當前URL匹配的第一個<Route>的內容 */} <Switch> <Route path={'/about'}> <About /> </Route> <Route path={'/users'} children={<Users />}/> <Route path={'/'}> <Home /> </Route> </Switch> </div> </BrowserRouter> } ReactDOM.render(<App />, document.querySelector('#root'));
此示例顯示了嵌套路由的工做方式。路線/topics會加載Topics組件,在這個組件上的path:id值上有條件地渲染任何其餘<Route>。ajax
import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter, Switch, Route, Link, useRouteMatch, useParams } from 'react-router-dom'; function Home(props) { console.log('Home=>', props); return <h2>Home</h2> } function About(props) { console.log('About=>', props); return <h2>About</h2>; } function Topic() { let { topicId } = useParams(); return <h3>Requested topic ID: {topicId}</h3> } function Topics() { const match = useRouteMatch(); console.log('match=>', match); return ( <div> <h2>Topics</h2> <ul> <li> <Link to={`${match.url}/components`}>Components</Link> </li> <li> <Link to={`${match.url}/props-v-state`}>Props v. State</Link> </li> </ul> {/* Topics頁面有本身的<Switch>,其中包含更多的路線,創建在/topics路徑之上 您能夠將第二個<Route>視爲全部主題的「索引」頁面,或者當未選擇任何主題時顯示的頁面 */} <Switch> <Route path={`${match.path}/:topicId`}> <Topic /> </Route> <Route path={match.path}> <h3>Please select a topic.</h3> </Route> </Switch> </div> ); } function App() { return <BrowserRouter> <ul> <li> <Link to={'/'}>Home</Link> </li> <li> <Link to={'/about'}>About</Link> </li> <li> <Link to={'/topics'}>Topics</Link> </li> </ul> <Switch> <Route path={'/about'}> <About /> </Route> <Route path={'/topics'}> <Topics /> </Route> <Route path={'/'}> <Home /> </Route> </Switch> </BrowserRouter> } ReactDOM.render(<App />, document.querySelector('#root'));
但願這些示例能讓您對使用React Router建立web app有點感受。繼續閱讀能夠了解有關React Router中主要組件的更多信息!express
React Router中的組件主要分爲三類:npm
在Web應用程序中使用的全部組件都應從react-router-dom導入。
每一個React Router應用程序的核心應該是路由器組件。對於Web項目,react-router-dom提供<BrowserRouter>和<HashRouter>路由器。二者之間的主要區別在於它們存儲URL和與Web服務器通訊的方式。
要使用路由器,只需確保將其渲染在元素層次結構的根目錄下便可。 一般,您會將頂級<App>元素包裝在路由器中,以下所示:
import React from "react"; import ReactDOM from "react-dom"; import { BrowserRouter } from "react-router-dom"; function App() { return <h1>Hello React Router</h1>; } ReactDOM.render( <BrowserRouter> <App /> </BrowserRouter>, document.getElementById("root") );
有兩個路線匹配組件:Switch和Route。渲染<Switch>時,它會搜索其子元素<Route>,以查找其路徑與當前URL匹配的元素。當找到一個時,它將渲染該<Route>並忽略全部其餘路由。這意味着您應該將<Route>包含更多特定路徑(一般較長)的路徑放在不那麼特定路徑以前。
若是沒有<Route>匹配,則<Switch>不渲染任何內容(null)。
import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter, Switch, Route } from 'react-router-dom'; function App() { return <div> <Switch> {/* 若是當前URL是/ about,那麼將渲染此路由,而其他路由將被忽略 */} <Route path={'/about'}> <h2>About</h2> </Route> {/* 請注意這兩個路由的順序。 更具體的path="/contact/id"位於path="/contact"以前,所以在查看單個聯繫人時將顯示這個<Route> */} <Route path={'/contact/:id'}> <h2>Contact</h2> </Route> <Route path={'/contact'}> <h2>AllContact</h2> </Route> {/* 若是先前的路由均未呈現任何內容,則此路由將充當後備路徑。 重要提示:路徑="/"的路線將始終匹配任何路徑的URL,由於全部URL均以/開頭。 因此這就是爲何咱們把這這個<Route>放在最後 */} <Route path={'/'}> <h2>Home</h2> </Route> </Switch> </div> } ReactDOM.render(<BrowserRouter> <App /> </BrowserRouter>, document.querySelector('#root'));
⚠️ 須要注意的重要一件事是<Route path>匹配URL的開頭,而不是整個開頭。因此,<Route path ="/">將始終與任意一個URL匹配。所以,咱們一般將此<Route>放在<Switch>的最後。另外一種可能的解決方案是使用確實與整個URL匹配的<Route exact path="">。exact屬性表示精準匹配。
⚠️注意:儘管React Router確實支持在<Switch>以外渲染<Route>元素,從5.1版開始,咱們建議您改用useRouteMatch鉤子。此外,咱們不建議您渲染不帶路徑的<Route>,而是建議您使用鉤子來訪問您所使用的任何變量。
React Router提供了一個<Link>組件來在您的應用程序中建立連接。 不管在何處渲染<Link>,錨點都將渲染在HTML文檔中。
<NavLink>是<Link>的一種特殊類型,當其prop與當前位置匹配時,能夠將其自身設置爲「active」。
任什麼時候候要強制導航,均可以渲染<Redirect>。渲染<Redirect>時,它將會使用其props進行導航
<Link to="/">Home</Link> // <a href="/">Home</a> <NavLink to="/react" activeClassName="hurray"> React </NavLink> // 當URL爲/react的時候, 渲染出來的如下內容: // <a href="/react" className="hurray">React</a> // 若是是其餘URL,則渲染爲: // <a href="/react">React</a> // 重定向到/login <Redirect to="/login" />
NavLink例子:
import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter, Switch, Route, NavLink } from 'react-router-dom'; function NavigationApp() { return <BrowserRouter> <ul> <li> <NavLink to={'/react'}>React</NavLink> </li> <li> <NavLink to={'/redux'}>redux</NavLink> </li> </ul> <div> <Switch> <Route path={'/react'}> <h1>React</h1> </Route> <Route path={'/redux'}> <h1>Redux</h1> </Route> </Switch> </div> </BrowserRouter> } ReactDOM.render(<NavigationApp />, document.querySelector('#root'));
網絡應用的一個重要特點就是:咱們無需讓訪問者下載整個應用程序便可使用,您能夠將代碼拆分視爲增量下載應用程序。爲此,咱們將使用webpack,@babel/plugin-syntax-dynamic-import,和loadable-components作代碼分割。
webpack內置了對動態導入的支持; 可是,若是您使用的是Babel(例如,將JSX編譯爲JavaScript),則須要使用@babel/plugin-syntax-dynamic-import插件。這是僅語法的插件,這意味着Babel不會進行任何其餘轉換。該插件僅容許Babel解析動態導入,所以webpack能夠將它們捆綁爲代碼拆分。 您的.babelrc應該使用以下配置:
{ "presets": ["@babel/preset-react"], "plugins": ["@babel/plugin-syntax-dynamic-import"] }
loadable-components是用於經過動態導入加載組件的庫。 它自動處理各類邊緣狀況,並使代碼拆分變得簡單! 這是有關如何使用loadable-components的示例:
import loadable from "@loadable/component"; import Loading from "./Loading.js"; const LoadableComponent = loadable(() => import("./Dashboard.js"), { fallback: <Loading /> }); export default class LoadableDashboard extends React.Component { render() { return <LoadableComponent />; } }
這一切就是這麼簡單! 只需使用LoadableDashboard(或任何您命名的組件),當您在應用程序中使用它時,它將自動加載並渲染。fallback是一個佔位符組件,用於在加載實際組件時顯示。
完整的文檔在這裏
loadable-components包含服務器端渲染的指南。
在早期版本的React Router中,咱們提供了對滾動恢復的開箱即用的支持,從那之後人們一直在要求它。 但願本文檔能夠幫助您從滾動條和路由中得到所需的信息!
瀏覽器開始以本身的history.pushState處理滾動還原,其處理方式與使用普通瀏覽器導航時的處理方式相同。它已經能夠在Chrome瀏覽器中使用,並且很是棒,這是滾動恢復規範。
因爲瀏覽器開始處理「默認狀況」,而且應用具備不一樣的滾動需求(例如本網站!),所以咱們不提供默認滾動管理功能。 本指南應幫助您實現任何滾動需求。
在大多數狀況下,您所須要作的只是「滾動到頂部」,由於您有一個較長的內容頁面,該頁面在導航到該頁面時始終保持向下滾動。 使用<ScrollToTop>組件能夠輕鬆處理此問題,該組件將在每次導航時向上滾動窗口:
建立滾動到頂部組件:
import { useEffect } from 'react'; import { useLocation } from 'react-router-dom'; export default function ScrollToTop() { const { pathname } = useLocation(); console.log('pathname=>', pathname); useEffect(() => { window.scrollTo(0, 0); }, [ pathname ]); return null; }
若是您還沒有運行React 16.8,則可使用React.Component子類執行相同的操做:
import React from "react"; import { withRouter } from "react-router-dom"; class ScrollToTop extends React.Component { componentDidUpdate(prevProps) { if ( this.props.location.pathname !== prevProps.location.pathname ) { window.scrollTo(0, 0); } } render() { return null; } } export default withRouter(ScrollToTop);
而後在您的應用程序的頂部渲染它,可是要把它路由器下面:
import React from 'react' import ReactDOM from 'react-dom'; import { BrowserRouter } from 'react-router-dom'; import ScrollToTop from './ScrollToTop' function App() { return <BrowserRouter> <ScrollToTop/> <h1>App</h1> </BrowserRouter> } ReactDOM.render(<App />, document.querySelector('#root'));
若是您將標籤頁接口鏈接到路由器,那麼當他們切換標籤頁時,您可能不想滾動到頂部。 那麼,您須要在的特定位置使用<ScrollToTopOnMount>?
import { useEffect } from "react"; function ScrollToTopOnMount() { useEffect(() => { window.scrollTo(0, 0); }, []); return null; } // 使用如下代碼將此內容渲染到某處: // <Route path="..." children={<LongContent />} /> function LongContent() { return ( <div> <ScrollToTopOnMount /> <h1>Here is my long content page</h1> <p>...</p> </div> ); }
再說一次,若是您運行的React小於16.8,則能夠對React.Component子類作一樣的事情:
with a React.Component subclass:import React from "react"; class ScrollToTopOnMount extends React.Component { componentDidMount() { window.scrollTo(0, 0); } render() { return null; } } // 使用如下代碼將此內容渲染到某處: // <Route path="..." children={<LongContent />} /> class LongContent extends React.Component { render() { return ( <div> <ScrollToTopOnMount /> <h1>Here is my long content page</h1> <p>...</p> </div> ); } }
對於通用解決方案(以及哪些瀏覽器已開始在本機實現),咱們談論的是兩件事:
一、向上滾動導航,這樣就不會啓動滾動到底部的新屏幕
二、恢復窗口的滾動位置和「後退」和「前進」單擊上的溢出元素(但不單擊「連接」單擊!)
在某一時刻,咱們但願提供一個通用的API。 這就是咱們要研究的方向:
<Router> <ScrollRestoration> <div> <h1>App</h1> <RestoredScroll id="bunny"> <div style={{ height: "200px", overflow: "auto" }}> I will overflow </div> </RestoredScroll> </div> </ScrollRestoration> </Router>
首先,ScrollRestoration在導航中向上滾動窗口。其次,它將使用location.key將窗口滾動位置和RestoredScroll組件的滾動位置保存到sessionStorage。而後,在安裝ScrollRestoration或RestoredScroll組件時,它們能夠從sessionStorage查找其位置。
最棘手的部分是定義一個"opt-out"的API,當你不想滾動窗口時進行管理。例如,若是您在頁面內容中浮動了一些選項卡導航,則可能不想滾動到頂部(選項卡可能會滾動到視圖以外!)。當咱們得知Chrome如今能夠爲咱們管理滾動位置,並意識到不一樣的應用程序將具備不一樣的滾動需求時,咱們有點迷失了咱們須要提供某些東西的信念,尤爲是當人們只想滾動到頂部時( 您能夠直接將其直接添加到您的應用中)。基於此,咱們再也不有足夠的力氣本身完成工做(就像您同樣,咱們的時間有限!)。 可是,咱們很樂意爲有志於實施通用解決方案的任何人提供幫助。 一個可靠的解決方案甚至能夠存在於項目中。 若是您開始使用它,請與咱們聯繫:)
本指南的目的是說明使用React Router時要具備的思惟模型。 咱們稱之爲「動態路由」,它與您可能更熟悉的「靜態路由」徹底不一樣。
若是您使用過Rails,Express,Ember,Angular等,則使用了靜態路由。 在這些框架中,您須要在進行任何渲染以前將路由聲明爲應用初始化的一部分。 React Router pre-v4也是靜態的(大部分是靜態的)。讓咱們看一下在express中如何配置路由:
Express路由配置模式: app.get("/", handleIndex); app.get("/invoices", handleInvoices); app.get("/invoices/:id", handleInvoice); app.get("/invoices/:id/edit", handleInvoiceEdit); app.listen();
請注意在app監聽以前如何聲明路由。 咱們使用的客戶端路由器類似。 在Angular中,您先聲明routes,而後在渲染以前將其導入頂級的AppModule中:
// Angular的路由配置樣式: 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具備常規的route.js文件,該版本會爲您讀取並導入到應用程序中。 一樣,這是在您的應用渲染以前發生的。
// Ember 路由配置樣式: Router.map(function() { this.route("about"); this.route("contact"); this.route("rentals", function() { this.route("show", { path: "/:rental_id" }); }); }); export default Router;
雖然API是不一樣的,他們都有着「靜態路由」的模式。 React Router也跟進了直到v4。
爲了成功使用React Router,您須要忘記全部這些!
坦率地說,咱們對v2採起React Router的方向感到很是沮喪。 咱們(Michael和Ryan)感到受到API的限制,認識到咱們正在從新實現React的各個部分(生命週期等),而這與React爲構建UI提供的思惟模型不符。
咱們走在一家酒店的走廊上,正在討論如何解決這個問題。咱們互相問:「若是咱們使用咱們在工做室裏教的模式來建造路由器,那會是什麼樣子?」
僅僅在開發的幾個小時內,咱們就有了一個概念證實,咱們知道這就是咱們想要的路由的將來。咱們最終獲得的API不是React的「外部」API,而是一個由React的其他部分組成的API,或者天然地與之匹配。咱們想你會喜歡的。
當說動態路由時,是指在您的應用渲染時發生的路由,而不是在運行的應用以外的配置或約定中進行。 這意味着幾乎全部內容都是React Router中的一個組件。 這是對該API的60秒回顧,以瞭解其工做原理:
首先,爲您要定位的環境獲取一個Router組件,並將其呈如今應用程序的頂部。
// react-native import { NativeRouter } from "react-router-native"; // react-dom (咱們將在這裏使用什麼) import { BrowserRouter } from "react-router-dom"; ReactDOM.render( <BrowserRouter> <App /> </BrowserRouter>, el );
接下來,獲取連接組件以連接到新位置:
const App = () => ( <div> <nav> <Link to="/dashboard">Dashboard</Link> </nav> </div> );
最後,渲染一個Route在用戶訪問/dashboard時顯示一些UI。
const App = () => ( <div> <nav> <Link to="/dashboard">Dashboard</Link> </nav> <div> <Route path="/dashboard" component={Dashboard} /> </div> </div> );
Route將渲染<Dashboard {... props} />,其中props是路由器特定的東西,props對象以這三個關鍵對象{match,location, history}。 若是用戶不在/dashboard上,則Route將渲染null。 差很少就夠了。
不少路由器都有「嵌套路由」的概念。若是您使用的是v4以前的React Router版本,那麼您也會知道它是這麼作的!當您從靜態路由配置轉移到動態渲染路由時,如何「嵌套路由」?如何嵌套div呢?
const App = () => ( <BrowserRouter> {/* 這是一個 div */} <div> {/* 這是一個 Route */} <Route path="/tacos" component={Tacos} /> </div> </BrowserRouter> ); // 當網址與`/ tacos`相匹配時,渲染此組件 const Tacos = ({ match }) => ( // 這是一個嵌套的div <div> {/* 這是一條嵌套路線match.url幫助咱們創建相對路徑 */} <Route path={match.url + "/carnitas"} component={Carnitas} /> </div> );
看到路由器沒有「嵌套」API了嗎?路由只是一個組件,就像div同樣。要嵌套一個路由或div,你只須要...
讓咱們更加棘手。
考慮用戶導航到/invoices。 您的應用程序適應不一樣的屏幕尺寸,它們的viewport狹窄,所以您只向他們顯示發票清單和發票儀表板的連接。 他們能夠從那裏更深刻地導航。
小屏幕 url: /invoices +----------------------+ | | | Dashboard | | | +----------------------+ | | | Invoice 01 | | | +----------------------+ | | | Invoice 02 | | | +----------------------+ | | | Invoice 03 | | | +----------------------+ | | | Invoice 04 | | | +----------------------+
在較大的屏幕上,咱們想顯示一個主從視圖,其中導航在左側,儀表板或特定發票在右側。
大屏幕 url: /invoices/dashboard +----------------------+---------------------------+ | | | | Dashboard | | | | Unpaid: 5 | +----------------------+ | | | Balance: $53,543.00 | | Invoice 01 | | | | Past Due: 2 | +----------------------+ | | | | | Invoice 02 | | | | +-------------------+ | +----------------------+ | | | | | | + + + | | | Invoice 03 | | | + | | | | | | | | | | + | + | | +----------------------+ | | | | | | | | | | | +--+-+--+--+--+--+--+ | | Invoice 04 | | | | | +----------------------+---------------------------+
如今暫停一分鐘,並考慮兩種屏幕尺寸的/invoices網址。 它甚至是大屏幕的有效路線嗎? 咱們應該在右邊放什麼?
大屏幕 url: /invoices +----------------------+---------------------------+ | | | | Dashboard | | | | | +----------------------+ | | | | | Invoice 01 | | | | | +----------------------+ | | | | | Invoice 02 | ??? | | | | +----------------------+ | | | | | Invoice 03 | | | | | +----------------------+ | | | | | Invoice 04 | | | | | +----------------------+---------------------------+
在大屏幕上,/invoices不是有效的路徑,但在小屏幕上則是! 爲了使事情變得更有趣,請考慮使用大型手機的人。 他們可能會縱向查看/invoices,而後將手機旋轉至橫向。 忽然,咱們有足夠的空間來顯示主從界面,所以您應該當即進行重定向!
React Router之前版本的靜態路由並無真正解決這個問題的方法。 可是,當路由是動態的時,您能夠聲明性地組合此功能。 若是您開始考慮將路由選擇爲UI,而不是靜態配置,那麼您的直覺將引導您進入如下代碼:
const App = () => ( <AppLayout> <Route path="/invoices" component={Invoices} /> </AppLayout> ); const Invoices = () => ( <Layout> {/* 老是顯示導航 */} <InvoicesNav /> <Media query={PRETTY_SMALL}> {screenIsSmall => screenIsSmall ? ( // 小屏幕沒有重定向 <Switch> <Route exact path="/invoices/dashboard" component={Dashboard} /> <Route path="/invoices/:id" component={Invoice} /> </Switch> ) : ( // 大屏幕呢! <Switch> <Route exact path="/invoices/dashboard" component={Dashboard} /> <Route path="/invoices/:id" component={Invoice} /> <Redirect from="/invoices" to="/invoices/dashboard" /> </Switch> ) } </Media> </Layout> );
當用戶將手機從縱向旋轉到橫向時,此代碼將自動將其重定向到儀表板。 有效routes會根據用戶手中移動設備的動態性質而變化。
這只是一個例子。 咱們能夠討論許多其餘內容,但咱們將總結如下建議:爲了使您的直覺與React Router的直覺相符,請考慮組件而不是靜態路由。 考慮一下如何使用React的聲明式可組合性解決問題,由於幾乎每一個「 React Router問題」均可能是「 React問題」。
React Router依靠React上下文來工做。 這會影響您如何測試在你的組件裏使用咱們的組件。
若是您嘗試對渲染<Link>或<Route>的組件之一進行單元測試,等等。您會收到一些有關上下文的錯誤和警告。 儘管您可能會想本身親自設置路由器上下文,咱們建議您將單元測試包裝在路由器組件之一中:具備history屬性的路由或<StaticRouter>,<MemoryRouter>或<BrowserRouter>的基本路由器(若是window.history在測試環境中可做爲全局變量使用)。建議使用MemoryRouter或自定義歷史記錄,以便可以在兩次測試之間重置路由器。
class Sidebar extends Component { // ... render() { return ( <div> <button onClick={this.toggleExpand}>expand</button> <ul> {users.map(user => ( <li> <Link to={user.path}>{user.name}</Link> </li> ))} </ul> </div> ); } } // broken test("it expands when the button is clicked", () => { render(<Sidebar />); click(theButton); expect(theThingToBeOpen); }); // fixed! test("it expands when the button is clicked", () => { render( <MemoryRouter> <Sidebar /> </MemoryRouter> ); click(theButton); expect(theThingToBeOpen); });
<MemoryRouter>支持initialEntries和initialIndex props,所以您能夠在特定位置啓動應用程序(或應用程序的任何較小部分)。
test("current user is active in sidebar", () => { render( <MemoryRouter initialEntries={["/users/2"]}> <Sidebar /> </MemoryRouter> ); expectUserToBeActive(2); });
咱們進行了不少測試,以檢查route在位置更改時是否有效,所以您可能不須要測試這些東西。 可是,若是您須要在應用程序中測試導航,則能夠這樣進行:
app.js (a component file) import React from "react"; import { Route, Link } from "react-router-dom"; // 咱們的主題,即應用,但您能夠測試任何子項 // 您的應用程序部分 const App = () => ( <div> <Route exact path="/" render={() => ( <div> <h1>Welcome</h1> </div> )} /> <Route path="/dashboard" render={() => ( <div> <h1>Dashboard</h1> <Link to="/" id="click-me"> Home </Link> </div> )} /> </div> );
// 您還能夠在此處使用"@testing-library/react"或"enzyme/mount"之類的渲染器 import { render, unmountComponentAtNode } from "react-dom"; import { act } from 'react-dom/test-utils'; import { MemoryRouter } from "react-router-dom"; // app.test.js it("navigates home when you click the logo", async => { // 在真實測試中,渲染器如"@testing-library/react" // 將負責設置DOM元素 const root = document.createElement('div'); document.body.appendChild(root); // Render app render( <MemoryRouter initialEntries={['/my/initial/route']}> <App /> <MemoryRouter>, root ); // 與頁面互動 act(() => { // 查找連接(可能使用文本內容) const goHomeLink = document.querySelector('#nav-logo-home'); // Click it goHomeLink.dispatchEvent(new MouseEvent("click", { bubbles: true })); }); // 檢查顯示的頁面內容是否正確 expect(document.body.textContent).toBe('Home'); });
在測試中,您沒必要常常訪問location或history對象,可是若是你這樣作了(好比驗證在url欄中設置了新的查詢參數),你能夠在測試中添加一個更新變量的路由:
// app.test.js test("clicking filter links updates product query params", () => { let history, location; render( <MemoryRouter initialEntries={["/my/initial/route"]}> <App /> <Route path="*" render={({ history, location }) => { history = history; location = location; return null; }} /> </MemoryRouter>, node ); act(() => { // example: click a <Link> to /products?id=1234 }); // assert about url expect(location.pathname).toBe("/products"); const searchParams = new URLSearchParams(location.search); expect(searchParams.has("id")).toBe(true); expect(searchParams.get("id")).toEqual("1234"); });
備選方案:
一、若是您的測試環境具備瀏覽器全局變量window.location和window.history(這是經過JSDOM在Jest中的默認設置,但您沒法重置測試之間的歷史記錄),則也可使用BrowserRouter。
二、您能夠將基本路由器與history包中的history props一塊兒使用,而不是將自定義路由傳遞給MemoryRouter:
// app.test.js import { createMemoryHistory } from "history"; import { Router } from "react-router"; test("redirects to login page", () => { const history = createMemoryHistory(); render( <Router history={history}> <App signedInUser={null} /> </Router>, node ); expect(history.location.pathname).toBe("/login"); });
請參閱官方文檔中的示例:Testing React Router with React Testing Library
Redux是React生態系統的重要組成部分。 對於想要同時使用React Router和Redux的人,咱們但願使其無縫集成。
一般,React Router和Redux能夠很好地協同工做。不過有時候,應用程序能夠包含一個組件,該組件在位置更改時(子routes或活動的導航links不會更新)不會更新。
在如下狀況下會發生這種狀況:
一、該組件經過connect()(Comp)鏈接到redux。
二、該組件不是「路由組件」,這意味着它的渲染方式不是這樣:<Route component = {SomeConnectedThing} />
問題在於Redux實現了shouldComponentUpdate,若是沒有從路由器接收props,則沒有任何跡象代表發生了任何變化。
這很容易姐姐,找到鏈接組件的位置,而後將組件使用withRouter包裝在一塊兒
有些人想:
一、從store同步路由數據,並從store訪問路由數據。
二、能夠經過dispatch action操做導航
三、在Redux devtools中支持對路徑更改進行時間行程調試。
全部這些都須要更深刻的集成。
咱們的建議是不要將routes徹底保留在Redux store中。論證:
一、路由數據已經成爲大多數關心它的組件的支持。 不管是來自store仍是router,您組件的代碼都基本相同。
二、在大多數狀況下,您可使用Link,NavLink和Redirect執行導航操做。有時您可能還須要以編程方式進行導航,有時您可能還須要以編程方式導航,在某個操做最初啓動的異步任務以後。例如,您在用戶提交登陸表單時調度操做。而後,您的使用thunk,saga或其餘異步處理程序會對憑據進行身份驗證,若是成功,則須要以某種方式導航到新頁面。此處的解決方案只是將history對象(提供給全部路由組件)包括在操做的payload,而且異步處理程序能夠在適當的時候使用此對象進行導航。
三、路線更改對於時間行程調試不過重要。惟一明顯的狀況是調試router/store同步中的問題,若是根本不一樣步它們,則該問題將消失。
可是,若是您強烈但願與store同步route,您可能須要嘗試Connected React Router,這是React Router v4和Redux的第三方綁定。
之前版本的React Router使用靜態路由來配置應用程序的路由。這樣能夠在渲染以前檢查和匹配路線。因爲v4轉移到動態組件而不是路由配置,所以一些之前的用例變得不那麼明顯和棘手。咱們正在開發一個可與靜態路由配置和React Router配合使用的軟件包,以繼續知足這些用例。 如今正在開發中,但咱們但願您能嘗試一下並提供幫助。
React Router附帶了一些鉤子,可以讓您訪問路由器的狀態並從組件內部執行導航。
請注意:您必須使用React> = 16.8才能使用這些鉤子中的任何一個!
useHistory鉤子使您能夠訪問可用於導航的history實例。
import { useHistory } from "react-router-dom"; function HomeButton() { let history = useHistory(); function handleClick() { history.push("/home"); } return ( <button type="button" onClick={handleClick}> Go home </button> ); }
useLocation鉤子返回表明當前URL的location對象。您能夠像useState同樣考慮它,只要URL更改,它就會返回一個新位置。
這可能很是有用,例如 在您但願每次加載新頁面時都使用Web分析工具觸發新的"page view"事件的狀況下,如如下示例所示:
import React from "react"; import ReactDOM from "react-dom"; import { BrowserRouter as Router, Switch, useLocation } from "react-router-dom"; function usePageViews() { let location = useLocation(); React.useEffect(() => { ga.send(["pageview", location.pathname]); }, [location]); } function App() { usePageViews(); return <Switch>...</Switch>; } ReactDOM.render( <Router> <App /> </Router>, node );
useParams返回URL參數的key/value的對象。 使用它來訪問當前<Route>的match.params。
import React from "react"; import ReactDOM from "react-dom"; import { BrowserRouter as Router, Switch, Route, useParams } from "react-router-dom"; function BlogPost() { let { slug } = useParams(); return <div>Now showing post {slug}</div>; } ReactDOM.render( <Router> <Switch> <Route exact path="/"> <HomePage /> </Route> <Route path="/blog/:slug"> <BlogPost /> </Route> </Switch> </Router>, node );
useRouteMatch鉤子嘗試以與<Route>相同的方式匹配當前URL。它主要用於在不實際渲染<Route>的狀況下訪問匹配數據。
不用useRouteMatch:
import { Route } from "react-router-dom"; function BlogPost() { return ( <Route path="/blog/:slug" render={({ match }) => { // 用match作你想作的一切... return <div />; }} /> ); }
使用useRouteMatch:
import { useRouteMatch } from "react-router-dom"; function BlogPost() { let match = useRouteMatch("/blog/:slug"); // 用match作你想作的一切... return <div />; }
一個<Router>,它使用HTML5 history API (pushState、replaceState和popstate事件)來保持UI與URL同步。
<BrowserRouter basename={optionalString} forceRefresh={optionalBool} getUserConfirmation={optionalFunc} keyLength={optionalNumber} > <App /> </BrowserRouter>
全部location的基本URL。若是您的應用是經過服務器上的子目錄提供的,則須要將其設置爲子目錄。格式正確的basename
應以斜槓開頭,但不能以斜槓結尾。
用於確認導航的功能。 默認使用window.confirm。
若是爲true,則路由器將在頁面導航中使用整頁刷新。您可能但願使用它來模仿傳統的服務器渲染應用程序在頁面導航之間刷新整個頁面的方式。
location.key的長度。 默認爲6。
要渲染的子元素。
注意:在React <16上,您必須使用單個子元素,由於render方法不能返回多個元素。 若是須要多個元素,則能夠嘗試將它們包裝在額外的<div>中。
<Router>使用URL的哈希部分(即window.location.hash)使UI與URL保持同步。
重要說明:Hash history不支持location.key或location.state。在之前的版本中,咱們試圖糾正這種行爲,可是有些邊緣狀況咱們沒法解決。任何須要此行爲的代碼或插件都將沒法使用。 因爲此技術僅旨在支持舊版瀏覽器,咱們建議您將服務器配置爲與<BrowserHistory>一塊兒使用。
<HashRouter basename={optionalString} getUserConfirmation={optionalFunc} hashType={optionalString} > <App /> </HashRouter>
全部location的基本URL。 格式正確的basename應以斜槓開頭,但不能以斜槓結尾。
<HashRouter basename="/calendar"/> <Link to="/today"/> // 渲染出來的樣子: <a href="#/calendar/today">
用於confirm導航的功能。 默認使用window.confirm。
<HashRouter getUserConfirmation={(message, callback) => { // this is the default behavior const allowTransition = window.confirm(message); callback(allowTransition); }} />
用於window.location.hash的編碼類型。 可用值爲:
#/
和#/sunshine/lollipops
的hash#
和#sunshine/lollipops
的hash#!/
和#!/sunshine/lollipops
之類的"ajax crawlable"(Google棄用)hash默認爲 "/"
要渲染的單個子元素。
import React, { useEffect } from 'react'; import ReactDOM from 'react-dom'; import { HashRouter, Switch, Route, Link, withRouter } from 'react-router-dom'; function INav() { let homeRef; let anchorRef = React.createRef(); console.log('before - anchorRef=>', anchorRef); useEffect(props => { console.log('after - anchorRef=>', anchorRef); console.log('after- homeRef=>', homeRef); }); return ( <ul className={'nav'}> <li> <Link to={'/home'} replace innerRef={homeRef}>Home</Link> </li> <li> <Link to={'/rule'} innerRef={anchorRef}>Rule</Link> </li> <li> <Link to={'/form'} innerRef={node => { // "node"指的是被掛載的DOM元素 // 組件被卸載時爲null console.log('node=>', node); }}>Form</Link> </li> <li> <Link to={location => `/table?sort=name`}>Table</Link> </li> <li> <Link to={location => { console.log('Charts - location=>', location); return { ...location, pathname: '/charts' } }}>Charts</Link> </li> <li> <Link to={{ pathname: '/example', search: '?sort=name', hash: '#the-hash', state: { fromDashboard: true, name: 'Jameswain' } }}>Example</Link> </li> </ul> ) } function Home(props) { console.log('Home:', props); return <h1> Home </h1> } function Form(props) { console.log('Form:', props); return <h1>Form</h1>; } function Table(props) { console.log('Table:', props); return <h1>Table</h1> } function Rule(props) { console.log('rule:', props); return <h1> Rule </h1> } const Example = withRouter((props) => { console.log('Example:', props); return <h1>Example</h1> }); const Charts = withRouter((props) => { console.log('Charts:', props); return <h1>Charts</h1> }); function App() { return ( <HashRouter hashType={'noslash'} basename={'/calendar'}> <div className={'app'}> <INav/> <Switch> <Route path={'/home'} exact> <Home /> </Route> <Route path={'/rule'} children={props => <Rule {...props} />} /> <Route path={'/form'} render={props => <Form {...props} />} /> <Route path={'/table'} component={props => <Table {...props} />} /> <Route path={'/charts'} children={<Charts />} /> <Route path={'/example'}> <Example /> </Route> </Switch> </div> </HashRouter> ); } ReactDOM.render(<App />, document.querySelector('#root'));
提供圍繞應用程序的聲明式、可訪問的導航,其實渲染出來的就是一個標籤,對標籤的封裝。
<Link to="/about">About</Link>
連接位置的字符串表示形式,是經過將location的pathname,search和hash屬性鏈接起來而建立的。
<Link to="/courses?sort=name" />
能夠具備如下任何屬性的對象:
location.state
中<Link to={{ pathname: "/courses", search: "?sort=name", hash: "#the-hash", state: { fromDashboard: true } }} />
將當前位置做爲參數傳遞給它的函數,該函數應該以字符串或對象的形式返回位置信息
<Link to={location => ({ ...location, pathname: "/courses" })} /> <Link to={location => `${location.pathname}?sort=name`} />
若是爲true,則將單擊連接替換爲history記錄堆棧中的當前條目,而不是添加一條新條目。
這樣就沒有回退功能了,由於它是把當前URL地址替換掉,不會產生歷史記錄。
<Link to="/courses" replace />
從React Router 5.1開始,若是您使用的是React16,則不須要此props,由於咱們會將ref轉發到基礎。容許訪問組件的基礎引用。
<Link to="/" innerRef={node => { // 「node」指的是被掛載的DOM元素 // 組件被卸載時爲null }} />
從React Router 5.1開始,若是您使用的是React16,則不須要此props,由於咱們會將ref轉發到基礎。使用React.createRef獲取組件的基礎引用。
let anchorRef = React.createRef() <Link to="/" innerRef={anchorRef} />
您還能夠傳遞想要在上顯示的props,例如title,id,className等。
<Link>的特殊版本,當它與當前URL匹配時,它將爲渲染的元素添加樣式屬性。
<NavLink to="/about">About</NavLink>
當元素處於active時給該元素設置的class,默認給定的class是active的,這將與className屬性鏈接在一塊兒。
<NavLink to="/faq" activeClassName="selected"> FAQs </NavLink>
元素處於active狀態時應用於該元素的樣式。
<NavLink to="/faq" activeStyle={{ fontWeight: "bold", color: "red" }} > FAQs </NavLink>
若是爲true,則僅在locatiuon徹底匹配時才應用active的class或style。
<NavLink exact to="/profile"> Profile </NavLink>
若是爲true,則在肯定位置是否與當前URL匹配時,將會考慮位置路徑名上的斜槓,它須要和<Route>配合使用。有關更多信息,請參見<Route strict>文檔。
// 嚴格模式,沒法匹配,URL必需要如出一轍才能匹配上 <NavLink strict to="/events"> Events </NavLink> <Switch> <Route path={'/events/'} strict children={<Events />} /> </Switch>
一種添加額外邏輯以肯定連接是否處於active狀態的功能。若是您要作的事情不只僅是驗證連接的路徑名是否與當前URL的路徑名匹配,則可使用此選項。
<NavLink to="/events/123" isActive={(match, location) => { if (!match) { return false; } // 僅當事件id爲奇數時元素才爲active狀態 const eventID = parseInt(match.params.eventID); return !isNaN(eventID) && eventID % 2 === 1; }} > Event 123 </NavLink>
isActive比較當前歷史記錄位置(一般是當前瀏覽器URL)。若是要與其餘location進行比較,能夠傳遞一個位置。
在active連接上使用的aria-current屬性的值。可用值爲:
"page"
- 用於指示一組分頁連接中的連接"step"
- 用於指示基於步驟的過程的步驟指示器中的連接"location"
- 用於指示視覺上突出顯示的圖像做爲流程圖的當前組成部分"date"
- 用於指示日曆中的當前日期"time"
- 用於指示時間表中的當前時間"true"
- 用於指示NavLink是否處於活動狀態默認值爲 "page"
基於WAI-ARIA 1.1規範
import React from 'react'; import ReactDOM from 'react-dom'; import { HashRouter, Switch, Route, NavLink } from 'react-router-dom'; function Home() { return <h1>Home</h1> } function About() { return <h1>About</h1> } const Charts = () => <h1>Charts</h1>; const Table = () => <h1>Table</h1>; const FAQ = () => <h1>FAQ</h1>; const Events = () => <h1>Events</h1>; function App() { return <div className={'app'}> <HashRouter hashType={'noslash'}> <ul> <li> <NavLink to={'/home'} className={'home'}>Home</NavLink> </li> <li> <NavLink to={'/about'} className={'about'}>About</NavLink> </li> <li> <NavLink to={'/charts'} className={'charts'} activeClassName={'selected'}>Charts</NavLink> </li> <li> <NavLink to={'/table'} className={'table'} activeClassName={'selected'}>Table</NavLink> </li> <li> <NavLink to={'/faq'} activeStyle={{ fontWeight: 'bold', color: 'red' }}>FAQ</NavLink> </li> <li> <NavLink strict to="/events">Events</NavLink> </li> </ul> <Switch> <Route path={'/home'} children={<Home/>} /> <Route path={'/about'} children={<About/>} /> <Route path={'/charts'} children={<Charts/>} /> <Route path={'/table'} children={<Table/>} /> <Route path={'/faq'} children={<FAQ />} /> <Route path={'/events/'} strict children={<Events />} /> </Switch> </HashRouter> </div> } ReactDOM.render(<App />, document.querySelector('#root'))
用於在離開頁面以前提示用戶。當您的應用程序進入應阻止用戶導航的狀態時(例如,表單已被半填滿),請渲染<Prompt>。
<Prompt when={formIsHalfFilledOut} message="您肯定要離開嗎?" />
當用戶嘗試離開時提示用戶的消息。
<Prompt message="Are you sure you want to leave?" />
將與用戶嘗試導航到的下一個位置和操做一塊兒調用。返回一個字符串以向用戶顯示提示,或者返回true以容許過渡。
<Prompt message={location => location.pathname.startsWith("/app") ? true : `Are you sure you want to go to ${location.pathname}?` } />
您能夠始終渲染它,而能夠經過when={true}或when={false}來阻止或容許進行相應的導航,而不是經過條件控制是否渲染<Prompt>。
<Prompt when={formIsHalfFilledOut} message="Are you sure?" />
import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter, Switch, Route, Link, Prompt } from 'react-router-dom'; function Home() { return <h1>Home</h1> } function Table() { return <h1>Table</h1>; } function Charts() { return <h1>Charts</h1> } function About() { return <h1>About</h1> } function App() { return <BrowserRouter> <Prompt message={location => { if (location.pathname !== '/home') { return `您肯定要前往${location.pathname}嗎?` } else { return true; } return true; }} when={true} /> <ul> <li> <Link to={'/home'}>Home</Link> </li> <li> <Link to={'/table'}>Table</Link> </li> <li> <Link to={'/charts'}>Charts</Link> </li> <li> <Link to={'/about'}>About</Link> </li> </ul> <Switch> <Route path={'/home'} children={props => <Home {...props} />} /> <Route path={'/table'} render={props => <Table {...props} />} /> <Route path={'/charts'} children={props => <Charts {...props} />} /> <Route path={'/about'} render={props => <About {...props} />} /> </Switch> </BrowserRouter>; } ReactDOM.render(<App />, document.querySelector('#root'));
渲染<Redirect>將導航到新位置。新位置將覆蓋歷史記錄堆棧中的當前位置,就像服務器端重定向(HTTP 3xx)同樣。
<Route exact path="/"> {loggedIn ? <Redirect to="/dashboard" /> : <PublicHomePage />} </Route>
重定向到的URL。path-to-regexp@^1.7.0能夠理解的任何有效URL路徑。to中使用的全部URL參數必須由from覆蓋。
<Redirect to="/somewhere/else" />
重定向到的位置。路徑名能夠是path-to-regexp@^1.7.0能夠理解的任何有效URL路徑。
<Redirect to={{ pathname: "/login", search: "?utm=your+face", state: { referrer: currentLocation } }} />
能夠經過重定向到組件中的this.props.location.state訪問狀態對象。而後,能夠經過路徑名"/login"指向的Login組件中的this.props.location.state.referrer訪問此新的引用關鍵字(不是特殊名稱)。
<Redirect push to="/somewhere/else" />
設置爲true時,重定向會將新條目推入歷史記錄,而不是替換當前條目。
要重定向的路徑名。 path-to-regexp@^1.7.0能夠理解的任何有效URL路徑。全部匹配的URL參數都提供給模式中的to。必須包含用於to中的全部參數。to不使用的其餘參數將被忽略。
<Switch> <Redirect from='/old-path' to='/new-path' /> <Route path='/new-path'> <Place /> </Route> </Switch> // 使用匹配的參數重定向 <Switch> <Redirect from='/users/:id' to='/users/profile/:id'/> <Route path='/users/profile/:id'> <Profile /> </Route> </Switch>
示例:
import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter, Switch, Route, Redirect, Link, useParams } from 'react-router-dom'; function App() { return( <BrowserRouter> <ul> <li> <Link to={'/home'}>Home</Link> </li> <li> <Link to={'/charts/123123'}>Charts</Link> </li> <li> <Link to={'/profile/111'}>Profile</Link> </li> </ul> <Switch> <Route path={'/home'} render={props => <Home {...props} />} /> <Route path={'/profile/:id'} render={props => <Profile {...props} />} /> <Redirect from={'/charts/:id'} to={'/profile/:id'} /> </Switch> </BrowserRouter> ) } function Home() { return <h1>Home</h1> } function Profile() { const params = useParams(); console.log('params=>', params); return <> <h1>Profile</h1> </> } ReactDOM.render(<App />, document.querySelector('#root'));
徹底匹配;等同於Route.exact。
注意:只有在<Switch>內渲染<Redirect>時,才能與from結合使用,以徹底匹配位置。有關更多詳細信息,請參見<Switch children>。
import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter, Switch, Route, Link, Redirect } from 'react-router-dom'; const Home = () => <h1>Home</h1>; const About = () => <h1>About</h1>; const App = () => <BrowserRouter> <ul> <li> <Link to={'/home'}>Home</Link> </li> <li> <Link to={'/about'}>About</Link> </li> </ul> <Switch> <Route path={'/home'} render={props => <Home {...props} />} /> <Route path={'/about'} children={props => <About {...props} />} /> {/*這個必定要放到Route後面,等Route渲染完了,才能夠重定向*/} <Redirect exact from={'/'} to={'/home'} /> </Switch> </BrowserRouter>; ReactDOM.render(<App />, document.querySelector('#root'));
嚴格匹配;等同於Route.strict。
注意:只有在<Switch>內部渲染<Redirect>時,此選項只有與from一塊兒使用才能以嚴格匹配位置。有關更多詳細信息,請參見<Switch children>。
import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter, Switch, Route, Link, Redirect } from 'react-router-dom'; const Home = () => <h1>Home</h1>; const About = () => <h1>About</h1>; const App = () => <BrowserRouter> <ul> <li> <Link to={'/home'}>Home</Link> </li> <li> <Link to={'/about'}>About</Link> </li> <li> <Link to={'/one'}>One</Link> </li> </ul> <Switch> <Route path={'/home'} render={props => <Home {...props} />} /> <Route path={'/about'} children={props => <About {...props} />} /> {/*這個必定要放到Route後面,等Route渲染完了,才能夠重定向*/} <Redirect strict from="/one/" to="/home" /> </Switch> </BrowserRouter>; ReactDOM.render(<App />, document.querySelector('#root'));
區分大小寫匹配;等同於Route.sensitive。
<Route sensitive path="/one"> <About /> </Route>
path | location.pathname | sensitive | 是否匹配 |
---|---|---|---|
/one | /one | true | yes |
/One | /one | true | no |
/One | /one | false | yes |
Router組件多是React Router中瞭解和學習使用的最重要組件。它的最基本職責是在其路徑與當前URL匹配時顯示一些UI。
研究如下代碼:
import React from "react"; import ReactDOM from "react-dom"; import { BrowserRouter as Router, Route } from "react-router-dom"; ReactDOM.render( <Router> <div> <Route exact path="/"> <Home /> </Route> <Route path="/news"> <NewsFeed /> </Route> </div> </Router>, node );
若是應用程序的location是/,則UI層次結構將相似於:
<div> <Home /> <!-- react-empty: 2 --> </div>
若是應用程序的location是/news,則UI層次結構將是:
<div> <!-- react-empty: 1 --> <NewsFeed /> </div>
"react-empty"註釋只是React空渲染的實現細節。可是出於咱們的目的,這是有益的。從技術上講,即便始終爲空,也老是對其進行"渲染"。當<Route>的路徑與當前URL匹配時,它將渲染其子級(您的組件)。
使用<Route>渲染某些內容的方法建議使用子元素,如上所示。 可是,還有一些其餘方法可用於使用<Route>渲染內容。 提供這些主要是爲了支持在引入鉤子以前使用早期版本的路由器構建的應用程序。
您應該在給定的<Route>上僅使用這些props。 請參閱下面的說明以瞭解它們之間的區別。
全部這三種渲染方法將經過相同的三個路由props
一個僅在location匹配時才渲染的React組件。 它將與route props一塊兒渲染。
import React from "react"; import ReactDOM from "react-dom"; import { BrowserRouter as Router, Route } from "react-router-dom"; // 用戶可使用全部route props(match, location and history) function User(props) { return <h1>Hello {props.match.params.username}!</h1>; } ReactDOM.render( <Router> <Route path="/user/:username" component={User} /> </Router>, node );
當您使用組件(而不是下面的渲染器或子組件)時,路由器會使用React.createElement從給定的組件中建立一個新的React元素。這意味着,若是您向組件prop提供內聯函數,則將在每一個渲染中建立一個新組件。這意味着,若是您向組件prop提供內聯函數,則將在每一個渲染中建立一個新組件。這將致使現有組件的卸載和新組件的安裝,而不只僅是更新現有組件。使用內聯函數進行內聯渲染時,請使用render或children屬性(以下)。
這樣能夠方便地進行內聯渲染和包裝,而無需進行上述沒必要要的從新安裝。
無需使用組件prop爲您建立新的React元素,而是能夠傳遞位置匹配時要調用的函數。render函數能夠訪問與組件渲染相同的全部route屬性(match,location和history)。
import React from "react"; import ReactDOM from "react-dom"; import { BrowserRouter as Router, Route } from "react-router-dom"; // 方便的內聯渲染 ReactDOM.render( <Router> <Route path="/home" render={props => <div>Home</div>} /> </Router>, node );
import React from "react"; import ReactDOM from "react-dom"; import { BrowserRouter as Router, Route } from "react-router-dom"; // wrapping/composing // 您能夠傳播 route 屬性 以使它們可用於渲染的組件 function FadingRoute({ component: Component, ...rest }) { return ( <Route {...rest} render={routeProps => ( <FadeIn> <Component {...routeProps} /> </FadeIn> )} /> ); } ReactDOM.render( <Router> <FadingRoute path="/cool" component={Something} /> </Router>, node );
警告:<Route component>優先於<Route render>,所以請勿在同一<Route>中同時使用二者。
有時您須要渲染路徑是否與位置匹配。 在這種狀況下,您可使用child道具功能。 它與render徹底同樣,除了是否存在匹配項而被調用。
子級渲染屬性接收組件渲染函數相同的全部路由屬性,除非路由未能與URL匹配,則match爲null。 這樣您能夠根據路由是否匹配來動態調整UI。 若是路由匹配,咱們在這裏添加一個active class。
import React from "react"; import ReactDOM from "react-dom"; import { BrowserRouter as Router, Link, Route } from "react-router-dom"; function ListItemLink({ to, ...rest }) { return ( <Route path={to} children={({ match }) => ( <li className={match ? "active" : ""}> <Link to={to} {...rest} /> </li> )} /> ); } ReactDOM.render( <Router> <ul> <ListItemLink to="/somewhere" /> <ListItemLink to="/somewhere-else" /> </ul> </Router>, node );
這對於動畫也可能有用:
<Route children={({ match, ...rest }) => ( {/* Animate將始終進行渲染,所以您可使用生命週期來對其子對象進行動畫製做 */} <Animate> {match && <Something {...rest}/>} </Animate> )} />
警告:<Route children>優先於<Route component>和<Route render>,所以請不要在同一<Route>中使用多個。
若是爲true,則僅在路徑與location.pathname徹底匹配時才匹配。
<Route exact path="/one"> <About /> </Route>
path | location.pathname | exact | 是否匹配 |
---|---|---|---|
/one | /one/two | true | no |
/one | /one/two | false | yes |
設置爲true時,帶有斜槓的路徑將只匹配帶有斜槓的location.pathname。當location.pathname中有其餘URL段時,這無效。
<Route strict path="/one/"> <About /> </Route> path
path | location.pathname | 是否匹配 |
---|---|---|
/one/ | /one | no |
/one/ | /one/ | yes |
/one/ | /one/two | yes |
警告:strict能夠用於強制location.pathname不帶斜槓,可是要作到這一點,strict和exact都必須爲true。
<Route exact strict path="/one"> <About /> </Route>
path | location.pathname | 是否匹配 |
---|---|---|
/one | /one | yes |
/one | /one/ | no |
/one | /one/two | no |
<Route>元素嘗試將其路徑與當前歷史記錄位置(一般是當前瀏覽器URL)匹配。可是,也能夠傳遞路徑名不一樣的位置進行匹配。
在須要將<Route>匹配到當前歷史記錄位置之外的位置時,這頗有用,如Animated Transitions示例所示。
若是<Route>元素包裝在<Switch>中而且與傳遞給<Switch>的位置(或當前歷史記錄位置)匹配,則傳遞給<Route>位置的prop將被<Switch>使用的那個props覆蓋(此處給出)。
爲true時,若是路徑區分大小寫,則將匹配。
<Route sensitive path="/one"> <About /> </Route>
path | location.pathname | sensitive | 是否匹配 |
---|---|---|---|
/one | /one | true | yes |
/One | /one | true | no |
/One | /one | false | yes |
全部路由器組件的通用底層接口。一般,應用將使用高級路由器之一代替:
使用底層<Router>的最多見用例是將自定義歷史記錄與狀態管理庫(如Redux或Mobx)進行同步。 請注意,並不須要將狀態管理庫與React Router一塊兒使用,它僅用於深度集成。
import React from "react"; import ReactDOM from "react-dom"; import { Router } from "react-router"; import { createBrowserHistory } from "history"; const history = createBrowserHistory(); ReactDOM.render( <Router history={history}> <App /> </Router>, node );
用於導航的history對象
import React from "react"; import ReactDOM from "react-dom"; import { createBrowserHistory } from "history"; const customHistory = createBrowserHistory(); ReactDOM.render(<Router history={customHistory} />, node);
要渲染的子元素。
<Router> <App /> </Router>
永遠不會更改位置的<Router>。
當用戶實際上沒有四處點擊時,這在服務器端渲染方案中頗有用,所以位置永遠不會發生實際變化。 所以,名稱爲:static。它在簡單測試中也頗有用,您只須要插入一個位置並在渲染輸出中進行斷言時。
示例:這是一個node服務器,它爲<Redirect>發送302狀態代碼,併爲其餘請求發送常規HTML:
requests:import http from "http"; import React from "react"; import ReactDOMServer from "react-dom/server"; import { StaticRouter } from "react-router"; http .createServer((req, res) => { // This context object contains the results of the render const context = {}; const html = ReactDOMServer.renderToString( <StaticRouter location={req.url} context={context}> <App /> </StaticRouter> ); // context.url will contain the URL to redirect to if a <Redirect> was used if (context.url) { res.writeHead(302, { Location: context.url }); res.end(); } else { res.write(html); res.end(); } }) .listen(3000);
全部位置的基本URL。格式正確的基本名稱應以斜槓開頭,但不能以斜槓結尾。
<StaticRouter basename="/calendar"> <Link to="/today"/> // renders <a href="/calendar/today"> </StaticRouter>
服務器收到的URL,多是node服務器上的req.url.
<StaticRouter location={req.url}> <App /> </StaticRouter>
形狀爲{ pathname, search, hash, state }的location對象
<StaticRouter location={{ pathname: "/bubblegum" }}> <App /> </StaticRouter>
一個普通的JavaScript對象。在渲染期間,組件能夠向對象添加屬性以存儲有關渲染的信息
const context = {} <StaticRouter context={context}> <App /> </StaticRouter>
當<Route>匹配時,它將把上下文對象傳遞給它做爲staticContext屬性呈現的組件。請查看服務器渲染指南,以獲取有關如何自行執行此操做的更多信息。
渲染後,這些屬性可用於配置服務器的響應。
if (context.status === "404") { // ... }
要渲染的子元素。
注意:在React <16上,您必須使用單個子元素,由於render方法不能返回多個元素。若是須要多個元素,則能夠嘗試將它們包裝在額外的<div>
渲染與位置匹配的第一個子元素<Route>或<Redirect>。
這與僅使用一堆<Route>有什麼不一樣?
<Switch>的獨特之處在於它專門渲染一條路由。相反,每一個與該位置匹配的<Route>都將進行包含性渲染。研究如下route:
import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter, Route } from 'react-router-dom'; function SwitchExample() { return <BrowserRouter> <Route path="/about"> <h1>About</h1> </Route> <Route path="/:user"> <h1>User</h1> </Route> <Route> <h1>NoMatch</h1> </Route> </BrowserRouter>; } ReactDOM.render(<SwitchExample />, document.querySelector('#root'));
若是URL是/about,則渲染<About>,<User>和<NoMatch>將所有渲染,由於它們都與全部路徑都匹配。這是設計使然,容許咱們以多種方式將<Route>組合到咱們的應用中,例如邊欄和麪包屑,引導程序標籤等。
可是,有時咱們只選擇一個<Route>進行渲染。若是咱們位於/about,咱們不想同時匹配/:user(或顯示"404"頁面)。使用Switch的方法以下:
import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter, Switch, Route } from 'react-router-dom'; const Home = () => <h1>Home</h1>; const About = () => <h1>About</h1>; const User = () => <h1>User</h1>; const NoMatch = () => <h1>NoMatch</h1>; function SwitchExample() { return <BrowserRouter> <Switch> <Route exact path='/'> <Home /> </Route> <Route path='/about'> <About /> </Route> <Route path='/:user'> <User /> </Route> <Route> <NoMatch /> </Route> </Switch> </BrowserRouter> } ReactDOM.render(<SwitchExample />, document.querySelector('#root'));
如今,若是咱們位於/about,<Switch>將開始尋找匹配的<Route>。<Route path ="/about" />將匹配,而<Switch>將中止尋找匹配並渲染<About>。一樣,若是咱們在/michael位置,則會顯示<User>。
這對於動畫過渡也頗有用,由於匹配的<Route>呈如今與上一個相同的位置。
let routes = ( <Fade> <Switch> {/* 這裏只有一個子元素 */} <Route /> <Route /> </Switch> </Fade> ); let routes = ( <Fade> {/* 這裏永遠有兩個子元素,可是可能會呈現null,進行轉換,計算起來有點麻煩 */} <Route /> <Route /> </Fade> );
用於匹配子元素的位置對象,而不是當前歷史記錄位置(一般是當前瀏覽器URL)。
<Switch>的全部子代應爲<Route>或<Redirect>元素。僅第一個與當前位置匹配的子元素會被渲染。
<Route>元素使用其path屬性進行匹配,而<Redirect>元素使用其from屬性進行匹配。沒有path屬性的<Route>或沒有from屬性的<Redirect>將始終與當前位置匹配。
在<Switch>中包含<Redirect>時,它可使用<Route>的任何位置匹配屬性:path,exact和strict。 from只是path屬性的別名。
若是給<Switch>一個location屬性,它將覆蓋匹配的子元素上的location屬性。
import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter, Route, Switch, Redirect } from 'react-router-dom'; const Home = () => <h1>Home</h1>; const User = () => <h1>User</h1>; const NoMatch = () => <h1>NoMatch</h1>; function SwitchExample() { return <BrowserRouter> <Switch> <Route exact path="/"> <Home /> </Route> <Route path="/user"> <User /> </Route> <Redirect from="/account" to="/user" /> <Route> <NoMatch /> </Route> </Switch> </BrowserRouter>; } ReactDOM.render(<SwitchExample />, document.querySelector('#root'));
本文檔中的"history"和"history對象"一詞是指history包,它是React Router僅有的兩個主要依賴項之一(除了React自己)而且提供了幾種不一樣的實現,用於在各類環境中管理JavaScript中的會話歷史記錄。
也使用如下術語:
history對象一般具備如下屬性和方法:
location - (object)當前位置。可能具備如下屬性:
history對象是可變的,所以,建議從<Route>的渲染屬性中訪問location,而不是從history.location中訪問。這確保了您對React的假設在生命週期鉤子中是正確的。例如:
class Comp extends React.Component { componentDidUpdate(prevProps) { // 將爲 true const locationChanged = this.props.location !== prevProps.location; // 不正確,因爲history是可變的,所以*老是*爲假。 const locationChanged = this.props.history.location !== prevProps.history.location; } } <Route component={Comp} />;
根據您所使用的實現方式,可能還會顯示其餘屬性。請參閱history文檔以獲取更多詳細信息。
location表示該應用程序如今的位置,您但願其運行的位置,甚至是之前的位置。看起來像這樣:
{ key: 'ac3df4', // not with HashHistory! pathname: '/somewhere', search: '?some=search-string', hash: '#howdy', state: { [userDefined]: true } }
router將在幾個地方爲您提供location對象:
也能夠在history.location上找到它,可是您不該使用它,由於它是可變的。您能夠在history文檔
中閱讀有關此內容的更多信息.
location對象永遠不會發生變化,所以您能夠在生命週期鉤子中使用它來肯定什麼時候進行導航,這對於數據獲取和動畫處理很是有用。
componentWillReceiveProps(nextProps) { if (nextProps.location !== this.props.location) { // navigated! } }
你能夠提供位置,而不是字符串導航到不一樣的地方:
一般您只須要使用一個字符串,可是,若是您須要添加一些「位置狀態」,只要應用返回到該特定位置便可使用,則可使用location對象代替。若是您要基於導航歷史而不是僅基於路徑(如 modals)來分支UI,這將很是有用。
// 一般你所須要的 <Link to="/somewhere"/> // 可是你可使用location來代替 const location = { pathname: '/somewhere', state: { fromDashboard: true } } <Link to={location}/> <Redirect to={location}/> history.push(location) history.replace(location)
最後,您能夠將location傳遞給如下組件:
這樣能夠防止他們在路由器狀態下使用實際位置。這對於動畫和待處理的導航頗有用,或者在您想要誘使組件在與真實位置不一樣的位置進行渲染時,這頗有用。
match對象包含有關<Route path>如何與URL匹配的信息。匹配對象包含如下屬性:
params
- (object)從與路徑的動態段相對應的URL解析的鍵/值對isExact
- (boolean)若是整個網址都匹配,則爲「 true」(不包含結尾字符)path
- (string) 用於匹配的路徑模式。對於構建嵌套的<Route>有用url
- (string) URL的匹配部分。對於構建嵌套的<Link>有用您將能夠在各個地方使用match對象:
this.props.match
({ match }) => ()
({ match }) => ()
this.props.match
若是Route沒有path,所以會始終匹配,它將獲取最接近的父項匹配項。和withRouter同樣。
即便子路徑的路徑與當前位置不匹配,使用子項道具的<Route>也會調用其子函數。 在這種狀況下,匹配將爲空。 可以在匹配時呈現<Route>的內容可能會頗有用,可是這種狀況會帶來一些挑戰。
"解析"URL的默認方法是將match.url字符串鏈接到"相對"路徑。
let path = `${match.url}/relative-path`;
若是在匹配爲null時嘗試執行此操做,則最終將出現TypeError。這意味着在使用children prop時嘗試在<Route>內部加入"relative"路徑是不安全的。
當在生成空匹配對象的<Route>中使用無路徑<Route>時,會發生相似但更微妙的狀況。
// location.pathname = '/matches' <Route path="/does-not-match" children={({ match }) => ( // match === null <Route render={({ match: pathlessMatch }) => ( // pathlessMatch === ??? )} /> )} />
無路徑<Route>從其父級繼承其match對象。若是其父匹配項爲null,則其匹配項也將爲null。這意味着任何子級路由/連接都必須是絕對的,由於沒有父級能夠解析,而且父級匹配能夠爲null的無路徑路由將須要使用子級prop進行渲染。
這容許您使用與<Route>相同的匹配代碼(除了正常的渲染週期以外),好比在服務器上呈現以前收集數據依賴關係。
import { matchPath } from "react-router"; const match = matchPath("/users/123", { path: "/users/:id", exact: true, strict: false });
第一個參數是您要匹配的路徑名。若是您是在Node.js的服務器上使用它,則爲req.path。
第二個參數是要匹配的props,它們與Route接受的匹配props相同。它也能夠是字符串或字符串數組,做爲{path}的快捷方式:
{ path, // 像/users/:id;單個字符串或字符串數組 strict, // 可選,默認爲false exact, // 可選,默認爲false }
當提供的路徑名與路徑屬性匹配時,它將返回一個對象。
matchPath("/users/2", { path: "/users/:id", exact: true, strict: true }); // { // isExact: true // params: { // id: "2" // } // path: "/users/:id" // url: "/users/2" // }
若是提供的路徑名與路徑屬性不匹配,則返回null。
matchPath("/users", { path: "/users/:id", exact: true, strict: true }); // null
您能夠經過withRouter高階組件訪問history對象的屬性和最接近的<Route>匹配項。每當渲染時,withRouter都會將更新的match,location和history屬性傳遞給包裝的組件。
import React from "react"; import PropTypes from "prop-types"; import { withRouter } from "react-router"; // 一個簡單的組件,顯示當前位置的路徑名 class ShowTheLocation extends React.Component { static propTypes = { match: PropTypes.object.isRequired, location: PropTypes.object.isRequired, history: PropTypes.object.isRequired }; render() { const { match, location, history } = this.props; return <div>You are now at {location.pathname}</div>; } } // 建立一個「鏈接」到路由器的新組件(借用redux術語)。 const ShowTheLocationWithRouter = withRouter(ShowTheLocation);
重要的提示:
withRouter不像React Redux的connect那樣訂閱位置更改以進行狀態更改。而是在位置更改後從<Router>組件傳播出去後從新渲染。這意味着withRouter不會在路由轉換時從新渲染,除非其父組件從新渲染。
靜態方法和屬性
包裝組件的全部非特定於反應的靜態方法和屬性將自動複製到「connected」組件。
包裝的組件在返回的組件上做爲靜態屬性WrappedComponent公開,它能夠用於隔離測試組件等。
// MyComponent.js export default withRouter(MyComponent) // MyComponent.test.js import MyComponent from './MyComponent' render(<MyComponent.WrappedComponent location={{...}} ... />)
該函數將做爲ref prop傳遞給包裝的組件。
class Container extends React.Component { componentDidMount() { this.component.doSomething(); } render() { return ( <MyComponent wrappedComponentRef={c => (this.component = c)} /> ); } }