이 글은 React Router 지식(코드 예제)에 대한 종합적인 분석을 제공합니다. 도움이 필요한 친구들이 참고할 수 있기를 바랍니다.
여러 페이지 애플리케이션에서는 하나의 URL 하나의 HTML 페이지에 해당하며 하나의 웹 응용 프로그램에는 여러 HTML 페이지가 포함되어 있습니다. 다중 페이지 응용 프로그램에서는 서버가 페이지 라우팅 제어를 담당합니다.
다중 페이지 애플리케이션에서는 각 페이지 전환이 서버에 요청을 보내야 하며, 페이지에서 사용하는 정적 리소스도 다시 로드해야 하므로 일정량의 낭비가 발생합니다. 또한, 페이지 전체 새로 고침은 사용자 경험에도 영향을 미칩니다. 탐색 모음, 사이드바 등과 같이 서로 다른 페이지 간에 공통 부분이 있는 경우가 많고, 페이지 전체 새로 고침으로 인해 페이지 새로 고침이 발생하는 경우가 많기 때문입니다. 공통 부분.
단방향 애플리케이션에서는 URL이 서버에 새 요청을 보내지 않으므로 "논리 페이지"의 라우팅이 프런트엔드에서만 처리할 수 있으므로 이 라우팅 방법을 프런트엔드 라우팅이라고 합니다.
현재 대부분의 국내 검색 엔진은 단일 페이지 애플리케이션의 SEO를 지원하지 않습니다. 따라서 웹
애플리케이션(예: 기업 공식 웹사이트, 전자상거래)에서는 SEO가 매우 중요합니다. 웹사이트 등)에서는 일반적으로 다중 페이지 애플리케이션을 사용하도록 선택합니다. React는 단일 페이지 애플리케이션 개발만을 위한 것이 아닙니다.
여기서 사용된 React Router의 메이저 버전 번호는 v4이며, 이 역시 최신 버전입니다.
React Router에는 3개의 라이브러리(react-router, React-router-dom 및 React-router-native)가 포함되어 있습니다. React-router는 가장 기본적인 라우팅 기능을 제공하며, 실제 사용 시에는 React-Router를 직접 설치하지 않고, 브라우저에서 사용하는 React-Router-dom 또는 React-Router-Native를 선택하여 설치하겠습니다. 애플리케이션이 실행되는 환경). React-Router-dom과 React-Router-Native는 모두 React-Router에 의존하므로 설치 중에 React-Router도 자동으로 설치됩니다.
웹 애플리케이션 생성,
npm install react-router-dom
사용 navtive 애플리케이션 생성,
npm install react-router-native
React Router Through Router and Route A 구성 요소는 라우팅 기능을 완성합니다. 라우터는 라우터로 이해될 수 있습니다. 애플리케이션에는 라우터 인스턴스가 필요합니다. 모든 구성 구성 요소 경로는 라우터의 하위 구성 요소로 정의됩니다. 웹 애플리케이션에서는 일반적으로 Router를 래핑하는 두 가지 구성 요소인 BrowserRouter 또는 HashRouter를 사용합니다. BrowserRouter는 HTML5의 기록 API(pushState, replacementState 등)를 사용하여 애플리케이션의 UI와 URL을 동기화합니다. HashRouter는 URL의 해시를 사용하여 애플리케이션 UI와 URL을 동기화합니다.
http://example.com/some/path
http://example.com/#/some/path
BrowserRouter를 사용하는 경우 , 일반적으로 서버에서 가능한 모든 URL을 올바르게 처리할 수 있도록 구성해야 합니다. 예를 들어, 브라우저가 http://example.com/some/path와 http://example.com/some/path2라는 두 가지 요청을 하면 서버는 올바른 HTML 페이지(즉, 단일 페이지 애플리케이션의 HTML 페이지만)
HashRouter에는 해시 부분의 내용이 서버에서 자동으로 무시되기 때문에 이 문제가 없습니다. 실제로 효과적인 정보는 해시입니다. 프론트 엔드 부분이며 단일 페이지 애플리케이션의 경우 이 부분이 수정되었습니다.
Router는 기록 개체를 생성하고 URL을 추적하는 데 기록을 사용합니다. URL이 변경되면 라우터의 하위 구성 요소가 다시 렌더링됩니다. React Router에 제공되는 다른 구성 요소는 컨텍스트를 통해 히스토리 객체를 얻을 수 있으며, 이는 또한 React Router의 다른 구성 요소가 Router 구성 요소의 자손으로 사용되어야 함을 암시적으로 나타냅니다. 그러나 Router에는 하위 요소가 하나만 있을 수 있습니다. 예:
// 正确 ReactDOM.render( ( <BrowserRouter> <App /> </BrowserRouter>), document.getElementById('root') ) //错误,Router 中包含两个子元素 ReactDOM.render( ( <BrowserRouter> <App1 /> <App2 /> </BrowserRouter>), document.getElementById('root') )
Route는 React Router에서 라우팅 정보를 구성하는 데 사용되는 구성 요소이며 또한 React Router의 구성요소 중 가장 자주 사용되는 구성요소입니다. 구성 요소가 URL을 기반으로 렌더링할지 여부를 결정해야 할 때마다 경로를 생성해야 합니다.
각 경로는 경로 속성을 정의해야 합니다. Path는 HashRouter를 사용할 때 이 라우터와 일치하는 URL의 경로 이름을 설명하는 데 사용됩니다. , 경로는 이 경로와 일치하는 URL의 해시를 설명하는 데 사용됩니다. 예를 들어, BrowserRouter를 사용하는 경우
URL이 Route와 일치하면 Route는 일치 개체를 생성하고 이를 props의 속성으로 렌더링된 구성 요소에 전달합니다. 이 객체에는 다음 4가지 속성이 포함되어 있습니다.
(1) 매개변수: 경로의 경로에는 매개변수가 포함될 수 있습니다. 예를 들어 (2) isExact: 부울 값입니다. URL이 완전히 일치하면 값은 true이고, URL이 부분적으로 일치하면 값은 false입니다. , URL= "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 중국어 웹사이트의 기타 관련 기사를 참조하세요!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
})