尝试不实用React Router
import React from 'react';
import ReactDOM from 'react-dom';
const About = () => {
return <div>About</div>;
};
const Inbox = () => {
return <div>Inbox</div>;
};
const Home = () => {
return <div>Home</div>;
};
const App = () => {
const [hash, setHash] = React.useState('');
const updateMenu = () => {
const hashValue = window.location.hash.replace('#', '') || 'home';
setHash(hashValue);
};
React.useEffect(() => {
updateMenu();
window.addEventListener('hashchange', updateMenu);
return () => {
window.removeEventListener('hashchange', updateMenu);
};
}, []);
const renderChild = () => {
let Child;
switch (hash) {
case '/about':
Child = About;
break;
case '/inbox':
Child = Inbox;
break;
default:
Child = Home;
}
return <Child />;
};
return (
<div>
<h1>App</h1>
<ul>
<li>
<a href="#/about">About</a>
</li>
<li>
<a href="#/inbox">Inbox</a>
</li>
</ul>
{renderChild()}
</div>
);
};
ReactDOM.render(<App />, document.body);
当 URL 的 hash 部分(指的是 #
后的部分)变化后,<App>
会根据 hash
来渲染不同的 <Child>
。看起来很直接,但它会随着业务的发展很快会变得复杂起来。
为了让我们的 URL 解析变得更智能,我们需要编写很多代码来实现指定 URL 应该渲染哪一个嵌套的 UI 组件分支。
使用router改造
import React from 'react';
import ReactDOM from 'react-dom';
import { Route, BrowserRouter, Link, Switch } from 'react-router-dom';
const About = () => {
return <div>About</div>;
};
const Inbox = () => {
return <div>Inbox</div>;
};
const home = () => {
return <div>home</div>;
};
const App = () => {
return (
<div>
<BrowserRouter>
<h1>App</h1>
<ul>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/inbox">Inbox</Link>
</li>
</ul>
<Switch>
<Route exact={true} path="/" component={home} />
<Route exact={true} path="/about" component={About} />
<Route exact={true} path="/inbox" component={Inbox} />
</Switch>
</BrowserRouter>
</div>
);
};
ReactDOM.render(<App />, document.body);
通过上面的配置,这个应用知道如何渲染下面三个 URL
URL | 组件 |
---|---|
/ | home |
/about | About |
/inbox | Inbox |
react-router-dom 列子
URL Parameters(路由参数)
import React from 'react';
import ReactDOM from 'react-dom';
import { Route, BrowserRouter, Link, Switch, useParams } from 'react-router-dom';
const About = () => {
return <div>About</div>;
};
const Inbox = () => {
return <div>Inbox</div>;
};
const home = () => {
return <div>home</div>;
};
const Child = () => {
console.log('useParams:', useParams());
const { id } = useParams() as any;
return <div>ID:{id}</div>;
};
const App = () => {
return (
<div>
<BrowserRouter basename="test">
<h1>App</h1>
<ul>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/inbox">Inbox</Link>
</li>
<li>
<Link to="/child/yahoo">Yahoo</Link>
</li>
<li>
<Link to="/child/modus-create">Modus Create</Link>
</li>
</ul>
<Switch>
<Route exact={true} path="/" component={home} />
<Route exact={true} path="/about" component={About} />
<Route exact={true} path="/inbox" component={Inbox} />
<Route exact={true} path="/child/:id" children={<Child />} />
</Switch>
</BrowserRouter>
</div>
);
};
ReactDOM.render(<App />, document.body);
useParams
返回 URL 参数的键/值对对象。使用它来访问match.params
当前<Route>
.
tip:键/值对对象的key(id),与 path="/child/:id"的ID对应,如:path="/child/:slug", 则{slug: xxx}
Nesting(嵌套)
import React from 'react';
import ReactDOM from 'react-dom';
import { Route, BrowserRouter, Link, Switch, useParams, useRouteMatch } from 'react-router-dom';
const Topics = () => {
console.log('useRouteMatch:', useRouteMatch());
const { path, url } = useRouteMatch();
return (
<div>
<h2>Topics</h2>
<ul>
<li>
<Link to={`${url}/rendering`}>Rendering with React</Link>
</li>
<li>
<Link to={`${url}/components`}>Components</Link>
</li>
<li>
<Link to={`${url}/props-v-state`}>Props v. State</Link>
</li>
</ul>
<Switch>
<Route exact path={path}>
<h3>Please select a topic.</h3>
</Route>
<Route path={`${path}/:topicId`}>
<Topic />
</Route>
</Switch>
</div>
);
};
const Topic = () => {
const { topicId } = useParams() as any;
return (
<div>
<h3>{topicId}</h3>
</div>
);
};
const home = () => {
return <div>home</div>;
};
const App = () => {
return (
<div>
<BrowserRouter basename="test">
<h1>App</h1>
<ul>
<li>
<Link to="/">home</Link>
</li>
<li>
<Link to="/topics">topics</Link>
</li>
</ul>
<Switch>
<Route exact={true} path="/" component={home} />
<Route path="/topics" component={Topics} />
</Switch>
</BrowserRouter>
</div>
);
};
ReactDOM.render(<App />, document.body);
useRouteMatch
尝试以与<Route>
相同的方式匹配当前URL。它主要用于访问匹配数据,而无需实际渲染<Route>
注意exact的使用
重定向(身份验证)
import React, { useContext, createContext, useState } from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Switch, Route, Link, Redirect, useHistory, useLocation } from 'react-router-dom';
/*
* 使用createContext进行跨组件间数据传递方法
*首先需要在父组件中定义一个容器和需要传递的默认值,
*然后通过Provider(生产者,简单来说就是定义数据的东西),
*定义共享的数据,然后通过Consumer(消费者,就是子组件或孙子组件来使用),具体实现代码如下:
*/
const authContext = createContext({});
const ProvideAuth = (props: any) => {
// 返回一个权限管理对象
const auth = useProvideAuth();
return <authContext.Provider value={auth}>{props.children}</authContext.Provider>;
};
// react16.8版本之后增加了hooks,可以使用hooks中的useContext来获取消费者
const useAuth = () => {
return useContext(authContext);
};
// 用于管理权限下发数据
const useProvideAuth = () => {
const [user, setUser] = useState(null as null | string);
const signin = (cb: () => void) => {
// 登录操作
setUser('user');
// 登录成功后的操作
setTimeout(cb, 100);
};
const signout = (cb: () => void) => {
// 退出操作
setUser(null);
setTimeout(cb, 100);
};
return {
user,
signin,
signout,
};
};
const AuthButton = () => {
let history = useHistory();
let auth = useAuth() as any;
return auth.user ? (
<p>
欢迎!
<br />
用户:{auth.user}
<br />
<button
onClick={() => {
auth.signout(() => history.push('/'));
}}
>
Sign out
</button>
</p>
) : (
<p>您没有登录</p>
);
};
// 重定向到登录
// 如果您尚未通过身份验证,请选登录
const PrivateRoute = (props: any) => {
// 获取权限数据
let auth = useAuth() as any;
return (
<Route
{...props.rest}
// 存在用户则渲染,否则重定向login
render={({ location }) =>
auth.user ? (
props.children
) : (
<Redirect
to={{
pathname: '/login',
state: { from: location },
}}
/>
)
}
/>
);
};
const PublicPage = () => {
return <h3>公开页面</h3>;
};
const ProtectedPage = () => {
return <h3>保护页面</h3>;
};
function LoginPage() {
let history = useHistory();
let location = useLocation() as any;
let auth = useAuth() as any;
let { from } = location.state || { from: { pathname: '/' } };
let login = () => {
auth.signin(() => {
history.replace(from);
});
};
return (
<div>
<p>您必须登录才能查看该页面 {from.pathname}</p>
<button onClick={login}>Log in</button>
</div>
);
}
export default function AuthExample() {
return (
<ProvideAuth>
<Router>
<div>
<AuthButton />
<ul>
<li>
<Link to="/public">公共页</Link>
</li>
<li>
<Link to="/protected">保护页</Link>
</li>
</ul>
<Switch>
<Route path="/public">
<PublicPage />
</Route>
<Route path="/login">
<LoginPage />
</Route>
<PrivateRoute path="/protected">
<ProtectedPage />
</PrivateRoute>
</Switch>
</div>
</Router>
</ProvideAuth>
);
}
ReactDOM.render(<AuthExample />, document.body);
Redirect
重定向, 如果使用了Switch,请把Redirect放后面
<Route path="/home"
render={() => (
<Switch>
<Route path="/home/page1" />
<Route path="/home/page2" />
<Redirect to="/home/page1" />
</Switch>
)}
/>
无匹配 (404)
const NoMatch = () => {
let location = useLocation();
return (
<div>
<h3>
没有与之 <code>{location.pathname}</code> 对应的页面,404
</h3>
</div>
);
};
<Switch>
...
<Route path="*" component={NoMatch} />
</Switch>
path="*",无匹配时候渲染。如有兜底页面,则可以重定向到404页面
<Route render={() => <Redirect to="/404" />} />
侧边栏(sidebar)
import React from "react";
import ReactDOM from "react-dom";
import {
BrowserRouter as Router,
Switch,
Route,
Link
} from "react-router-dom";
const routes = [
{
path: "/",
exact: true,
sidebar: () => <div>home!</div>,
main: () => <h2>Home</h2>
},
{
path: "/bubblegum",
sidebar: () => <div>bubblegum!</div>,
main: () => <h2>Bubblegum</h2>
},
{
path: "/shoelaces",
sidebar: () => <div>shoelaces!</div>,
main: () => <h2>Shoelaces</h2>
}
];
const SidebarExample = () => {
return (
<Router>
<div style={{ display: "flex" }}>
<div
style={{
padding: "10px",
width: "40%",
background: "#f0f0f0"
}}
>
<ul style={{ listStyleType: "none", padding: 0 }}>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/bubblegum">Bubblegum</Link>
</li>
<li>
<Link to="/shoelaces">Shoelaces</Link>
</li>
</ul>
<Switch>
{routes.map((route, index) => (
<Route
key={index}
path={route.path}
exact={route.exact}
children={<route.sidebar />}
/>
))}
</Switch>
</div>
<div style={{ flex: 1, padding: "10px" }}>
<Switch>
{routes.map((route, index) => (
<Route
key={index}
path={route.path}
exact={route.exact}
children={<route.main />}
/>
))}
</Switch>
</div>
</div>
</Router>
);
}
ReactDOM.render(<SidebarExample />, document.body);
效果: