react脚手架
1.xxx脚手架:用来帮助程序员快速创建一个基于xxx库的模板项目
- 包含了所有需要的配置(语法检查,jsx编译,devServer)
- 下载好了所有相关的依赖
- 可以直接运行一个简单结果
2.react提供了一个用于创建react项目的脚手架库:create-react-app
3.项目的整体技术架构为:react+webpack+es6+eslint
4.使用脚手架开发的项目的特点:模块化,组件化,工程化
创建项目并启动
第一步,全局安装:npm i -g create-react-app
第二步,切换到想创项目的目录,使用命令:create-react-app hello-react
第三步,进入项目文件夹,cd hello-react
第四步,启动项目:npm start
react脚手架项目结构
public ---- 静态资源文件夹
favicon.icon ------ 网站页签图标
index.html -------- 主页面
logo192.png ------- logo图
logo512.png ------- logo图
manifest.json ----- 应用加壳的配置文件
robots.txt -------- 爬虫协议文件
src ---- 源码文件夹
App.css -------- App组件的样式
App.js --------- App组件
App.test.js ---- 用于给App做测试
index.css ------ 样式
index.js ------- 入口文件
logo.svg ------- logo图
reportWebVitals.js
--- 页面性能分析文件(需要web-vitals库的支持)
setupTests.js
---- 组件单元测试的文件(需要jest-dom库的支持)
样式的模块化
将组件中样式的类名改为index.module.css
index.jsx
import React,{Component} from 'react'
import hello from './index.module.css'export default class Hello extends Component{render(){return <h2 className={hello.title}>Hello,React!</h2>}
}
todoList案例
App.jsx
import React, { Component } from 'react'
import Header from './components/Header'
import List from './components/List'
import Footer from './components/Footer'
import './App.css'export default class App extends Component {//状态在哪里,操作状态的方法就在哪里//初始化状态state = {todos:[{id:'001',name:'吃饭',done:true},{id:'002',name:'睡觉',done:true},{id:'003',name:'打代码',done:false},{id:'004',name:'逛街',done:false}]}//addTodo用于添加一个todo,接收的参数是todo对象addTodo = (todoObj)=>{//获取原todosconst {todos} = this.state//追加一个todoconst newTodos = [todoObj,...todos]//更新状态this.setState({todos:newTodos})}//updateTodo用于更新一个todo对象updateTodo = (id,done)=>{//获取状态中的todosconst {todos} = this.state//匹配处理数据const newTodos = todos.map((todoObj)=>{if(todoObj.id === id) return {...todoObj,done}else return todoObj})this.setState({todos:newTodos})}//deleteTodo用于删除一个todo对象deleteTodo = (id)=>{//获取原来的todosconst {todos} = this.state//删除指定id的todo对象const newTodos = todos.filter((todoObj)=>{return todoObj.id !== id})//更新状态this.setState({todos:newTodos})}//checkAllTodo用于全选checkAllTodo = (done)=>{//获取原来的todosconst {todos} = this.state//加工数据const newTodos = todos.map((todoObj)=>{return {...todoObj,done}})//更新状态this.setState({todos:newTodos})}//clearAllDone用于清除所有已完成的clearAllDone = ()=>{//获取原来的todosconst {todos} = this.state//过滤数据const newTodos = todos.filter((todoObj)=>{return !todoObj.done})//更新状态this.setState({todos:newTodos})}render() {const {todos} = this.statereturn (<div className="todo-container"><div className="todo-wrap"><Header addTodo={this.addTodo}/><List todos={todos} updateTodo={this.updateTodo} deleteTodo={this.deleteTodo}/><Footer todos={todos} checkAllTodo={this.checkAllTodo} clearAllDone={this.clearAllDone}/></div></div>)}
}
Header.jsx
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import {nanoid} from 'nanoid'
import './index.css'export default class Header extends Component {//对接收的props进行:类型、必要性的限制static propTypes = {addTodo:PropTypes.func.isRequired}//键盘事件的回调handleKeyUp = (event)=>{//解构赋值获取keyCode,targetconst {keyCode,target} = event//判断是否是回车按键if(keyCode !== 13) return//添加的todo名字不能为空if(target.value.trim() === ''){alert('输入不能为空')return}//准备好一个todo对象const todoObj = {id:nanoid(),name:target.value,done:false}//将todoObj传递给Appthis.props.addTodo(todoObj)//清空输入target.value = ''}render() {return (<div className="todo-header"><input onKeyUp={this.handleKeyUp} type="text" placeholder="请输入你的任务名称,按回车键确认"/></div>)}
}
List.jsx
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Item from '../Item'
import './index.css'export default class List extends Component {//对接收的props进行:类型、必要性的限制static propTypes = {todos:PropTypes.array.isRequired,updateTodo:PropTypes.func.isRequired,deleteTodo:PropTypes.func.isRequired,}render() {const {todos,updateTodo,deleteTodo} = this.propsreturn (<ul className="todo-main">{todos.map( todo =>{return <Item key={todo.id} {...todo} updateTodo={updateTodo} deleteTodo={deleteTodo}/>})}</ul>)}
}
Item.jsx
import React, { Component } from 'react'
import './index.css'export default class Item extends Component {state = {mouse:false} //标识鼠标移入、移出//鼠标移入、移出的回调handleMouse = (flag)=>{return ()=>{this.setState({mouse:flag})}}//勾选、取消勾选某一个todo的回调handleCheck = (id)=>{return (event)=>{this.props.updateTodo(id,event.target.checked)}}//删除一个todo的回调handleDelete = (id)=>{if(window.confirm('确定删除吗?')){this.props.deleteTodo(id)}}render() {const {id,name,done} = this.propsconst {mouse} = this.statereturn (<li style={{backgroundColor:mouse ? '#ddd' : 'white'}} onMouseEnter={this.handleMouse(true)} onMouseLeave={this.handleMouse(false)}><label><input type="checkbox" checked={done} onChange={this.handleCheck(id)}/><span>{name}</span></label><button onClick={()=> this.handleDelete(id) } className="btn btn-danger" style={{display:mouse?'block':'none'}}>删除</button></li>)}
}
Footer.jsx
import React, { Component } from 'react'
import './index.css'export default class Footer extends Component {//全选checkbox的回调handleCheckAll = (event)=>{this.props.checkAllTodo(event.target.checked)}//清除已完成任务的回调handleClearAllDone = ()=>{this.props.clearAllDone()}render() {const {todos} = this.props//已完成的个数const doneCount = todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0),0)//总数const total = todos.lengthreturn (<div className="todo-footer"><label><input type="checkbox" onChange={this.handleCheckAll} checked={doneCount === total && total !== 0 ? true : false}/></label><span><span>已完成{doneCount}</span> / 全部{total}</span><button onClick={this.handleClearAllDone} className="btn btn-danger">清除已完成任务</button></div>)}
}
TodoList案例总结
1.拆分组件、实现静态组件,注意className,style({{}})的写法
2.动态初始化列表,如何确定将数据放在哪个组件的state中?
- 某个组件使用:放在其自身的state中
- 某些组件使用,放在他们共同的父组件state中(官方称此操作为:状态提升)
3.关于父子之间通信:
- 父组件给子组件传递数据:通过props传递
- 子组件给父组件传递数据:通过props传递,要求父提前给子传递一个函数
4.注意defaultChecked(只在第一次指定的时候起作用)和checked的区别,类似的还有:defaultValue和value
"proxy":"http://localhost:5000"
5.状态在哪里,操作状态的方法就在哪里
配置代理
方法一
在package.json中追加如下配置
"proxy":"http://localhost:5000"
说明:
- 优点:配置简单,前端请求资源时可以不加任何前缀
- 缺点:不能配置多个代理、
- 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000(优先匹配前端资源)
方法二
1.第一步:创建代理配置文件:在src下创建配置文件:src/setupProxy.js
2.编写setupProxy.js配置具体代理规 则:
const proxy = require('http-proxy-middleware')module.exports = function(app) {app.use(proxy('/api1', { //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)target: 'http://localhost:5000', //配置转发目标地址(能返回数据的服务器地址)changeOrigin: true, //控制服务器接收到的请求头中host字段的值/*changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000changeOrigin默认值为false,但我们一般将changeOrigin值设为true*/pathRewrite: {'^/api1': ''} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)}),proxy('/api2', { target: 'http://localhost:5001',changeOrigin: true,pathRewrite: {'^/api2': ''}}))}
说明:
- 优点:可以配置多个代理,可以灵活的控制请求是否走代理
- 缺点:配置繁琐,前端请求资源时必须加前缀
github搜索案例--axios
list.jsx
import React, { Component } from 'react'
import './index.css'export default class List extends Component {render() {const {users,isFirst,isLoading,err} = this.propsreturn (<div className="row">{isFirst ? <h2>欢迎使用,输入关键字,随后点击搜索</h2> :isLoading ? <h2>Loading......</h2> :err ? <h2 style={{color:'red'}}>{err}</h2> :users.map((userObj)=>{return (<div key={userObj.id} className="card"><a rel="noreferrer" href={userObj.html_url} target="_blank"><img alt="head_portrait" src={userObj.avatar_url} style={{width:'100px'}}/></a><p className="card-text">{userObj.login}</p></div>)})}</div>)}
}
search.jsx
import React, { Component } from 'react'
import axios from 'axios'export default class Search extends Component {search = ()=>{//获取用户的输入(连续解构赋值+重命名)const {keyWordElement:{value:keyWord}} = this//发送请求前通知App更新状态this.props.updateAppState({isFirst:false,isLoading:true})//发送网络请求axios.get(`/api1/search/users?q=${keyWord}`).then(response => {//请求成功后通知App更新状态this.props.updateAppState({isLoading:false,users:response.data.items})},error => {//请求失败后通知App更新状态this.props.updateAppState({isLoading:false,err:error.message})})}render() {return (<section className="jumbotron"><h3 className="jumbotron-heading">搜索github用户</h3><div><input ref={c => this.keyWordElement = c} type="text" placeholder="输入关键词点击搜索"/> <button onClick={this.search}>搜索</button></div></section>)}
}
App.jsx
import React, { Component } from 'react'
import Search from './components/Search'
import List from './components/List'export default class App extends Component {state = { //初始化状态users:[], //users初始值为数组isFirst:true, //是否为第一次打开页面isLoading:false,//标识是否处于加载中err:'',//存储请求相关的错误信息} //更新App的stateupdateAppState = (stateObj)=>{this.setState(stateObj)}render() {return (<div className="container"><Search updateAppState={this.updateAppState}/><List {...this.state}/></div>)}
}
github搜索案例---pubsub
list.jsx
import React, { Component } from 'react'
import PubSub from 'pubsub-js'
import './index.css'export default class List extends Component {state = { //初始化状态users:[], //users初始值为数组isFirst:true, //是否为第一次打开页面isLoading:false,//标识是否处于加载中err:'',//存储请求相关的错误信息} componentDidMount(){this.token = PubSub.subscribe('atguigu',(_,stateObj)=>{this.setState(stateObj)})}componentWillUnmount(){PubSub.unsubscribe(this.token)}render() {const {users,isFirst,isLoading,err} = this.statereturn (<div className="row">{isFirst ? <h2>欢迎使用,输入关键字,随后点击搜索</h2> :isLoading ? <h2>Loading......</h2> :err ? <h2 style={{color:'red'}}>{err}</h2> :users.map((userObj)=>{return (<div key={userObj.id} className="card"><a rel="noreferrer" href={userObj.html_url} target="_blank"><img alt="head_portrait" src={userObj.avatar_url} style={{width:'100px'}}/></a><p className="card-text">{userObj.login}</p></div>)})}</div>)}
}
search.jsx
import React, { Component } from 'react'
import PubSub from 'pubsub-js'
import axios from 'axios'export default class Search extends Component {search = ()=>{//获取用户的输入(连续解构赋值+重命名)const {keyWordElement:{value:keyWord}} = this//发送请求前通知List更新状态PubSub.publish('atguigu',{isFirst:false,isLoading:true})//发送网络请求axios.get(`/api1/search/users?q=${keyWord}`).then(response => {//请求成功后通知List更新状态PubSub.publish('atguigu',{isLoading:false,users:response.data.items})},error => {//请求失败后通知App更新状态PubSub.publish('atguigu',{isLoading:false,err:error.message})})}render() {return (<section className="jumbotron"><h3 className="jumbotron-heading">搜索github用户</h3><div><input ref={c => this.keyWordElement = c} type="text" placeholder="输入关键词点击搜索"/> <button onClick={this.search}>搜索</button></div></section>)}
}
App.jsx
import React, { Component } from 'react'
import Search from './components/Search'
import List from './components/List'export default class App extends Component {render() {return (<div className="container"><Search/><List/></div>)}
}
github搜索案例---fetch
list.jsx
import React, { Component } from 'react'
import PubSub from 'pubsub-js'
import './index.css'export default class List extends Component {state = { //初始化状态users:[], //users初始值为数组isFirst:true, //是否为第一次打开页面isLoading:false,//标识是否处于加载中err:'',//存储请求相关的错误信息} componentDidMount(){this.token = PubSub.subscribe('atguigu',(_,stateObj)=>{this.setState(stateObj)})}componentWillUnmount(){PubSub.unsubscribe(this.token)}render() {const {users,isFirst,isLoading,err} = this.statereturn (<div className="row">{isFirst ? <h2>欢迎使用,输入关键字,随后点击搜索</h2> :isLoading ? <h2>Loading......</h2> :err ? <h2 style={{color:'red'}}>{err}</h2> :users.map((userObj)=>{return (<div key={userObj.id} className="card"><a rel="noreferrer" href={userObj.html_url} target="_blank"><img alt="head_portrait" src={userObj.avatar_url} style={{width:'100px'}}/></a><p className="card-text">{userObj.login}</p></div>)})}</div>)}
}
search.jsx
import React, { Component } from 'react'
import PubSub from 'pubsub-js'
// import axios from 'axios'export default class Search extends Component {search = async()=>{//获取用户的输入(连续解构赋值+重命名)const {keyWordElement:{value:keyWord}} = this//发送请求前通知List更新状态PubSub.publish('atguigu',{isFirst:false,isLoading:true})//#region 发送网络请求---使用axios发送/* axios.get(`/api1/search/users2?q=${keyWord}`).then(response => {//请求成功后通知List更新状态PubSub.publish('atguigu',{isLoading:false,users:response.data.items})},error => {//请求失败后通知App更新状态PubSub.publish('atguigu',{isLoading:false,err:error.message})}) *///#endregion//发送网络请求---使用fetch发送(未优化)/* fetch(`/api1/search/users2?q=${keyWord}`).then(response => {console.log('联系服务器成功了');return response.json()},error => {console.log('联系服务器失败了',error);return new Promise(()=>{})}).then(response => {console.log('获取数据成功了',response);},error => {console.log('获取数据失败了',error);}) *///发送网络请求---使用fetch发送(优化)try {const response= await fetch(`/api1/search/users2?q=${keyWord}`)const data = await response.json()console.log(data);PubSub.publish('atguigu',{isLoading:false,users:data.items})} catch (error) {console.log('请求出错',error);PubSub.publish('atguigu',{isLoading:false,err:error.message})}}render() {return (<section className="jumbotron"><h3 className="jumbotron-heading">搜索github用户</h3><div><input ref={c => this.keyWordElement = c} type="text" placeholder="输入关键词点击搜索"/> <button onClick={this.search}>搜索</button></div></section>)}
}
App.jsx
import React, { Component } from 'react'
import Search from './components/Search'
import List from './components/List'export default class App extends Component {render() {return (<div className="container"><Search/><List/></div>)}
}
github搜索案例相关总结
1.设计状态时要考虑全面,例如带有网络请求的组件,要考虑请求失败怎么办
2.ES6知识点:解构赋值+命名
let obj = {a:{b:1}}
const {a} = obj; //传统解构赋值
const {a:{b}} = obj; //连续解构赋值
const {a:{b:value}} = obj; //连续解构赋值+重命名
3.消息订阅与发布机制
- 先订阅,再发布(理解:有一种隔空对话的感觉)
- 适用于任意组件间通信
- 要在组件的componentWillUnmount中取消订阅
4.fetch发送请求(关注分离的设计思想)
try {const response= await fetch(`/api1/search/users2?q=${keyWord}`)const data = await response.json()console.log(data);} catch (error) {console.log('请求出错',error);}
react脚手架中未下载的库:
prop-types npm add prop-typesreact-router-dom npm i react-router-dom@5
路由的基本使用
NavLink的使用
App.jsx
import React, { Component } from 'react'
import {NavLink,Route} from 'react-router-dom'
import Home from './pages/Home' //Home是路由组件
import About from './pages/About' //About是路由组件
import Header from './components/Header' //Header是一般组件export default class App extends Component {render() {return (<div><div className="row"><div className="col-xs-offset-2 col-xs-8"><Header/></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中靠路由链接实现切换组件--编写路由链接 */}<NavLink activeClassName="atguigu" className="list-group-item" to="/about">About</NavLink><NavLink activeClassName="atguigu" className="list-group-item" to="/home">Home</NavLink></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>)}
}
About.jsx
import React, { Component } from 'react'export default class About extends Component {render() {// console.log('About组件收到的props是',this.props);return (<h3>我是About的内容</h3>)}
}
Home.jsx
import React, { Component } from 'react'export default class Home extends Component {render() {return (<h3>我是Home的内容</h3>)}
}
index.js(入口文件)
//引入react核心库
import React from 'react'
//引入ReactDOM
import ReactDOM from 'react-dom'
//
import {BrowserRouter} from 'react-router-dom'
//引入App
import App from './App'ReactDOM.render(<BrowserRouter><App/></BrowserRouter>,document.getElementById('root')
)
1.明确好界面中的导航区,展示区
2.导航区的a标签改为Link标签
<Link to="/xxx">Demo</Link>
3.展示区写Route标签进行路径的匹配
<Route path='/xxx' component={Demo}/>
4.<App>的最外层包裹了一个<BrowserRouter>或<HashRouter>
路由组件与一般组件
1.写法不同:
一般组件:<Demo/>
路由组件:<Route path="/demo" component="{Demo}"/>
2.存放位置不同:
一般组件:components
路由组件:pages
3.接收到的props不同:
一般组件:写组件标签时传递了什么,就能收到什么、
路由组件:接收到三个固定的属性
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"
NavLink与封装NavLink
- NavLink可以实现路由链接的高亮,通过activeClassName指定样式名
- 标签体内容是一个特殊的标签属性
- 通过this.props.children可以获取标签体内容
MyNavLink
import React, { Component } from 'react'
import {NavLink} from 'react-router-dom'export default class MyNavLink extends Component {render() {// console.log(this.props);return (<NavLink activeClassName="atguigu" className="list-group-item" {...this.props}/>)}
}
{/* 在React中靠路由链接实现切换组件--编写路由链接 */}
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home">Home</MyNavLink>
Switch的使用
1.通常情况下,path和component是一一对应的关系
2.Switch可以提高路由匹配效率(单一匹配)
<Switch><Route path="/about" component={About}/><Route path="/home" component={Home}/><Route path="/home" component={Test}/>
</Switch>
如果没有Switch包裹,/home路径下会显示Home和Test两个组件,但是如果有Switch,当/home匹配到Home组件时,就不会继续往下匹配
解决多级路径样式丢失问题
当路由组件路径写为多级路径时,浏览器申请css文件的路径就会变化从而找不到该文件导致样式丢失,此时显示的界面默认是public下的index.html解决方法有以下三种方法:
1.public/index.html中引入样式时不写./写/(常用)
2.public/index.html中引入样式时不写./写%PUBLIC_URL%(常用)
3.使用HashRouter(#后面的路径不会传至服务器上,不会和前面的端口号混淆)
路由的严格匹配与模糊匹配
- 默认使用的是模糊匹配(简单记:输入的路径必须包含要匹配的路径,且顺序要一致)
- 开启严格匹配:<Route exact={true} path="/about" component={About}/>
- 严格匹配不要随便开启,需要再开,有时候开启会导致无法继续匹配二级路由
<Switch><Route exact path="/about" component={About}/><Route exact path="/home" component={Home}/>
</Switch>
Redirect的使用
一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
<Switch><Route path="/about" component={About}/><Route path="/home" component={Home}/><Redirect to="/about"/>
</Switch>
嵌套路由
- 注册子路由时要写上父路由的path值
- 路由的匹配是按照注册路由的顺序进行的
import React, { Component } from 'react'
import MyNavLink from '../../components/MyNavLink'
import {Route,Switch,Redirect} from 'react-router-dom'
import News from './News'
import Message from './Message'export default class Home extends Component {render() {return (<div><h3>我是Home的内容</h3><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/news"/></Switch></div></div>)}
}
向路由组件传递参数
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.statereturn (<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'
// import qs from 'querystring'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 // 接收search参数// const {search} = this.props.location// const {id,title} = qs.parse(search.slice(1))// 接收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>)}
}
1.params参数
路由链接(携带参数):<Link to='/demo/test/tom/18'}>详情</Link>
注册路由(声明接收):<Route path="/demo/test/:name/:age" component={Test}/>
接收参数: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
备注:获取到的search是urlencoded编码字符串,需要借助querystring解析
3.state参数
路由链接(携带参数):<Link to={{pathname:'/demo/test',state:{name:'tom',age:18}}}>详情</Link>
注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
接收参数:this.props.location.state
备注:刷新也可以保留住参数,且地址栏中不会有传递的参数
编程式路由导航
借助this.props.history对象上的API对路由进行跳转,前进,后退等操作
-this.props.history.push()
-this.props.history.replace()
-this.props.history.goBack()
-this.props.history.goForward()
-this.props.history.go()
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,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参数 push: ƒ push(path, state)this.props.history.push(`/home/message/detail`,{id,title})}back = ()=>{this.props.history.goBack()}forward = ()=>{this.props.history.goForward()}go = ()=>{this.props.history.go(-2)}render() {const {messageArr} = this.statereturn (<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>)}
}
WithRouter
由于一般组件上没有路由组件的API,要想让一般组件使用路由组件的API,必须先让WithRouter处理一般组件,获得拥有了路由组件API新组件。
import React, { Component } from 'react'
import {withRouter} from 'react-router-dom'class Header extends Component {back = ()=>{this.props.history.goBack()}forward = ()=>{this.props.history.goForward()}go = ()=>{this.props.history.go(-2)}render() {console.log('Header组件收到的props是',this.props);return (<div className="page-header"><h2>React Router Demo</h2><button onClick={this.back}>回退</button> <button onClick={this.forward}>前进</button> <button onClick={this.go}>go</button></div>)}
}export default withRouter(Header)//withRouter可以加工一般组件,让一般组件具备路由组件所特有的API
//withRouter的返回值是一个新组件
BrowserRouter与HashRouter的区别
1.底层原理不一样:
- BrowserRouter使用的是H5的history API,不兼容IE9以下版本
- HashRouter使用的是URL的哈希值
2.path表现形式不一样
- BrowerRouter的路径中没有#,例如:localhost:3000/demo/test
- HashRouter的路径包含#,例如:localhost:3000/#/demo/test
3.刷新后对路由的state参数的影响
- BrowerRouter没有任何影响,因为state保存在history对象中
- HashRouter刷新后会导致路由state参数的缺失
4.备注:HashRouter可以用于解决一些路径错误的问题
antd的按需引入+自定主题
1.安装依赖:yarn add react-app-rewired customize-cra babel-plugin-import less less-loader
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,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'应该删掉