2020-06-24
Ann Ann
React Router 安装命令如下。
$ npm install -S react-router
使用时,路由器Router
就是React的一个组件。
import { Router } from 'react-router';
render(<Router/>, document.getElementById('app'));
Router
组件本身只是一个容器,真正的路由要通过Route
组件定义。
import { Router, Route, hashHistory } from 'react-router';
render((
<Router history={hashHistory}>
<Route path="/" component={App}/>
</Router>
), document.getElementById('app'));
上面代码中,用户访问根路由/
(比如http://www.example.com/
),组件APP
就会加载到document.getElementById('app')
。
你可能还注意到,Router
组件有一个参数history
,它的值hashHistory
表示,路由的切换由URL的hash变化决定,即URL的#
部分发生变化。举例来说,用户访问http://www.example.com/
,实际会看到的是http://www.example.com/#/
。
Route
组件定义了URL路径与组件的对应关系。你可以同时使用多个Route
组件。
<Router history={hashHistory}>
<Route path="/" component={App}/>
<Route path="/repos" component={Repos}/>
<Route path="/about" component={About}/>
</Router>
上面代码中,用户访问/repos
(比如http://localhost:8080/#/repos
)时,加载Repos
组件;访问/about
(http://localhost:8080/#/about
)时,加载About
组件。
Route
组件还可以嵌套。
<Router history={hashHistory}>
<Route path="/" component={App}>
<Route path="/repos" component={Repos}/>
<Route path="/about" component={About}/>
</Route>
</Router>
上面代码中,用户访问/repos
时,会先加载App
组件,然后在它的内部再加载Repos
组件。
<App>
<Repos/>
</App>
App
组件要写成下面的样子。
export default React.createClass({
render() {
return <div>
{this.props.children}
</div>
}
})
上面代码中,App
组件的this.props.children
属性就是子组件。
子路由也可以不写在Router
组件里面,单独传入Router
组件的routes
属性。
let routes = <Route path="/" component={App}>
<Route path="/repos" component={Repos}/>
<Route path="/about" component={About}/>
</Route>;
<Router routes={routes} history={browserHistory}/>
Route
组件的path
属性指定路由的匹配规则。这个属性是可以省略的,这样的话,不管路径是否匹配,总是会加载指定组件。
请看下面的例子。
<Route path="inbox" component={Inbox}>
<Route path="messages/:id" component={Message} />
</Route>
上面代码中,当用户访问/inbox/messages/:id
时,会加载下面的组件。
<Inbox>
<Message/>
</Inbox>
如果省略外层Route
的path
参数,写成下面的样子。
<Route component={Inbox}>
<Route path="inbox/messages/:id" component={Message} />
</Route>
现在用户访问/inbox/messages/:id
时,组件加载还是原来的样子。
<Inbox>
<Message/>
</Inbox>
``path`属性可以使用通配符。
<Route path="/hello/:name">
// 匹配 /hello/michael
// 匹配 /hello/ryan
<Route path="/hello(/:name)">
// 匹配 /hello
// 匹配 /hello/michael
// 匹配 /hello/ryan
<Route path="/files/*.*">
// 匹配 /files/hello.jpg
// 匹配 /files/hello.html
<Route path="/files/*">
// 匹配 /files/
// 匹配 /files/a
// 匹配 /files/a/b
<Route path="/**/*.jpg">
// 匹配 /files/hello.jpg
// 匹配 /files/path/to/file.jpg
通配符的规则如下。
(1)
:paramName
:paramName
匹配URL的一个部分,直到遇到下一个/
、?
、#
为止。这个路径参数可以通过this.props.params.paramName
取出。(2)
()
()
表示URL的这个部分是可选的。(3)
*
*
匹配任意字符,直到模式里面的下一个字符为止。匹配方式是非贪婪模式。(4)
**
**
匹配任意字符,直到下一个/
、?
、#
为止。匹配方式是贪婪模式。
path
属性也可以使用相对路径(不以/
开头),匹配时就会相对于父组件的路径,可以参考上一节的例子。嵌套路由如果想摆脱这个规则,可以使用绝对路由。
路由匹配规则是从上到下执行,一旦发现匹配,就不再其余的规则了。
<Route path="/comments" ... />
<Route path="/comments" ... />
上面代码中,路径/comments
同时匹配两个规则,第二个规则不会生效。
设置路径参数时,需要特别小心这一点。
<Router>
<Route path="/:userName/:id" component={UserPage}/>
<Route path="/about/me" component={About}/>
</Router>
上面代码中,用户访问/about/me
时,不会触发第二个路由规则,因为它会匹配/:userName/:id
这个规则。因此,带参数的路径一般要写在路由规则的底部。
此外,URL的查询字符串/foo?bar=baz
,可以用this.props.location.query.bar
获取。
下面的例子,你会不会觉得有一点问题?
<Router>
<Route path="/" component={App}>
<Route path="accounts" component={Accounts}/>
<Route path="statements" component={Statements}/>
</Route>
</Router>
上面代码中,访问根路径/
,不会加载任何子组件。也就是说,App
组件的this.props.children
,这时是undefined
。
因此,通常会采用{this.props.children || <Home/>}
这样的写法。这时,Home
明明是Accounts
和Statements
的同级组件,却没有写在Route
中。
IndexRoute
就是解决这个问题,显式指定Home
是根路由的子组件,即指定默认情况下加载的子组件。你可以把IndexRoute
想象成某个路径的index.html
。
<Router>
<Route path="/" component={App}>
<IndexRoute component={Home}/>
<Route path="accounts" component={Accounts}/>
<Route path="statements" component={Statements}/>
</Route>
</Router>
现在,用户访问/
的时候,加载的组件结构如下。
<App>
<Home/>
</App>
这种组件结构就很清晰了:App
只包含下级组件的共有元素,本身的展示内容则由Home
组件定义。这样有利于代码分离,也有利于使用React Router提供的各种API。
注意,IndexRoute
组件没有路径参数path
。
<Redirect>
组件用于路由的跳转,即用户访问一个路由,会自动跳转到另一个路由。
<Route path="inbox" component={Inbox}>
{/* 从 /inbox/messages/:id 跳转到 /messages/:id */}
<Redirect from="messages/:id" to="/messages/:id" />
</Route>
现在访问/inbox/messages/5
,会自动跳转到/messages/5
。
IndexRedirect
组件用于访问根路由的时候,将用户重定向到某个子组件。
<Route path="/" component={App}>
<IndexRedirect to="/welcome" />
<Route path="welcome" component={Welcome} />
<Route path="about" component={About} />
</Route>
上面代码中,用户访问根路径时,将自动重定向到子组件welcome
。
Link
组件用于取代<a>
元素,生成一个链接,允许用户点击后跳转到另一个路由。它基本上就是<a>
元素的React 版本,可以接收Router
的状态。
render() {
return <div>
<ul role="nav">
<li><Link to="/about">About</Link></li>
<li><Link to="/repos">Repos</Link></li>
</ul>
</div>
}
如果希望当前的路由与其他路由有不同样式,这时可以使用Link
组件的activeStyle
属性。
<Link to="/about" activeStyle={{color: 'red'}}>About</Link>
<Link to="/repos" activeStyle={{color: 'red'}}>Repos</Link>
上面代码中,当前页面的链接会红色显示。
另一种做法是,使用activeClassName
指定当前路由的Class
。
<Link to="/about" activeClassName="active">About</Link>
<Link to="/repos" activeClassName="active">Repos</Link>
上面代码中,当前页面的链接的class
会包含active
。
在Router
组件之外,导航到路由页面,可以使用浏览器的History API,像下面这样写。
import { browserHistory } from 'react-router';
browserHistory.push('/some/path');
如果链接到根路由/
,不要使用Link
组件,而要使用IndexLink
组件。
这是因为对于根路由来说,activeStyle
和activeClassName
会失效,或者说总是生效,因为/
会匹配任何子路由。而IndexLink
组件会使用路径的精确匹配。
<IndexLink to="/" activeClassName="active">
Home
</IndexLink>
上面代码中,根路由只会在精确匹配时,才具有activeClassName
。
另一种方法是使用Link
组件的onlyActiveOnIndex
属性,也能达到同样效果。
<Link to="/" activeClassName="active" onlyActiveOnIndex={true}>
Home
</Link>
实际上,IndexLink
就是对Link
组件的onlyActiveOnIndex
属性的包装。
Router
组件的history
属性,用来监听浏览器地址栏的变化,并将URL解析成一个地址对象,供 React Router 匹配。
history
属性,一共可以设置三种值。
- browserHistory
- hashHistory
- createMemoryHistory
如果设为hashHistory
,路由将通过URL的hash部分(#
)切换,URL的形式类似example.com/#/some/path
。
import { hashHistory } from 'react-router'
render(
<Router history={hashHistory} routes={routes} />,
document.getElementById('app')
)
如果设为browserHistory
,浏览器的路由就不再通过Hash
完成了,而显示正常的路径example.com/some/path
,背后调用的是浏览器的History API。
import { browserHistory } from 'react-router'
render(
<Router history={browserHistory} routes={routes} />,
document.getElementById('app')
)
但是,这种情况需要对服务器改造。否则用户直接向服务器请求某个子路由,会显示网页找不到的404错误。
如果开发服务器使用的是webpack-dev-server
,加上--history-api-fallback
参数就可以了。
$ webpack-dev-server --inline --content-base . --history-api-fallback
createMemoryHistory
主要用于服务器渲染。它创建一个内存中的history
对象,不与浏览器URL互动。
const history = createMemoryHistory(location)
Link
组件用于正常的用户点击跳转,但是有时还需要表单跳转、点击按钮跳转等操作。这些情况怎么跟React Router对接呢?
下面是一个表单。
<form onSubmit={this.handleSubmit}>
<input type="text" placeholder="userName"/>
<input type="text" placeholder="repo"/>
<button type="submit">Go</button>
</form>
第一种方法是使用browserHistory.push
import { browserHistory } from 'react-router'
// ...
handleSubmit(event) {
event.preventDefault()
const userName = event.target.elements[0].value
const repo = event.target.elements[1].value
const path = `/repos/${userName}/${repo}`
browserHistory.push(path)
},
第二种方法是使用context
对象。
export default React.createClass({
// ask for `router` from context
contextTypes: {
router: React.PropTypes.object
},
handleSubmit(event) {
// ...
this.context.router.push(path)
},
})
每个路由都有Enter
和Leave
钩子,用户进入或离开该路由时触发。
<Route path="about" component={About} />
<Route path="inbox" component={Inbox}>
<Redirect from="messages/:id" to="/messages/:id" />
</Route>
上面的代码中,如果用户离开/messages/:id
,进入/about
时,会依次触发以下的钩子。
/messages/:id
的onLeave
/inbox
的onLeave
/about
的onEnter
下面是一个例子,使用onEnter
钩子替代<Redirect>
组件。
<Route path="inbox" component={Inbox}>
<Route
path="messages/:id"
onEnter={
({params}, replace) => replace(`/messages/${params.id}`)
}
/>
</Route>
下面是一个高级应用,当用户离开一个路径的时候,跳出一个提示框,要求用户确认是否离开。
const Home = withRouter(
React.createClass({
componentDidMount() {
this.props.router.setRouteLeaveHook(
this.props.route,
this.routerWillLeave
)
},
routerWillLeave(nextLocation) {
// 返回 false 会继续停留当前页面,
// 否则,返回一个字符串,会显示给用户,让其自己决定
if (!this.state.isSaved)
return '确认要离开?';
},
})
)
上面代码中,setRouteLeaveHook
方法为Leave
钩子指定routerWillLeave
函数。该方法如果返回false
,将阻止路由的切换,否则就返回一个字符串,提示用户决定是否要切换。
(完)
React-router
提供两个方法,用于服务器渲染。
match
方法的回调函数,可以接受三个参数。第一个参数是匹配错误时抛出的错误,第二个参数是路径存在重定向时,将要导向的路径, 第三个参数是匹配成功时,页面渲染所需要的参数。
import { renderToString } from 'react-dom/server'
import { match, RouterContext } from 'react-router'
import routes from './routes'
serve((req, res) => {
// req.url应该是原始请求的完整的URL路径,包括查询字符串
match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {
if (error) {
res.status(500).send(error.message)
} else if (redirectLocation) {
res.redirect(302, redirectLocation.pathname + redirectLocation.search)
} else if (renderProps) {
// You can also check renderProps.components or renderProps.routes for
// your "not found" component or route respectively, and send a 404 as
// below, if you're using a catch-all route.
res.status(200).send(renderToString(<RouterContext {...renderProps} />))
} else {
res.status(404).send('Not found')
}
})
})
第一步,输出routes
组件。
module.exports = (
<Route path="/" component={App}>
<IndexRoute component={Home}/>
<Route path="/repos" component={Repos}>
<Route path="/repos/:userName/:repoName" component={Repo}/>
</Route>
<Route path="/about" component={About}/>
</Route>
)
第二步,将该组件加载到Router
组件。
import React from 'react'
import { render } from 'react-dom'
import { Router, browserHistory } from 'react-router'
// import routes and pass them into <Router/>
import routes from './modules/routes'
render(
<Router routes={routes} history={browserHistory}/>,
document.getElementById('app')
)
第三步,生成静态网页。
import { match, RouterContext } from 'react-router';
// match the routes to the url
match({ routes: routes, location: req.url }, (err, redirect, props) => {
// `RouterContext` is the what `Router` renders. `Router` keeps these
// `props` in its state as it listens to `browserHistory`. But on the
// server our app is stateless, so we need to use `match` to
// get these props before rendering.
const appHtml = renderToString(<RouterContext {...props}/>)
// dump the HTML into a template, lots of ways to do this, but none are
// really influenced by React Router, so we're just using a little
// function, `renderPage`
res.send(renderPage(appHtml))
})
更详细的版本。
app.get('*', (req, res) => {
match({ routes: routes, location: req.url }, (err, redirect, props) => {
// in here we can make some decisions all at once
if (err) {
// there was an error somewhere during route matching
res.status(500).send(err.message)
} else if (redirect) {
// we haven't talked about `onEnter` hooks on routes, but before a
// route is entered, it can redirect. Here we handle on the server.
res.redirect(redirect.pathname + redirect.search)
} else if (props) {
// if we got props then we matched a route and can render
const appHtml = renderToString(<RouterContext {...props}/>)
res.send(renderPage(appHtml))
} else {
// no errors, no redirect, we just didn't match anything
res.status(404).send('Not Found')
}
})
})
AI一日Workshop:学会GPT-4o Canvas、Perplexity AI、NotebookLM三大工具
2025/03/15 05:00 (Sydney)
商业数据分析实战班第17期(Self-Paced + Tutor)
2025/03/15 06:13 (Sydney)
数据工程全栈班第16期
2025/03/23 07:02 (Sydney)
地址
Level 10b, 144 Edward Street, Brisbane CBD(Headquarter)Level 2, 171 La Trobe St, Melbourne VIC 3000四川省成都市武侯区桂溪街道天府大道中段500号D5东方希望天祥广场B座45A13号Business Hub, 155 Waymouth St, Adelaide SA 5000Disclaimer
JR Academy acknowledges Traditional Owners of Country throughout Australia and recognises the continuing connection to lands, waters and communities. We pay our respect to Aboriginal and Torres Strait Islander cultures; and to Elders past and present. Aboriginal and Torres Strait Islander peoples should be aware that this website may contain images or names of people who have since passed away.
匠人学院网站上的所有内容,包括课程材料、徽标和匠人学院网站上提供的信息,均受澳大利亚政府知识产权法的保护。严禁未经授权使用、销售、分发、复制或修改。违规行为可能会导致法律诉讼。通过访问我们的网站,您同意尊重我们的知识产权。 JR Academy Pty Ltd 保留所有权利,包括专利、商标和版权。任何侵权行为都将受到法律追究。查看用户协议
© 2017-2024 JR Academy Pty Ltd. All rights reserved.
ABN 26621887572