一、 SPA
- 单页Web应用(single page web application,SPA)。整个应用只有一个完整的页面。
- 点击页面中的链接不会刷新页面,只会做页面的局部更新。
- 数据都需要通过ajax请求获取,并在前端异步展现
二、什么是路由
1. 一个路由其实就是一个映射关系(k:v)
key为路径,value可能是function 或者是 component
2. 后端路由:
value是function,用来处理客户端提交的请求
注册路由:router.get(path,function(req,res))
工作过程:当node接收一个请求的时候,根据请求路径找到匹配的路由,调用路由中的函数来处理请求,返回响应的数据
3. 前端路由:
浏览器端路由,value是Component,用于展示页面内容
注册路由:< Route path="/test" component={Test}>
工作过程:当浏览器的path变为/test的时候,当前路由组件就会变成Test组件
4. 前端路由的原理
这个主要是依靠于history,也就是浏览器的历史记录。
浏览器上的记录其实就是一个栈,前进一次就是入栈,后退一次就是出栈。
并且历史记录上有一个监听的方法,可以时时刻刻监听记录的变化。从而判断是否改变路径
三、 react-router-dom
1. Chrome您的连接不是私密连接解决办法
在当前页面用键盘输入 thisisunsafe ,不是在地址栏输入,就直接敲键盘就行了,页面即会自动刷新进入网页。
2. react的路由有三类:
- web【主要适用于前端】
- native【主要适用于本地】
- anywhere【任何地方】
3. 在这主要使用web也就是这个标题 react-router-dom
- react的一个插件库。
- 专门用来实现一个SPA应用。
- 基于react的项目基本都会用到此库。
4. 基本的使用:
1.安装react-router库
yarn add react-router-dom
2.在需要用到react-router的组件中按需引入
import {Link,Route} from 'react-router-dom'
3.导航中的a标签改写成Link标签
< Link to="/路径" >xxx< /Link>
4.展示区写Route标签进行路径的匹配
< Route path = '/路径' component={组件名称}>
5.< App />最外侧包裹了一个< BrowserRouter>
或者< HashRouter>
App.jsx
import React, { Component } from 'react'
import {Link,Route} from 'react-router-dom'
import Home from './components/Home'
import About from './components/About'
export default class App extends Component {
render() {
return (
<div>
<div className="row">
<div className="col-xs-offset-2 col-xs-8">
<div className="page-header"><h2>React Router Demo</h2></div>
</div>
</div>
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
{/* 原生html中,靠<a>跳转不同的页面 */}
{/* <a className="list-group-item" href="./about.html">About</a>
<a className="list-group-item active" href="./home.html">Home</a> */}
{/* 在React中靠路由链接实现切换组件--编写路由链接 */}
<Link className="list-group-item" to="/about">About</Link>
<Link className="list-group-item" to="/home">Home</Link>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{/* 注册路由,进行路径与组件匹配 */}
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
</div>
</div>
</div>
</div>
</div>
)
}
}
index.jsx
//引入react核心库
import React from 'react'
//引入ReactDOM
import ReactDOM from 'react-dom'
// 引入BrowserRouter,包裹住App
import { BrowserRouter } from 'react-router-dom'
//引入App
import App from './App'
//<APP/>最外侧包裹路由器
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
)
About
import React, { Component } from 'react'
export default class About extends Component {
render() {
return (
<h3>我是About的内容</h3>
)
}
}
Home
import React, { Component } from 'react'
export default class Home extends Component {
render() {
return (
<h3>我是Home的内容</h3>
)
}
}
那么使用Link代替a标签之后,在页面上会是什么呢,我们发现其实页面上也是最终把link转化为了a标签
5. 路由组件以及一般组件的区别
1.写法不一样
一般组件:< Demo />
路由组件:< Route path="/demo" component ={Demo}/>
2.存放的位置一般不同
一般组件:components
路由组件:pages
3.接收的内容【props】不同
一般组件:写组件标签的时候传递什么,就能收到什么
路由组件:接收到三个固定的属性【history,location,match】
//常用的属性
history:
go: ƒ go(n)
goBack: ƒ goBack()
goForward: ƒ goForward()
push: ƒ push(path, state)
replace: ƒ replace(path, state)
location:
pathname: "/about"
search: ""
state: undefined
match:
params: {}
path: "/about"
url: "/about"
6. NavLink
NavLink
可以实现路由链接的高亮:
1. NavLink的使用内联样式
<NavLink exact to="/" activeStyle={{ color: "red", fontSize: "30px" }}>
首页
</NavLink>
<NavLink to="/about" activeStyle={{ color: "red", fontSize: "30px" }}>
关于
</NavLink>
<NavLink to="/profile" activeStyle={{ color: "red", fontSize: "30px" }}>
我的
</NavLink>
注意:NavLink
中也有exact
,但功能和<Route>
中不同,只是让样式精确匹配,在link
中没有
2. activeClassName指定样式
默认类名:
<NavLink exact to="/">
首页
</NavLink>
/* 是a元素且类名为active */
a.active {
color: red;
font-size: 30px;
}
自定义类名:
{/*NavLink在点击的时候就会去找activeClassName="zgc"所指定的class的值,
如果不添加activeClassName默认其属性值为active(activeClassName="active")*/}
<NavLink activeClassName="zgc" className="list-group-item" to="/about">About</NavLink>
/* 类名为zgc */
.zgc {
color: red;
font-size: 30px;
}
但是可能一个导航又很多标签,如果这样重复的写NavLink也会造成很多的重复性的代码问题。
因此可以自定义一个NavLink,减少书写重复的样式:
import React, { Component } from 'react'
import {NavLink} from 'react-router-dom'
export default class MyNavLink extends Component {
render() {
// console.log(this.props);
//<NavLink to = {this.props.to} children = {this.props.children}/>
// 通过{...对象}的形式解析对象,相当于将props对象中接收的属性全部展开
//标签体内容props是特殊的一个属性,叫做children
return (
<NavLink activeClassName="atguigu" className="list-group-item" {...this.props}/>
)
}
}
在使用的时候:直接写每个标签中不一样的部分就行,比如路径和名称
{/*将NavLink进行封装,成为MyNavLink,通过props进行传参数*/}
import MyNavLink from './components/MyNavLink'
<MyNavLink to = "/about" >About</MyNavLink>
7. Switch的使用
- 通常情况下,path和component是一一对应的关系。
- Switch可以提高路由匹配效率(单一匹配)。
{/* 用Switch包裹只要匹配到了路径就会展示相应组件,不会再向下寻找 */}
{/* 如下代码只展示Home组件的内容,如果不用Switch组件包裹则相同路径组件都会展示*/}
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Route path="/home" component={Test}/>
</Switch>
8. 样式错误
拿上面的案例来说:
这里面会有一个样式:
此时,加载该样式的路径为:
但是在写路由的时候,你想在路由前面加个前缀,会形成多级目录(注意不是二级路由),
<MyNavLink to = "/cyk/about" >About</MyNavLink>
<Route path="/cyk/about"component={About}/>
这个时候就在刷新页面,就会出现问题:
样式因为路径问题加载失败,此时页面返回public下面的Index.html,因为它将前缀也当成了路径的一部分,导致路径错误,找不到相应文件
解决这个问题,有三个方法:
1.public/index.html中,样式加载使用绝对位置
//<link rel="stylesheet" href="./css/bootstrap.css"> 相对路径
<link rel="stylesheet" href="/css/bootstrap.css">
2.public/index.html中,使用 %PUBLIC_URL%
代替 .
<link rel="stylesheet" href="%PUBLIC_URL%/css/bootstrap.css" >
3.使用HashRouter
因为HashRouter会添加#,默认不会处理#后面的路径,所以也是可以解决的
9. 模糊匹配和精准匹配
react默认是开启模糊匹配的。
比如:
<MyNavLink to = "/home/a/b" >Home</MyNavLink>
此时该标签匹配的路由,分为三个部分 home a b;将会根据这个先后顺序匹配路由。
如下就可以匹配到相应的路由:
<Route path="/home"component={Home}/>
但是如果是下面这个就会失败,也就是说他是根据路径一级一级查询的,可以包含前面那一部分,但并不是只包含部分就可以。
<Route path="/a" component={Home}/>
当然也可以使用这个精确的匹配 exact={true}
如以下:这样就精确的匹配/home,则上面的/home/a/b就不行了
<Route exact={true} path="/home" component={Home}/>
或者
<Route exact path="/home" component={Home}/>
- 默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
- 开启严格匹配:
<Route exact={true} path="/about" component={About}/>
- 严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
10. 初始化路由--Redirect(重定向)
在配置好路由,最开始打开页面的时候,应该是不会匹配到任意一个组件。这个时候页面就显得极其不合适,此时应该默认的匹配到一个组件。
此时就需要使用Redirect进行默认匹配了,一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
//刚上来默认跳转到<Home/>组件
<Redirect from = "/" to="/home"/>
</Switch>
就可以做到如下的效果:
再比如说判断是否登录:如果已登录显示用户信息,否则重定向的登录界面
export default class User extends PureComponent {
state = {
isLogin: true
}
render() {
return this.state.isLogin ? (
<div>
<h2>User</h2>
</div>
): <Redirect to="/login"/>
}
}
11. 嵌套路由
- 注册子路由时要写上父路由的path值
- 路由的匹配是按照注册路由的顺序进行的 简单来说就是在一个路由组件中又使用了一个路由,就形成了嵌套路由。
举个例子来说:
我们在home这个路由组件中又添加两个组件:
APP.jsx:
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Redirect to="/about"/>
</Switch>
Home.jsx:
<div>
<ul className="nav nav-tabs">
<li>
<MyNavLink to = "/home/news">News</MyNavLink>
</li>
<li>
<MyNavLink to = "/home/message">Message</MyNavLink>
</li>
</ul>
<Switch>
<Route path = "/home/news" component={News} />
<Route path = "/home/message" component={Message} />
<Redirect to="/home/message"/>
</Switch>
</div>
react中路由的注册是有顺序的,因此在匹配的时候也是按照这个顺序进行的,因此会先匹配父组件中的路由
比如上面的 /home/news的路由处理过程:
1.因为父组件home的路由是先注册的,因此在匹配的时候先去找home的路由,也就是根据/home/news先模糊匹配到/home,如果在父组件使用精确匹配,则出现问题
2.在去Home组件里面去匹配相应的路由,从而找到了/home/news进行匹配,因此找到了News组件。
但是如果开启精确匹配,就会在第一步的时候卡住,这个时候就走不下去了。因此不要轻易的使用精确匹配
12. 向路由组件传递参数
1.params参数
路由链接(携带参数):<Link to='/demo/test/tom/18'}>详情</Link>
注册路由(声明接收):<Route path="/demo/test/:name/:age" component={Test}/>
接收参数:const {需要接收的参数} = this.props.match.params
备注:参数在地址栏中体现,刷新之后有效
2.search参数(麻烦)
路由链接(携带参数):<Link to='/demo/test?name=tom&age=18'}>详情</Link>
注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
接收参数:this.props.location.search
备注:
1.获取到的search是urlencoded编码字符串,需要借助querystring解析,过时了
2.参数在地址栏中体现,刷新之后有效
3.state参数
路由链接(携带参数):<Link to={{pathname:'/demo/test',state:{name:'tom',age:18}}}>详情</Link>
注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
接收参数:this.props.location.state
备注:
1. 参数不在地址栏中体现,但刷新也依然有效
2. state可以直接接收一个对象
<NavLink
to={{
pathname: "/detail3",
search: "?name=abc",
state: info,
}}
activeClassName="link-active"
>详情3</NavLink>
1.params参数
Message.jsx
import React, { Component } from 'react'
import {Link,Route} from 'react-router-dom'
import Detail from './Detail'
export default class Message extends Component {
state = {
messageArr:[
{id:'01',title:'消息1'},
{id:'02',title:'消息2'},
{id:'03',title:'消息3'},
]
}
render() {
const {messageArr} = this.state
return (
<div>
<ul>
{
messageArr.map((msgObj)=>{
return (
<li key={msgObj.id}>
{/* 向路由组件传递params参数 */}
{/* <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link> */}
{/* 向路由组件传递search参数 */}
{/* <Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link> */}
{/* 向路由组件传递state参数 */}
<Link to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link>
</li>
)
})
}
</ul>
<hr/>
{/* 声明接收params参数 */}
{/* <Route path="/home/message/detail/:id/:title" component={Detail}/> */}
{/* search参数无需声明接收,正常注册路由即可 */}
{/* <Route path="/home/message/detail" component={Detail}/> */}
{/* state参数无需声明接收,正常注册路由即可 */}
<Route path="/home/message/detail" component={Detail}/>
</div>
)
}
}
Detail.jsx
import React, { Component } from 'react'
const DetailData = [
{id:'01',content:'你好,中国'},
{id:'02',content:'你好,尚硅谷'},
{id:'03',content:'你好,未来的自己'}
]
export default class Detail extends Component {
render() {
console.log(this.props);
// 接收params参数
const {id,title} = this.props.match.params
const findResult = DetailData.find((detailObj)=>{
return detailObj.id === id
})
return (
<ul>
<li>ID:{id}</li>
<li>TITLE:{title}</li>
<li>CONTENT:{findResult.content}</li>
</ul>
)
}
}
2.search参数(麻烦)
Detail.jsx
import React, { Component } from 'react'
// import {qs} from 'query-tring' 已废弃
import {qs} from 'url-parse' //要引入
const DetailData = [
{id:'01',content:'你好,中国'},
{id:'02',content:'你好,尚硅谷'},
{id:'03',content:'你好,未来的自己'}
]
export default class Detail extends Component {
render() {
// console.log(this.props);
// 接收search参数
const {search} = this.props.location
// console.log(search);
// const {id,title} = qs.parse(search.slice(1)) 已废弃
const {id,title} = qs.parse(search)
const findResult = DetailData.find((detailObj)=>{
return detailObj.id === id
})
return (
<ul>
<li>ID:{id}</li>
<li>TITLE:{title}</li>
<li>CONTENT:{findResult.content}</li>
</ul>
)
}
}
3.state参数
Detail.jsx
import React, { Component } from 'react'
const DetailData = [
{id:'01',content:'你好,中国'},
{id:'02',content:'你好,尚硅谷'},
{id:'03',content:'你好,未来的自己'}
]
export default class Detail extends Component {
render() {
console.log(this.props);
// 接收state参数
const {id,title} = this.props.location.state || {}
const findResult = DetailData.find((detailObj)=>{
return detailObj.id === id
}) || {}
return (
<ul>
<li>ID:{id}</li>
<li>TITLE:{title}</li>
<li>CONTENT:{findResult.content}</li>
</ul>
)
}
}
13. push 与 replace 模式
默认情况下,开启的是 push 模式,也就是说,每次点击跳转,都会向栈中压入一个新的地址,在点击返回时,可以返回到上一个打开的地址,
当我们在读消息的时候,有时候我们可能会不喜欢这种繁琐的跳转,我们可以开启 replace 模式,这种模式与 push 模式不同,它会将当前地址替换成点击的地址,也就是替换了新的栈顶
我们只需要在需要开启的链接上加上replace={true}
或者简写为replace
<Link replace to={{ pathname: '/home/message/detail', state: { id: msgObj.id, title: msgObj.title } }}>{msgObj.title}</Link>
14. 编程式路由导航
通过onClick
函数调用的形式,将Link
或NavLink
换成了history
上的API,其余部分没啥区别(传参,接收参数之类的)
借助this.prosp.history对象上的API对操作路由跳转、前进、后退
-this.prosp.history.push()
-this.prosp.history.replace()
-this.prosp.history.goBack()
-this.prosp.history.goForward()
-this.prosp.history.go()
Message
import React, { Component } from 'react'
import {Link,Route} from 'react-router-dom'
import Detail from './Detail'
export default class Message extends Component {
state = {
messageArr:[
{id:'01',title:'消息1'},
{id:'02',title:'消息2'},
{id:'03',title:'消息3'},
]
}
replaceShow = (id,title)=>{
//replace跳转+携带params参数
//this.props.history.replace(`/home/message/detail/${id}/${title}`)
//replace跳转+携带search参数
// this.props.history.replace(`/home/message/detail?id=${id}&title=${title}`)
//replace跳转+携带state参数
this.props.history.replace(`/home/message/detail`,{id:id,title:title})
}
pushShow = (id,title)=>{
//push跳转+携带params参数
// this.props.history.push(`/home/message/detail/${id}/${title}`)
//push跳转+携带search参数
// this.props.history.push(`/home/message/detail?id=${id}&title=${title}`)
//push跳转+携带state参数
this.props.history.push(`/home/message/detail`,{id:id,title:title})
}
back = ()=>{
this.props.history.goBack()
}
forward = ()=>{
this.props.history.goForward()
}
go = ()=>{
this.props.history.go(-2)
}
render() {
const {messageArr} = this.state
return (
<div>
<ul>
{
messageArr.map((msgObj)=>{
return (
<li key={msgObj.id}>
{/* 向路由组件传递params参数 */}
{/* <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link> */}
{/* 向路由组件传递search参数 */}
{/* <Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link> */}
{/* 向路由组件传递state参数 */}
<Link to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link>
<button onClick={()=> this.pushShow(msgObj.id,msgObj.title)}>push查看</button>
<button onClick={()=> this.replaceShow(msgObj.id,msgObj.title)}>replace查看</button>
</li>
)
})
}
</ul>
<hr/>
{/* 声明接收params参数 */}
{/* <Route path="/home/message/detail/:id/:title" component={Detail}/> */}
{/* search参数无需声明接收,正常注册路由即可 */}
{/* <Route path="/home/message/detail" component={Detail}/> */}
{/* state参数无需声明接收,正常注册路由即可 */}
<Route path="/home/message/detail" component={Detail}/>
<button onClick={this.back}>回退</button>
<button onClick={this.forward}>前进</button>
<button onClick={this.go}>go</button>
</div>
)
}
}
15. withRouter
withRouter
是专门用于解决在一般组件中使用路由组建API的问题
当我们需要在页面内部添加回退前进等按钮时,由于这些组件我们一般通过一般组件的方式去编写,因此我们会遇到一个问题,无法获得 history 对象,这正是因为我们采用的是一般组件造成的。只有路由组件才能获取到 history 对象
因此我们需要如何解决这个问题呢
我们可以利用 react-router-dom
对象下的 withRouter
函数来对我们导出的 Header
组件进行包装,这样我们就能获得一个拥有 history
对象的一般组件
我们需要对哪个组件包装就在哪个组件下引入
import React, { Component } from 'react'
import {withRouter} from 'react-router-dom' //引入withRouter
class Header extends Component {
back = ()=>{
this.props.history.goBack()
}
render() {
console.log('Header组件收到的props是',this.props);
return (
<div className="page-header">
<h2>React Router Demo</h2>
<button onClick={this.back}>回退</button>
</div>
)
}
}
export default withRouter(Header)
//withRouter可以加工一般组件,让一般组件具备路由组件所特有的API
//withRouter的返回值是带着路由组件所特有的API的新组件
16. BrowserRouter 和 HashRouter 的区别
1.底层原理不一样:
BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。
HashRouter使用的是URL的哈希值。
2.path表现形式不一样
BrowserRouter的路径中没有#,例如:localhost:3000/demo/test
HashRouter的路径包含#,例如:localhost:3000/#/demo/test
3.刷新后对路由state参数的影响
(1).BrowserRouter没有任何影响,因为state保存在history对象中。
(2).HashRouter刷新后会导致路由state参数的丢失!!!
4.备注:HashRouter可以用于解决一些路径错误相关的问题。
17. hash与history实现原理
hash
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<a href="#/home">首页</a>
<a href="#/about">关于</a>
<div class="router-view"></div>
</div>
<script>
// 获取router-view的DOM
const routerViewEl = document.getElementsByClassName("router-view")[0];
// 监听URL的改变
window.addEventListener("hashchange", () => {
switch (location.hash) {
case "#/home":
routerViewEl.innerHTML = "首页";
break;
case "#/about":
routerViewEl.innerHTML = "关于";
break;
default:
routerViewEl.innerHTML = "";
}
})
</script>
</body>
</html>
history
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<a href="/home">首页</a>
<a href="/about">关于</a>
<div class="router-view"></div>
</div>
<script>
// 1.获取router-view的DOM
const routerViewEl = document.getElementsByClassName("router-view")[0];
// 获取所有的a元素, 自己来监听a元素的改变
const aEls = document.getElementsByTagName("a");
for (let el of aEls) {
el.addEventListener("click", e => {
e.preventDefault();
const href = el.getAttribute("href");
history.pushState({}, "", href);
urlChange();
})
}
// 执行返回操作时, 依然来到urlChange
window.addEventListener('popstate',urlChange);
// 监听URL的改变
function urlChange() {
switch (location.pathname) {
case "/home":
routerViewEl.innerHTML = "首页";
break;
case "/about":
routerViewEl.innerHTML = "关于";
break;
default:
routerViewEl.innerHTML = "";
}
}。。
</script>
</body>
</html>
18. react-router-config--路由统一配置
目前我们所有的路由定义都是直接使用Route组件,并且添加属性来完成的。 但是这样的方式会让路由变得非常混乱,我们希望将所有的路由配置放到一个地方进行集中管理: 这个时候可以使用react-router-config来完成
- 安装react-router-config :
yarn add react-router-config
- 配置路由映射的关系数组
- 使用renderRoutes函数完成配置 在src目录下新建router文件夹
router/index.js
import Home from '../pages/home';
import About, { AboutHisotry, AboutCulture, AboutContact, AboutJoin } from '../pages/about';
import Profile from '../pages/profile';
import User from '../pages/user';
const routes = [
{
path: "/",
exact: true,
component: Home
},
{
path: "/about",
component: About,
routes: [
{
path: "/about",
exact: true,
component: AboutHisotry
},
{
path: "/about/culture",
component: AboutCulture
},
{
path: "/about/contact",
component: AboutContact
},
{
path: "/about/join",
component: AboutJoin
},
]
},
{
path: "/profile",
component: Profile
},
{
path: "/user",
component: User
}
]
export default routes;
在App中使用react-router-config:
import React, { PureComponent } from "react";
import { renderRoutes } from "react-router-config";
//导入routes
import routes from "./router";
import {
NavLink,
withRouter,
} from "react-router-dom";
import "./App.css";
class App extends PureComponent {
render() {
return (
<div>
<NavLink exact to="/" activeClassName="link-active">
首页
</NavLink>
<NavLink to="/about" activeClassName="link-active">
关于
</NavLink>
<NavLink to="/profile" activeClassName="link-active">
我的
</NavLink>
<NavLink to="/user" activeClassName="link-active">
用户
</NavLink>
{/*
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/profile" component={Profile} />
<Route path="/user" component={User} />
</Switch>
*/}
{renderRoutes(routes)}
</div>
);
}
}
export default withRouter(App);
在子路由中使用react-router-config:
注意:在子路由组件中不能导入全部的routes,只需要当前组件下需要的routes
import React, { PureComponent } from "react";
import { NavLink, Switch, Route } from "react-router-dom";
import { renderRoutes, matchRoutes } from "react-router-config";
export function AboutHisotry(props) {
return <h2>企业成立于2000年, 拥有悠久的历史文化</h2>;
}
export function AboutCulture(props) {
return <h2>创新/发展/共赢</h2>;
}
export function AboutContact(props) {
return <h2>联系电话: 020-68888888</h2>;
}
export function AboutJoin(props) {
return <h2>投递简历到aaaa@123.com</h2>;
}
export default class About extends PureComponent {
render() {
// 如果组件通过renderRoutes(routes)方式渲染,则会有一个route属性,可以得到当前点击路由的子路由
console.log(this.props.route);
// matchRoutes返回所有你匹配到的route,如下,返回this.props.route.routes中/about
// const branch = matchRoutes(this.props.route.routes, "/about");
//console.log(branch);
return (
<div>
<NavLink exact to="/about" activeClassName="about-active">
企业历史
</NavLink>
<NavLink exact to="/about/culture" activeClassName="about-active">
企业文化
</NavLink>
<NavLink exact to="/about/contact" activeClassName="about-active">
联系我们
</NavLink>
<button onClick={(e) => this.jumpToJoin()}>加入我们</button>
{/* <Switch>
<Route exact path="/about" component={AboutHisotry}/>
<Route path="/about/culture" component={AboutCulture}/>
<Route path="/about/contact" component={AboutContact}/>
<Route path="/about/join" component={AboutJoin}/>
</Switch>
*/}
{renderRoutes(this.props.route.routes)}
</div>
);
}
}
四、 流行的开源 React UI组件库
1. material-ui(国外)
1. 官网: www.material-ui.com/#/
2. github: github.com/callemall/m…
2. ant-design(国内蚂蚁金服)
1. 官网: ant.design/index-cn
2. Github: github.com/ant-design/…
antd的按需引入+自定主题
3.x版本按需引入:
1.安装依赖:yarn add react-app-rewired customize-cra babel-plugin-import
yarn add less@3.12.2\
yarn add less-loader@7.1.0
2.修改package.json
....
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
},
....
3.根目录下创建config-overrides.js
//配置具体的修改规则
//组件按需加载
const { override, fixBabelImports } = require('customize-cra');
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: 'css',
}),
);
//组件按需加载并且自定义主题
const { override, fixBabelImports,addLessLoader} = require('customize-cra');
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: true,
}),
addLessLoader({
lessOptions:{
javascriptEnabled: true,
modifyVars: { '@primary-color': 'green' },
}
}),
);
4.备注:不用在组件里亲自引入样式了,即:import 'antd/dist/antd.css'应该删掉,这是全部引入
4.x版本css按需引入:github.com/umijs/babel…
实例:
import React, { Component } from 'react'
import { Button,DatePicker } from 'antd';
// import 'antd/dist/antd.css' 全部引入,若配置按需引入则删除
import {WechatOutlined,WeiboOutlined,SearchOutlined} from '@ant-design/icons'
const { RangePicker } = DatePicker;
export default class App extends Component {
render() {
return (
<div>
App....
<button>点我</button>
<Button type="primary">按钮1</Button>
<Button >按钮2</Button>
<Button type="link">按钮3</Button>
<WechatOutlined />
<Button type="primary" icon={<SearchOutlined />}>
Search
</Button>
<WechatOutlined />
<WeiboOutlined />
<DatePicker/>
<RangePicker/>
</div>
)
}
}
Craco的使用步骤
- 第一步:安装craco:
yarn add @craco/craco
- 第二步:修改package.json文件
- 原本启动时,我们是通过react-scripts来管理的;
- 现在启动时,我们通过craco来管理;
"scripts": {
- "start": "react-scripts start",
- "build": "react-scripts build",
- "test": "react-scripts test",
"eject": "react-scripts eject",
+ "start": "craco start",
+ "build": "craco build",
+ "test": "craco test",
}
- 第三步:在根目录下创建craco.config.js文件用于修改默认配置
module.exports = {
// 配置文件
}
配置主题
按照配置主题的要求,自定义主题需要用到类似less-loader提供的 less 变量覆盖功能:
首先把 src/App.css
文件修改为 src/App.less
,然后修改样式引用为 less 文件。
/* src/App.js */
- import './App.css';
+ import './App.less';
/* src/App.less */
- @import '~antd/dist/antd.css';
+ @import '~antd/dist/antd.less';
我们可以引入 craco-less 来帮助加载 less 样式和修改变量;
安装 craco-less:
yarn add craco-less
修改craco.config.js中的plugins: 使用modifyVars可以在运行时修改LESS变量;
const CracoLessPlugin = require('craco-less');
module.exports = {
plugins: [
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
lessOptions: {
modifyVars: { '@primary-color': '#1DA57A' },
javascriptEnabled: true,
},
},
},
}
],
}
引入antd的样式时,引入antd.less文件:
import 'antd/dist/antd.less';
修改后重启 yarn start,如果看到一个绿色的按钮就说明配置成功了。
配置别名
在项目开发中,某些组件或者文件的层级会较深,
- 如果我们通过上层目录去引入就会出现这样的情况:../../../../components/button;
- 如果我们可以配置别名,就可以直接从根目录下面开始查找文件:@/components/button,甚至是:components/button; 配置别名也需要修改webpack的配置,当然我们也可以借助于 craco 来完成
const path = require("path");
//dir为当前文件(craco-根路径)路径,__dirname为下方resolve()传递过来的路径,然后拼接
const resolve = dir => path.resolve(__dirname, dir);
module.exports = {
plugins: [
//...
],
webpack: {
alias: {
"@": resolve("src"),
"components": resolve("src/components")
}
}
}
在导入时就可以按照下面的方式来使用了:
import HYCommentInput from '@/components/comment-input';
import HYCommentItem from 'components/comment-item';
案例:
让moment显示时间为中文--导入国际化文件;
简体:import "moment/locale/zh-cn";
繁体:import "moment/locale/zh-hk";
//App.js
import React, { PureComponent } from "react";
import CommentInput from "./components/CommentInput";
import CommentItem from "./components/CommentItem";
export default class App extends PureComponent {
state = {
commentList: [],
};
render() {
return (
<div style={{ width: "500px", padding: "20px" }}>
{this.state.commentList.map((item, index) => {
return (
<CommentItem
key={item.id}
comment={item}
//removeItem={(e) => this.removeComment(index)}
removeItem={(e) => this.removeComment(item.id)}
/>
);
})}
<CommentInput submitComment={this.submitComment} />
</div>
);
}
//子调父
submitComment = (info) => {
console.log(info);
this.setState({
commentList: [...this.state.commentList, info],
});
};
//子调父
removeComment(index) {
// const newCommentList = [...this.state.commentList];
// newCommentList.splice(index, 1);
// this.setState({
// commentList: newCommentList,
// });
this.setState({
commentList: this.state.commentList.filter((item) => item.id !== index),
});
}
}
//CommentInput.js
import React, { PureComponent } from "react";
import moment from "moment";
import { Input, Button } from "antd";
export default class CommentInput extends PureComponent {
state = {
content: "",
};
render() {
return (
<div>
<Input.TextArea
rows={4}
value={this.state.content}
onChange={(e) => this.handleChange(e)}
/>
<Button type="primary" onClick={(e) => this.addComment()}>
添加评论
</Button>
</div>
);
}
handleChange(event) {
this.setState({
content: event.target.value,
});
}
addComment() {
const commentInfo = {
id: moment().valueOf(),
avatar:
"https://upload.jianshu.io/users/upload_avatars/1102036/c3628b478f06.jpeg?imageMogr2/auto-orient/strip|imageView2/1/w/240/h/240",
nickname: "zgc",
datetime: moment(),
content: this.state.content,
comments: [],
};
this.props.submitComment(commentInfo);
this.setState({
content: "",
});
}
}
//CommentItem.js
import React, { PureComponent } from "react";
import { Comment, Avatar, Tooltip } from "antd";
import { DeleteOutlined } from "@ant-design/icons";
export default class CommentItem extends PureComponent {
render() {
const { nickname, avatar, content, datetime } = this.props.comment;
return (
<Comment
author={<a href="/#">{nickname}</a>}
avatar={<Avatar src={avatar} alt={nickname} />}
content={<p>{content}</p>}
datetime={
<Tooltip title={datetime.format("YYYY-MM-DD HH:MM:ss")}>
<span>{datetime.fromNow()}</span>
</Tooltip>
}
actions={[
<span onClick={(e) => this.removeItem()}>
<DeleteOutlined />
删除
</span>,
]}
/>
);
}
removeItem() {
this.props.removeItem();
}
}