一、React简介
1 react特点
React:用于构建用户界面的javascript库。是一个将数据渲染为HTML视图的开源JavaScript库。
react的特点:
1.采用组件化模式、声明式编码,提高开发效率及组件复用率。
2.在React Native中可以使用React语法进行移动端开发。
3.使用虚拟DOM+优秀的Diffing算法,尽量减少与真实DOM的交互。
2 react基本使用
2.1 相关js库
- react.js:React核心库。
- react-dom.js:提供操作DOM的react扩展库。
- babel.min.js:解析JSX语法代码转为JS代码的库。
- prop-types.js:用于对组件标签属性进行限制
2.2 创建虚拟DOM的两种方式
- 纯JS方式(一般不用)
- JSX方式
2.3 虚拟DOM与真实DOM
- 本质是Object类型的对象(一般对象)
- 虚拟DOM比较“轻”,真实DOM比较“重”,因为虚拟DOM是React内部在使用,无需真实DOM上那么多属性。
- 虚拟DOM最终会被React转化成真实DOM,呈现在页面上
2.4 jsx语法规则
1.介绍
(1)全称: JavaScript XML
(2)react定义的一种类似于XML的JS扩展语法: JS + XML本质是 React.createElement(component, props, ...children)方法的语法糖
(3)作用: 用来简化创建虚拟DOM
2.规则
(1)定义虚拟dom时,不要写引号
(2)标签中混入JS表达式时要用{}
(3)样式的类名要用className
(4)内联样式要用style={{}}的形式去写
(5)只有一个根标签
(6)标签必须闭合
(7)标签首字母
A.若小写字母开头,则将该标签转为html中同名标签,若html中无该标签对应的
同名的元素,则报错。
B.若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。
const myID = 'aTguigu'
const myData = 'hello,React'
const VDOM = (
<div>
<h2 className="title" id={myID.toLowerCase()}>
<span style={{color:'white',fontSize:'30px'}}>{myData.toUpperCase()}</span>
</h2>
<h2 className="title" id={myID.toLowerCase() + '2'}>
<span style={{color:'white',fontSize:'30px'}}>{myData.toUpperCase()}</span>
</h2>
</div>
)
ReactDOM.render(VDOM,document.getElementById('test'))
额外知识:
1. 区分js表达式和js语句
(1)js表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方
例如:a 、a+b、demo()、arr.map()、function test()
(2)js语法(代码):
例如:if语句、for循环、switch语句
2.react的jsx语法里的{},可以自动遍历数组,不能遍历obj
3.react定义一个数组,如需遍历生成<li></li>,使用data.map()
const data = ['Angular','React','Vue']
const VDOM = (
<h1>前端js框架列表</h1>
<ul>
{
data.map((item,index) =>{
return <li key={index}>{item}</li>
})
}
</ul>
)
2.5 模块与组件、模块化与组件化的理解
1.模块
(1)理解:向外提供特定功能的js程序,一般就是一个js文件
(2)为什么要拆成模块:随着业务逻辑的增加,代码越来越多且复杂
(3)作用:复用js,简化js的编写,提高js的效率
2.组件
(1)理解:用来实现局部功能效果的代码和资源的集合
(2)为什么:一个界面的功能更复杂
(3)作用:复用编码,简化项目编码,提高运行效率
3.模块化
当应用的js都已模块来编写,这个应用就是一个模块化的应用
4.组件化
当应用是以多组件的方式实现,这个应用就是一个组件化的应用
二、react面向组件编程
1 基本理解和使用
(1)使用react开发者工具调试
(2)组件的定义
A.函数式组件:
ReactDOM.render(<Demo/>......)之后发生了什么:
① React解析组件标签,找到了Demo组件
② 发现组件是使用函数定义的,调用该函数,将返回的虚拟DOM转为真实DOM
function Demo(){
console.log(this) //此处的this是underfined,因为babel编译后开启了严格模式
return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
}
ReactDOM.render(<Demo/>,document.getElementById('test'))
B.类式组件
ReactDOM.render(<Demo/>......)之后发生了什么:
① React解析组件标签,找到了Demo组件
② 发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型 上的render方法。将render返回的虚拟DOM转为真实DOM,呈现在页面上。
class Demo extends React.Commonent {
render(){
// render 是放在哪里的? -- Demo的原型对象上,供实例使用
// render中的this是谁? -- Demo的实例对象
return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>
}
}
ReactDOM.render(<Demo/>,document.getElementById('test'))
2 组件实例的三大核心属性:state
2.1 理解
(1)state是组件对象最重要的属性,值是对象(可以包含多个key-value的组合)
(2)组件被成为“状态机”,通过更改state来更新对应的页面的显示(重新渲染组件)
2.2 注意
(1)组件中的render方法中的this为组件实例对象
(2)组件自定义的方法中this为underfined,如何解决?
a.强制绑定this,通过函数对象的bind()
b.箭头函数
(3)状态数据,不能直接修改或更新
class Weather extends React.Component {
constructor(props){
super(props)
// 初始化状态
this.state = {isHot:true}
// 解决changeWeather中this指向问题
this.changeWeather = this.changeWeather.bind(this)
}
render(){
console.log(this)
return <h1 onClick={this.changeWeather} className='title'>今天天气很{this.state.isHot ? '炎热' : '寒冷'}</h1>
}
changeWeather(){
// changeWeather放在哪里? -- Weather的原型对象上,供实例使用
// 由于changeWeather是作为onClick的回调,所以changeWeather不是通过实例调用,是直接调用
// 类中的方法默认开启了局部的严格模式,所以changeWeather中的this为underfined
// 获取原来的isHot值
const isHot = this.state.isHot
// 严重注意:状态(state)不可直接更改,要借助一个setState去更改
// setState更改是合并state的值,不是替换
this.setState({isHot:!isHot})
}
}
ReactDOM.render(<Weather/>,document.getElementById('test'))
class Weather extends React.Component {
state = {isHot:true}
render(){
return <h1 onClick={this.changeWeather} className='title'>今天天气很{this.state.isHot ? '炎热' : '寒冷'}</h1>
}
// 自定义方法 -- 要用赋值语句的形式 + 箭头函数
changeWeather = ()=>{
const isHot = this.state.isHot
this.setState({isHot:!isHot})
}
}
ReactDOM.render(<Weather/>,document.getElementById('test'))
3 组件实例的三大核心属性:props
3.1 实例中使用props(类式组件)
class Person extends React.Component{
render(){
const {name,age,sex} = this.props
return(
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
}
//对标签属性类型进行类型、必要性的限制
Person.propTypes = {
name:PropTypes.string.isRequired, //限制name属性为字符串,并且必传
sex:PropTypes.string, //限制name属性为字符串
age:PropTypes.number, //限制name属性为数值
speak:PropTypes.func //限制name属性为函数
}
//指定默认的标签属性值
Person.defaultProps = {
sex:'男',
age:18
}
ReactDOM.render(<Person name='jerry' speak={speak} />,document.getElementById('test1'))
ReactDOM.render(<Person name='tom' age={18} sex='女' />,document.getElementById('test2'))
const p = {name:'老刘',age:18,sex:'女'}
ReactDOM.render(<Person {...p} />,document.getElementById('test3'))
function speak(){
console.log('我说话了')
}
class Person extends React.Component{
static propTypes = {
name:PropTypes.string.isRequired,
sex:PropTypes.string,
age:PropTypes.number,
speak:PropTypes.func
}
static defaultProps = {
sex:'男',
age:18
}
state = {}
render(){
const {name,age,sex} = this.props
return(
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
}
ReactDOM.render(<Person name='jerry' speak={speak} />,document.getElementById('test1'))
ReactDOM.render(<Person name='tom' age={18} sex='女' />,document.getElementById('test2'))
const p = {name:'老刘',age:18,sex:'女'}
ReactDOM.render(<Person {...p} />,document.getElementById('test3'))
function speak(){
console.log('我说话了')
}
注意:类中写构造器的情况,只有在构造器需要用到this.props访问props才写。
类中构造器可以不写的情况下尽量不写。
构造器是否接收props,是否传递给super,取决于:是佛希望在构造器中通过this访问props
class Person extends React.Component{
constructor(props){
super(props)
console.log('constructor',this.props)
}
}
3.2 函数式组件中使用props
function Person(props){
const {name,age,sex} = props
return(
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
Person.propTypes = {
name:PropTypes.string.isRequired,
sex:PropTypes.string,
age:PropTypes.number,
speak:PropTypes.func
}
Person.defaultProps = {
sex:'男',
age:18
}
ReactDOM.render(<Person name='jerry' age={19} sex='男' />,document.getElementById('test'))
3.3 理解和作用
(1)每个组件对象都会有props(properties的简写)属性
(2)组件标签的所有属性都保存在props中
(3)通过标签属性从组件外向组件内传递变化的数据
(4)注意:组件内部不要修改props数据
4 组件实例的三大核心属性:refs
4.1 字符串形式的refs(被弃用)
<input ref="input1" type="text" placeholder="点击按钮提示数据" />
4.2 函数回调形式的refs
(1)内联形式的函数回调(大多数情况下使用这种方式)
<input ref={c => this.input1 = c;console.log('@',c)} type="text" placeholder="点击按钮提示数据" />
注意:在更新过程中函数会被执行两次,第一次传入参数null,第二次传入DOM元素,即
第一次输出@,=null;第二次输出@,DOM元素
原因:在每一次渲染时会创建一个新的函数实例,所以React清空旧的ref并设置新的。
(2)class的绑定函数(解决更新过程中被执行两次)
<input ref={this.saveRef} type="text" placeholder="点击按钮提示数据" />
saveRef = (c)=>{
this.input1 = c;
console.log('@',c)
}
3.3 React.createRef(官网推荐使用)
class Demo extends React.Component{
myRef = React.createRef()
myRef2 = React.createRef()
render(){
return(
<div>
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据" />
<button onClick={this.showData}>点我提示左侧的数据</button>
<input onBlur={this.showData2} ref={this.myRef2} type="text" placeholder="失去焦点提示数据" />
</div>
)
}
showData = ()=>{
alert(this.myRef.current.value)
}
showData2 = ()=>{
alert(this.myRef2.current.value)
}
}
ReactDOM.render(<Demo/>,document.getElementById('test'))
注意:React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,
该容器是“专人专用”,一个ref标识只能创建有一个容器。
5 事件处理
(1)通过onXxx属性指定事件处理函数(注意大小写)
1.React使用的是自定义(合成事件),而不是使用的原生DOM事件 -- 为了更好的兼容性
2.React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) -- 为了高效
(2)通过event.target得到发生事件的Dom元素对象 -- 不要过度使用ref
class Demo extends React.Component{
myRef = React.createRef()
render(){
return(
<div>
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据" />
<button onClick={this.showData}>点我提示左侧的数据</button>
<input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据" />
</div>
)
}
showData = ()=>{
alert(this.myRef.current.value)
}
showData2 = (event)=>{
alert(event.target.value)
}
}
ReactDOM.render(<Demo/>,document.getElementById('test'))
6 收集表单数据 - 阶函数与函数柯里化
(1)高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数
1.若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数
2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数
(2)函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数 编码形式
(3)函数柯里化写法:
class Demo extends React.Component{
state = {
username:'',
password:''
}
handleSubmit = (event)=>{
event.preventDefault()
alert(`用户名是:${this.state.username},密码是:${this.state.password}`)
}
saveFormData = (dataType)=>{
return (event) => {
this.setState({[dataType]:event.target.value})
}
}
render(){
return(
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveFormData('username')} type="text" name="username" />
密码<input onChange={this.saveFormData('password')} type="password" name="password" />
<button>提交</button>
</form>
)
}
}
ReactDOM.render(<Demo/>,document.getElementById('test'))
(4)不用函数柯里化写法:
class Demo extends React.Component{
state = {
username:'',
password:''
}
handleSubmit = (event)=>{
event.preventDefault()
alert(`用户名是:${this.state.username},密码是:${this.state.password}`)
}
saveFormData = (dataType,event)=>{
this.setState({[dataType]:event.target.value})
}
render(){
return(
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={ event => this.saveFormData('username',event) } type="text" name="username" />
密码<input onChange={ event => this.saveFormData('password',event) } type="password" name="password" />
<button>提交</button>
</form>
)
}
}
ReactDOM.render(<Demo/>,document.getElementById('test'))
7 生命周期
1.组件从创建到死亡会经历一些特定的阶段
2.React组件中包含一系列钩子函数,会在特定的时期调用
3.我们在定义组件时,会在特定的声明周期回调函数中做特定的事情
三、react脚手架
1 初始化脚手架
npm i create-react-app -g
create-react-app 项目名
2 todoList案例
(1)app.js
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)=>{
//获取原todos
const {todos} = this.state
//追加一个todo
const newTodos = [todoObj,...todos]
//更新状态
this.setState({todos:newTodos})
}
//updateTodo用于更新一个todo对象
updateTodo = (id,done)=>{
//获取状态中的todos
const {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)=>{
//获取原来的todos
const {todos} = this.state
//删除指定id的todo对象
const newTodos = todos.filter((todoObj)=>{
return todoObj.id !== id
})
//更新状态
this.setState({todos:newTodos})
}
//checkAllTodo用于全选
checkAllTodo = (done)=>{
//获取原来的todos
const {todos} = this.state
//加工数据
const newTodos = todos.map((todoObj)=>{
return {...todoObj,done}
})
//更新状态
this.setState({todos:newTodos})
}
//clearAllDone用于清除所有已完成的
clearAllDone = ()=>{
//获取原来的todos
const {todos} = this.state
//过滤数据
const newTodos = todos.filter((todoObj)=>{
return !todoObj.done
})
//更新状态
this.setState({todos:newTodos})
}
render() {
const {todos} = this.state
return (
<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>
)
}
}
(2)Header组件
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,target
const {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传递给App
this.props.addTodo(todoObj)
//清空输入
target.value = ''
}
render() {
return (
<div className="todo-header">
<input onKeyUp={this.handleKeyUp} type="text" placeholder="请输入你的任务名称,按回车键确认"/>
</div>
)
}
}
(3)List组件
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.props
return (
<ul className="todo-main">
{
todos.map( todo =>{
return <Item key={todo.id} {...todo} updateTodo={updateTodo} deleteTodo={deleteTodo}/>
})
}
</ul>
)
}
}
(4)Item组件
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.props
const {mouse} = this.state
return (
<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>
)
}
}
(5)Footer组件
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.length
return (
<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>
)
}
}
四、React AJAX
1 理解
(1)前置说明
1. React本身只关注页面,并不包含发送ajax请求的代码
2. 前端应用需要通过ajax请求与后台进行交互
3. react应用中需要集成第三方ajax库(或自己封装)
(2)常用的ajax请求库
1. Jqyery:比较重,如果需要另外引入,不建议使用
2. axios:轻量级,建议使用
a. 封装XmlHttpRequest对象的ajax
b. promise风格
c. 可以在浏览器端和node服务器端
2 react配置proxy代理
(1)方法一:在package.json中追加配置:
"proxy":"http://localhost:5000"
说明:
1. 优点:配置简单,前端请求资源时可以不加任何前缀。
2. 缺点:不能配置多个代理。
3. 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)
(2)方法二
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:5000
changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
changeOrigin默认值为false,但我们一般将changeOrigin值设为true
*/
pathRewrite: {'^/api1': ''} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
}),
proxy('/api2', {
target: 'http://localhost:5001',
changeOrigin: true,
pathRewrite: {'^/api2': ''}
})
)
}
说明:
1. 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
2. 缺点:配置繁琐,前端请求资源时必须加前缀。
3 axios发送请求
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})
}
)
}
4 fetch发送请求
search = async ()=>{
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})
}
}
五 react路由
1 路由基本使用
注意:由于react-router-dom在2021年11月升级到6版本,而教程使用的是5版本
npm i react-router-dom@5
1.明确好界面中的导航区、展示区
2.导航区的a标签改为Link标签
<Link to="/xxxxx">Demo</Link>
3.展示区写Route标签进行路径的匹配
<Route path='/xxxx' component={Demo}/>
4.<App>的最外侧包裹了一个<BrowserRouter>或<HashRouter>
ReactDOM.render(
<BrowserRouter>
<App/>
</BrowserRouter>,
document.getElementById('root')
)
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>
)
}
}
2 路由组件与一般组件
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"
3 NavLink与封装NavLink
1. NavLink可以实现路由链接的高亮,通过activeClassName指定样式名
.atguigu{
color:red
}
<NavLink activeClassName="atguigu" className="list-group-item" to="/about">About</NavLink>
2. 封装NavLink
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}/>
)
}
}
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home">Home</MyNavLink>
3. 标签体内容是一个特殊的标签属性
4. 通过this.props.children可以获取标签体内容
4 Switch的使用 -- 改用Routers
1.通常情况下,path和component是一一对应的关系。
2.Switch可以提高路由匹配效率(单一匹配)。
3.两个以上的路由就使用Switch包裹路由
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Route path="/home" component={Test}/>
</Switch>
5 解决多级路径刷新页面样式丢失的问题
<MyNavLink to="/api/about">About</MyNavLink>
1.public/index.html 中 引入样式时不写 ./ 写 / (常用)
2.public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用)-- 只适用脚手架
3.使用HashRouter
6 路由的严格匹配与模糊匹配
1.默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
2.开启严格匹配:
<Route exact={true} path="/about" component={About}/>
3.严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
7 Redirect的使用
1.一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
2.具体编码:
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Redirect to="/about"/>
</Switch>
8 嵌套路由
1.注册子路由时要写上父路由的path值
2.路由的匹配是按照注册路由的顺序进行的
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>
)
}
}
9 向路由组件传递参数
(1)params参数
a. 路由链接(携带参数):
<Link to='/demo/test/tom/18'}>详情</Link>
b. 注册路由(声明接收):
<Route path="/demo/test/:name/:age" component={Test}/>
c. 接收参数:
this.props.match.params
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>
</li>
)
})
}
</ul>
<hr/>
{/* 声明接收params参数 */}
<Route path="/home/message/detail/:id/:title" component={Detail}/>
</div>
)
}
}
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参数
路由链接(携带参数):
<Link to='/demo/test?name=tom&age=18'}>详情</Link>
注册路由(无需声明,正常注册即可):
<Route path="/demo/test" component={Test}/>
接收参数:
this.props.location.search
备注:获取到的search是urlencoded编码字符串,需要借助querystring解析
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}>
{/* 向路由组件传递search参数 */}
<Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link>
</li>
)
})
}
</ul>
<hr/>
{/* search参数无需声明接收,正常注册路由即可 */}
<Route path="/home/message/detail" component={Detail}/>
</div>
)
}
}
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);
// 接收search参数
const {search} = this.props.location
const {id,title} = qs.parse(search.slice(1))
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参数
路由链接(携带参数):
<Link to={{pathname:'/demo/test',state:{name:'tom',age:18}}}>详情</Link>
注册路由(无需声明,正常注册即可):
<Route path="/demo/test" component={Test}/>
接收参数:
this.props.location.state
备注:刷新也可以保留住参数
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}>
{/* 向路由组件传递state参数 */}
<Link to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link>
</li>
)
})
}
</ul>
<hr/>
{/* state参数无需声明接收,正常注册路由即可 */}
<Route path="/home/message/detail" component={Detail}/>
</div>
)
}
}
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>
)
}
}
10 编程式路由导航
借助this.props.history对象上的API对操作路由跳转、前进、后退
-this.props.history.push()
-this.props.history.replace()
-this.props.history.goBack()
-this.props.history.goForward()
-this.props.history.go()
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参数
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.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>
)
}
}
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>
)
}
}
11 withRouter的使用
withRouter可以加工一般组件,让一般组件具备路由组件所特有的API
withRouter返回的是一个新组件
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)
12 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可以用于解决一些路径错误相关的问题。
六 redux
1 redux简介
(1)redux是什么
1. redux是一个专门用做状态管理的js库
2. 它可以用在react,angular,vue等项目中,但基本和react配合使用
3. 作用:集中式管理react应用中多个组件共享的状态
(2)什么情况下用redux
1. 某个状态的状态,需要让其他组件可以随时拿到(共享)
2. 一个组件需要改变另一个组件的状态(通信)
3. 总体原则:能不用就不用,如果不用比较吃力才使用
2 redux原理
3 redux的使用
3.1 同步action
1. store.js
1).引入redux中的createStore函数,创建一个store
2).createStore调用时要传入一个为其服务的reducer
3).记得暴露store对象
新版本使用这种方式引入store
import { legacy_createStore as createStore} from 'redux'
/*
该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/
//引入createStore,专门用于创建redux中最为核心的store对象
import {createStore} from 'redux'
//引入为Count组件服务的reducer
import countReducer from './count_reducer'
//暴露store
export default createStore(countReducer)
2.count_reducer.js
1).reducer的本质是一个函数,接收:preState,action,返回加工后的状态
2).reducer有两个作用:初始化状态,加工状态
3).reducer被第一次调用时,是store自动触发的,
传递的preState是undefined,
传递的action是:{type:'@@REDUX/INIT_a.2.b.4}
/*
1.该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数
2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
*/
import {INCREMENT,DECREMENT} from './constant'
const initState = 0 //初始化状态
export default function countReducer(preState=initState,action){
// console.log(preState);
//从action对象中获取:type、data
const {type,data} = action
//根据type决定如何加工数据
switch (type) {
case INCREMENT: //如果是加
return preState + data
case DECREMENT: //若果是减
return preState - data
default:
return preState
}
}
3.count_action.js
专门用于创建action对象
/*
该文件专门为Count组件生成action对象
*/
import {INCREMENT,DECREMENT} from './constant'
export const createIncrementAction = data => ({type:INCREMENT,data})
export const createDecrementAction = data => ({type:DECREMENT,data})
4.constant.js
放置容易写错的type值
/*
该模块是用于定义action对象中type类型的常量值,目的只有一个:便于管理的同时防止程序员单词写错
*/
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
5.index.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import store from './redux/store'
ReactDOM.render(<App/>,document.getElementById('root'))
store.subscribe(()=>{
ReactDOM.render(<App/>,document.getElementById('root'))
})
3.2 异步action
1. store.js
/*
该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/
//引入createStore,专门用于创建redux中最为核心的store对象
import {createStore,applyMiddleware} from 'redux'
//引入为Count组件服务的reducer
import countReducer from './count_reducer'
//引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'
//暴露store
export default createStore(countReducer,applyMiddleware(thunk))
2.count_reducer.js
/*
1.该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数
2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
*/
import {INCREMENT,DECREMENT} from './constant'
const initState = 0 //初始化状态
export default function countReducer(preState=initState,action){
// console.log(preState);
//从action对象中获取:type、data
const {type,data} = action
//根据type决定如何加工数据
switch (type) {
case INCREMENT: //如果是加
return preState + data
case DECREMENT: //若果是减
return preState - data
default:
return preState
}
}
3.count_action.js
/*
该文件专门为Count组件生成action对象
*/
import {INCREMENT,DECREMENT} from './constant'
//同步action,就是指action的值为Object类型的一般对象
export const createIncrementAction = data => ({type:INCREMENT,data})
export const createDecrementAction = data => ({type:DECREMENT,data})
//异步action,就是指action的值为函数,异步action中一般都会调用同步action,异步action不是必须要用的。
export const createIncrementAsyncAction = (data,time) => {
return (dispatch)=>{
setTimeout(()=>{
dispatch(createIncrementAction(data))
},time)
}
}
4.constant.js
/*
该模块是用于定义action对象中type类型的常量值,目的只有一个:便于管理的同时防止程序员单词写错
*/
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
5.count组件
import React, { Component } from 'react'
//引入store,用于获取redux中保存状态
import store from '../../redux/store'
//引入actionCreator,专门用于创建action对象
import {
createIncrementAction,
createDecrementAction,
createIncrementAsyncAction
} from '../../redux/count_action'
export default class Count extends Component {
state = {carName:'奔驰c63'}
/* componentDidMount(){
//检测redux中状态的变化,只要变化,就调用render
store.subscribe(()=>{
this.setState({})
})
} */
//加法
increment = ()=>{
const {value} = this.selectNumber
store.dispatch(createIncrementAction(value*1))
}
//减法
decrement = ()=>{
const {value} = this.selectNumber
store.dispatch(createDecrementAction(value*1))
}
//奇数再加
incrementIfOdd = ()=>{
const {value} = this.selectNumber
const count = store.getState()
if(count % 2 !== 0){
store.dispatch(createIncrementAction(value*1))
}
}
//异步加
incrementAsync = ()=>{
const {value} = this.selectNumber
// setTimeout(()=>{
store.dispatch(createIncrementAsyncAction(value*1,500))
// },500)
}
render() {
return (
<div>
<h1>当前求和为:{store.getState()}</h1>
<select ref={c => this.selectNumber = c}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
<button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
<button onClick={this.incrementAsync}>异步加</button>
</div>
)
七 react-redux
7.1 完整写法
1. index.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import store from './redux/store'
ReactDOM.render(<App/>,document.getElementById('root'))
//监测redux中状态的改变,如redux的状态发生了改变,那么重新渲染App组件
store.subscribe(()=>{
ReactDOM.render(<App/>,document.getElementById('root'))
})
2.app.jsx
import React, { Component } from 'react'
import Count from './containers/Count'
import store from './redux/store'
export default class App extends Component {
render() {
return (
<div>
{/* 给容器组件传递store */}
<Count store={store} />
</div>
)
}
3.store.js
/*
该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/
//引入createStore,专门用于创建redux中最为核心的store对象
import {createStore,applyMiddleware} from 'redux'
//引入为Count组件服务的reducer
import countReducer from './count_reducer'
//引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'
//暴露store
export default createStore(countReducer,applyMiddleware(thunk))
4. count_reducer.js
/*
1.该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数
2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
*/
import {INCREMENT,DECREMENT} from './constant'
const initState = 0 //初始化状态
export default function countReducer(preState=initState,action){
// console.log(preState);
//从action对象中获取:type、data
const {type,data} = action
//根据type决定如何加工数据
switch (type) {
case INCREMENT: //如果是加
return preState + data
case DECREMENT: //若果是减
return preState - data
default:
return preState
}
}
5. constant.js
/*
该模块是用于定义action对象中type类型的常量值,目的只有一个:便于管理的同时防止程序员单词写错
*/
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
6. count/-action.js
/*
该文件专门为Count组件生成action对象
*/
import {INCREMENT,DECREMENT} from './constant'
//同步action,就是指action的值为Object类型的一般对象
export const createIncrementAction = data => ({type:INCREMENT,data})
export const createDecrementAction = data => ({type:DECREMENT,data})
//异步action,就是指action的值为函数,异步action中一般都会调用同步action,异步action不是必须要用的。
export const createIncrementAsyncAction = (data,time) => {
return (dispatch)=>{
setTimeout(()=>{
dispatch(createIncrementAction(data))
},time)
}
}
7. containers\Count
//引入Count的UI组件
import CountUI from '../../components/Count'
//引入action
import {
createIncrementAction,
createDecrementAction,
createIncrementAsyncAction
} from '../../redux/count_action'
//引入connect用于连接UI组件与redux
import {connect} from 'react-redux'
/*
1.mapStateToProps函数返回的是一个对象;
2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
3.mapStateToProps用于传递状态
*/
function mapStateToProps(state){
return {count:state}
}
/*
1.mapDispatchToProps函数返回的是一个对象;
2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
3.mapDispatchToProps用于传递操作状态的方法
*/
function mapDispatchToProps(dispatch){
return {
jia:number => dispatch(createIncrementAction(number)),
jian:number => dispatch(createDecrementAction(number)),
jiaAsync:(number,time) => dispatch(createIncrementAsyncAction(number,time)),
}
}
//使用connect()()创建并暴露一个Count的容器组件
export default connect(mapStateToProps,mapDispatchToProps)(CountUI)
8. components\Count
import React, { Component } from 'react'
export default class Count extends Component {
state = {carName:'奔驰c63'}
//加法
increment = ()=>{
const {value} = this.selectNumber
this.props.jia(value*1)
}
//减法
decrement = ()=>{
const {value} = this.selectNumber
this.props.jian(value*1)
}
//奇数再加
incrementIfOdd = ()=>{
const {value} = this.selectNumber
if(this.props.count % 2 !== 0){
this.props.jia(value*1)
}
}
//异步加
incrementAsync = ()=>{
const {value} = this.selectNumber
this.props.jiaAsync(value*1,500)
}
render() {
//console.log('UI组件接收到的props是',this.props);
return (
<div>
<h1>当前求和为:{this.props.count}</h1>
<select ref={c => this.selectNumber = c}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
<button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
<button onClick={this.incrementAsync}>异步加</button>
</div>
)
}
}
7,2 containers\Count的简化
import React, { Component } from 'react'
//引入action
import {
createIncrementAction,
createDecrementAction,
createIncrementAsyncAction
} from '../../redux/count_action'
//引入connect用于连接UI组件与redux
import {connect} from 'react-redux'
//定义UI组件
class Count extends Component {
state = {carName:'奔驰c63'}
//加法
increment = ()=>{
const {value} = this.selectNumber
this.props.jia(value*1)
}
//减法
decrement = ()=>{
const {value} = this.selectNumber
this.props.jian(value*1)
}
//奇数再加
incrementIfOdd = ()=>{
const {value} = this.selectNumber
if(this.props.count % 2 !== 0){
this.props.jia(value*1)
}
}
//异步加
incrementAsync = ()=>{
const {value} = this.selectNumber
this.props.jiaAsync(value*1,500)
}
render() {
//console.log('UI组件接收到的props是',this.props);
return (
<div>
<h1>当前求和为:{this.props.count}</h1>
<select ref={c => this.selectNumber = c}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
<button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
<button onClick={this.incrementAsync}>异步加</button>
</div>
)
}
}
//使用connect()()创建并暴露一个Count的容器组件
export default connect(
state => ({count:state}),
//mapDispatchToProps的一般写法
/* dispatch => ({
jia:number => dispatch(createIncrementAction(number)),
jian:number => dispatch(createDecrementAction(number)),
jiaAsync:(number,time) => dispatch(createIncrementAsyncAction(number,time)),
}) */
//mapDispatchToProps的简写
{
jia:createIncrementAction,
jian:createDecrementAction,
jiaAsync:createIncrementAsyncAction,
}
)(Count)
7.3 react-redux优化
(1).容器组件和UI组件整合一个文件
(2).无需自己给容器组件传递store,给<App/>包裹一个<Provider store={store}>即可。
(3).使用了react-redux后也不用再自己检测redux中状态的改变了,容器组件可以自动完成这个工作。
(4).mapDispatchToProps也可以简单的写成一个对象
(5).一个组件要和redux“打交道”要经过哪几步?
(1).定义好UI组件---不暴露
(2).引入connect生成一个容器组件,并暴露,写法如下:
connect(
state => ({key:value}), //映射状态
{key:xxxxxAction} //映射操作状态的方法
)(UI组件)
(4).在UI组件中通过this.props.xxxxxxx读取和操作状态
1. index.js -- 使用Provider传递 store、不用使用store.subscribe
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import store from './redux/store'
import {Provider} from 'react-redux'
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>,
document.getElementById('root')
)
2. app.js -- 不用传递store
import React, { Component } from 'react'
import Count from './containers/Count'
export default class App extends Component {
render() {
return (
<div>
<Count/>
</div>
)
}
}
3. containers\ Conut
import React, { Component } from 'react'
//引入action
import {
createIncrementAction,
createDecrementAction,
createIncrementAsyncAction
} from '../../redux/count_action'
//引入connect用于连接UI组件与redux
import {connect} from 'react-redux'
//定义UI组件
class Count extends Component {
state = {carName:'奔驰c63'}
//加法
increment = ()=>{
const {value} = this.selectNumber
this.props.jia(value*1)
}
//减法
decrement = ()=>{
const {value} = this.selectNumber
this.props.jian(value*1)
}
//奇数再加
incrementIfOdd = ()=>{
const {value} = this.selectNumber
if(this.props.count % 2 !== 0){
this.props.jia(value*1)
}
}
//异步加
incrementAsync = ()=>{
const {value} = this.selectNumber
this.props.jiaAsync(value*1,500)
}
render() {
//console.log('UI组件接收到的props是',this.props);
return (
<div>
<h1>当前求和为:{this.props.count}</h1>
<select ref={c => this.selectNumber = c}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
<button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
<button onClick={this.incrementAsync}>异步加</button>
</div>
)
}
}
//使用connect()()创建并暴露一个Count的容器组件
export default connect(
state => ({count:state}),
//mapDispatchToProps的一般写法
/* dispatch => ({
jia:number => dispatch(createIncrementAction(number)),
jian:number => dispatch(createDecrementAction(number)),
jiaAsync:(number,time) => dispatch(createIncrementAsyncAction(number,time)),
}) */
//mapDispatchToProps的简写
{
jia:createIncrementAction,
jian:createDecrementAction,
jiaAsync:createIncrementAsyncAction,
}
)(Count)
7.4 数据共享版
1.index.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import store from './redux/store'
import {Provider} from 'react-redux'
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>,
document.getElementById('root')
)
2. app.js
import React, { Component } from 'react'
import Count from './containers/Count'
import Person from './containers/Person'
export default class App extends Component {
render() {
return (
<div>
<Count/>
<hr/>
<Person/>
</div>
)
}
}
2.redux/actions
(1)count.js
/*
该文件专门为Count组件生成action对象
*/
import {INCREMENT,DECREMENT} from '../constant'
//同步action,就是指action的值为Object类型的一般对象
export const createIncrementAction = data => ({type:INCREMENT,data})
export const createDecrementAction = data => ({type:DECREMENT,data})
//异步action,就是指action的值为函数,异步action中一般都会调用同步action,异步action不是必须要用的。
export const createIncrementAsyncAction = (data,time) => {
return (dispatch)=>{
setTimeout(()=>{
dispatch(createIncrementAction(data))
},time)
}
}
(2)person.js
import {ADD_PERSON} from '../constant'
//创建增加一个人的action动作对象
export const createAddPersonAction = personObj => ({type:ADD_PERSON,data:personObj})
3. redux/reducers
(1)count.js
/*
1.该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数
2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
*/
import {INCREMENT,DECREMENT} from '../constant'
const initState = 0 //初始化状态
export default function countReducer(preState=initState,action){
// console.log('countReducer@#@#@#');
//从action对象中获取:type、data
const {type,data} = action
//根据type决定如何加工数据
switch (type) {
case INCREMENT: //如果是加
return preState + data
case DECREMENT: //若果是减
return preState - data
default:
return preState
}
}
(2)person.js
import {ADD_PERSON} from '../constant'
//初始化人的列表
const initState = [{id:'001',name:'tom',age:18}]
export default function personReducer(preState=initState,action){
// console.log('personReducer@#@#@#');
const {type,data} = action
switch (type) {
case ADD_PERSON: //若是添加一个人
return [data,...preState]
default:
return preState
}
}
4. redux/constant.js
/*
该模块是用于定义action对象中type类型的常量值,目的只有一个:便于管理的同时防止程序员单词写错
*/
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
export const ADD_PERSON = 'add_person'
5. redux/store.js
/*
该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/
//引入createStore,专门用于创建redux中最为核心的store对象
import {createStore,applyMiddleware,combineReducers} from 'redux'
//引入为Count组件服务的reducer
import countReducer from './reducers/count'
//引入为Count组件服务的reducer
import personReducer from './reducers/person'
//引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'
//汇总所有的reducer变为一个总的reducer
const allReducer = combineReducers({
he:countReducer,
rens:personReducer
})
//暴露store
export default createStore(allReducer,applyMiddleware(thunk))
6.containers/Count/index.jsx
import React, { Component } from 'react'
//引入action
import {
createIncrementAction,
createDecrementAction,
createIncrementAsyncAction
} from '../../redux/actions/count'
//引入connect用于连接UI组件与redux
import {connect} from 'react-redux'
//定义UI组件
class Count extends Component {
state = {carName:'奔驰c63'}
//加法
increment = ()=>{
const {value} = this.selectNumber
this.props.jia(value*1)
}
//减法
decrement = ()=>{
const {value} = this.selectNumber
this.props.jian(value*1)
}
//奇数再加
incrementIfOdd = ()=>{
const {value} = this.selectNumber
if(this.props.count % 2 !== 0){
this.props.jia(value*1)
}
}
//异步加
incrementAsync = ()=>{
const {value} = this.selectNumber
this.props.jiaAsync(value*1,500)
}
render() {
//console.log('UI组件接收到的props是',this.props);
return (
<div>
<h2>我是Count组件,下方组件总人数为:{this.props.renshu}</h2>
<h4>当前求和为:{this.props.count}</h4>
<select ref={c => this.selectNumber = c}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
<button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
<button onClick={this.incrementAsync}>异步加</button>
</div>
)
}
}
//使用connect()()创建并暴露一个Count的容器组件
export default connect(
state => ({
count:state.he,
renshu:state.rens.length
}),
{
jia:createIncrementAction,
jian:createDecrementAction,
jiaAsync:createIncrementAsyncAction,
}
)(Count)
7. containers/Person/index.jsx
import React, { Component } from 'react'
import {nanoid} from 'nanoid'
import {connect} from 'react-redux'
import {createAddPersonAction} from '../../redux/actions/person'
class Person extends Component {
addPerson = ()=>{
const name = this.nameNode.value
const age = this.ageNode.value
const personObj = {id:nanoid(),name,age}
this.props.jiaYiRen(personObj)
this.nameNode.value = ''
this.ageNode.value = ''
}
render() {
return (
<div>
<h2>我是Person组件,上方组件求和为{this.props.he}</h2>
<input ref={c=>this.nameNode = c} type="text" placeholder="输入名字"/>
<input ref={c=>this.ageNode = c} type="text" placeholder="输入年龄"/>
<button onClick={this.addPerson}>添加</button>
<ul>
{
this.props.yiduiren.map((p)=>{
return <li key={p.id}>{p.name}--{p.age}</li>
})
}
</ul>
</div>
)
}
}
export default connect(
state => ({yiduiren:state.rens,he:state.he}),//映射状态
{jiaYiRen:createAddPersonAction}//映射操作状态的方法
)(Person)
7.5 纯函数 -- redux的reducer函数必须是一个纯函数
1.一类特别的函数:只要是同样的输入(实参),必定得到同样的输出(返回)
2. 必须遵守以下一些束缚:
(1)不得改写参数数据
(2)不会产生任何副作用,例如网络请求,输入和输出设备
(3)不能调用Date.now()或者Math.random()等不纯的函数
所以使用return [data,...preState]
而不是perState.unshift(data)等修改原数组的方法
import {ADD_PERSON} from '../constant'
//初始化人的列表
const initState = [{id:'001',name:'tom',age:18}]
export default function personReducer(preState=initState,action){
// console.log('personReducer@#@#@#');
const {type,data} = action
switch (type) {
case ADD_PERSON: //若是添加一个人
return [data,...preState]
default:
return preState
}
}
7.6 redux开发者工具
(1).安装插件
yarn add redux-devtools-extension
(2).store中进行配置
import {composeWithDevTools} from 'redux-devtools-extension'
const store = createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))
(3)谷歌浏览器安装插件 -- Redux DevTools
八 项目打包运行
1. 打包
npm run build
2. 准备服务器并测试运行
npm i serve -g
serve build(文件夹根目录)
九 react扩展
1 setState
(1). setState(stateChange, [callback])------对象式的setState
1.stateChange为状态改变对象(该对象可以体现出状态的更改)
2.callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
this.setState({count:count+1},()=>{
console.log(this.state.count);
})
(2). setState(updater, [callback])------函数式的setState
1.updater为返回stateChange对象的函数。
2.updater可以接收到state和props。
4.callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
this.setState((state,props) => {
return {count:state.count+1}
})
总结:
1.对象式的setState是函数式的setState的简写方式(语法糖)
2.使用原则:
(1).如果新状态不依赖于原状态 ===> 使用对象方式
(2).如果新状态依赖于原状态 ===> 使用函数方式
(3).如果需要在setState()执行后获取最新的状态数据,
要在第二个callback函数中读取
2 lazyLoad
(1)通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
const Login = lazy(()=>import('@/pages/Login'))
(2)通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面
<Suspense fallback={<h1>loading.....</h1>}>
<Switch>
<Route path="/xxx" component={Xxxx}/>
<Redirect to="/login"/>
</Switch>
</Suspense>
3 Hooks -- 三个常用的Hook
3.1 State Hook
(1). State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
(2). 语法: const [xxx, setXxx] = React.useState(initValue)
(3). useState()说明:
参数: 第一次初始化指定的值在内部作缓存
返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
(4). setXxx()2种写法:
setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
function Demo(){
const [count,setCount] = React.useState(0)
const [name,setName] = React.useState('tom')
function add(){
// setCount(count+1) 第一种写法
setCount(count => count+1)
}
function changeName(){
setName('zhangsan')
}
return(
<div>
<h2>当前求和为:{count}</h2>
<h2>我的名字是:{name}</h2>
<button onClick={add}>点我+1</button>
<button onClick={changeName}>点我改名</button>
</div>
)
}
3.2 Effect Hook
(1). Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
(2). React中的副作用操作:
发ajax请求数据获取
设置订阅 / 启动定时器
手动更改真实DOM
(3). 语法和说明:
useEffect(() => {
// 在此可以执行任何带副作用操作
return () => { // 在组件卸载前执行
// 在此做一些收尾工作, 比如清除定时器/取消订阅等
}
}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
(4). 可以把 useEffect Hook 看做如下三个函数的组合
componentDidMount()
componentDidUpdate()
componentWillUnmount()
function Demo(){
const [count,setCount] = React.useState(0)
React.useEffect(()=>{
let timer = setInterval(()=>{
setCount(count => count+1 )
},1000)
return ()=>{
clearInterval(timer)
}
},[])
function add(){
setCount(count => count+1)
}
//卸载组件的回调
function unmount(){
ReactDOM.unmountComponentAtNode(document.getElementById('root'))
}
return(
<div>
<h2>当前求和为:{count}</h2>
<button onClick={add}>点我+1</button>
<button onClick={unmount}>卸载组件</button>
</div>
)
}
3.3 Ref Hook
(1). Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
(2). 语法: const refContainer = useRef()
(3). 作用:保存标签对象,功能与React.createRef()一样
import React from 'react'
import ReactDOM from 'react-dom'
//类式组件
/* class Demo extends React.Component {
state = {count:0}
myRef = React.createRef()
add = ()=>{
this.setState(state => ({count:state.count+1}))
}
unmount = ()=>{
ReactDOM.unmountComponentAtNode(document.getElementById('root'))
}
show = ()=>{
alert(this.myRef.current.value)
}
componentDidMount(){
this.timer = setInterval(()=>{
this.setState( state => ({count:state.count+1}))
},1000)
}
componentWillUnmount(){
clearInterval(this.timer)
}
render() {
return (
<div>
<input type="text" ref={this.myRef}/>
<h2>当前求和为{this.state.count}</h2>
<button onClick={this.add}>点我+1</button>
<button onClick={this.unmount}>卸载组件</button>
<button onClick={this.show}>点击提示数据</button>
</div>
)
}
} */
function Demo(){
//console.log('Demo');
const [count,setCount] = React.useState(0)
const myRef = React.useRef()
React.useEffect(()=>{
let timer = setInterval(()=>{
setCount(count => count+1 )
},1000)
return ()=>{
clearInterval(timer)
}
},[])
//加的回调
function add(){
//setCount(count+1) //第一种写法
setCount(count => count+1 )
}
//提示输入的回调
function show(){
alert(myRef.current.value)
}
//卸载组件的回调
function unmount(){
ReactDOM.unmountComponentAtNode(document.getElementById('root'))
}
return (
<div>
<input type="text" ref={myRef}/>
<h2>当前求和为:{count}</h2>
<button onClick={add}>点我+1</button>
<button onClick={unmount}>卸载组件</button>
<button onClick={show}>点我提示数据</button>
</div>
)
}
export default Demo
4 Fragment
<Fragment><Fragment>
<></>
import React, { Component,Fragment } from 'react'
export default class Demo extends Component {
render() {
return (
<Fragment key={1}>
<input type="text"/>
<input type="text"/>
</Fragment>
)
}
}
import React, { Component,Fragment } from 'react'
export default class Demo extends Component {
render() {
return (
<>
<input type="text"/>
<input type="text"/>
</>
)
}
}
5 Context
一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信
使用:
1. 创建Context容器对象:
const XxxContext = React.createContext()
2. 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:
<xxxContext.Provider value={数据}>
子组件
</xxxContext.Provider>
3. 后代组件读取数据:
(1)第一种方式:仅适用于类组件
static contextType = xxxContext // 声明接收context
this.context // 读取context中的value数据
(2)第二种方式: 函数组件与类组件都可以 -- 官网使用useContext,以官网为主
<xxxContext.Consumer>
{
value => ( // value就是context中的value数据
要显示的内容
)
}
</xxxContext.Consumer>
注意:
在应用开发中一般不用context, 一般都它的封装react插件
import React, { Component } from 'react'
import './index.css'
//创建Context对象
const MyContext = React.createContext()
const {Provider,Consumer} = MyContext
export default class A extends Component {
state = {username:'tom',age:18}
render() {
const {username,age} = this.state
return (
<div className="parent">
<h3>我是A组件</h3>
<h4>我的用户名是:{username}</h4>
<Provider value={{username,age}}>
<B/>
</Provider>
</div>
)
}
}
class B extends Component {
render() {
return (
<div className="child">
<h3>我是B组件</h3>
<C/>
</div>
)
}
}
/* class C extends Component {
//声明接收context
static contextType = MyContext
render() {
const {username,age} = this.context
return (
<div className="grand">
<h3>我是C组件</h3>
<h4>我从A组件接收到的用户名:{username},年龄是{age}</h4>
</div>
)
}
} */
function C(){
return (
<div className="grand">
<h3>我是C组件</h3>
<h4>我从A组件接收到的用户名:
<Consumer>
{value => `${value.username},年龄是${value.age}`}
</Consumer>
</h4>
</div>
)
}
6 Component的2个问题
1. 只要执行setState(),即使不改变状态数据, 组件也会重新render()
2. 只当前组件重新render(), 就会自动重新render子组件 ==> 效率低
效率高的做法:只有当组件的state或props数据发生改变时才重新render()
原因:Component中的shouldComponentUpdate()总是返回true
解决:
(1)办法1:
重写shouldComponentUpdate()方法
比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false
(2)办法2:
使用PureComponent
PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true
注意:
A. 只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false
B. 不要直接修改state数据, 而是要产生新数据
C. 项目中一般使用PureComponent来优化
import React, { PureComponent } from 'react'
import './index.css'
export default class Parent extends PureComponent {
state = {carName:"奔驰c36",stus:['小张','小李','小王']}
addStu = ()=>{
/* const {stus} = this.state
stus.unshift('小刘')
this.setState({stus}) */
const {stus} = this.state
this.setState({stus:['小刘',...stus]})
}
changeCar = ()=>{
//this.setState({carName:'迈巴赫'})
const obj = this.state
obj.carName = '迈巴赫'
console.log(obj === this.state);
this.setState(obj)
}
/* shouldComponentUpdate(nextProps,nextState){
// console.log(this.props,this.state); //目前的props和state
// console.log(nextProps,nextState); //接下要变化的目标props,目标state
return !this.state.carName === nextState.carName
} */
render() {
console.log('Parent---render');
const {carName} = this.state
return (
<div className="parent">
<h3>我是Parent组件</h3>
{this.state.stus}
<span>我的车名字是:{carName}</span><br/>
<button onClick={this.changeCar}>点我换车</button>
<button onClick={this.addStu}>添加一个小刘</button>
<Child carName="奥拓"/>
</div>
)
}
}
class Child extends PureComponent {
/* shouldComponentUpdate(nextProps,nextState){
console.log(this.props,this.state); //目前的props和state
console.log(nextProps,nextState); //接下要变化的目标props,目标state
return !this.props.carName === nextProps.carName
} */
render() {
console.log('Child---render');
return (
<div className="child">
<h3>我是Child组件</h3>
<span>我接到的车是:{this.props.carName}</span>
</div>
)
}
}
7 render props
1. 如何向组件内部动态传入带内容的结构(标签)?
Vue中:
使用slot技术, 也就是通过组件标签体传入结构 <AA><BB/></AA>
React中:
使用children props: 通过组件标签体传入结构
使用render props: 通过组件标签属性传入结构, 一般用render函数属性
2. children props
<A>
<B>xxxx</B>
</A>
{this.props.children}
问题: 如果B组件需要A组件内的数据, ==> 做不到
3. render props
<A render={(data) => <C data={data}></C>}></A>
A组件: {this.props.render(内部state数据)}
C组件: 读取A组件传入的数据显示 {this.props.data}
import React, { Component } from 'react'
import './index.css'
import C from '../1_setState'
export default class Parent extends Component {
render() {
return (
<div className="parent">
<h3>我是Parent组件</h3>
<A render={(name)=><C name={name}/>}/>
</div>
)
}
}
class A extends Component {
state = {name:'tom'}
render() {
console.log(this.props);
const {name} = this.state
return (
<div className="a">
<h3>我是A组件</h3>
{this.props.render(name)}
</div>
)
}
}
class B extends Component {
render() {
console.log('B--render');
return (
<div className="b">
<h3>我是B组件,{this.props.name}</h3>
</div>
)
}
}
8 错误边界
错误边界:用来捕获后代组件错误,渲染出备用页面
特点:只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件
在合成事件、定时器中产生的错误。
使用方式:getDerivedStateFromError配合componentDidCatch
// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
console.log(error);
// 在render之前触发
// 返回新的state
return {
hasError: true,
};
}
componentDidCatch(error, info) {
// 统计页面的错误。发送请求发送到后台去
console.log(error, info);
}
9 组件通信方式总结
9.1 方式
props:
(1).children props
(2).render props
消息订阅-发布:pubs-sub、event等等
集中式管理:redux、dva等等
conText:生产者-消费者模式
9.2 组件间的关系
父子组件:props
兄弟组件(非嵌套组件):消息订阅-发布、集中式管理
祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(用的少)
十 React Router6
1 概述
1. React Router以三个不同的包发布到npm上,它们分别是:
1. react-router:路由的核心库,提供了很多的:组件、钩子。
2. react-router-dom:包含react-router所有内容,并添加一些专门用于DOM的组件,例如:
<BrowserRouter>等
3. react-router-native:包括react-router所有内容,并添加一些专门用于ReactNative的Api, 例如:<NativeRouter>等
2. 与React Router5版本相比,改变了什么?
1. 内置组件的变化:移除<Switch>,新增<Routers>等
2. 语法的变化:component={About /}变为element={<About/>}等
3. 新增多个hook:useParams、useNavigate、useMatch等
4. 官方明确推荐函数式组件
2 Component
1. <BrowerRouter>
2. <HashRouter>
3. <Routes /> 与<Route/>
1. v6版本中移除了先前的<Switch>,引入了新的替代者<Routes>
2. <Routes>和<Route>要配合使用,且必须要用<Routes>包裹<Route>
3. <Route>相当于一个if语句,如果其路径与当前URL匹配,则呈现其对应的组件
4. <Routes caseSensitive>属性用于指定:匹配时是否区分大小写(默认为false)
5. 当URL发生变化时,<Routes>都会查看其所有子<Routes>元素
以找到最佳匹配并呈现组件
6. <Route>也可以嵌套使用,且可配合useRoutes()配置路由表,
但需要通过<Outlet>组件来渲染子路由
import React from 'react'
import { NavLink,Routes,Route,Navigate } from 'react-router-dom'
import About from './pages/About'
import Home from './pages/Home'
export default function App() {
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">
{/* 路由导航 */}
<NavLink className="list-group-item" to="/about">About</NavLink>
<NavLink className="list-group-item" to="/home">Home</NavLink>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{/* 注册路由 */}
<Routes>
<Route path="/about" element={<About/>} />
<Route path="/home" element={<Home/>} />
<Route path="/" element={<Navigate to="/about" />} />
</Routes>
</div>
</div>
</div>
</div>
</div>
)
}
4. NavLink高亮效果
1. App.css
.activeClass{
background: orange;
color: aliceblue;
}
2. app.jsx
function computedClassName({isActive}){
return isActive ? 'list-group-item activeClass' : 'list-group-item'
}
<NavLink className={computedClassName} to="/about">About</NavLink>
<NavLink className={computedClassName} to="/home">Home</NavLink>
5 useRoutes路由表使用
1. 新建routes文件夹,新建index.js
import About from '../pages/About'
import Home from '../pages/Home'
import {Navigate} from 'react-router-dom'
const routes = [
{
path:'/about',
element:<About/>
},
{
path:'/home',
element:<Home/>
},
{
path:'/',
element:<Navigate to="/about"/>
}
]
export default routes
2. app.jsx使用
import React from 'react'
import { NavLink,useRoutes } from 'react-router-dom'
import routes from './routes'
export default function App() {
// 根据路由表生成对应的路由规则
const element = useRoutes(routes)
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">
{/* 路由导航 */}
<NavLink className="list-group-item" to="/about">About</NavLink>
<NavLink className="list-group-item" to="/home">Home</NavLink>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{element}
</div>
</div>
</div>
</div>
</div>
)
}
6 嵌套路由
1. routes/index.js
import About from '../pages/About'
import Home from '../pages/Home'
import Message from '../pages/Message'
import News from '../pages/News'
import { Navigate } from 'react-router-dom'
const routes = [
{
path: '/about',
element: <About />
},
{
path: '/home',
element: <Home />,
children: [{
path: 'news',
element: <News />,
},{
path: 'message',
element: <Message />,
}]
},
{
path: '/',
element: <Navigate to="/about" />
}
]
export default routes
2.Home.jsx
import React from 'react'
import { NavLink,Outlet } from 'react-router-dom'
export default function Home() {
return (
<div>
<h2>Home组件内容</h2>
<div>
<ul className="nav nav-tabs">
<li>
<NavLink className="list-group-item" to="/home/news">News</NavLink>
</li>
<li>
<NavLink className="list-group-item" to="/home/message">Message</NavLink>
</li>
</ul>
{/* 指定路由组件呈现的位置 */}
<Outlet/>
</div>
</div>
)
}
7 路由Params传参
1. routes/index.js占位
{
path: 'message',
element: <Message />,
children: [
{
path: 'detail/:id/:title/:content',
element: <Detail />,
}
]
}
2. Message.jsx传值
<Link to={`detail/${m.id}/${m.title}/${m.content}`}>{m.title}</Link>
3. Detail.jsx接收参数 -- useParams
import React from 'react'
import { useParams, useMatch } from 'react-router-dom'
export default function Detail() {
const { id, title, content } = useParams()
// const x = useMatch('/home/message/detail/:id/:title/:content')
return (
<ul>
<li>消息编号:{id}</li>
<li>消息标题:{title}</li>
<li>消息内容:{content}</li>
</ul>
)
}
8 路由search传参
1. message.jsx传递参数
<Link to={`detail?id=${m.id}&title=${m.title}&content=${m.content}`}>{m.title}</Link>
2. Detail.jsx接收参数
import React from 'react'
import { useSearchParams,useLocation } from 'react-router-dom'
export default function Detail() {
const [search,setSearch] = useSearchParams()
const id = search.get('id')
const title = search.get('title')
const content = search.get('content')
const x = useLocation ()
console.log(x)
return (
<ul>
<li>
<button onClick={()=>{setSearch('id=005&title=哈哈&content=嘻嘻')}}>点我更新收到的search参数</button>
</li>
<li>消息编号:{id}</li>
<li>消息标题:{title}</li>
<li>消息内容:{content}</li>
</ul>
)
}
9 路由state传参
1. Message.jsx传参
<Link
to="detail"
state={{
id:m.id,
title:m.title,
content:m.content
}}
>{m.title}</Link>
2.Detail.jsx接收参数
import React from 'react'
import { useLocation } from 'react-router-dom'
export default function Detail() {
const {state} = useLocation()
return (
<ul>
<li>消息编号:{state.id}</li>
<li>消息标题:{state.title}</li>
<li>消息内容:{state.content}</li>
</ul>
)
}
10 编程式路由导航 -- useNavigate
1.使用
import React, { useState } from 'react'
import{Link,Outlet} from 'react-router-dom'
import { useNavigate } from 'react-router-dom'
export default function Message() {
const [messages] = useState([
{ id: '001', title: '消息1', content: '消息1的内容' },
{ id: '002', title: '消息2', content: '消息2的内容' },
{ id: '003', title: '消息3', content: '消息3的内容' },
{ id: '004', title: '消息4', content: '消息4的内容' }
])
const navigate = useNavigate()
function showDetail(m){
navigate('detail',{
replace:false,
state:{
id:m.id,
title:m.title,
content:m.content
}
})
}
return (
<div>
<ul>
{
messages.map((m) => {
return (
<li key={m.id}>
<Link
to="detail"
state={{
id:m.id,
title:m.title,
content:m.content
}}
>{m.title}</Link>
<button onClick={()=>{showDetail(m)}}>查看详情</button>
</li>
)
})
}
</ul>
<hr/>
<Outlet />
</div>
)
}
2. 前进后退
import React from 'react'
import { useNavigate } from 'react-router-dom'
export default function Header() {
const navigate = useNavigate()
function back(){
navigate(-1)
}
function forward(){
navigate(1)
}
return (
<div className="col-xs-offset-2 col-xs-8">
<div className="page-header"><h2>React Router Demo</h2></div>
<button onClick={back}>后退</button>
<button onClick={forward}>前进</button>
</div>
)
}
11 useInRouterContext()
作用:如果组件在<Router>的上下文中呈现,则useInRouterContext钩子返回true,否则返回false
12 useNavigationType()
1. 作用:返回当前的导航类型(用户是如何来到当前页面的)
2. 返回值:POP、PUSH、REPLACE
3. 备注:POP是指在浏览器中直接打开了这个路由的文件(刷新页面)
13 useOutlet()
1. 作用:用来呈现当前组件中渲染的嵌套路由
const result = useOutlet()
console.log(result)
//如果嵌套的路由没有挂载,则result为null
//如果嵌套的路由已经挂载,则展示嵌套的路由对象
14 useResolvePath()
作用:给定一个URL值,解析其中的:path、search、hash值