1、路由方式
React-Router 中的 3 个核心:
- 路由器,比如 BrowserRouter 和 HashRouter
- 路由,比如 Route 和 Switch
- 导航,比如 Link、NavLink、Redirect
路由主要分为两种方式,一种是 history 模式,另一种是 Hash 模式;
- history 模式下:www.xxx.com/home import { BrowserRouter } from 'react-router-dom'
- hash 模式下: www.xxx.com/#/home import { HashRouter } from 'react-router-dom'
2、React-Router 基本构成
2.1 history,location,match
- history 对象:history对象保存改变路由方法 push ,replace,和监听路由方法 listen 等。
- location 对象:可以理解为当前状态下的路由信息,包括 pathname ,state 等。
- match 对象:这个用来证明当前路由的匹配信息的对象。存放当前路由path 等信息。
2.2 exact
Route 可以加上exact,来进行精确匹配,精确匹配原则,pathname 必须和 Route 的 path 完全匹配,才能展示该路由信息。
2.3 使用react-router-config
使用react-router-config库来配置路由:
import { renderRoutes } from "react-router-config";
const RouteList = [
{
name: '首页',
path: '/router/home',
exact:true,
component:Home
},
{
name: '列表页',
path: '/router/list',
render:()=><List />
},
{
name: '详情页',
path: '/router/detail',
component:detail
},
{
name: '我的',
path:'/router/person',
component:personal
}
]
function Index(){
return <div>
<Meuns/>
{ renderRoutes(RouteList) }
</div>
}
2.4 Switch
Switch 作用是先通过匹配选出一个正确路由 Route 进行渲染。
<Switch>
<Route path='/home' component={Home} />
<Route path='/list' component={List} />
<Route path='/my' component={My} />
</Switch>
2.5 Redirect
Redirect 可以在路由不匹配情况下跳转指定某一路由。
<Switch>
<Route path='/router/home' component={Home} />
<Route path='/router/list' component={List} />
<Route path='/router/my' component={My} />
<Redirect from={'/router/*'} to={'/router/home' } />
</Switch>
3、路由使用方式
3.1 路由组件 props
被 Route 包裹的路由组件 props 中会默认混入 history 等信息;
class Home extends React.Component{
render(){
return <div>
<Children {...this.props} />
</div>
}
}
3.2 withRouter
对于距离路由组件比较远的深层次组件,通常可以用 react-router 提供的 withRouter 高阶组件方式获取 histroy ,loaction 等信息。
import { withRouter } from 'react-router-dom'
@withRouter
class Home extends React.Component{
componentDidMount(){
console.log(this.props.history)
}
render(){
return <div>
{ /* ....*/ }
</div>
}
}
3.3 useHistory 和 useLocation
对于函数组件,可以用 React-router 提供的自定义 hooks 中的 useHistory 获取 history 对象,用 useLocation 获取 location 对象。
import { useHistory ,useLocation } from 'react-router-dom'
function Home(){
const history = useHistory() /* 获取history信息 */
const useLocation = useLocation() /* 获取location信息 */
}
4、使用技巧
4.1 路由跳转
路由跳转有声明式路由和函数式路由两种。
- 声明式:
<NavLink to='/home' />
,利用 react-router-dom 里面的 Link 或者 NavLink 。 - 函数式:
history.push('/home')
。
4.2 参数传递
url拼接
const name = '小明'
const age = '15'
history.push(`/home?name=${name}&age=${age}`)
state路由状态
const name = '小明'
const age = '15'
history.push({
pathname:'/home',
state:{
name,
age
}
})
// 获取
const {state:{name ,age}} = props.location
动态路径参数路由
// 参数做路径
<Route path="/post/:id" />
// 跳转
history.push('/post/'+id) // id为动态的文章id
5、路由扩展
5.1 嵌套路由
嵌套路由子路由一定要跟随父路由。比如父路由是 /home ,那么子路由的形式就是 /home/xxx ,否则路由页面将展示不出来。
/* 第二层嵌套路由 */
function Home(){
return <div>
<Route path='/home/home1' component={home1} />
<Route path='/home/home2' component={home2} />
</div>
}
/* 第一层父级路由 */
function Index(){
return (
<Switch>
<Route path="/home" component={Home} />
</Switch>
)
}
5.2 自定义路由
// 定义组件
function CustomRouter(props){
return xxx(判断条件)? <Route {...props} /> : null />
}
// 使用
<CustomRouter path='/index' component={Index} />
6、路由本质
路由本质就是对 url 进行改变和监听,来让某个 dom 节点显示对应的视图。
7、浏览器的 hash 与 history
我们先来了解一下浏览器对hash模式和history模式的处理。
7.1 hash模式
hash 的改变:我们可以通过 location 暴露出来的属性,直接去修改当前 URL 的 hash 值:
window.location.hash = 'index';
// 路径就会变为 xx网址/#index
hash 的感知:通过监听 “hashchange”事件,可以用 JS 来捕捉 hash 值的变化,进而决定我们页面内容是否需要更新:
// 监听hash变化,点击浏览器的前进后退会触发
window.addEventListener('hashchange', function(event){
// 根据 hash 的变化更新内容
},false)
7.2 history 模式
使用history.pushState 和 history.replaceState:
history.pushState(data[,title][,url]); // 向浏览历史中追加一条记录
// history.pushState({}, '', 'index')
// 路径就会变为 xx网址/index
history.replaceState(data[,title][,url]); // 修改(替换)当前页在浏览历史中的信息
popstate用来控制浏览器后退:
window.addEventListener('popstate', function(e) {
console.log(e)
});
8、BrowserRouter & HashRouter
react-router-dom 提供了 BrowserRouter 和 HashRouter 两个组件来实现应用的 UI 和 URL 同步。 我们先来看 BrowserRoute 组件。
// react-router-dom/modules/BrowserRouter.js
// ...
import { Router } from "react-router"
import { createBrowserHistory as createHistory } from "history"
class BrowserRouter extends React.Component {
history = createHistory(this.props) // 创建一个 history 对象
render() {
return <Router history={this.history} children={this.props.children} />
}
}
// ...
在来看一下HashRouter
// react-router-dom/modules/HashRouter.js
// 最主要的区别就是引用了 history 的不同函数,其他核心代码都是完全一致的。
import { createHashHistory as createHistory } from "history"
可以看出,Router 组件是 BrowserRoute 与 HashRouter 的底层组件,将 history 作为 props 传递给 Router 组件。
9、实现React-Router
9.1 Router
从上面Router 的核心原理就是通过 Provider 把 location 和 history 等路由关键信息传递给子组件,并且在路由发生变化的时候要让子组件可以感知到。
我们使用ts来实现一个简易版:
// 存储 history.listen 的回调函数
let listeners: Listener[] = [];
// listen方法
function listen(fn: Listener) {
listeners.push(fn);
return function() {
listeners = listeners.filter(listener => listener !== fn);
};
}
import React, { useState, useEffect, ReactNode } from 'react';
import { history, Location } from './history';
interface RouterContextProps {
history: typeof history;
location: Location;
}
export const RouterContext = React.createContext<RouterContextProps | null>(
null,
);
export const Router: React.FC = ({ children }) => {
const [location, setLocation] = useState(history.location);
useEffect(() => {
// 监听
const unlisten = history.listen(location => {
setLocation(location);
});
return unlisten;
}, []);
return (
<RouterContext.Provider value={{ history, location }}>
{children}
</RouterContext.Provider>
);
};
- 在组件创建前使用 history 库的 listen 函数注册监听 history 的改变。当切换路由时会更新
location
的值,会触发render
重新渲染,Router 会向下传递history和location ,下层组件获取最新的内容来判断是否渲染; - 在组件卸载时,也就是在useEffect return卸载;
- 将
history
、location
属性以上下文方式传给后代组件。
9.2 Route
一句话概括 Route 的作用是:匹配路由并渲染组件,代码也很简单。
import { useLocation } from "./hooks";
interface RouteProps {
path: string;
children: JSX.Element;
}
export const Route = ({ path, children }: RouteProps) => {
// 就是上层传递的localtion
const { pathname } = useLocation();
const matched = path === pathname;
if (matched) {
return children;
}
return null;
};
9.3 浏览器跳转功能
push方法主要是用作浏览器页面跳转:
function push(to: string, state?: State) {
location = getNextLocation(to, state);
window.history.pushState(state, '', to); // 向浏览历史中追加一条记录
listeners.forEach(fn => fn(location));
}
9.4 浏览器后退功能
popstate方法用于处理浏览器前进后退操作:
// 用于处理浏览器前进后退操作
window.addEventListener('popstate', () => {
location = getLocation();
listeners.forEach(fn => fn(location));
});
10、实现效果
示例代码:
import { Router, Route, useHistory } from "./Router";
const Home = () => {
const history = useHistory();
const go = (path: string) => {
const state = { name: path };
history.push(path, state);
};
return (
<div>
我是home页面 <button onClick={() => go("/list")}>list</button>
</div>
);
};
const List = () => <div>我是list页面</div>;
export default () => {
return (
<div>
<Router>
{/* <Links /> */}
<Route path="/index">
<Home />
</Route>
<Route path="/list">
<List />
</Route>
</Router>
</div>
);
};
index页面效果:
点击list按钮跳转到list页面效果:
11、参考链接
1、深入探索前端路由,手写 react-mini-router