react-router的核心是一个管理location的状态容器,它追踪location的变化并渲染不同的<Route>,还为开发者提供<Link>和history API来更新location。
react-router 5.1中值得注意的是发布了一些hooks,如useParams, useHistory等,相比较于之前版本的组合组件,5.1版本提供了组合状态(state)和行为(behavior)的能力。下面简单介绍一个各个hooks:
1. useParams
通过传入的组件的props.match或者<Route>的render属性里传入的props.match, 如下
// before
function BlogPost({ match }) {
let { slug } = match.params
// ...
}
ReactDOM.render(
<Router>
<div>
<Switch>
{/* 方式一:Using the `component` prop */}
<Route path="/blog/:slug" component={BlogPost} />
{/* 方式二:Using the `render` prop */}
<Route
path="/posts/:slug"
render={({ match }) => <BlogPost match={match} />}
/>
</Switch>
</div>
</Router>,
document.getElementById('root')
)
// after
function BlogPost() {
// We can call useParams() here to get the params,
// or in any child element as well!
let { slug } = useParams()
// ...
}
ReactDOM.render(
<Router>
<div>
<Switch>
{/* No weird props here, just use
regular `children` elements! */}
<Route path="/posts/:slug">
<BlogPost />
</Route>
</Switch>
</div>
</Router>,
document.getElementById('root')
)
方式一则需要router使用createElement(component)创建一个组件,而方式二则需要手动传递参数。
使用useParams之后,可以在BlogPost及其任意子组件中使用useParams,而且不需要手动传递参数
2. useLocation
useLocation返回当前的location对象,可以用于任何需要获取location的地方,下面的例子封装了一个统计pv的hook: usePageViews
import { Switch, useLocation } from 'react-router-dom'
function usePageViews() {
let location = useLocation()
useEffect(
() => {
ga.send(['pageview', location.pathname])
},
[location]
)
}
function App() {
usePageViews()
return <Switch>{/* your routes here */}</Switch>
}use
3. useHistory
类似,5.1还提供了获取history的勾子(注:useHistory是为后面的useNavigate铺垫的 )
import { useHistory } from 'react-router-dom'
function BackButton({ children }) {
let history = useHistory()
return (
<button type="button" onClick={() => history.goBack()}>
{children}
</button>
)
}
4. useRouteMatch<{}>(path?: string | RouteProps | undefined): match<{}> | null
在一些场景中,我们可能会嵌套一层<Route>来获取match的数据,useRouteMatch则是可以直接获取匹配的url,包括了exact、strict、sensitive选项
// before
import { Route } from 'react-router-dom'
function App() {
return (
<div>
{/* ... */}
<Route
path="/BLOG/:slug/"
strict
sensitive
render={({ match }) => {
return match ? <BlogPost match={match} /> : <NotFound />
}}
/>
</div>
)
}
// after
import { useRouteMatch } from 'react-router-dom'
function App() {
let match = useRouteMatch({
path: '/BLOG/:slug/',
strict: true,
sensitive: true
})
return (
<div>
{/* ... */}
{match ? <BlogPost match={match} /> : <NotFound />}
</div>
)
}
也可以直接使用
function Topics() {
const match = useRouteMatch();
if (match === null) {
return <></>;
}
return (
<div>
<h2>Topics</h2>
<ul>
<li>
<Link to={`${match.url}/components`}>Components</Link>
</li>
<li>
<Link to={`${match.url}/props-v-state`}>Props v. State</Link>
</li>
</ul>
<Switch>
<Route path={`${match.path}/:topicId`}>
<Topic />
</Route>
<Route path={match.path}>
<h3>Please select a topic.</h3>
</Route>
</Switch>
</div>
);
}
function Topic() {
const { topicId } = useParams();
return <h3>Requested topic ID: {topicId}</h3>;
}
结语
- 推荐使用
<Route children>替代<Route component>和<Route render>,这样可以使用jsx来组合元素,使用hooks来组合状态; - 推荐使用
hooks替代withRouter, 用useRouteMatch来代替<Switch>外的‘悬浮(仅为了获取匹配路由的信息)’的<Route>。 - 虽然
<Route component>和<Route render>以及withRouter在5.1中依然可用,但是未来很有可能会被废弃。