React使用react-router-dom
进行页面之间的封装。
[TOC]
npm install react-router-dom --save
//或
yarn add react-router-dom
- 官方文档
- 其他参考文档
主要概念
react-router-dom:基于浏览器的路由(包含react-router),提供了BrowerRouter,HashRouter,Route,Link,NavLink。提供了一些router的核心api,包括Router,Route,Switch等
-
HashRouter
http://localhost:3000/#/admin/buttons
-
BrowserRouter
http://localhost:3000/admin/buttons
- 标签
- BrowserRouter as Router,
- Switch,
- Route,
- Link,
- Redirect,
- 钩子
-
useRouteMatch,
-
useParams
-
useHistory,
-
useLocation
-
最外围用Router
包裹。
Link
实现导航菜单功能,to
要跳转的页面。
点击后,由后面的Switch
拦截,其中Route
用来配置path
<Router>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Switch>
<Route exact path="/">
<Home />
</Route>
</Switch>
</Router>
basename
可以追加一个域名前缀
<BrowserRouter basename="/calendar">
<Link to="/today"/> // renders <a href="/calendar/today">
<Link to="/tomorrow"/> // renders <a href="/calendar/tomorrow">
...
</BrowserRouter>
可以添加不同的参数
<Link to="/about">About</Link>
<Link to="/courses?sort=name" />
<Link
to={{
pathname: "/courses",
search: "?sort=name",
hash: "#the-hash",
state: { fromDashboard: true }
}}
/>
<Link to={location => ({ ...location, pathname: "/courses" })} />
location: object
可以看4.14章节,通过Switch的location来进行弹出层的配置。
exact: bool 是否精确匹配
path | location.pathname | exact | matches? |
---|---|---|---|
/one |
/one/two |
true |
no |
/one |
/one/two |
false |
yes |
默认参数
默认会向子组件传递三个参数
-
match
-
location
-
history
刷新方法
有三种方法,可以有不同的写法
<Route component>
<Route render>
<Route children>
function
component
function User(props) {
return <h1>Hello {props.match.params.username}!</h1>;
}
<Route path="/user/:username" component={User} />
//普通的做法
<Route path="/home" render={() => <div>Home</div>} />
render
//自己组建
// wrapping/composing
// You can spread routeProps to make them available to your rendered Component
function FadingRoute({ component: Component, ...rest }) {
return (
<Route
{...rest}
render={routeProps => (
<FadeIn>
<Component {...routeProps} />
</FadeIn>
)}
/>
);
}
//如何使用
<Router>
<FadingRoute path="/cool" component={Something} />
</Router>
children: 可以传递一些参数
<Route
path={to}
children={({ match }) => (
<li className={match ? "active" : ""}>
<Link to={to} {...rest} />
</li>
)}
/>
//path 对应:<Route exact path="/">中的path,有可能是通配符
//url是一个链接
let { path, url } = useRouteMatch();
useParams
返回URL参数总的 key/value Ojbect. 用来 match.params
当前的 <Route>
.
function BlogPost() {
let { slug } = useParams();
return <div>Now showing post {slug}</div>;
}
ReactDOM.render(
<Router>
<Switch>
<Route exact path="/">
<HomePage />
</Route>
<Route path="/blog/:slug">
<BlogPost />
</Route>
</Switch>
</Router>,
);
let history = useHistory();
console.log(history)
function handleClick() {
history.push("/02-advance/01-dragList");
//history.goBack();
}
用来在页面跳转之间传递数据。
在Link
中添加target="_blank"
history.push('/download')
跳转到外链:
window.location.href = 'https://你的url'
在Swith
中拦截
有一个专有的页面,这个页面没有Link
,只有Switch Route
来显示页面。
Redirect
的用法
<Switch>
<Redirect from='/old-path' to='/new-path' />
<Route path='/new-path'>
<Place />
</Route>
</Switch>
to
的复杂用法
<Redirect
to={{
pathname: "/login",
search: "?utm=your+face",
state: { referrer: currentLocation }
}}
/>
<Switch>
<Route path="/:id" children={<Child />} />
</Switch>
....................................
let { id } = useParams();
<Link to="/account?name=yahoo">Yahoo</Link>
//或者
<Link to={{
pathname:"/account",
search: "name=yahoo122&age=12",
}}>Yahoo</Link>
......................................
//自定义钩子
function useQuery() {
return new URLSearchParams(useLocation().search);
}
......................................
//使用
let query = useQuery();
{query.get("name")}
通过Route来实现
- 根目录
/
通过 exact 来匹配 - 放在最后,通过
<Route path="*">
匹配到404 - 如果想强行跳转,通过
<Redirect to="/will-match" />
通过useHistory来实现。
let history = useHistory();
history.goBack();
//也可以跳转到另外一个页面
history.push("/home");
可以通过CSS来去掉颜色与下划线,下面给出了要给例子
.headerLink{
color:rgba(255,255,255,.95);
text-decoration:none;
margin-left: 35px;
cursor:pointer;
}
封装一个用来标记选中的Link
主要使用useRouteMatch
,判断是否与当前的链接匹配
let match = useRouteMatch({
path: to,
exact: activeOnlyWhenExact
});
{match && "> "}
将这个标签放到Switch
中
<Switch>
<Route path="/public">
<PublicPage />
</Route>
<Route path="/login">
<LoginPage />
</Route>
<PrivateRoute path="/protected">
<ProtectedPage />
</PrivateRoute>
</Switch>
在标签中判断,如果登陆了,就跳转到指定的页面,否则跳转到login页面
在跳转到login页面前,通过location将当前页面设置过去,这样login后可以返回。
在login中得到从那个页面来,并且调用登陆函数,如果登陆成功,就跳转到那个页面。这里使用了history.replace(from);
function LoginPage() {
let history = useHistory();
let location = useLocation();
let { from } = location.state || { from: { pathname: "/" } };
let login = () => {
fakeAuth.authenticate(() => {
history.replace(from);
});
};
return (
<div>
<p>You must log in to view the page at {from.pathname}</p>
<button onClick={login}>Log in</button>
</div>
);
}
<Route path="/:id" children={<Child />} />
<Route path="/:id" component={Child} />
function PrivateRoute({ children, ...rest }) {
return (
<Route
{...rest}
render={({ location }) =>
fakeAuth.isAuthenticated ? (
children
) : (
<Redirect
to={{
pathname: "/login",
state: { from: location }
}}
/>
)
}
/>
);
}
有三个链接,点击后,分别显示出对应的组件。
使用了:<Router>
, <Link>
, <Switch>
, <Rout>
点击下面的链接,会将url后面的内容,作为参数传递。
使用了下面的新功能:
<Route path="/:id" children={<Child />} />
Route中带有一个:
- 也可以使用component,
<Route path="/:id" component={Child} />
- 也可以使用component,
let { id } = useParams()
使用了useParams
点击第一层菜单后,出现下面的内容。 点击Topics
页面中的链接后,出现详细的信息。
- 第一层中使用了基本的用法。
- 第二层
Topics
中用到了以下特殊用法:let { path, url } = useRouteMatch();
- 这里的path与url,用来撰写子
Link Switch Route
- 这里的path与url,用来撰写子
- 第三层与
Url参数
章节的写法一样,通过参数得到了信息。
点击Public Page
可以访问,但是点击Protected Page
需要登陆后才可以访问。
定义一个全局变量isAuthenticated
,模拟登陆
const fakeAuth = {
isAuthenticated: false,
authenticate(cb) {
fakeAuth.isAuthenticated = true;
setTimeout(cb, 100); // fake async
},
signout(cb) {
fakeAuth.isAuthenticated = false;
setTimeout(cb, 100);
}
};
功能描述
如果发现没有登陆,那么就跳转到登陆页面,同时将从那里来传递给登陆页面。
实现方法
这里定义了一个<PrivateRoute>
标签。使用了下面的功能
<Route>
中的render
- render: func 。 与
component
的区别是不用新创建一个对象,直接输出。
- render: func 。 与
- 如果没有登陆,使用了
Redirect
,跳转到登陆页面。- 这里设置了一个中间变量
state
- 这里设置了一个中间变量
function PrivateRoute({ children, ...rest }) {
return (
<Route
{...rest}
render={({ location }) =>
fakeAuth.isAuthenticated ? (
children
) : (
<Redirect
to={{
pathname: "/login",
state: { from: location }
}}
/>
)
}
/>
);
}
Redirect
的用法
<Switch>
<Redirect from='/old-path' to='/new-path' />
<Route path='/new-path'>
<Place />
</Route>
</Switch>
to
的复杂用法
<Redirect
to={{
pathname: "/login",
search: "?utm=your+face",
state: { referrer: currentLocation }
}}
/>
使用了history
来跳转到要去的页面。
使用location
得到上一个页面传递过来的数据。
function LoginPage() {
let history = useHistory();
let location = useLocation();
let { from } = location.state || { from: { pathname: "/" } };
let login = () => {
fakeAuth.authenticate(() => {
history.replace(from);
});
};
return (
<div>
<p>You must log in to view the page at {from.pathname}</p>
<button onClick={login}>Log in</button>
</div>
);
}
相关代码 ,自己可以定义标签的样式。
当点击某个标签后,可以显示一个>
实现原理
使用了useRouteMatch
来判断是否选中,如果选中,那么就显示>
function OldSchoolMenuLink({ label, to, activeOnlyWhenExact }) {
let match = useRouteMatch({
path: to,
exact: activeOnlyWhenExact
});
return (
<div className={match ? "active" : ""}>
{match && "> "}
<Link to={to}>{label}</Link>
</div>
);
}
在form
中,如果在text中输入内容,不点击Submit to stop blocking
,点击 其他链接,那么就弹出框,提示是否要离开。代码
通过useState
来设置isBlocking
<Prompt
when={isBlocking}
message={location =>
`Are you sure you want to go to ${location.pathname}`
}
/>
如果点击后两个,就显示404页面,并显示要跳转的链接。详细代码
几个关键点
在一个Route
可以嵌套Redirect
,来跳转到相应的页面。
<Route path="/old-match">
<Redirect to="/will-match" />
</Route>
把这段代码放在最后,然后显示404页面。
<Route path="*">
<NoMatch />
</Route>
使用let location = useLocation();
然后通过location.pathname
得到要访问的页面。
function NoMatch() {
let location = useLocation();
return (
<div>
<h3>
No match for <code>{location.pathname}</code>
</h3>
</div>
);
}
相关代码. 点击第一个标签后,会显示下一个人的好友,还可以继续点击。
实现的主要原理是:在不同的标签里面再撰写<Link> <Switch> <Route>
左侧的菜单,可以控制下面两个区域。
其实很简单,就是在这个页面中,添加两个Switch
区域。 详细代码见
把整个菜单配置到一个常量中,详细代码点击这里
核心是定义了一个标签,这个标签可以循环显示子菜单。
这里面有component
很重要。
const routes = [
{
path: "/sandwiches",
component: Sandwiches
},
{
path: "/tacos",
component: Tacos,
routes: [
{
path: "/tacos/bus",
component: Bus
},
{
path: "/tacos/cart",
component: Cart
}
]
}
];
<route.component {...props} routes={route.routes} />
其中 route.component
是定义在配置文件中的显示组件。
function RouteWithSubRoutes(route) {
return (
<Route
path={route.path}
render={props => (
// pass the sub-routes down to keep nesting
<route.component {...props} routes={route.routes} />
)}
/>
);
}
在主窗口中,将菜单全部循环出来,用RouteWithSubRoutes
显示,并将参数传递到子组件。
<Switch>
{routes.map((route, i) => (
<RouteWithSubRoutes key={i} {...route} />
))}
</Switch>
function Tacos({ routes })
中通过routes
得到上面传入的参数。
function Tacos({ routes }) {
return (
<div>
<h2>Tacos</h2>
<ul>
<li>
<Link to="/tacos/bus">Bus</Link>
</li>
<li>
<Link to="/tacos/cart">Cart</Link>
</li>
</ul>
<Switch>
{routes.map((route, i) => (
<RouteWithSubRoutes key={i} {...route} />
))}
</Switch>
</div>
);
}
点击不同的链接,切换到不同的页面中。详细代码
其实很简单,就是在default
页面中没有<Link>
,而在其中的一个页面中有相关的<Link>
看下一个,如何在弹出层中,还能重用页面。
第一步:在弹出层的Link中设置location
把当前页面的location,作为background
<Link
key={i.id}
to={{
pathname: `/img/${i.id}`,
// This is the trick! This link sets
// the `background` in location state.
state: { background: location }
}}
>
第二步:Switch中设置 location,并在Switch以外追加
return (
<div>
<Switch location={background || location}>
<Route exact path="/" children={<Home />} />
<Route path="/gallery" children={<Gallery />} />
<Route path="/img/:id" children={<ImageView />} />
</Switch>
{/* Show the modal when a background page is set */}
{background && <Route path="/img/:id" children={<Modal />} />}
</div>
);
第三步:修改弹出层的样式
做了两个框,其中一个框是透明的。 而另外一个有边框,不透明。
return (
<div
onClick={back}
style={{
position: "absolute",
top: 0,
left: 0,
bottom: 0,
right: 0,
background: "rgba(0, 0, 0, 0.15)"
}}
>
<div
className="modal"
style={{
position: "absolute",
background: "#fff",
top: 25,
left: "10%",
right: "10%",
padding: 15,
border: "2px solid #444"
}}
>
<h1>{image.title}</h1>
<Image color={image.color} />
<button type="button" onClick={back}>
Close
</button>
</div>
</div>
);
这在服务器端渲染场景中非常有用,因为用户实际上没有点击,所以位置实际上并未发生变化。因此,名称是(静态的)。当你只需要插入一个位置,并在渲染输出上作出断言以便进行简单测试时,它也很有用。
实际上没有看明白这个的用途
得到https://mfh8p.csb.app/account?name=netflix&age=10
后面的参数:name
,age
<Link to="/account?name=modus-create&age=10">Modus Create</Link>
function useQuery() {
return new URLSearchParams(useLocation().search);
}
let query = useQuery();
console.log(query.get("age"))
自己写一个测试页面,每次都要修改index.js来加载,很麻烦,能不能动态加载某个目录下的页面呢?
function getLeftMenu(path){
const req=require.context(`./03-reactrouter`, false, /\.js$/);;
let ren= req.keys().map(key=>{
const prts = key.replace(/js/g, '').split(/[./]/).filter(x => x)
const fullpath = path + '/' + prts.join('/')
const fileName = prts[0]
return {
fileName,
path:fullpath,
component : req(key).default
};
});
return ren;
}
仅载入一个文件
使用require(XXX),可以载入一个文件
let req1=require(`.${url}`);
<Route key={url} component={req1.default} path={url} />
批量载入整个目录的文件
例如上节的component : req(key).default
// routers == getLeftMenu得到
{routers.map((x, i) => {
return <Route key={x.path} component={x.component} path={x.path} />
})}
当然通过上面的知识,自己也可以封装一个路由,但并不建议用。
因为现在有很多类似的脚手架,使用一个更新速度较快,大家比骄推荐的脚手架就可以了。
由于要用到antDesign,这里推荐看一下UMI