react-router 学习笔记
学习地址
https://github.com/ReactTraining/react-router
准备前工作
- 创建一个react项目
npm install -g create-react-app yarn
create-react-app antd-demo
cd antd-demo
$ yarn start
- 下载添加路由
npm i -D react-router react-router-dom
基本功
- 添加导航
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/topics">Topics</Link>
</li>
</ul>
- 添加导航对应的组件
<Route exact path="/" component={Home} />
<Route path="/topics" component={Topics} />
- 添加一级组件
// 设置Home组件
const Home = () => (
<div>
<h2>Home</h2>
</div>
);
// 设置 Topics组件
const Topics = ({ match }) => (
<div>
<h2>Topics</h2>
<ul>
<li>
<Link to={`${match.url}/rendering`}>Rendering with React</Link>
</li>
<li>
<Link to={`${match.url}/props-v-state`}>Props v. State</Link>
</li>
</ul>
// 设置一个组件,
<Route path={`${match.url}/:topicId`} component={Topic} />
// 设置默认组件
<Route
exact
path={match.url}
render={() => <h3>Please select a topic.</h3>}
/>
</div>
);
- 添加一级组件内二级导航
<ul>
<li>
<Link to={`${match.url}/rendering`}>Rendering with React</Link>
</li>
<li>
<Link to={`${match.url}/props-v-state`}>Props v. State</Link>
</li>
</ul>
- 添加二级导航组件
// 设置一个组件,
<Route path={`${match.url}/:topicId`} component={Topic} />
- 添加二级导航默认组件
// 设置默认组件
<Route
exact
path={match.url}
render={() => <h3>Please select a topic.</h3>}
/>
- 添加二级组件
// 设置二级组件
const Topic = ({ match }) => {
console.log(match);
return (
<h3>{match.url}</h3>
)
};
- 完整示范
- src/views/basic/index.js
import React from "react";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
// 设置根路由
const BasicExample = () => (
// 根路由标签
<Router>
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/topics">Topics</Link>
</li>
</ul>
<hr />
{/*// 设置跳转的组件*/}
<Route exact path="/" component={Home} />
<Route path="/topics" component={Topics} />
</div>
</Router>
);
// 设置Home组件
const Home = () => (
<div>
<h2>Home</h2>
</div>
);
// 设置 Topics组件
const Topics = ({ match }) => (
<div>
<h2>Topics</h2>
<ul>
<li>
<Link to={`${match.url}/rendering`}>Rendering with React</Link>
</li>
<li>
<Link to={`${match.url}/props-v-state`}>Props v. State</Link>
</li>
</ul>
// 设置一个组件,
<Route path={`${match.url}/:topicId`} component={Topic} />
// 设置默认组件
<Route
exact
path={match.url}
render={() => <h3>Please select a topic.</h3>}
/>
</div>
);
// 设置二级组件
const Topic = ({ match }) => {
console.log(match);
return (
<h3>{match.url}</h3>
)
};
export default BasicExample;
- src/App.js
import React, { Component } from 'react';
import './App.css';
import BasicExample from './views/basic/index.js';
class App extends Component {
render() {
return (
<div className="App">
<BasicExample></BasicExample>
</div>
);
}
}
export default App;
- redirect 与登录验证
- 导入包
import React from "react";
import {
BrowserRouter as Router,
Route,
Link,
Redirect,
withRouter
} from "react-router-dom";
- 添加路由导航
<ul>
<li>
<Link to="/public">Public Page</Link>
</li>
<li>
<Link to="/protected">Protected Page</Link>
</li>
<li>
<Link to="/logout">logout Page</Link>
</li>
</ul>
- 添加路由组件
{/*// 定义导航对应组件*/}
<Route path="/public" component={Public} />
{/*// 未登录的*/}
<Route path="/login" component={Login} />
{/*// 登录后的*/}
<PrivateRoute path="/logout" component={Logout} />
<PrivateRoute path="/protected" component={Protected} />
- 定义路由组件
- 验证组件
// 判断是否登录, 如果登录, 则显示传递进来的组件, 否则重定向到登录组件
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props =>
fakeAuth.isAuthenticated ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
);
- Public组件
- Protected 组件
const Public = () => <h3>Public</h3>;
const Protected = () => <h3>Protected</h3>;
- 退出组件
class Logout extends React.Component {
constructor(props){
super(props)
this.state = {
redirectToReferrer: fakeAuth.isAuthenticated
}
}
// 退出
logout = () => {
fakeAuth.signout(() => {
this.setState({ redirectToReferrer: false });
});
};
render() {
const { from } = this.props.location.state || { from: { pathname: "/" } };
const { redirectToReferrer } = this.state;
// 判断是否登录,
if (!redirectToReferrer) {
return (<Redirect
to={{
pathname: "/login",
state: { from: this.props.location }
}}
/>)
}
// 未登录则提示登录
return (
<div>
<p>You must log in to view the page at {from.pathname}</p>
<button onClick={this.logout}>Log out</button>
</div>
);
}
}
- 登录组件
class Login extends React.Component {
constructor(props){
super(props)
this.state = {
redirectToReferrer: fakeAuth.isAuthenticated
}
}
// 登录, 通过验证,
login = () => {
fakeAuth.authenticate(() => {
this.setState({ redirectToReferrer: true });
});
};
render() {
const { from } = this.props.location.state || { from: { pathname: "/" } };
const { redirectToReferrer } = this.state;
// 判断是否登录,
if (redirectToReferrer) {
return <Redirect to={from} />;
}
// 未登录则提示登录
return (
<div>
<p>You must log in to view the page at {from.pathname}</p>
<button onClick={this.login}>Log in</button>
</div>
);
}
}
- 验证数据
// 验证函数, 登录登出
const fakeAuth = {
isAuthenticated: false,
authenticate(cb) {
this.isAuthenticated = true;
setTimeout(cb, 100); // fake async
},
signout(cb) {
this.isAuthenticated = false;
setTimeout(cb, 100);
}
};
匹配当前路径
- 定义匹配Link组件
- children 返回当前路径, 会与当前route组件进行匹配, 如果匹配, 则返回当了路径match, 否则返回Null
/*
* 有时您需要渲染路径是否与位置匹配。在这些情况下,您可以使用儿童道具功能。它的工作方式与渲染完全相同,只是它会被调用,无论是否匹配。子渲染道具接收与组件和渲染方法相同的所有路径道具,除非路径无法匹配URL,否则匹配为空。这允许您根据路径是否匹配动态调整UI。如果路线匹配,我们在这里添加一个活动类
* */
const OldSchoolMenuLink = ({ label, to, activeOnlyWhenExact }) => (
<Route
path={to}
exact={activeOnlyWhenExact}
children={({ match }) => {
return (
<div className={match ? "active" : ""}>
{match ? "> " : ""}
<Link to={to}>{label}</Link>
</div>
)
}}
/>
);
- 添加导航
<OldSchoolMenuLink activeOnlyWhenExact={true} to="/" label="Home" />
<OldSchoolMenuLink to="/about" label="About" />
- 添加导航对应组件
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
- 定义组件
const Home = () => (
<div>
<h2>Home</h2>
</div>
);
const About = () => (
<div>
<h2>About</h2>
</div>
);
- 完整代码
import React from "react";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
import './index.css'
const CustomLinkExample = () => (
<Router>
<div>
<OldSchoolMenuLink activeOnlyWhenExact={true} to="/" label="Home" />
<OldSchoolMenuLink to="/about" label="About" />
<hr />
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
</div>
</Router>
);
/*
* 有时您需要渲染路径是否与位置匹配。在这些情况下,您可以使用儿童道具功能。它的工作方式与渲染完全相同,只是它会被调用,无论是否匹配。子渲染道具接收与组件和渲染方法相同的所有路径道具,除非路径无法匹配URL,否则匹配为空。这允许您根据路径是否匹配动态调整UI。如果路线匹配,我们在这里添加一个活动类
* */
const OldSchoolMenuLink = ({ label, to, activeOnlyWhenExact }) => (
<Route
path={to}
exact={activeOnlyWhenExact}
children={({ match }) => {
console.log(match)
return (
<div className={match ? "active" : ""}>
{match ? "> " : ""}
<Link to={to}>{label}</Link>
</div>
)
}}
/>
);
const Home = () => (
<div>
<h2>Home</h2>
</div>
);
const About = () => (
<div>
<h2>About</h2>
</div>
);
export default CustomLinkExample;
路由配置基础
- 引入对应的库
import React from "react";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
- 添加路由配置
const routes = [
{
path: "/sandwiches",
component: Sandwiches
},
{
path: "/tacos",
component: Tacos,
routes: [
{
path: "/tacos/bus",
component: Bus,
routes: [
{
path: "/tacos/bus/bus2",
component: Bus2
},
{
path: "/tacos/bus/bus3",
component: Bus3
}
]
},
{
path: "/tacos/cart",
component: Cart
}
]
}
];
- 添加一级导航
<ul>
<li>
<Link to="/tacos">Tacos</Link>
</li>
<li>
<Link to="/sandwiches">Sandwiches</Link>
</li>
</ul>
- 定义一个渲染当前路由配置的组件
传入一个route, 根据route配置绑定给Route, 并使用render渲染该组件, 其中 route.component 是 route配置中的component, 是Route.render渲染时返回的渲染标签
const RouteWithSubRoutes = route => (
<Route
path={route.path}
render={props =>(
// pass the sub-routes down to keep nesting
<route.component {...props} routes={route.routes} />
)
}
/>
);
- 定义一个循环渲染当前路由数组的组件
- 该组件值渲染当前数组的一级数组
const renderRoutes = (routes) => {return (routes.map((route, i) => <RouteWithSubRoutes key={i} {...route} />))};
- 渲染路由
{renderRoutes(routes)}
- 定义一级组件
- Sandwiches组件
const Sandwiches = () => <h2>Sandwiches</h2>;
- Tacos组件
const Tacos = ({ routes }) => (
<div>
<h2>Tacos</h2>
</div>
);
- 准备渲染二级组件, 在路由配置对象内, 只有tacos组件才有子组件, 因此修改tacos组件
- 这里添加了 二级导航, 和渲染二级导航对应的组件
- 这里的routes 是
render={props =>(<route.component {...props} routes={route.routes} />)
这里的routes={route.routes}
- renderRoutes组件是上面定义的组件, 他会帮我们循环routes数组, 然后渲染出当前数组的一维数组的组件
const Tacos = ({ routes }) => (
<div>
<h2>Tacos</h2>
<ul>
<li>
<Link to="/tacos/bus">Bus</Link>
</li>
<li>
<Link to="/tacos/cart">Cart</Link>
</li>
</ul>
{renderRoutes(routes)}
</div>
);
- 定义二级组件
- Bus
const Bus = () => <h3>Bus</h3>;
- Cart
const Cart = () => <h3>Cart</h3>;
10 二级组件定义好, 从路由配置上, 可以看出, 还有三级组件, 因此修改二级组件, 而在这里, 只有Bus组件才有三级组件, 因此只需要修改Bus组件就好
- Bus
跟前面的 Tacos 组件一样, 这里添加了三级导航, 并渲染了三级组件
const Bus = ({routes}) => {
return (
<div>
<h3>Bus constructor
<ul>
<li> <Link to="/tacos/bus/bus2">/tacos/bus/bus2</Link> </li>
<li> <Link to="/tacos/bus/bus3">/tacos/bus/bus3</Link> </li>
</ul>
</h3>
{renderRoutes(routes)}
</div>
)
};
- 定义三级组件
const Bus2 = () => <h3>Bus2</h3>;
const Bus3= () => <h3>Bus3</h3>;
- 完整代码
import React from "react";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
// first our route components
const Sandwiches = () => <h2>Sandwiches</h2>;
const Tacos = ({ routes }) => (
<div>
<h2>Tacos</h2>
<ul>
<li>
<Link to="/tacos/bus">Bus</Link>
</li>
<li>
<Link to="/tacos/cart">Cart</Link>
</li>
</ul>
{renderRoutes(routes)}
</div>
);
const renderRoutes = (routes) => {return (routes.map((route, i) => <RouteWithSubRoutes key={i} {...route} />))};
const Bus = ({routes}) => {
console.log(routes)
return (
<div>
<h3>Bus constructor
<ul>
<li> <Link to="/tacos/bus/bus2">/tacos/bus/bus2</Link> </li>
<li> <Link to="/tacos/bus/bus3">/tacos/bus/bus3</Link> </li>
</ul>
</h3>
{renderRoutes(routes)}
</div>
)
};
const Bus2 = () => <h3>Bus2</h3>;
const Bus3= () => <h3>Bus3</h3>;
const Cart = () => <h3>Cart</h3>;
////////////////////////////////////////////////////////////
// then our route config
const routes = [
{
path: "/sandwiches",
component: Sandwiches
},
{
path: "/tacos",
component: Tacos,
routes: [
{
path: "/tacos/bus",
component: Bus,
routes: [
{
path: "/tacos/bus/bus2",
component: Bus2
},
{
path: "/tacos/bus/bus3",
component: Bus3
}
]
},
{
path: "/tacos/cart",
component: Cart
}
]
}
];
// wrap <Route> and use this everywhere instead, then when
// sub routes are added to any route it'll work
const RouteWithSubRoutes = route => (
<Route
path={route.path}
render={props =>{
console.log(props)
console.log(route)
return (
// pass the sub-routes down to keep nesting
<route.component {...props} routes={route.routes} />
)
}}
/>
);
const RouteConfigExample = () => (
<Router>
<div>
<ul>
<li>
<Link to="/tacos">Tacos</Link>
</li>
<li>
<Link to="/sandwiches">Sandwiches</Link>
</li>
</ul>
{renderRoutes(routes)}
</div>
</Router>
);
export default RouteConfigExample;
升级路由配置
- 定义一个路由配置
const routes = [
{
path: "/login",
name: 'Login',
component: Login
},
{
path: "/tacos",
name: 'Tacos',
component: Tacos,
routes: [
{
path: "/tacos/bus",
name: "/tacos/bus",
component: Bus,
routes: [
{
path: "/tacos/bus/bus2",
name: "/tacos/bus/bus2",
component: Bus2
},
{
path: "/tacos/bus/bus3",
name: "/tacos/bus/bus3",
component: Bus3
}
]
},
{
path: "/tacos/cart",
name: "/tacos/Cart",
component: Cart
}
]
}
];
- 定义一级导航
<ul>
<li>
<Link to="/tacos">Tacos</Link>
</li>
<li>
<Link to="/login">login</Link>
</li>
</ul>
- 定义一级组件
{routes.map((route, i) => <RouteWithSubRoutes key={i} {...route} />)}
- 封装渲染当前组件的组件
const RouteWithSubRoutes = route => (
<Route
path={route.path}
render={props =>{
return (
<route.component {...props} routes={route.routes} />
)
}}
/>
);
- 定义一级组件
- Login 组件
- Tacos 组件
const Login = () => <h2>login</h2>;
const Tacos = ({ routes }) => (
<div >
<h2>Tacos</h2>
</div>
);
- 根据路由配置, 因为要在 Tacos组件内, 渲染二级组件, 所以需要修改Tacos组件
const Tacos = ({ routes }) => (
<div >
<h2>Tacos</h2>
<ul className="slider">
{slideMenu(routes)}
</ul>
<div className="content">
{renderRoutes(routes)}
</div>
</div>
);
- 循环渲染Link组件 slideMenu
- OldSchoolMenuLink是封装的一层Link, 主要用来匹配当前组件并修改当前选中组件的样式
const slideMenu = (routes) => Array.isArray(routes) && routes.map(item => (
<li key={item.path}>
{/*<Link to={item.path}>{item.name}</Link>*/}
<OldSchoolMenuLink to={item.path} label={item.name} exact={item.exact}></OldSchoolMenuLink>
{Array.isArray(item.routes) && item.routes.length > 0 && (
<ul>
{slideMenu(item.routes)}
</ul>
)}
</li>
)
);
- 渲染当前路由数组组件
const renderRoutes = (routes) => {return (routes.map((route, i) => <RouteWithSubRoutes key={i} {...route} />))};
- 定义二级组件
const Bus= () => <h3>Bus</h3>;
const Cart = () => <h3>Cart</h3>;
- 修改 Bus组件
- renderRoutes 这里会渲染当前组件下面的子组件
const Bus = ({routes}) => {
return (
<div>
<h3>Bus constructor
</h3>
{renderRoutes(routes)}
</div>
)
};
- 定义三级组件
const Bus3= () => <h3>Bus3</h3>;
const Cart = () => <h3>Cart</h3>;
- 完整代码
- js
import React from "react";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
import './index.css'
// Some folks find value in a centralized route config.
// A route config is just data. React is great at mapping
// data into components, and <Route> is a component.
////////////////////////////////////////////////////////////
// first our route components
const Login = () => <h2>login</h2>;
const Tacos = ({ routes }) => (
<div >
<h2>Tacos</h2>
<ul className="slider">
{slideMenu(routes)}
</ul>
<div className="content">
{renderRoutes(routes)}
</div>
</div>
);
const slideMenu = (routes) => Array.isArray(routes) && routes.map(item => (
<li key={item.path}>
{/*<Link to={item.path}>{item.name}</Link>*/}
<OldSchoolMenuLink to={item.path} label={item.name} exact={item.exact}></OldSchoolMenuLink>
{Array.isArray(item.routes) && item.routes.length > 0 && (
<ul>
{slideMenu(item.routes)}
</ul>
)}
</li>
)
);
const renderRoutes = (routes) => {return (routes.map((route, i) => <RouteWithSubRoutes key={i} {...route} />))};
const Bus = ({routes}) => {
return (
<div>
<h3>Bus constructor
</h3>
{renderRoutes(routes)}
</div>
)
};
const Bus2 = () => <h3>Bus2</h3>;
const Bus3= () => <h3>Bus3</h3>;
const Cart = () => <h3>Cart</h3>;
////////////////////////////////////////////////////////////
// then our route config
const routes = [
{
path: "/login",
name: 'Login',
component: Login
},
{
path: "/tacos",
name: 'Tacos',
component: Tacos,
routes: [
{
path: "/tacos/bus",
name: "/tacos/bus",
component: Bus,
routes: [
{
path: "/tacos/bus/bus2",
name: "/tacos/bus/bus2",
component: Bus2
},
{
path: "/tacos/bus/bus3",
name: "/tacos/bus/bus3",
component: Bus3
}
]
},
{
path: "/tacos/cart",
name: "/tacos/Cart",
component: Cart
}
]
}
];
const OldSchoolMenuLink = ({ label, to, exact }) => (
<Route
path={to}
exact={exact}
children={({ match }) => {
return (
<React.Fragment >
{match ? "> " : ""}
<Link to={to} className={match ? "active" : ""}>{label}</Link>
</React.Fragment>
)
}}
/>
);
// wrap <Route> and use this everywhere instead, then when
// sub routes are added to any route it'll work
const RouteWithSubRoutes = route => (
<Route
path={route.path}
render={props =>{
console.log(props)
console.log(route)
return (
// pass the sub-routes down to keep nesting
<route.component {...props} routes={route.routes} />
)
}}
/>
);
const RouteConfigExample = () => (
<Router>
<div>
<ul>
<li>
<Link to="/tacos">Tacos</Link>
</li>
<li>
<Link to="/login">login</Link>
</li>
</ul>
{routes.map((route, i) => <RouteWithSubRoutes key={i} {...route} />)}
</div>
</Router>
);
export default RouteConfigExample;
- css
.slider{
width: 200px;
float: left;
}
.content {
width: 100%;
padding-left: 200px;
}
ul{
list-style: none;
padding: 0;
margin: 0;
}
li {
padding: 0;
text-align: left;
}
ul>li>ul {
padding-left: 20px;
}