简介
今天我们再来对比学习下Vue-Router
和React-Router
。
如果你已经熟练使用Vue-Router
和React-Router
,并且也知道Vue-Router4
新特性和React-Router6
新特性可以直接阅读本文。如果还不清的话可以先看看笔者前面写的Vue-Router3和Vue-Router4的对比,以及React-Router4/5和React-Router6的对比。
希望通过这种对比方式的学习能让我们学习的时候印象更深刻,希望能够帮助到大家。
路由模式
路由模式这块Vue-Router
和React-Router
都支持原生浏览器行为的history、hash
模式和使用数据结构实现的abstract/memory
模式。
但是使用方式上稍有区别。
Vue-Router支持 history、hash、abstract
Vue-Router
的路由模式是在创建时指定的。
const router = createRouter({
mode: "history",
routes
})
React-Router支持 history、hash、memory
React-Router
是在具体使用的时候指定的。
<BrowserRouter>
<App />
</BrowserRouter>
路由跳转
Vue-Router
和React-Router
都支持链接式导航和编程式导航,并且都支持自定义设置激活链接的样式。
Vue-Router使用<router-link />
实现链接式导航
<router-link to="/home">Home</router-link>
Vue-Router使用this.$router
或useRouter
实现编程式导航
// Vue-Router3
this.$router.push("/home")
this.$router.replace("/home")
// Vue-Router4
const router = useRouter()
router.push("/home")
router.replace("/home")
React-Router使用<Link />、<NavLink />
实现链接式导航
<Link to="/home">Home</Link>
// 可以定义激活时的class或者style,也就是多了样式设置。
<NavLink to="/home">Home</NavLink>
React-Router使用history
或navigate
实现编程式导航
// React-Router4/5 类组件
this.props.history.push("/home")
this.props.history.replace("/home")
// React-Router6 函数组件
const navigate = useNavigate()
navigate("/home")
路由渲染
在Vue-Router
中,使用 <router-view />
组件就能把匹配到组件渲染出来,而React-Router
需要使用定义路由的<Route />
组件或<Outlet />
。
Vue-Router 使用 <router-view />
就能把匹配到的组件渲染出来。
在 Vue
中我们传递路由配置创建好路由后,只需要使用<router-view />
就能把匹配到的组件渲染出来。
// App.vue
<template>
<router-view />
</template>
React-Router 使用 <Route />
或 <Outlet />
在React
中,不用提前通过配置文件创建路由,而是在使用的时候才定义路由,所以定义路由和路由渲染是在一起的,都是使用 <Route />
组件。
<Switch>
<Route path="/home" component={Home}></Route>
</Switch>
在React-Router6
中子组件支持<Outlet />
的写法,具体可以看React-Router4/5和React-Router6的对比。
// User.jsx 子组件
<Outlet />
路由参数
Vue-Router
和React-Router
都支持路由参数的传递和获取,并且都支持显示传参和隐式传参。
Vue-Router 使用router传递参数
Vue-Router
支持传递query
和params
两种参数,使用时需要注意以下两点。
query
传参会在浏览器路径看到,并且刷新页面参数不会丢失。params
传参会在浏览器路径看不到,并且刷新页面参数会丢失。并且可以用在普通路由或动态参数路由上。但是需要注意,当应用到动态参数路由上时我们就不能通过params
传参了,只能通过params
动态传参。
// vue-Router3
// params传参
this.$router.push({ name: 'user', params: { name: 'randy' } })
// query传参
this.$router.push({ path: '/register', query: { name: 'randy' } })
// Vue-Router4
const router = useRouter()
// params传参
router.push({ name: 'user', params: { name: 'randy' } })
// query传参
router.push({ path: '/register', query: { name: 'randy' } })
Vue-Router 使用query和params接收参数
// Vue-Router3
this.$route.query.xxx
this.$route.params.xxx
// Vue-Router4
const route = useRoute()
route.query.xxx
route.params.xxx
React-Router使用history或navigate传参
React-Router
支持传递search
、params
和state
三种参数,使用时需要注意以下三点。
search
传参会在浏览器路径看到,并且刷新页面参数不会丢失。但是参数需要自己获取并处理,一般我们使用URLSearchParams
处理。params
传参只能应用在动态路由上,普通路由传参不能使用。state
传参不会在浏览器路径看到,在history
模式下刷新页面参数不会丢失,但是在hash
模式下刷新页面参数会丢失。
// React-Router4/5
this.props.history.push({
pathname: "route3",
search: "?sort=name",
hash: "#the-hash",
state: { fromDashboard: 1 },
});
// React-Router6
navigate(
{ pathname: "../route3/78", search: "name=demi", hash: "hash3" },
{
state: { fromDashboard: "navigate state" },
replace: false,
}
);
React-Router使用search
、params
和state
接收参数
// React-Router4/5类组件
this.props.location.state
this.props.location.search
this.props.match.params
// React-Router4/5函数式组件
const params = useParams()
const location = useLocation()
location.state
location.search //search字符串参数 '?name=randy'
// React-Router6
const params = useParams()
const location = useLocation()
location.state
location.search //search字符串参数 '?name=randy'
const [searchParams, setSearchParams] = useSearchParams(); // search参数,已经使用URLSearchParams封装好了
监听动态路由参数变化
Vue-Router
和React-Router
都支持动态路由,都能通过params
获取到动态路由参数。
动态路由在Vue
和React
中都做了优化,也就是动态参数的变化并不会让组件重新创建和销毁。也就不会触发组件创建、绑定和销毁相关的钩子函数。所以经常会遇到我们写在mounted
或者unmounted
中的代码不会被重新触发的问题。
比如我们写在mounted
生命周期函数中的逻辑,通过获取动态参数然后去后台请求获取数据,参数改变需要重新获取数据的逻辑就行不通了,那应该怎么修改呢?
Vue-Router监听参数变化的五种方式
在Vue
中我们可以通过beforeUpdate、updated、beforeRouteUpdate、watch、watchEffect
五种方式来监听动态路由参数的变化。
动态路由参数的变化会触发beforeUpdate、updated
生命周期函数以及beforeRouteUpdate
路由组件内钩子函数。所以上面的例子,通过动态参数获取后台数据的逻辑可以在这些钩子中任选一个加上就可以。推荐使用updated
。
onBeforeUpdated(() => {
console.log("route2 onUpdated",route.params.id);
});
onUpdated(() => {
console.log("route2 onUpdated",route.params.id);
});
onBeforeRouteUpdate((to, from) => {
console.log("route2 onBeforeRouteUpdate", to, from);
});
或者使用watch、watchEffect
监听路由的方式来监听参数的变化也是可以的。
watch(
() => route.params.id,
(newVal, oldVal) => {
console.log("watch route: ", newVal, oldVal);
}
);
watchEffect(() => {
console.log("watchEffect route: ", route.params.id);
});
如果配置了props模式的话我们还可以监听props
。
watch(
() => props.id,
(newVal, oldVal) => {
console.log("watch props: ", newVal, oldVal);
}
);
watchEffect(() => {
console.log("watchEffect props: ", props.id);
});
React-Router监听参数变化的四种方式
在React
中我们可以通过老版本生命周期函数componentWillReceiveProps、componentWillUpdate、componentDidUpdate
或新版本生命周期函数getDerivedStateFromProps、getSnapshotBeforeUpdate、componentDidUpdate
以及watchEffect
四种方式来监听动态路由参数的变化。
动态路由参数的变化会触发老版本生命周期函数componentWillReceiveProps、componentWillUpdate、componentDidUpdate
或新版本生命周期函数getDerivedStateFromProps、getSnapshotBeforeUpdate、componentDidUpdate
生命周期函数所以上面的例子,通过动态参数获取后台数据的逻辑可以在这些钩子中任选一个加上就可以。推荐使用componentDidUpdate
。
如果是函数组件还可以使用useEffect
监听路由的方式来监听参数的变化。
const params = useParams()
useEffect(() => {
console.log(params);
}, [params]);
路由匹配规则
在Vue-Router
和React-Router
中,不配置任何规则相关参数的话默认是不用区分大小写和尾部/
的。
比如我们配置了一个路由/user/info
,在Vue
和React
中下面这些路径都是可以匹配上的。
/user/info
/user/info/
/USER/INFO
/USER/INFO/
...
Vue-Router支持strict、sensitive
在Vue-Router3
只支持配置canSensitive
来控制是否忽略大小写。在Vue-Router4
支持strict、sensitive
来控制是否忽略尾部/
和是否忽略大小写。
{
path: "/home",
component: Home,
strict,
sensitive
}
React-Router支持strict、sensitive
在React-Router
中一直支持strict、sensitive
来控制是否忽略尾部/
和是否忽略大小写。
<Route path="/home" element={<Home />} strict sensitive>
路由获取
Vue任何组件都能获取
在Vue
中,只要使用了路由,不管是不是路由组件都可以获取到路由(也就是能方便的使用router
和route
)。
React非路由组件默认获取不到
但是在React
中,非路由组件是不会自动在props
中注入路由相关参数的(也就是在props
中没有history、location、match
),这就需要用到withRouter
高阶组件了。使用了withRouter
后才能在非路由使用history、location、match
。
当然如果是函数组件我们还可以使用路由相关hooks
获取的。
滚动行为
使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。 Vue-Router
能方便的做到,它让你可以自定义路由切换时页面如何滚动。
但是React-Router
并没有封装这一块的东西,需要自己实现。
Vue-Router定义scrollBehavior即可
const router = createRouter({
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { top: 0 }
}
},
})
React-Router需要自己实现
window.scroll(00, 0)
路由嵌套
Vue-Router
通过配置方式创建路由,路由嵌套一直都很方便使用。
但是React-Router
就没那么容易了,需要继续在子组件定义路由。
Vue路由嵌套可直接配置
Vue-Router
只需要在routes
里面配置好就可以了。
{
path: "/home",
component: Home,
children: [
{
path: "/child1",
component: Child1,
}
]
}
父组件只需要使用<router-view />
就能渲染出子组件。
// Home
<template>
<router-view />
</template>
React路由嵌套需要在子组件定义
在React-Router4/5
中,子组件需要继续定义它的孙子组件。
// App.jsx 父组件
<Switch>
<Route path="/home" component={<Home/>} />
<Route path="/user" component={<User/>} />
</Switch>
// User.jsx 子组件
<Switch>
<Route path="/user/info" component={<UserInfo/>} />
<Route path="/user/detail" component={<UserDetail/>} />
</Switch>
当然React-Router6
得到了改进,可以在根组件直接一次性配置。
// App.jsx 父组件
<Routes>
<Route path="/home" element={<Home/>} />
<Route path="/user" element={<User/>}>
<Route path="info" element={<UserInfo/>} />
<Route path="detail" element={<UserDetail/>} />
</Route>
</Routes>
// User.jsx 子组件
<Outlet />
配置式路由
Vue-Router
一直使用的是配置式路由,配置渲染相分离很方便。但是React-Router
如果想要使用配置式路由的话,需要我们自己使用map
或者借助react-router-config
插件来完成。好消息是React-Router6
有了很大的改进。
Vue-Router直接使用配置式路由
Vue-Router
一直使用的是配置式路由,这个就不细说了。
{
path: "/home",
component: Home,
children: [
{
path: "/child1",
component: Child1,
}
]
}
React-Router4/5 使用 map 或 react-router-config
首先我们需要定义routes
// routerConfig/routes.js
import Parent from "./Parent";
import Child1 from "./Child1";
import Child2 from "./Child2";
const routes = [
{
component: Parent,
path: "/parent",
routes: [
{
path: "/parent/child1",
exact: true,
component: Child1,
},
{
path: "/parent/child2",
component: Child2,
},
],
},
];
export default routes;
在根组件,我们需要自己使用map
遍历渲染,并把子路由通过routes
传递下去。
import routes from "./routerConfig/routes";
<BrowserRouter>
<Switch>
{routes.map((route) => {
return (
<Route
path={route.path}
key={route.path}
render={(props) => {
return (
<route.component
{...props}
routes={route.routes}
></route.component>
);
}}
></Route>
);
})}
</Switch>
</BrowserRouter>
在子组件我们还需要继续使用map
遍历渲染。
// Parent.js
<Switch>
{this.props.routes.map((route) => {
return (
<Route
path={route.path}
key={route.path}
render={(props) => {
return (
<route.component
{...props}
routes={route.routes}
></route.component>
);
}}
></Route>
);
})}
</Switch>
是不是很复杂,但是我们可以使用react-router-config
插件进行简化。
首先安装react-router-config
npm i react-router-config
在根组件,我们只需要使用renderRoutes
方法就可以了。
import { renderRoutes } from "react-router-config";
import routes from "./routerConfig/routes";
{renderRoutes(routes)}
在子组件我们继续使用renderRoutes
。
// Parent.js
{renderRoutes(this.props.routes)}
相对自己写遍历是不是方便了很多呢?其实react-router-config
插件内部实现跟笔者上面写的差不多。
React-Router6 使用 useRoutes
在React-Router6
能更方便的实现。只需要使用useRoutes
方法。
首先定义routes
// routerConfig/routes.js
import Parent from "./Parent";
import Child1 from "./Child1";
import Child2 from "./Child2";
const routes = [
{
element: <Parent></Parent>,
path: "/parent",
children: [
{
path: "child1",
element: <Child1></Child1>,
},
{
path: "child2",
element: <Child2></Child2>,
},
],
},
];
在根组件,我们只需要使用useRoutes
方法就可以了。
这里需要注意useRoutes
是hook
,所以只能在函数组件或自定义组件使用。
import { useRoutes } from "react-router-dom";
import routes from "./routerConfig/routes";
function App() {
return {useRoutes(routes)}
}
在子组件我们继续使用Outlet
渲染子组件就可以了。
// Parent.js
import { Outlet } from "react-router-dom";
<Outlet />
路由钩子
Vue-Router实现了7个钩子函数
Vue-Router
实现了7个钩子函数,可以清除的知道从哪个路由来并去哪个路由。还可以通过next
方法我们可以很方便的对路由跳转进行控制。
beforeEach(to, from, next)
beforeResolve(to, from, next)
beforeEnter(to, from, next)
beforeRouteEnter(to, from, next)
beforeRouteUpdate(to, from, next)
beforeRouteLeave(to, from, next)
afterEach(to, from)
比如我们取消跳转,只需要给next()
方法传递false
就可以了,非常方便。
beforeEach(to, from, next) {
console.log(to, from)
// 阻止跳转
next(false)
}
React-Router没有钩子函数
在React-Router
中并没有实现钩子函数,只在history
上提供了listen、block
两个函数。
listen
用来监听路由的变化,路由变了就会被触发。但是需要注意,点击相同路由也会被触发。
componentDidMount() {
// 路由监听,统一路由重复点击也会重复触发
this.unlisten = this.props.history.listen((location, action) => {
console.log(location, action);
});
// 组件卸载时,解除监听
componentWillUnmount() {
this.unlisten();
}
block
用来阻止路由跳转,可以实现Vue-Router
中类似next(false)
的功能。
componentDidMount() {
this.unblock = this.props.history.block((location, action) => {
console.log(location, action);
return "离开?";
});
}
// 组件卸载时,解除监听
componentWillUnmount() {
this.unblock();
}
上面的例子当我们点击路由跳转后会弹出
由于React-Router6
已经移除了history
,所以上面的例子只针对React-Router4/5
。
路由鉴权
路由鉴权这块可以说是每个系统中必备的。
在Vue-Router
中我们通过钩子函数就能很好实现。但是React-Router
相对来说就没那么方便。
Vue-Router中通过钩子函数
比如我们常用的登录权限判断就可以写在beforeEach(to, from, next)
中。
beforeEach(to, from, next) {
const token = localStorage.getItem("token")
if(token) {
next()
} else {
if(to.paht==="/login") {
next()
} else {
next("/login")
}
}
}
React-Router中通过 listen 或高阶组件
我们可以在根组件,使用listen
来实现登录权限判断。
componentDidMount() {
this.unlisten = this.props.history.listen((location, action) => {
const { pathname } = location;
const token = localStorage.getItem("token")
if(!token && pathname !== "/login") {
this.props.history.replace('/login')
}
});
// 组件卸载时,解除监听
componentWillUnmount() {
this.unlisten();
}
或者使用HOC
假设我们的路由配置如下
const routes = [
{
component: Parent,
path: "/parent",
auth: false,
routes: [
{
path: "/parent/child1",
component: Child1,
auth: false,
},
{
path: "/parent/child2",
component: Child2,
auth: true,
},
],
},
{
path: "/login",
component: Login,
auth: false,
},
];
export default routes;
然后我们定义一个Auth
高阶组件,用来判断用户是否登录。
import React from "react";
import { Route, Redirect } from "react-router-dom";
function Auth(props) {
const { component: Component, path, auth, routes } = props;
const token = localStorage.getItem("token");
if (!auth) {
return (
<Route
path={path}
render={(props) => <Component {...props} routes={routes} />}
></Route>
);
}
// 如果用户有权限,就渲染对应的路由
if (auth && token) {
return (
<Route
path={path}
render={(props) => <Component {...props} routes={routes} />}
></Route>
);
} else {
// 如果没有权限,返回配置的默认路由
return <Redirect to="/login" />;
}
}
export default Auth;
根组件我们渲染的时候使用我们的Auth
组件包裹。
// index.js
import routes from "./routerConfig/routes";
<BrowserRouter>
<Switch>
{routes.map((route) => {
return (
// 路由鉴权
<Auth key={route.path} {...route}></Auth>
);
})}
</Switch>
</BrowserRouter>
子组件我们也使用Auth
组件包裹。
// Parent.js
<Switch>
{this.props.routes.map((route) => {
return (
// 路由鉴权
<Auth key={route.path} {...route}></Auth>
);
})}
</Switch>
这样就达到了路由鉴权的效果,当我们本地没token
的时候,进入/parent/child2
是会重定向到/login
页面的。
路由原信息
Vue-Router
中路由元信息已经封装好。但是在React-Router
中是需要我们通过HOC
来实现。
Vue-Router使用meta定义原数据
Vue-Router
我们只需要使用meta
存放好信息后面通过this.$route.meta
就能获取。
// 定义
{
path: "/home",
component: Home,
meta: {title: "home"}
}
// 获取
this.$route.meta.title
React-Router使用HOC
类似上面的鉴权,首先在路由配置里面定义好元数据。
const routes = [
{
component: Parent,
path: "/parent",
auth: false,
meta: { title: "Parent" },
routes: [
{
path: "/parent/child1",
component: Child1,
auth: false,
meta: { title: "Child1" },
},
{
path: "/parent/child2",
component: Child2,
auth: true,
meta: { title: "Child2" },
},
],
},
{
path: "/login",
component: Login,
auth: false,
meta: { title: "Login" },
},
];
export default routes;
然后我们需要定义一个HOC
高阶组件。最后最重要的就是我们传递给子组件的属性不再具体的传递routes
而是otherProps
。这样子组件通过props.meta
就能获取到元数据啦。
import React from "react";
import { Route, Redirect } from "react-router-dom";
function Auth(props) {
const { component: Component, path, auth, ...otherProps } = props;
const token = localStorage.getItem("token");
if (!auth) {
return (
<Route
path={path}
render={(props) => <Component {...props} {...otherProps} />}
></Route>
);
}
// 如果用户有权限,就渲染对应的路由
if (auth && token) {
return (
<Route
path={path}
render={(props) => <Component {...props} {...otherProps} />}
></Route>
);
} else {
// 如果没有权限,返回配置的默认路由
return <Redirect to="/login" />;
}
}
export default Auth;
在子组件通过props
获取
props.meta.title
动态路由
Vue-Router需要使用addRoute、addRoute方法
由于Vue-Router
在创建路由的时候就需要传递路由配置routes
,所以导致在动态添加路由这块不够灵活,需要使用addRoute
或addRoutes
方法。
React-Router直接渲染即可
由于React-Router
不需要提前创建路由,所以可以很轻松的实现路由的动态添加,只需要拿到新路由渲染出来即可。
对比总总结
总体感觉Vue-Router
封装的更完善,React-Router
更灵活。并且它们相互借鉴,分别引用各自的优点,不断更新达到完善。
相同点
- 都支持三种路由模式。
- 都支持链接式和编程式导航。
- 都支持显示和隐式传参。
- 都支持动态路由参数,并且都做了优化,动态参数的改变不会导致组件的重新创建和销毁,并通过各自的方法检测到参数的变化。
- 都支持路由匹配规则的设置,可以设置大小写和尾
/
。 - 都支持路由嵌套。
- 都支持配置式路由。
- 都支持动态路由。
不同点
Vue-Router
需要先通过路由配置创建后再使用,而React-Router
是一边定义一边使用。- 由于
Vue-Router
需要先创建再使用,所以在动态添加路由这方面比React-Router
复杂,需要使用特定的方法。 Vue-Router
路由传参这块封装的更好,特别是query
参数这块,以对象的形式使用非常方便。但是React-Router
需要自己使用URLSearchParams
处理。- 在路由嵌套这块
Vue-Router
支持的更好,只需要在routes
定义好就可以了。而React-Router
需要在组件里面层层定义,但是React-Router6
已经有了很大的改动,支持在根组件通过嵌套的方式一次性定义好。 Vue-Router
封装了7个钩子函数,我们可以很方便对路由实行鉴权以及路由原信息的定义。React-Router
没有提供路由钩子函数,需要使用HOC
。- 在设置链接激活时的样式时,
Vue-Router
的<router-link />
是自带被激活时的类名的,并且支持自定义。但是React-Router
需要单独使用<NavLink>
组件才会有被激活时的类名。 - 滚动行为
Vue-Router
已经封装好了,直接使用。但是React-Router
需要自己实现。 - 配置式路由
Vue-Router
原生支持。但是React-Router
需要使用map或者react-router-config
插件来实现。当然React-Router6
已经有了很大的优化,可以直接使用useRoutes
来渲染配置路由。 - 在
Vue
中,每个组件都能获取到路由信息。但是在React
中,只有路由组件才能获取到路由信息,非路由组件需要使用withRouter
或者路由相关hooks
。
系列文章
Vue和React对比学习之生命周期函数(Vue2、Vue3、老版React、新版React)
Vue和React对比学习之组件传值(Vue2 12种、Vue3 9种、React 7种)
Vue和React对比学习之路由(Vue-Router、React-Router)
Vue和React对比学习之状态管理 (Vuex和Redux)
Vue和React对比学习之条件判断、循环、计算属性、属性监听
后记
感谢小伙伴们的耐心观看,本文为笔者个人学习笔记,如有谬误,还请告知,万分感谢!如果本文对你有所帮助,还请点个关注点个赞~,您的支持是笔者不断更新的动力!