[译] 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前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

相关文章
相关标签/搜索