[譯] React 路由和 React 組件的愛恨情仇

做爲 React 開發者,咱們大部分人享用着使用 React Router 爲 React 應用的路由帶來的便利。前端

爲何咱們 ❤️ React 路由:react

  • 與 React 完美結合而且遵循相同的原則
  • 路由的導航方面很是容易理解
  • 組件組合、聲明性 UI、狀態管理 而且它緊密地追隨着 React 的工做流 (事件 => 狀態變化 => 從新渲染)
  • 可靠的 瀏覽歷史特徵 容許用戶在追蹤視圖狀態的同時在應用中導航。

然而在使用 React 路由的時候,若是你的應用程序特定需求變得比你在 web 上的每一個教程中看到的常規用法稍微複雜一些,你將面對一些困難。android

好消息是即便在那些場景下,React 路由仍然容許咱們以一種乾淨的方式解決問題;可是解決方案可能並不像一眼能開出來那麼明顯。這兒有個咱們在 Fjong 開發團隊 👗 的案例,咱們在路由路徑改變查詢參數而且指望一個組件被從新渲染,React Router 的表現卻不是那麼回事兒。ios

在咱們描述具體問題和咱們如何解決這個問題以前,讓咱們聊聊 React 路由和 React 組件之間巨大關係的幾個方面。git

相愛關係

React 路由和 React 組件之間有不少的聯繫。這主要是由於它們都遵循上面提到的相同的事件循環 (事件 => 狀態變化 => 從新渲染)。如今記住這個流程,咱們將解決在應用程序中導航的一個常見問題;當路由更改的時候滾動到頁面的頂部github

假設你有一組名爲 HomeAboutSearch 的組件web

<Router history={History}>
  <Switch>
    <Route exact path="/" component={Home}/>
    <Route exact path="/about" component={About}/>
    <Route exact path="/search" component={Search}/>
    <Route exact component={NoMatch}/>
  </Switch>
</Router>
複製代碼

如今假設當你跳轉至 /search 的時候,你須要滾動不少次才能在 Search 頁面看到你想看到的項目。後端

而後,你在地址欄輸入跳轉至 /about 的連接,而後忽然看到了 About Us 頁面的底部,而不是頂部,這可能很煩人。這有一些方法解決這個問題,可是 React 路由爲你提供了全部必要的工具來正確地完成這個任務。讓咱們來看看實際狀況。設計模式

/* globals window */

/* Global Dependencies */
const React = require('react');
const { Component } = require('react');
const PropTypes = require('prop-types');
const { Route, withRouter } = require('react-router-dom');

class ScrollToTopRoute extends Component {

	componentDidUpdate(prevProps) {
		if (this.props.location.pathname !== prevProps.location.pathname) {
			window.scrollTo(0, 0);
		}
	}

	render() {
		const { component: Component, ...rest } = this.props;
    
		return <Route {...rest} render={props => (<Component {...props} />)} />;
	}
}

ScrollToTopRoute.propTypes = {
	path: PropTypes.string,
	location: PropTypes.shape({
		pathname: PropTypes.string,
	}),
	component: PropTypes.instanceOf(Component),
};

module.exports = withRouter(ScrollToTopRoute);

// Usage in App.jsx
<Router history={History}>
  <Switch>
    <ScrollToTopRoute exact path="/" component={Home}/>
    <ScrollToTopRoute exact path="/about" component={About}/>
    <ScrollToTopRoute exact path="/search" component={Search}/>
    <ScrollToTopRoute exact component={NoMatch}/>
  </Switch>
</Router>
複製代碼

討厭的關係

可是對於任何關係來講,事情並非在每種狀況下都進展順利。這與 React 路由和 React 組件的狀況相同。爲了更好地理解這一點,咱們來看看應用程序中的一個可能的場景。api

假設你要從 /search/about,而後當你到達 About Us 頁面時,頁面顯然會像你所指望的那樣從新渲染。從 /about 導航到 /search 也是如此。

如今假設從 /search?tags=Dresses/search?tags=Bags 的時候,你的 SearchPage 將搜索查詢參數附加到 URL 上,而且你但願從新渲染這些參數。在這,咱們更改了 React 路由路徑 location.path = /search 上的搜索查詢,它被 React 路由識別爲同一位置對象上的屬性 location.search = ?tags=Dresses or ?tags=Bags

不管是 React 路由仍是你的組件都沒有意識到它們須要從新渲染頁面,由於從技術上講,咱們仍是在同一個頁面。React 組件不容許在相同路徑可是不一樣搜索查詢間的路由跳轉觸發從新渲染。

目前咱們的路由和組件彷佛有點脫節。好難過 :(

因此,咱們如何才能解決這個問題呢?其實他們每一個人都有解決這個問題的方法。React 路由告訴咱們 URL 中的搜索查詢參數是否發生了變化並且更重要的是根據 React 正確的生命週期來作這件事。以後,組件將負責決定如何處理這些信息。

在這個案例中,若是組件須要從新渲染(由一個叫 RouteKey 的 boolean 屬性(prop)決定)它將向組件傳遞一個惟一的鍵,該鍵是 location.pathnamelocation.search 的組合(這傳遞了鍵的通常經驗法則,鍵應該是惟一的、穩定的和可預測的)在這個場景中,每當路由被請求,組件都能接受一個新的鍵;並且即便你停留在同一個頁面,它也會爲你從新渲染,沒有任何反作用。咱們來看看它是如何在實際中放回做用的!

/* globals window */

/** Global Dependencies */
const React = require('react');
const { Component } = require('react');
const PropTypes = require('prop-types');
const { Route, withRouter } = require('react-router-dom');

class ScrollToTopRoute extends Component {

	componentDidUpdate(prevProps) {
		if (this.props.location.pathname !== prevProps.location.pathname) {
			window.scrollTo(0, 0);
		}
	}

	render() {
		const { component: Component, RouteKey, location, ...rest } = this.props;

		/**
		 * Sometimes we need to force a React Route to re-render when the
		 * search params (query) in the url changes. React Router does not
		 * do this automatically if you are on the same page when the query
		 * changes. By passing the `RouteKey`ro the `ScrollToTopRoute` and
		 * setting it to true, we are passing the combination of pathname and
		 * search params as a unique key to the component and that is a enough
		 * and clear trigger for the component to re-render without side effects
		 */
		const Key = RouteKey ? location.pathname + location.search : null;

		return <Route {...rest} render={props => (<Component {...props} key={Key} />)} />;
	}
}

ScrollToTopRoute.propTypes = {
	path: PropTypes.string,
	location: PropTypes.shape({
		pathname: PropTypes.string,
	}),
	component: PropTypes.instanceOf(Component),
	RouteKey: PropTypes.boolean,
};

module.exports = withRouter(ScrollToTopRoute);

// Usage in App.jsx
<Router history={History}>
  <Switch>
    <ScrollToTopRoute exact path="/" component={Home}/>
    <ScrollToTopRoute exact path="/about" component={About}/>
    <ScrollToTopRoute exact path="/search" component={Search} RouteKey={true} />
    <ScrollToTopRoute exact component={NoMatch}/>
  </Switch>
</Router>
複製代碼

結論

咱們介紹了React 路由和組件完美結合的例子,以及它們稍微分離時的場景。可是重要的是要記住,在大部分狀況下,React 路由遵循和 React 相同的原則和設計模式,花時間熟悉這些原則及其相關的執行上下文,對於在 React 路由中修復 bug 會有很大幫助。

若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索