この記事は、React Router に関する知識 (コード例) を包括的に分析したものです。必要な方は参考にしていただければ幸いです。
マルチページ アプリケーションでは、URL は HTML ページに対応します。 Web アプリケーションには多数の HTML ページが含まれており、複数ページのアプリケーションでは、このルーティング方法はバックエンド ルーティングと呼ばれます。
複数ページのアプリケーションでは、各ページの切り替えごとにサーバーにリクエストを送信する必要があり、ページで使用される静的リソースも再ロードする必要があるため、ある程度の無駄が発生します。さらに、ページ全体の更新もユーザー エクスペリエンスに影響します。これは、ナビゲーション バーやサイドバーなど、異なるページ間に共通の部分が存在することが多く、ページ全体の更新によって、共通部分。
片面アプリケーションでは、URL はサーバーに新しいリクエストを送信しないため、「論理ページ」のルーティングのみを処理できます。このようなルーティングをフロントエンドルーティングと呼びます。
現時点では、ほとんどの国内検索エンジンはシングルページ アプリケーションの SEO を十分にサポートしていないため、Web
アプリケーション (たとえば、企業の公式 Web サイトや電子商取引 Web サイトなど) については、は SEO にとって非常に重要です。一般に、複数ページのアプリケーションを使用することを選択しますか。 React は、単一ページのアプリケーションを開発するためだけのものではありません。
ここで使用する React Router のメジャー バージョン番号は v4 で、これも最新バージョンです。
React Router には、react-router、react-router-dom、react-router-native の 3 つのライブラリが含まれています。 act-router は最も基本的なルーティング機能を提供します。実際の使用では、react-router を直接インストールするのではなく、react-router-dom (ブラウザで使用される) または reverse-router-native() をインストールすることを選択します。アプリケーションが実行されている環境に反映されます)。反応ルーターダムと反応ルーターネイティブは両方とも反応ルーターに依存するため、インストール中に反応ルーターも自動的にインストールされます。
Web アプリケーションを作成し、
npm install react-router-dom
ネイティブ アプリケーションを作成し、
npm install react-router-native
BrowserRouter によって作成される URL は次の形式です。
http://example.com/some/path
http://example.com/#/some/path
HashRouter にはこの問題はありません。これは、ハッシュ部分の内容がサーバーによって自動的に無視されるためです。実際に有効な情報はハッシュ フロントです。最後の部分、および単一ページのアプリケーションの場合、この部分は固定されています。
Router は履歴オブジェクトを作成し、URL が変更されると、その履歴を使用して Router の子孫コンポーネントが再レンダリングされます。 React Router で提供される他のコンポーネントは、コンテキストを通じて履歴オブジェクトを取得できます。これは、React Router の他のコンポーネントが Router コンポーネントの子孫として使用される必要があることも暗黙的に示します。ただし、Router に子要素は 1 つだけです。例:
// 正确 ReactDOM.render( ( <BrowserRouter> <App /> </BrowserRouter>), document.getElementById('root') ) //错误,Router 中包含两个子元素 ReactDOM.render( ( <BrowserRouter> <App1 /> <App2 /> </BrowserRouter>), document.getElementById('root') )
4. Router
1) path
2)match
(1) params: ルートのパスにはパラメータを含めることができます。たとえば、 (2) isExact: URL が完全に一致する場合、値は true になります。たとえば、path='/foo'、URL の場合、値は false になります。 ="http://example.com/foo" の場合は完全一致、URL="http://example.com/foo/1" の場合は部分一致です。 (3)path: Route 的 path 属性,构建嵌套路由时会使用到。 (4)url: URL 的匹配的方式 (1)component component 的值是一个组件,当 URL 和 Route 匹配时,Component属性定义的组件就会被渲染。例如: Foo 组件接收了一个额外的 data 属性。 (3)children 如果 Route 匹配当前 URL,待渲染元素的根节点 p 的 class 将设置成 active. 当URL 和多个 Route 匹配时,这些 Route 都会执行渲染操作。如果只想让第一个匹配的 Route 沉浸,那么可以把这些 Route 包到一个 Switch 组件中。如果想让 URL 和 Route 完全匹配时,Route才渲染,那么可以使用 Route 的 exact 属性。Switch 和 exact 常常联合使用,用于应用首页的导航。例如: 如果不使用 Switch,当 URL 的 pathname 为 "/posts" 时, 嵌套路由是指在Route 渲染的组件内部定义新的 Route。例如,在上一个例子中,在 Posts 组件内再定义两个 Route: Link 是 React Router提供的链接组件,一个 Link 组件定义了当点击该 Link 时,页面应该如何路由。例如: Link 使用 to 属性声明要导航到的URL地址。to 可以是 string 或 object 类型,当 to 为 object 类型时,可以包含 pathname、search、hash、state 四个属性,例如: 除了使用Link外,我们还可以使用 history 对象手动实现导航。history 中最常用的两个方法是 push(path,[state]) 和 replace(path,[state]),push会向浏览器记录中新增一条记录,replace 会用新记录替换记录。例如: 路由设计的过程可以分为两步: 为每一个页面定义有语义的路由名称(path) 组织 Route 结构层次 我们有三个页面,按照页面功能不难定义出如下的路由名称: 登录页: /login 帖子列表页: /posts 帖子详情页: /posts/:id(id代表帖子的ID) 但是这些还不够,还需要考虑打开应用时的默认页面,也就是根路径"/"对应的页面。结合业务场景,帖子列表作为应用的默认页面为合适,因此,帖子列表对应两个路由名称: '/posts'和 '/' React Router 4并不需要在一个地方集中声明应用需要的所有 Route, Route实际上也是一个普通的 React 组件,可以在任意地方使用它(前提是,Route必须是 Router 的子节点)。当然,这样的灵活性也一定程度上增加了组织 Route 结构层次的难度。 第一个Route 使用了 exact 属性,保证只有当访问根路径时,第一个 Route 才会匹配成功。Home 是首页对应组件,可以通过 "/posts" 和 “/” 两个路径访问首页。注意,这里并没有直接渲染帖子列表组件,真正渲染帖子列表组件的地方在 Home 组件内,通过第二层级的路由处理帖子列表组件和帖子详情组件渲染,components/Home.js 的主要代码如下: Home的render内定义了两个 Route,分别用于渲染帖子列表和帖子详情。PostList 是帖子列表组件,Post是帖子详情组件,代码使用Router 的render属性渲染这两个组件,因为它们需要接收额外的 username 属性。另外,无论访问是帖子列表页面还是帖子详情页面,都会共用相同 Header 组件。 默认情况下,当在项目根路径下执行 npm run build 时 ,create-react-app内部使用 webpack将 src路径下的所有代码打包成一个 JS 文件和一个 Css 文件。 当项目代码量不多时,把所有代码打包到一个文件的做法并不会有什么影响。但是,对于一个大型应用,如果还把所有的代码都打包到一个文件中,显然就不合适了。 create-react-app 支持通过动态 import() 的方式实现代码分片。import()接收一个模块的路径作为参数,然后返回一个 Promise 对象, Promise 对象的值就是待导入的模块对象。例如 上面代码会将 moduleA.js 和它所有依赖的其他模块单独打包到一个chunk文件中,只有当用户点击加载按钮,才开始加载这个 chunk 文件。 asyncComponent接收一个函数参数 importComponent, importComponent 内通过import()语法动态导入模块。在AsyncComponent被挂载后,importComponent就会阴调用,进而触发动态导入模块的动作。 这样,只有当路由匹配时,对应的组件才会被导入,实现按需加载的效果。 这里还有一个需要注意的地方,打包后没有单独的CSS文件了。这是因为 CSS样子被打包到各个 chunk 文件中,当 chunk文件被加载执行时,会有动态把 CSS 样式插入页面中。如果希望把 chunk 中的 css打包到一个单独的文件中,就需要修改 webpack 使用的 ExtractTextPlugin 插件的配置,但 create-react-app 并没有直接把 webpack 的配置文件暴露给用户,为了修改相应配置 项目中会多出两个文件夹:config和 scripts,scrips中包含项目启动、编译和测试的脚本,config 中包含项目使用的配置文件, 然后重新编译项目,各个chunk 文件 使用的 CSS 样式 又会统一打包到 main.css 中。 以上がReact Router の知識の包括的な分析 (コード例)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。3)Route 渲染组件的方式
<Route path='/foo' component={Foo} ></p>
<p>当 URL = "http://example.com/foo" 时,Foo组件会被渲染。</p>
<p>(2) render<br>render 的值是一个函数,这个函数返回一个 React 元素。这种方式方便地为待渲染的组件传递额外的属性。例如:</p>
<pre class="brush:php;toolbar:false"><Route path='/foo' render={(props) => {
<Foo {...props} data={extraProps} />
}}>
</Route>
children 的值也是一个函数,函数返回要渲染的 React 元素。 与前两种方式不同之处是,无论是否匹配成功, children 返回的组件都会被渲染。但是,当匹配不成功时,match 属性为 null。例如:<Route path='/foo' render={(props) => {
<p className={props.match ? 'active': ''}>
<Foo {...props} data={extraProps} />
</p>
}}>
</Route>
4)Switch 和 exact
<Router>
<Switch>
<Route exact path='/' component={Home}/>
<Route exact path='/posts' component={Posts} />
<Route exact path='/:user' component={User} />
</Switch>
</Router>
5)嵌套路由
const Posts = ({match}) => {
return (
<p>
{/* 这里 match.url 等于 /posts */}
<Route path={`${match.url}/:id`} component={PostDetail} />
<Route exact path={match.url} component={PostList} />
</p>
)
}
五、链接
const Navigation = () => {
<header>
<nav>
<ul>
<li><Link to='/'>Home</Link></li>
<li><Link to='/posts'>Posts</Link></li>
</ul>
</nav>
</header>
}
<Link to={{
pathname: '/posts',
search: '?sort=name',
hash:'#the-hash',
state: { fromHome: true}
}}>
</Link>
history.push('/posts');
history.replace('/posts');
六、路由设计
1)定义路由名称
2)组织 Route 结构层次
我们先考虑第一层级的路由。登录页和帖子列表页(首页)应该属于第一层级:<Router>
<Switch>
<Route exact path="/" component={Home}></Route>
<Route exact path="/login" component={Login}></Route>
<Route exact path="/posts" component={Home}></Route>
</Switch>
</Router>
class Home extends Component {
/**省略其余代码 */
render() {
const {match, location } = this.props;
const { username } = this.state;
return(
<p>
<Header
username = {username}
onLogout={this.handleLogout}
location = {location}
>
</Header>
{/* 帖子列表路由配置 */}
<Route
path = {match.url}
exact
render={props => <PostList username={username} {...this.props}></PostList>}
></Route>
</p>
)
}
}
七、代码分片
// moduleA.js
const moduleA = 'Hello'
export { moduleA };
// App.js
import React, { Component } from 'react';
class App extends Component {
handleClick = () => {
// 使用import 动态导入 moduleA.js
import('./moduleA')
.then(({moduleA}) => {
// 使用moduleA
})
.catch(err=> {
//处理错误
})
};
render() {
return(
<p>
<button onClick={this.handleClick}>加载 moduleA</button>
</p>
)
}
}
export default App;
当项目中使用 React Router 是,一般会根据路由信息将项目代码分片,每个路由依赖的代码单独打包成一个chunk文件。我们创建一个函数统一处理这个逻辑:import React, { Component } from 'react';
// importComponent 是使用 import()的函数
export default function asyncComponent(importComponent) {
class AsyncComponent extends Component {
constructor(props) {
super(props);
this.state = {
component: null //动态加载的组件
}
}
componentDidMount() {
importComponent().then((mod) => {
this.setState({
// 同时兼容 ES6 和 CommonJS 的模块
component: mod.default ? mod.default : mod;
});
})
}
render() {
// 渲染动态加载组件
const C = this.state.component;
return C ? <C {...this.props}></C> : null
}
}
return AsyncComponent;
}
下面利用 asyncComponent 对上面的例子进行改造,代码如下:import React, { Component } from 'react';
import { ReactDOM, BrowserRouter as Router, Switch, Route } from 'react-dom';
import asyncComponent from './asyncComponent'
//通过asyncComponent 导入组件,创建代码分片点
const AsyncHome = asyncComponent(() => import("./components/Home"))
const AsyncLogin = asyncComponent(() => import("./components/Login"))
class App extends component {
render() {
return(
<Router>
<Switch>
<Route exact path="/" component={AsyncHome}></Route>
<Route exact path="/login" component={AsyncLogin}></Route>
<Route exact path="/posts" component={AsyncHome}></Route>
</Switch>
</Router>
)
}
}
export default App;
,需要将 create-react-app 管理的配置文件“弹射”出来,在项目根路径下执行:npm run eject
webpack配置文件 就在这个路径下,打包 webpack.config.prod.js 找到配置 ExtractTextPlugin 的地方,添加 allChunks:true 这项配置:new ExtractTextPlugin({
filename: cssFilename,
allChunks: true
})