React Router v6 alpha 版本发布了,本周经过 A Sneak Peek at React Router v6 这篇文章分析一下带来的改变。前端
一个不痛不痒的改动,使 API 命名更加规范。react
// v5
import { BrowserRouter, Switch, Route } from "react-router-dom";
function App() {
return (
<BrowserRouter> <Switch> <Route exact path="/"> <Home /> </Route> <Route path="/profile"> <Profile /> </Route> </Switch> </BrowserRouter>
);
}
复制代码
在 React Router v6 版本里,直接使用 Routes
替代 Switch
:git
// v6
import { BrowserRouter, Routes, Route } from "react-router-dom";
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="profile/*" element={<Profile />} />
</Routes>
</BrowserRouter>
);
}
复制代码
在 v5 版本立,想要给组件传参数是不太直观的,须要利用 RenderProps 的方式透传 routeProps
:github
import Profile from './Profile';
// v5
<Route path=":userId" component={Profile} />
<Route
path=":userId"
render={routeProps => (
<Profile {...routeProps} animate={true} />
)}
/>
// v6
<Route path=":userId" element={<Profile />} />
<Route path=":userId" element={<Profile animate={true} />} />
复制代码
而在 v6 版本中,render
与 component
方案合并成了 element
方案,能够轻松传递 props 且不须要透传 roteProps
参数。微信
在 v5 版本中,嵌套路由须要经过 useRouteMatch
拿到 match
,并经过 match.path
的拼接实现子路由:react-router
// v5
import {
BrowserRouter,
Switch,
Route,
Link,
useRouteMatch
} from "react-router-dom";
function App() {
return (
<BrowserRouter>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/profile" component={Profile} />
</Switch>
</BrowserRouter>
);
}
function Profile() {
let match = useRouteMatch();
return (
<div>
<nav>
<Link to={`${match.url}/me`}>My Profile</Link>
</nav>
<Switch>
<Route path={`${match.path}/me`}>
<MyProfile />
</Route>
<Route path={`${match.path}/:id`}>
<OthersProfile />
</Route>
</Switch>
</div>
);
}
复制代码
在 v6 版本中省去了 useRouteMatch
这一步,支持直接用 path
表示相对路径:框架
// v6
import { BrowserRouter, Routes, Route, Link, Outlet } from "react-router-dom";
// Approach #1
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="profile/*" element={<Profile />} />
</Routes>
</BrowserRouter>
);
}
function Profile() {
return (
<div>
<nav>
<Link to="me">My Profile</Link>
</nav>
<Routes>
<Route path="me" element={<MyProfile />} />
<Route path=":id" element={<OthersProfile />} />
</Routes>
</div>
);
}
// Approach #2
// You can also define all
// <Route> in a single place
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="profile" element={<Profile />}>
<Route path=":id" element={<MyProfile />} />
<Route path="me" element={<OthersProfile />} />
</Route>
</Routes>
</BrowserRouter>
);
}
function Profile() {
return (
<div>
<nav>
<Link to="me">My Profile</Link>
</nav>
<Outlet />
</div>
);
}
复制代码
注意 Outlet
是渲染子路由的 Element。dom
在 v5 版本中,主动跳转路由能够经过 useHistory
进行 history.push
等操做:ide
// v5
import { useHistory } from "react-router-dom";
function MyButton() {
let history = useHistory();
function handleClick() {
history.push("/home");
}
return <button onClick={handleClick}>Submit</button>;
}
复制代码
而在 v6 版本中,能够经过 useNavigate
直接实现这个经常使用操做:优化
// v6
import { useNavigate } from "react-router-dom";
function MyButton() {
let navigate = useNavigate();
function handleClick() {
navigate("/home");
}
return <button onClick={handleClick}>Submit</button>;
}
复制代码
react-router 内部对 history 进行了封装,若是须要 history.replace
,能够经过 { replace: true }
参数指定:
// v5
history.push("/home");
history.replace("/home");
// v6
navigate("/home");
navigate("/home", { replace: true });
复制代码
因为代码几乎重构,v6 版本的代码压缩后体积从 20kb 缩小到 8kb。
react-router v6 源码中有一段比较核心的理念,笔者拿出来与你们分享,对一些框架开发是大有裨益的。咱们看 useRoutes
这段代码节选:
export function useRoutes(routes, basename = "", caseSensitive = false) {
let {
params: parentParams,
pathname: parentPathname,
route: parentRoute
} = React.useContext(RouteContext);
if (warnAboutMissingTrailingSplatAt) {
// ...
}
basename = basename ? joinPaths([parentPathname, basename]) : parentPathname;
let navigate = useNavigate();
let location = useLocation();
let matches = React.useMemo(
() => matchRoutes(routes, location, basename, caseSensitive),
[routes, location, basename, caseSensitive]
);
// ...
// Otherwise render an element.
let element = matches.reduceRight((outlet, { params, pathname, route }) => {
return (
<RouteContext.Provider children={route.element} value={{ outlet, params: readOnly({ ...parentParams, ...params }), pathname: joinPaths([basename, pathname]), route }} /> ); }, null); return element; } 复制代码
能够看到,利用 React.Context
,v6 版本在每一个路由元素渲染时都包裹了一层 RouteContext
。
拿更方便的路由嵌套来讲:
在 v6 版本中省去了
useRouteMatch
这一步,支持直接用path
表示相对路径。
这就是利用这个方案作到的,由于给每一层路由文件包裹了 Context,因此在每一层均可以拿到上一层的 path
,所以在拼接路由时能够彻底由框架内部实现,而不须要用户在调用时预先拼接好。
再以 useNavigate
举例,有人以为 navigate
这个封装仅停留在形式层,但其实在功能上也有封装,好比若是传入可是一个相对路径,会根据当前路由进行切换,下面是 useNavigate
代码节选:
export function useNavigate() {
let { history, pending } = React.useContext(LocationContext);
let { pathname } = React.useContext(RouteContext);
let navigate = React.useCallback(
(to, { replace, state } = {}) => {
if (typeof to === "number") {
history.go(to);
} else {
let relativeTo = resolveLocation(to, pathname);
let method = !!replace || pending ? "replace" : "push";
history[method](relativeTo, state);
}
},
[history, pending, pathname]
);
return navigate;
}
复制代码
能够看到,利用 RouteContext
拿到当前的 pathname
,并根据 resolveLocation
对 to
与 pathname
进行路径拼接,而 pathname
就是经过 RouteContext.Provider
提供的。
不少时候咱们利用 Context 停留在一个 Provider
,多个 useContext
的层面上,这是 Context 最基础的用法,但相信读完 React Router v6 这篇文章,咱们能够挖掘出 Context 更多的用法:多层 Context Provider。
虽说 Context Provider 存在多层会采起最近覆盖的原则,但这不单单是一条规避错误的功能,咱们能够利用这个功能实现 React Router v6 这样的改良。
为了更仔细说明这个特性,这里再举一个具体的例子:好比实现搭建渲染引擎时,每一个组件都有一个 id,但这个 id 并不透出在组件的 props 上:
const Input = () => {
// Input 组件在画布中会自动生成一个 id,但这个 id 组件没法经过 props 拿到
};
复制代码
此时若是咱们容许 Input 组件内部再建立一个子元素,又但愿这个子元素的 id 是由 Input 推导出来的,咱们可能须要用户这么作:
const Input = ({ id }) => {
return <ComponentLoader id={id + "1"} />; }; 复制代码
这样作有两个问题:
这里遇到的问题和 React Router 遇到的同样,咱们能够将代码简化成下面这样,但功能不变吗?
const Input = () => {
return <ComponentLoader id="1" />; }; 复制代码
答案是能够作到,咱们能够利用 Context 实现这种方案。关键点就在于,渲染 Input 但组件容器须要包裹一个 Provider:
const ComponentLoader = ({ id, element }) => {
<Context.Provider value={{ id }}>{element}</Context.Provider>;
};
复制代码
那么对于内部的组件来讲,在不一样层级下调用 useContext
拿到的 id 是不一样的,这正是咱们想要的效果:
const ComponentLoader = ({id,element}) => {
const { id: parentId } = useContext(Context)
<Context.Provider value={{ id: parentId + id }}>
{element}
</Context.Provider>
}
复制代码
这样咱们在 Input
内部调用的 <ComponentLoader id="1" />
实际上拼接的实际 id 是 01
,而这彻底抛到了外部引擎层处理,用户无需手动拼接。
React Router v6 彻底利用 Hooks 重构后,不只代码量精简了不少,还变得更好用了,等发正式版的时候能够快速升级一波。
另外从 React Router v6 作的这些优化中,咱们从源码中挖掘到了关于 Context 更巧妙的用法,但愿这个方法能够帮助你运用到其余更复杂的项目设计中。
若是你想参与讨论,请 点击这里,每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。
关注 前端精读微信公众号
版权声明:自由转载-非商用-非衍生-保持署名(创意共享 3.0 许可证)