—配合尚硅谷视频—
1.React初认识
(1)React是什么
react是用于构建用户 界面(视图) 的JavaScript库
也可以说react是一个将数据渲染为html视图开端的开源JS库
(2)React的功能是什么
操作Dom呈现页面
(3)为什么学React:原生JS的痛点
1 原生js操作dom效率低,繁琐
document.getElamentById('name')
2 使用js直接操作dom,浏览器会进行大量的重汇重排
3.原生js没有组件化的编码方案,代码复用率低
组件化:html、css、js都拆
(4)React的特点
1.采用组件化的模式,声明式编码,提高开发效率及组件的复用率
2.在React Native中可以使用React语法进行移动端开发
3.使用虚拟DOM+优秀的Diffing算法,尽量少的与真实的DOM的交互
2 React入门-HelloReact
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- 准备好容器 -->
<div id="test"></div>
<!-- 引入react 核心库-->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom 用于支持react操作dom -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入bable 用于将jsx转化为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<!-- 此处一定要写babel -->
<script type="text/babel">
//1.创建虚拟dom
const Vdom=<h1>hello-react</h1>
//2.渲染虚拟dom到页面 ReactDom.render(虚拟DOM,容器)
ReactDOM.render(Vdom,document.getElementById('test'))
</script>
</body>
</html>
3 为什么用jsx?对比虚拟dom的两种创建方式
//创建内容展示:
<h1 id="title">
<span id="thespan">hello-react</span>
</h1>
(1)使用jsx创建虚拟dom
<script type="text/babel">
//1.创建虚拟dom
const Vdom=<h1 id="title"><span id="thespan">hello-react</span></h1>
//2.渲染虚拟dom到页面 ReactDom.render(虚拟DOM,容器)
ReactDOM.render(Vdom,document.getElementById('test'))
</script>
(2)使用js创建虚拟dom
api:React.createElement(标签名,标签属性,标签内容):用于创建虚拟dom
<script type="text/javascript">
// 创建虚拟dom React.createElement(标签名,标签属性,标签内容)
const Vdom=React.createElement('h1',{id:'title'},React.createElement('span',{id:'thespan'},'hello-react'))
//2.渲染虚拟dom到页面 ReactDom.render(虚拟DOM,容器)
ReactDOM.render(Vdom,document.getElementById('test'))
</script>
(3)结论
jsx是js在react方面的语法糖
4.什么是虚拟dom
它其实是一个Object对象
关于虚拟dom
- 虚拟dom本质是object类型的对象
- 虚拟dom相比于真实dom“轻”(虚拟dom的属性较少),因为虚拟dom是react内部在用,所以无需真实dom那么多的属性
- 虚拟dom最终会被react转化为真实dom,呈现在页面中
<script type="text/babel">
//1.创建虚拟dom
const Vdom=<h1><span>hello-react</span></h1>
//2.渲染虚拟dom到页面 ReactDom.render(虚拟DOM,容器)
ReactDOM.render(Vdom,document.getElementById('test'))
//创建真实际dom
const Tdom=document.getElementById('test')
console.log('虚拟dom',Vdom)
console.log('真实dom',Tdom)
</script>
5.JSX的语法规则
5.1 关于JSX
- jsx全称JavaScript XML
- 是react定义的一种类似于XML的JS扩展语法JS+XML
- 作用是用来简化创建虚拟DOM
- 标签名任意:HTML标签或者其他标签
XML 早期用于存储和传输数据
< student >
< name> 18< /name >
</ student >
5.2 JSX语法规则
- 定义虚拟dom的时候不要写引号
- 标签中混入JS表达式时要用{ }
注意区分:js表达式和js语句
- 表达式:表达式产生一个值可以放在任何一个需要值的地方。
例如:a,a+b,add(1),arr.map(),function(){}- 语句: 例如:if(){},for(){},switch(){}
<script type="text/babel">
const myId="guigu"
const myName="lihua"
//1.创建虚拟dom
const Vdom2=(
<h2 id="{myId}">
<span>{myName}</span>
</h2>
)
//2.渲染虚拟dom到页面 ReactDom.render(虚拟DOM,容器)
ReactDOM.render(Vdom2,document.getElementById('test'))
</script>
- 样式的类名不要用class,要用className
const Vdom2=(
<h2 id="{myId}" className="title">
<span>{myName}</span>
</h2>
)
- 内联样式要用 style={{key:'value’,key2:‘value2’}} 的形式来写
const Vdom2=(
<h2 id="{myId}" className="title">
<span style={{color:'white',fontSize:'40px'}}>{myName}</span>
</h2>
)
- 虚拟dom必须只有一个根标签
- 标签必须闭合
<input type="text"/>
<input type="text"></input>
- 标签首字母:
(1)若小写字母开头,标签转化为html中同名标签,如果html找不到此标签-----报错
(2)若大写字母开头,React就去渲染对应的组件,若组件没有定义-----报错 - render中写注释的格式:
{/*<input type="text" placeholder="点击按钮提示数据" ref={currentNode=>{this.input1=currentNode;console.log(currentNode)}}/> */}
6 React中的数据循环遍历
<script type="text/babel">
// 模拟数据
const title="前端框架列表"
const data=['angular','vue','react']
//1.创建虚拟dom
const Vdom=(
<div>
<h2>{title}</h2>
<ul>
{
data.map((item,index)=>{
return <li key={index}>{item}</li>
})
}
</ul>
</div>
)
//2.渲染虚拟dom到页面 ReactDom.render(虚拟DOM,容器)
ReactDOM.render(Vdom,document.getElementById('test'))
7 模块与组件、模块化与组件化的理解
(1)模块:
- 理解:向外提供特定功能的js程序,一般就是一个js文件。
- 为什么要拆分成模块:随着业务逻辑的增加,代码越来越复杂
- 作用:复用js,简化js的编写,提高js运行效率
- 模块化:当应用的js都以模块来编写,这个应用就是一个模块化的应用
(2)组件:
- 理解:用来实现局部功能效果的代码和资源的集合 包括js、html、css等
- 为什么:一个界面的功能复杂
- 作用:复用编码,简化项目代码的编写,提高项目运行效率
- 组件化:当应用是以多组件的方式实现,这个应用就是一个组件化的应用
8 React组件
8.1 分类
名称 | 描述 |
---|---|
函数式组件 | 用函数定义的组件,适用于简单组件的定义 |
类式组件 | 用类定义的组件,适用于复杂组件的定义 |
8.2 函数式组件
<script type="text/babel">
//1.创建函数式组件
function Demo(){
//此处的this是undefined 因为bable翻译后开启了严格模式
return <h2>我是函数定义的简单组件,适用于简单组件的定义</h2>
}
//2.渲染组件到页面 ReactDom.render(虚拟DOM,容器)
ReactDOM.render(<Demo/>,document.getElementById('test'))
</script>
执行ReactDOM.render(<Demo/,document.getElementById(‘xxx’))之后发生了什么?
1.react解析了组件标签,找到了Demo组件
2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟dom转化为真实dom
3.将dom呈现在页面中。
8.3 类式组件
<script type="text/babel">
//1.创建类式组件 必须继承react中内置的类 React.Component
class MyComponent extends React.Component{
render(){
return <h2>我是定义的类式组件</h2>
}
}
//2.渲染组件到页面 ReactDom.render(虚拟DOM,容器)
ReactDOM.render(<MyComponent/>,document.getElementById('test'))
</script>
ReactDOM.render(< MyComponent/>,document.getElementById(‘xxxx’))之后发生了什么?
1.MyComponent解析了组件标签,找到了MyComponent组件
2.发现组件是类定义的,随后new出来该类的实例,并通过该实例调用到原型上的rander方法
3.将rander返回的虚拟dom转化为真实dom
4.将dom渲染在页面中
9 组件三大属性之---- state
名称 | 描述 |
---|---|
简单组件 | 没有state的 |
复杂组件 | 有state的 |
(1)实例
script type="text/babel">
class Weather extends React.Component{
constructor(props){
super(props)
// 初始化状态
this.state={isHot:true}
// 将原型对象上的方法转为实例自身上的方法
this.changeWeather=this.changeWeather.bind(this)
}
changeWeather(){
//1.类中的方法默认开启严格模式
//2. 由于changeWeather是作为onclick事件的回调 不是通过实例调用的而是直接调用
//结论:changeWeather中的this是undefined
// 严重注意:状态不可直接跟改 要使用内置的api
this.setState({isHot:!this.state.isHot})
}
render(){
return <h2 id='title' onClick={this.changeWeather}>今天天气很{this.state.isHot?'炎热':'凉爽'}</h2>
}
}
ReactDOM.render(<Weather/>,document.getElementById('test'))
</script>
(2)使用this.setState更新state
这里的更新是一种合并不会影响其他属性,而不是覆盖
this.setState({isHot:!this.state.isHot})
(3)state的简写方式
主要运用原理:类中可以直接写赋值语句,含义是给car的实例对象添加一个属性。
<script type="text/babel">
class Weather extends React.Component{
constructor(props){
super(props)
}
// 类中可以直接写赋值语句 含义是给类的实例添加一个属性
state={isHot:true}
changeWeather=()=>{
this.setState({isHot:!this.state.isHot})
}
render(){
return <h2 id='title' onClick={this.changeWeather}>今天天气很{this.state.isHot?'炎热':'凉爽'}</h2>
}
}
ReactDOM.render(<Weather/>,document.getElementById('test'))
</script>
(4)总结state
-
组件中rander方法中的this为组件的实例对象
-
组件自定义的方法中this为undefined,如何解决?
(1)使用bind强制改变指向
(2)使用箭头函数 -
状态数据不能直接修改,要用this.setState({})
10 组件三大属性之-----prop
(1)基本用法
<script type="text/babel">
class Person extends React.Component{
constructor(props){
super(props)
}
render(){
console.log(this)
return(
<ul>
<li>姓名{this.props.name}</li>
<li>年龄{this.props.age}</li>
<li>性别{this.props.sex}</li>
</ul>
)
}
}
ReactDOM.render(<Person name="tom" age="18" sex="man"/>,document.getElementById('test'))
</script>
(2)对标签属性进行限制和设置默认值
<!-- 引入prop-types用于对组件标签属性进行限制 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
<script type="text/babel">
class Person extends React.Component{
constructor(props){
super(props)
}
render(){
const {name,age,sex} = this.props
return(
<ul>
<li>姓名{name}</li>
<li>年龄{age}</li>
<li>性别{sex}</li>
</ul>
)
}
}
// 指定参数类型
Person.propTypes={
name:PropTypes.string.isRequired,
sex:PropTypes.string,
age:PropTypes.number,
fun:PropTypes.func,
}
// 指定默认值
Person.defaultProps={
sex:'男',
age:18
}
const p={name:'tom',age:"18",sex:'man'}
ReactDOM.render(<Person {...p}/>,document.getElementById('test'))
//会出现报错信息:Warning: Failed prop type: Invalid prop `age` of type `string` supplied to `Person`, expected `number`.in Person
(3)props值是只读的
render(){
this.props.age=19 //报错
const {name,age,sex} = this.props
return(
<ul>
<li>姓名{name}</li>
<li>年龄{age}</li>
<li>性别{sex}</li>
</ul>
)
}
(4)props简写形式
将propTypes和defaultProps放在类定义的内部
<script type="text/babel">
// 创建组件
class Person extends React.Component{
// 指定类型
static propTypes={
name:PropTypes.string.isRequired,
sex:PropTypes.string,
age:PropTypes.number,
fun:PropTypes.func,
}
// 设置默认值
static defaultProps={
sex:'男',
age:18
}
state={secname:'lihua'}
render(){
const {name,age,sex} = this.props
return(
<ul>
<li>姓名{name}</li>
<li>年龄{age}</li>
<li>性别{sex}</li>
<li>state:{this.state.secname}</li>
</ul>
)
}
}
const p={name:'tom',age:18,sex:'man'}
ReactDOM.render(<Person {...p}/>,document.getElementById('test'))
</script>
(5)函数式组件中使用props
<script type="text/babel">
//1.创建函数式组件
function MyComponent(props){
return <h2 className='title'>我是{props.name}</h2>
}
//2.渲染组件到页面 ReactDom.render(虚拟DOM,容器)
const p={name:'tom',age:18,sex:'man'}
ReactDOM.render(<MyComponent {...p}/>,document.getElementById('test'))
</script>
11 类中的构造器
类中的构造器是否接收props,是否传递给super取决于:是否希望在构造器中通过this访问props
构造器可以直接删除
constructor(props){
super(props)
}
12 组件的三大属性-----ref
(1)字符串形式的ref
不推荐
因为string类型的ref存在效率的问题。
<script type="text/babel">
//创建组件
class Demo extends React.Component{
//展示输入框的数据
showData = ()=>{
alert(this.refs.input1.value)
}
showData2 = ()=>{
alert(this.refs.input2.value)
}
render(){
return (
<div>
<input type="text" placeholder="点击按钮提示数据" ref="input1"/>
<button onClick={this.showData}>点我提示左侧数据</button>
<input type="text" placeholder="失去焦点提示数据" onBlur={this.showData2} ref="input2"/>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo/>,document.getElementById('test'))
</script>
(2)回调函数形式的ref
<script type="text/babel">
//创建组件
class Demo extends React.Component{
//展示输入框的数据
showData = ()=>{
alert(this.input1.value)
}
showData2 = ()=>{
alert(this.input2.value)
}
render(){
return (
<div>
<input type="text" placeholder="点击按钮提示数据" ref={currentNode=>this.input1=currentNode}/>
<button onClick={this.showData}>点我提示左侧数据</button>
<input type="text" placeholder="失去焦点提示数据" onBlur={this.showData2} ref={currentNode=>this.input2=currentNode}/>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo/>,document.getElementById('test'))
</script>
(3)createRef API形式的ref
React.createRef:调用后可以返回一个容器,该容器可以存储被Ref所标识的节点
该容器是专人专用的
<script type="text/babel">
//创建组件
class Demo extends React.Component{
input1 = React.createRef()
input2 = React.createRef()
showData = ()=>{
alert(this.input1.current.value)
}
showData2 = ()=>{
alert(this.input2.current.value)
}
render(){
return (
<div>
<input type="text" placeholder="点击按钮提示数据" ref={this.input1 }/>
<input type="text" placeholder="点击按钮提示数据" ref={this.input2 }/>
<button onClick={this.showData}>点我提示左侧数据</button>
<button onClick={this.showData2}>点我提示左侧数据</button>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo/>,document.getElementById('test'))
</script>
13 React中的事件处理
1.通过onXxx属性指定事件处理函数(注意大小写)
(1)React使用的是自定义事件,而不是原生dom事件------为了更好的兼容性
(2)React中的事件是通过事件委托方式处理的(委托给组件最外层的元素----------为了高效
2. 通过event.target得到发生事件的dom元素
3.不要过度的使用ref
<script type="text/babel">
//创建组件
class Demo extends React.Component{
showData2 = (event)=>{
alert(event.target.value)
}
render(){
return (
<div>
<input type="text" placeholder="失去焦点提示数据" onBlur={this.showData2}/>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo/>,document.getElementById('test'))
</script>
14 React中的收集表单数据
14.1 非受控组件
页面中输入类的dom 是现用现取的状态 就是非受控组件
<script type="text/babel">
//创建组件
class Login extends React.Component{
handleSubmit=(event)=>{
event.preventDefault()//阻止默认事件
let {username,password} = this
alert(`你输入的用户名是${username.value},你输入的密码是${password.value}`)
}
render(){
return(
<form action="" onSubmit={this.handleSubmit}>
用户名:<input type="text" id="" placeholder="请输入用户名" name="username" ref={c=>this.username=c}/>
密码:<input type="password" id="" placeholder="请输入密码" name="password" ref={c=>this.password=c}/>
<button>登录</button>
</form>
)
}
}
//渲染组件到页面
ReactDOM.render(<Login/>,document.getElementById('test'))
</script>
14.2 受控组件
受控组件:页面中所有输入类的dom随着输入,输入的值被维护在状态【state】中。
<script type="text/babel">
//创建组件
class Login extends React.Component{
state={username:'',password:''}
// 提交
handleSubmit=(event)=>{
event.preventDefault()//阻止默认事件
alert(`你输入的用户名是${this.state.username},你输入的密码是${this.state.password}`)
}
// 保存用户名在状态中
saveUsername=(event)=>{
this.setState({username:event.target.value})
}
// 保存密码在状态中
savePassword=(event)=>{
this.setState({password:event.target.value})
}
render(){
return(
<form action="" onSubmit={this.handleSubmit}>
用户名:<input type="text" id="" placeholder="请输入用户名" name="username" onChange={this.saveUsername}/>
密码:<input type="password" id="" placeholder="请输入密码" name="password" onChange={this.savePassword}/>
<button>登录</button>
</form>
)
}
}
//渲染组件到页面
ReactDOM.render(<Login/>,document.getElementById('test'))
</script>
15 高阶函数,函数的柯里化
高阶函数: 如果一个函数符合下面两个规范中的任何一个,那这个函数就是高阶函数。
(1)若a函数,接收的参数是一个函数,那么a函数就可以称为高阶函数
(2)若a函数调用的返回值为一个函数,那么a就可以称为高阶函数
常见的高阶函数 promise、setTimeOut、arr.map…
函数柯里化: 通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式
function add(a){
return (b)=>{
return (c)=>{
return a+b+c
}
}
}
let a = add(1)(2)(3)
console.log(a)
<script type="text/babel">
//创建组件
class Login extends React.Component{
state={username:'',password:''}
// 提交
handleSubmit=(event)=>{
event.preventDefault()//阻止默认事件
alert(`你输入的用户名是${this.state.username},你输入的密码是${this.state.password}`)
}
// 保存表单数据到状态中
saveFomeData=(type)=>{
return (event)=>{
// 使用[]来获取变量 因为直接写type会把type当做字符串而不是变量
this.setState({[type]:event.target.value})
}
}
render(){
return(
<form action="" onSubmit={this.handleSubmit}>
用户名:<input type="text" id="" placeholder="请输入用户名" name="username" onChange={this.saveFomeData('username')}/>
密码:<input type="password" id="" placeholder="请输入密码" name="password" onChange={this.saveFomeData('password')}/>
<button>登录</button>
</form>
)
}
}
//渲染组件到页面
ReactDOM.render(<Login/>,document.getElementById('test'))
</script>
saveUserinfo=(type)=>{
return (event)=>{
this.setState({[type]:event.target.value})
}
}
注意:这里为什么是return这个函数
是因为调用的时候onChange={this.saveFomeData('password')}的涵义是
将this.saveFomeData('password')的返回值作为onChange的回调。
注意:这里的[type]:event.target.value属性名要用[]来包裹(用于读取变量),否则会被视为普通字符串。
不用柯里化
onChange={(event)=>this.saveFomeData(‘username’,event)}
onchange需要函数作为回调,就利用箭头函数来传递一个函数作为回调。
<script type="text/babel">
//创建组件
class Login extends React.Component{
state={username:'',password:''}
// 提交
handleSubmit=(event)=>{
event.preventDefault()//阻止默认事件
alert(`你输入的用户名是${this.state.username},你输入的密码是${this.state.password}`)
}
// 保存表单数据到状态中
saveFomeData=(type,event)=>{
this.setState({[type]:event.target.value})
}
render(){
return(
<form action="" onSubmit={this.handleSubmit}>
用户名:<input type="text" id="" placeholder="请输入用户名" name="username" onChange={(event)=>this.saveFomeData('username',event)}/>
密码:<input type="password" id="" placeholder="请输入密码" name="password" onChange={(event)=>this.saveFomeData('password',event)}/>
<button>登录</button>
</form>
)
}
}
//渲染组件到页面
ReactDOM.render(<Login/>,document.getElementById('test'))
</script>
16 React的生命周期
(2)React生命周期(旧版本)
(2)React生命周期(新版本)
(16-1):componentDidMount
调动时机:组件挂载完毕时调用,一般用于做一些初始化的事情。(开启定时器,发送网络请求、订阅消息)
<script type="text/babel">
//创建组件
class Life extends React.Component{
state={opacity:1}
//使组件从页面移除
distory=()=>{
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
// 调动时机:组件挂载完毕时调用
componentDidMount(){
setInterval(()=>{
let {opacity} = this.state
opacity -= 0.1
if(opacity <= 0) opacity=1
this.setState({opacity})
},200)
}
render(){
return(
<div>
<h2 style={{opacity:this.state.opacity}}>ReactReactReactReact</h2>
<button onClick={this.distory}>消失</button>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Life/>,document.getElementById('test'))
</script>
(16-2):componentWillUnmount
调用时机:组件将要被卸载时调用,一般做一些收尾的事(关闭定时器、取消订阅消息)
卸载组件:this.distory
<script type="text/babel">
//创建组件
class Life extends React.Component{
state={opacity:1}
//使组件从页面移除
distory=()=>{
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
// 调用时机:组件将要被卸载时调用
componentWillUnmount(){
clearInterval(this.timer)
}
render(){
return(
<div>
<h2 style={{opacity:this.state.opacity}}>ReactReactReactReact</h2>
<button onClick={this.distory}>消失</button>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Life/>,document.getElementById('test'))
(16-3):新版本和旧版本的区别
1.新版本中要用
UNSAFE_componentWillReceiveProps
UNSAFE_componentWillMount
UNSAFE_componentWillUpdate
2 新版本出现两个新的钩子函数
使用的情况比较少
getDerivedStateFromProps
getSnapshotBeforeUpdate
getDerivedStateFromProps:从props中得到一个派生的状态,若state中的值在任何时候都取决于props中时使用【一般不使用】
// getDerivedStateFromProps
//返回值为一个对象:state中的同名值被props替代且不能被修改
//返回值为null:没有影响
//返回值为props:state中的同名值被props替代且不能被修改
static getDerivedStateFromProps(props,state){
console.log('Count-getDerivedStateFromProps',props,state)
return props
}
getSnapshotBeforeUpdate:在更新之前获取快照 【不常用】
//getSnapshotBeforeUpdate
getSnapshotBeforeUpdate(){
console.log('Count-getSnapshotBeforeUpdate')
return 'weijiamin'//会被componentDidUpdate接收
}
17 React脚手架
步骤
1全局安装: npm install -g create-react-app/npm i create-react-app -g
2.切换到想创建项目的目录,使用命令:create-react-app hello-react
3.进入项目文件夹:cd hello-react
4 启动项目:npm start
5ts+react :create react-app xxx --template typescript
快捷键
rcc:快速生成React类式组件框架
rfc:快速生成React函数式组件
18 父子组件传值
1.父-子
父组件
export default class App extends Component {
state = {
todos: [
{ id: "001", name: "吃饭" ,done:true},
{ id: "002", name: "睡觉" ,done:false},
{ id: "003", name: "上班" ,done:false},
{ id: "004", name: "打游戏" ,done:false},
],
};
render() {
return (
<List todos={this.state.todos} />
);
}
}
子组件
export default class List extends Component {
render() {
return (
<ul className="todo-main">
{
this.props.todos.map((item,index)=>{
return <Item key={item.id} todo={item}/>
})
}
</ul>
);
}
}
2.子-父:实际是方法的传递
父组件
export default class App extends Component {
a=(data)=>{
console.log('父组件接收到的值',data)
}
render() {
return (
<Header a={this.a}/>
);
}
}
子组件
export default class Header extends Component {
handleKeyUp=(event)=>{
this.props.a(event.target.value)
}
render() {
return (
<input type="text" placeholder="请输入你的任务名称,按回车键确认" onKeyUp={this.handleKeyUp}/>
);
}
}
注意:当子组件调用的父组件的函数中使用到了this相关的数据的时候,绑定方法要手动指定this指向否则this会默认指向当前组件
父组件:
export default class TestA extends Component {
state={
list:['1','2','3'],
}
changeItem(index){
console.log(index,this.state)
let list = this.state.list
list[index] = '你好'
this.setState({list:list})
}
render() {
const _this = this
return (
<div>
{this.state.list.map((item,index)=>{
return <div key={index} className='list-item' onClick={()=>{this.changeItem(index)}}>{item}</div>
})}
<div>这里调用子组件</div>
<TestB list={this.state.list} changeItem={_this.changeItem.bind(this)}></TestB>
</div>
)
}
}
子组件:
export default class TestB extends Component {
changeItemByParent(index){
this.props.changeItem(index)
}
render() {
return (
<div>
{this.props.list.map((item,index)=>{
return <div key={index} onClick={()=>{this.changeItemByParent(index)}}>{item}</div>
})}
</div>
)
}
}
参考:react 子组件调用父组件的方法时,this指向子组件的问题
19 消息订阅与发布机制-兄弟组件传值
订阅消息:1.消息名 2.发布消息
下载:npm i pubsub-js --save
使用:
(1)import PubSub from ‘pubsub-js’ 引入
(2)let token=PubSub.subscribe(‘delete’,function(data){} 订阅 【token相对于订阅的id】
(3)PubSub.publish(‘delete’,data) 发布消息 【参数一:消息名;参数二:携带的数据】
(4)PubSub.unsubscribe(‘token’) 取消订阅 【传入订阅id取消订阅】
组件1:订阅消息
//初始化状态
state={
users:[],
isFirst:true,//是否为第一次打开页面
isLoading:false,//是否为加载状态
err:''//存储请求相关的错误信息
}
// 接收到两个参数 参数1:消息的名称 参数2:消息传递的值
componentDidMount(){
this.token=PubSub.subscribe('message',(msg,data)=>{
this.setState(data)
})
}
//取消订阅
componentWillUnmount(){
PubSub.unsubscribe(this.token)
}
组件2:发布消息
search=()=>{
//获取用户输入
let keyword = this.keyWordNode.current.value
// 发送请求前通知List更新状态
PubSub.publish('message',{isFirst:false,isLoading:true})
//发送网络请求
axios.get(`http://localhost:3000/api1/search/users?q=${keyword}`).then(
res=>{
//请求成功后通知List更新状态
PubSub.publish('message',{isLoading:false,users:res.data.items})
},
reason=>{
//请求失败后通知更新List状态
PubSub.publish('message',{isLoading:false,err:reason.message})
}
)
}
20 -React 配置代理
方式1
方式:修改package.josn文件
注意:要重新启动脚手架
发送请求:
axios.get('http://localhost:3000/students').then(
res=>{console.log('成功',res.data)},
error=>{console.log('失败')}
)
3000端口 要向5000端口发送请求 修改代理之后发送请求的端口改为3000
缺点: 只能配置一个代理地址,当服务端地址改变后失效
方式2
在src文件夹下面添加setupProxy.js文件
const { createProxyMiddleware } = require("http-proxy-middleware")
module.exports = function (app) {
app.use(
createProxyMiddleware("/api1",{
target: "http://localhost:5000", //配置转发目标地址(能返回数据的服务器地址)
changeOrigin: true, //控制服务器接收到的请求头中host字段的值
pathRewrite: { "^/api1": "" }, //重写请求路径
}),
createProxyMiddleware("/api2",{
target: "http://localhost:5001", //配置转发目标地址(能返回数据的服务器地址)
changeOrigin: true, //控制服务器接收到的请求头中host字段的值
pathRewrite: { "^/api2": "" },
})
)
}
发送请求
getStudentInfo = () => {
axios.get("http://localhost:3000/api1/students").then(
res => {
console.log("成功", res.data);
},
error => {
console.log("失败");
}
);
};
21 fatch发送网络请求
没看
22 React路由
1.SPA的理解
1.单页web应用 (single web page SPA)
2.整个应用只有一个完整的页面
3.点击页面中的链接不会刷新页面,只会做页面的局部更新
4.数据都需要通过ajax请求获取,并在前端异步呈现
2.路由的理解
一个路由就是一个映射关系(key:value)
key为路径,value可能是function或者component
注册路由 <Route path="./test component={Test}>
使用路由:浏览器的路径变为/test时 当前路由的组件就变味Test组件
3.react-route-dom的理解
1 react的一个插件库
2 专门用来实现一个SPA应用
3 基于react的项目基本都会用到此库
下载:npm i react-router-dom@5 下载的是router5的版本
4 路由的基本使用
import {Link,Route } from "react-router-dom";
注意:要用BrowserRouter包裹 < App/> ,因为都需要包裹直接在index.js中操作
Link,Route都是路由组件 为了规范About,Home都应该放在pages文件夹中 而不是components
所以组件可以分为 一般组件和路由组件
区别:
1 路由组件放在pages文件中,一般组件放在component文件中。
2 路由组件需要通过路由匹配调用 一般组件直接使用
3 路由组件默认会收到props,一般组件只有手动传值才会有
22 封装NavLike
组件
import React, { Component } from 'react'
import {NavLink } from "react-router-dom";
export default class MyNavLink extends Component {
render() {
return (
<NavLink className="list-group-item" activeClassName="active_nav" {...this.props}></NavLink>
)
}
}
NavLink可以通过指定activeClassName来指定active时的样式
这里的标签体内容【如about】会被接收到this.props.children中
接收的时候直接用 {…this.props} 来接收 可以将属性直接放在标签中
使用
<MyNavLink to="/about">about</MyNavLink>
<MyNavLink to="/home">home</MyNavLink>
23 使用Switch组件控制路由匹配
作用:当匹配到一个路由后就中断匹配,提高匹配效率。
import {Route,Switch } from "react-router-dom";
//当路由为/home时 结果:只显示Home组件而不显示Test组件。
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Route path="/home" component={Test}/>
</Switch>
24 多层级路由刷新后样式丢失问题解决
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
<MyNavLink to="/weijiamin/about">about</MyNavLink>
<MyNavLink to="/weijiamin/home">home</MyNavLink>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{/* 注册路由 */}
<Switch>
<Route path="/weijiamin/about" component={About}/>
<Route path="/weijiamin/home" component={Home}/>
</Switch>
</div>
</div>
</div>
</div>
方法1.修改pubilc/index.js中的样式引入
//不加./
<link rel="stylesheet" href="/css/bootstrap.css">
方法2使用 %PUBLIC_URL%
<link rel="stylesheet" href="%PUBLIC_URL%/css/bootstrap.css">
方法3 使用 HashRouter 【不常用】
import { HashRouter} from "react-router-dom";
//渲染App组件到页面
ReactDOM.render(
<HashRouter>
<App/>
</HashRouter>
,document.getElementById('root')
)
25 路由匹配规则
1.默认是模糊匹配(最左匹配)
可以匹配的情况
<MyNavLink to="/about/weijiamin">about</MyNavLink>
<Route path="/about" component={About}/>
<MyNavLink to="/about">about</MyNavLink>
<Route path="/about" component={About}/>
2.开启精准匹配(全部都要相等)
exact
使用的原则:一般不使用
<MyNavLink to="/weijiamin/about">about</MyNavLink>
<Route exact path="/weijiamin/about" component={About}/>
26 设置默认的路由,路由重定向-Redirect
使用Redirect【重定向】
一般写在所有路由注册的最下方,当所有路由都无法匹配的时候,跳转到Redirect指定的路由 一般用于编写默认路由
{/* 注册路由 */}
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Redirect to="/home"></Redirect>
</Switch>
27 嵌套路由
export default class Home extends Component {
render() {
return (
<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"></Redirect>
</Switch>
</div>
)
}
注意:
- 注册子路由的时候要写上父路由的path值。
- 路由的匹配是按照注册路由的顺序执行的。
28 路由传参
(28-1)路由传递params参数
{/* 像路由组件传递params参数 */}
<Link to={`/home/message/detail/${item.id}/${item.title}`}>{item.title}</Link>
{/* 声明接收params参数 */}
<Route path="/home/message/detail/:id/:title" component={Detail}/>
组件接收参数
(28-2)路由传递search参数
像路由组件传递search参数
<Link to={`/home/message/detail?id=${item.id}&title=${item.title}`}>{item.title}</Link>
{/* search参数无需声明接收 正常声明即可*/}
<Route path="/home/message/detail" component={Detail}/>
import qs from 'qs'
接收saerch参数
let {search} = this.props.location
let {id,title}= qs.parse(search.slice(1))
(28-3)路由接收state参数
区别:参数没有暴露在地址栏中。
像路由组件传递state参数
<Link to={{pathname:'/home/message/detail',state:{id:item.id,title:item.title}}}>{item.title}</Link>
{/* state参数无需声明接收 正常声明即可*/}
<Route path="/home/message/detail" component={Detail}/>
接收state参数
const {state}=this.props.location
虽然没有在地址栏中但是刷新state参数也不会消失
因为history对象一直记录了
但是清除内存后会消失
解决:
const {state}=this.props.location||{}
29 路由的push与replace
1.push(默认状态)
路由(栈) |
---|
http://localhost:3001/home/message/detail |
http://localhost:3001/home/message |
http://localhost:3001/home |
http://localhost:3001/about |
2.replace模式
开启replace模式
<Link replace={true} to="/home/message/detail">{item.title}</Link>
路由(栈) |
---|
http://localhost:3001/home/message/detail |
http://localhost:3001/home |
http://localhost:3001/about |
30 编程式路由导航
设置按钮
<button onClick={()=>{this.showReplace(item.id,item.title)}}>replace查看</button>
<button onClick={()=>{this.showPush(item.id,item.title)}}>push查看</button>
showReplace=(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})
}
showPush=(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})
}
注意:无论是哪种方式传递,声明接收参数和接收参数的要与之匹配
31 编程式路由前进和后退
<button onClick={this.back}>回退</button>
<button onClick={this.forward}>前进</button>
<button onClick={this.goByNum}>前进或后退几部</button>
back=()=>{
this.props.history.goBack()
}
forward=()=>{
this.props.history.goForward()
}
goByNum=()=>{
// 正数为前进 负数为后退
this.props.history.go(-2)
}
32 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()
}
goByNum=()=>{
// 正数为前进 负数为后退
this.props.history.go(-2)
}
render() {
return (
<div className="page-header">
<h2>React Router Demo</h2>
<button onClick={this.back}>回退</button>
<button onClick={this.forward}>前进</button>
<button onClick={this.goByNum}>前进或后退几部</button>
</div>
)
}
}
// 暴露的是withRouter加工后的Header
export default withRouter(Header)
33 BrowserRouter和HashRouter的区别
1.底层原理不同
BrowserRouter使用的H5的history API,不兼容IE9及以下
HashRouter使用的是URL的哈希值
2.url的表现形式不同
BrowserRouter:没#
HashRouter:有#
3.刷新后对路由state参数的影响
BrowserRouter:没有任何影响,因为state保存在location对象中
HashRouter:导致路由state丢失
备注:HashRouter可以用于解决一些路径错误相关问题。
34组件库 ant-design
(1)基本使用
安装 npm install antd --save
(2)优化antd样式
修改主题色
- 安装依赖的包
npm install --save @craco/craco
npm i craco-less
- 修改样式引入
- 在根目录下创建 craco.config.js文件并修改配置
const CracoLessPlugin = require('craco-less');
module.exports = {
plugins: [
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
lessOptions: {
modifyVars: { '@primary-color': '#1DA57A' },
javascriptEnabled: true,
},
},
},
},
],
};
4.修改package.json文件
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
"eject": "react-scripts eject"
},
5.运行
npm start
35 redux
35.0 redux概念
redux是什么?
- 是一个专门用于 管理状态 的js库,不是react插件库。
- 它可以用在react,angular,vue中,但基本与react配合。
- 作用:集中式管理react应用中多个组件 共享 的状态。
什么情况下用redux?
- 某个组件的状态需要让其他的组件可以 随时 拿到(共享)。
- 一个组件需要改变另一个组件的状态(通信)。
- 总体使用原则:能不用就不用,如果不用的情况比较吃力才用。
- Action Creators:创建动作对象;
- Store :管理者;
- Reducers:初始化状态、加工状态;
35.1 redux的三个核心概念
1.action
动作对象
包含两个属性:
- type:标识的属性,值为字符串,唯一,必要的属性
- data:数据的属性,值类型任意,可选属性
例:{type:‘ADD_STUDENT’,data:{name:‘tom’,age:18}}
2.reducer
1.用于初始化、加工状态
2.加工时,根据旧的state和action,产生新的state的纯函数
3.store
1.将state、action、reducer联系在一起的对象
2.如何得到此对象:
(1)import {createStore} from ‘redux’
(2)import reducer from ‘./reducers’
(3)const store=createStore(reducer)
3.此对象的功能:
(1)store.getState()得到state
(2)store.dispatch(action):分发action,触发reducer调用,产生新的state
(3)subscribe(listener)注册监听,当产生了新的state时自动调用
35.2 redux的精简使用
纯react版本
import React, { Component } from 'react'
export default class Count extends Component {
state = {count:0}
//加法
increment=()=>{
const {value} = this.selectNumber
const {count} = this.state
this.setState({count:count+value*1})
}
//减法
decrement=()=>{
const {value} = this.selectNumber
const {count} = this.state
this.setState({count:count-value*1})
}
//奇数加
incrementOfOdd=()=>{
const {value} = this.selectNumber
const {count} = this.state
if(count%2!==0){
this.setState({count:count+value*1})
}
}
//异步加
incrementOfAsync=()=>{
const {value} = this.selectNumber
const {count} = this.state
setTimeout(()=>{
this.setState({count:count+value*1})
},800)
}
render() {
return (
<div>
<h1>当前求和为:{this.state.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.incrementOfOdd}>当前求和为奇数加</button>
<button onClick={this.incrementOfAsync}>异步加</button>
</div>
)
}
}
redux版本
1.下载:npm i redux
store.js
// 该文件专门用于暴露一个store对象,整个应用只有一个store对象
// 引入createStore 用于创建store
import { createStore } from "redux";
// 引入为Count组件服务的reducer
import countReducer from './conut_reducer'
//暴露store
export default createStore(countReducer)
conut_reducer.js
//该文件是用于创建一个为count服务的reducer,reducer的本质就是一个函数
//reducer函数的两个参数
//preState:之前的状态
//action:要做的动作
//初始化状态
const initState = 0
export default function countReducer(preState=initState,action){ const {type,data} = action
switch(type){
case 'increment':
return preState + data
case 'decrement':
return preState - data
default:
return preState
}
}
注意:reducer中只写操作,判断条件等语句都写在组件中,reducers是纯函数。
count组件
import React, { Component } from "react";
//引入store用于获取store中保存的状态
import store from "../../redux/store";
export default class Count extends Component {
increment = () => {
//通知reducter加value
const { value } = this.selectNumber;
store.dispatch({type:'increment',data:value*1})
};
componentDidMount(){
//检查redux中状态的变化,只要变化就调用render
store.subscribe(()=>{
this.setState({})
})
}
render() {
return (
<div>
{/* 获取store中的值 */}
<h1>当前求和为{store.getState()}</h1>
<select name="" id="" 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>
</div>
);
}
}
使用的三个注意点:
- store.getState():获取store状态值。
- store.dispatch({type:‘increment’,data:value*1}):告诉reducer动作对象
- componentDidMount(){
store.subscribe(()=>{
this.setState({})
})
}:检查redux中状态的变化,只要变化就调用render
35.3 redux文件完整版
和精简版的区别在于
1.使用Action creator,不自传action
2.使用constant来规范类型
constant.js
// 该模块是用于定义action对象中的type类型 防止在编写过程中出错
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
count_action.js
// 该文件专为Count组件生成action
import { INCREMENT,DECREMENT } from "./constant"
export const createIncrementAction = data => {
return { type: INCREMENT, data };
};
export const createDecrementAction = data => {
return { type: DECREMENT, data };
};
conut_reducer.js
//该文件是用于创建一个为count服务的reducer,reducer的本质就是一个函数
import { INCREMENT,DECREMENT } from "./constant"
//reducer函数的两个参数
//preState:之前的状态
//action:要做的动作
export default function countReducer(preState,action){
console.log(preState,action)
//初始化状态
if(preState === undefined) preState = 0
const {type,data} = action
switch(type){
case INCREMENT:
return preState + data
case DECREMENT:
return preState - data
default:
return preState
}
}
store.js
// 该文件专门用于暴露一个store对象,整个应用只有一个store对象
// 引入createStore 用于创建store
import { createStore } from "redux";
// 引入为Count组件服务的reducer
import countReducer from './conut_reducer'
//暴露store
export default createStore(countReducer)
Count组件
import React, { Component } from "react";
//引入store用于获取store中保存的状态
import store from "../../redux/store";
//引入actionCreate 专门用于创建action对象
import { createIncrementAction,createDecrementAction } from "../../redux/count_action";
export default class Count extends Component {
increment = () => {
//通知reducter加value
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))
}
};
componentDidMount(){
//检查redux中状态的变化,只要变化就调用render
store.subscribe(()=>{
this.setState({})
})
}
render() {
return (
<div>
<h1>当前求和为{store.getState()}</h1>
<select name="" id="" 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>
);
}
}
35.4 异步action(复习的时候看这版)
action类型 | 说明 |
---|---|
对象类型 | 同步action |
函数类型 | 异步action |
下载依赖:npm i redux-thunk (异步action传递到store的中间件,使store可以接收到非对象类型的action)
异步action就是指action的值为函数,异步action中一般会调用同步action,异步action不是必须要使用的
store.js
// 该文件专门用于暴露一个store对象,整个应用只有一个store对象
// 引入createStore 用于创建store
import { createStore,applyMiddleware } from "redux";
// 引入为Count组件服务的reducer
import countReducer from './conut_reducer'
//引入 redux-thunk用于支持异步action
import thunk from "redux-thunk";
//暴露store
export default createStore(countReducer,applyMiddleware(thunk))
count_action.js
//同步action
export const createIncrementAction = data => {
return { type: INCREMENT, data };
};
//异步action
export const createDecrementAsyncAction = (data,time) => {
return (dispatch)=>{
setTimeout(() => {
dispatch(createIncrementAction(data))
}, time);
}
};
异步action提供dispatch方法
Count组件
import React, { Component } from "react";
//引入store用于获取store中保存的状态
import store from "../../redux/store";
//引入actionCreate 专门用于创建action对象
import { createDecrementAsyncAction,createDecrementAction } from "../../redux/count_action";
export default class Count extends Component {
incrementAsync = () => {
const { value } = this.selectNumber;
store.dispatch(createDecrementAsyncAction(value*1,2000))
};
componentDidMount(){
//检查redux中状态的变化,只要变化就调用render
store.subscribe(()=>{
this.setState({})
})
}
render() {
return (
<div>
<h1>当前求和为{store.getState()}</h1>
<select name="" id="" ref={c => (this.selectNumber = c)}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.incrementAsync}>异步加</button>
</div>
);
}
}
36 react-redux
安装:npm i react-redux
36.1 react-dedux的基本使用
- 将Count改名为CountUI,且只保存html结构
- 创建容器组件 在src下新建文件containers/Count/index用于放置CountUI的容器组件
//自定义容器组件
// 引入count的UI组件
import CountUI from '../../components/CountUI'
//引入connect用于链接容器组件和UI组件
import { connect } from "react-redux";
// 生成容器组件 并链接UI组件
const countContainer = connect()(CountUI)
export default countContainer
3.修改App.js中的引入,并向容器组件中传递store(到这一步就实现了容器和UI组件、容器和redux之间的连接)
import React, { Component } from 'react'
//渲染的是容器组件
import Count from './containers/Count'
import store from './redux/store'
export default class App extends Component {
render() {
return (
<div><Count store={store}></Count></div>
)
}
}
4.Count容器向CountUI传props参数
当容器组件与store连接后,UI组件的props会自动收到store
5.容器组件和redux的数据沟通:
import CountUI from '../../components/CountUI'
import { connect } from "react-redux";
import {createIncrementAction} from '../../redux/count_action'
//这个函数有一个参数为redux的state
function a(state){
return {count:state}
}
//这个函数有一个参数为dispatch 用于操作store的值
function b(dispatch){
return {add:(data)=>{
//通知redux执行increase
dispatch(createIncrementAction(value))
}}
}
const countContainer = connect(a,b)(CountUI)
export default countContainer
规范命名:
- a函数:mapStateToProps
- b函数:mapDispatchToProps
import CountUI from '../../components/CountUI'
import { connect } from "react-redux";
import { createIncrementAction,createDecrementAction } from "../../redux/count_action";
function mapStateToProps(state){
return {count:state}
}
function mapDispatchToProps(dispatch){
return {
increace:data=>dispatch(createIncrementAction(data)),
decreace:data=>dispatch(createDecrementAction(data))
}
}
const countContainer = connect(mapStateToProps,mapDispatchToProps)(CountUI)
export default countContainer
6.UI组件使用props
increment = () => {
const { value } = this.selectNumber;
this.props.add(1*value)
};
<h1>当前求和为:{this.props.count}</h1>
36.2 react-dedux的优化
1.容器的优化
import CountUI from '../../components/CountUI'
import { connect } from "react-redux";
import { createIncrementAction,createDecrementAction } from "../../redux/count_action";
//简写方式
const countContainer = connect(
//传递state参数
state=>{return {count:state}},
//对象的形式 只需提供action 就可以自动调用dispatch
{
increace:createIncrementAction,
decreace:createDecrementAction
}
)
(CountUI)
export default countContainer
2.无需使用store.subscribe进行监测
react-redux自动检测并更新,容器组件默认拥有检测redux变化的能力
3.App.js中传store的优化
优化:不在App.js中传,在index.js中传
App.js
import React, { Component } from 'react'
//渲染的是容器组件
import Count from './containers/Count'
export default class App extends Component {
render() {
return (
// 给容器组件传递store
<div><Count></Count></div>
)
}
}
index.js
import React from 'react';
import App from './App';
import { createRoot } from 'react-dom/client';
//引入store相关
import store from './redux/store';
import {Provider} from 'react-redux'
const container = document.getElementById('root');
const root = createRoot(container);
root.render(
<Provider store={store}>
<App />
</Provider>
);
4.整合容器组件和UI组件为一个组件
import React, { Component } from 'react'
import { connect } from "react-redux";
import {createIncrementAction,createDecrementAction,createIncrementActionAsync} from '../../redux/count_action'
//UI组件
class Count extends Component {
state = {count:0}
//加法
increment=()=>{
const { value } = this.selectNumber;
this.props.increace(value*1)
}
//减法
decrement=()=>{
const {value} = this.selectNumber
this.props.decreace(value*1)
}
//奇数加
incrementOfOdd=()=>{
const {value} = this.selectNumber
if(this.props.count%2!==0){
this.props.increace(value*1)
}
}
//异步加
incrementOfAsync=()=>{
const {value} = this.selectNumber
this.props.increaceAsyn(value*1,1000)
}
render() {
console.log(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.incrementOfOdd}>当前求和为奇数加</button>
<button onClick={this.incrementOfAsync}>异步加</button>
</div>
)
}
}
//容器组件
const countContainer = connect(
//传递state状态
state=>{return {count:state}},
//传递方法 react-redux自动分发
{
increace:createIncrementAction,
decreace:createDecrementAction,
increaceAsyn:createIncrementActionAsync
}
)(Count)
export default countContainer
37 redux数据共享
37.1 重新修改redux文件结构
- 文件也可以直接用count.js命名、不一定要用count_action.js/count_reducer.js
- 文件内容按照redux需要来写 可以参考 “redux文件完整版”
37.2 store.js
多个组件共同使用redux的情况
// 该文件专门用于暴露一个store对象,整个应用只有一个store对象
// 引入createStore 用于创建store
// applyMiddleware:使异步action可以被接收
// combineReducers:合并reducer
import { createStore,applyMiddleware,combineReducers } from "redux";
// 引入为Count组件服务的reducer
import countReducer from "./reducers/count_reducer";
//引入person的reducer
import personReducer from "./reducers/person_reducer";
//引入 redux-thunk用于支持异步action
import thunk from "redux-thunk";
//汇总reducer
const allReducer= combineReducers({
count:countReducer,
person:personReducer
})
//暴露store
export default createStore(allReducer,applyMiddleware(thunk))
37.3 constant.js
// 用于定义action对象中type的常量值 防止写错
//count
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
//person
export const ADDPERSON = 'addperson'
37.4 action.js
person_action.js
import { ADDPERSON } from "../constant";
export const createAddPersonAction=(data)=>{
return {type:ADDPERSON,data}
}
count_action.js
import { INCREMENT,DECREMENT } from "../constant";
export const createIncrementAction = data => {
return { type: INCREMENT, data };
};
export const createDecrementAction = data => {
return { type: DECREMENT, data };
};
export const createIncrementActionAsync = (data,time)=>{
return (dispatch)=>{
setTimeout(()=>{
dispatch(createIncrementAction(data))
},time)
}
}
37.5 reducer.js
person_reducer.js
import { ADDPERSON } from "../constant";
const initState=[{id:'001',name:'tome',age:15}]
export default function personReducer(preState=initState,action){
const {type,data} = action
switch(type){
case ADDPERSON:
// return preState.push(data)
return [data,...preState]
default:
return preState
}
}
count_reducer.js
// 初始化
import { INCREMENT,DECREMENT } from "../constant";
const initState = 0
export default function countReducer(preState = initState,action){
const {type,data} = action
switch(type){
case INCREMENT:
return preState = preState+data
case DECREMENT:
return preState = preState-data
default:
return preState
}
}
37.3 组件内使用
person
import React, { Component } from 'react'
import {nanoid} from 'nanoid'
import { connect } from 'react-redux'
import { createAddPersonAction } from '../../redux/actions/person_action'
class Person extends Component {
addPerson =()=>{
const age = this.age.value
const name = this.name.value
const person = {id:nanoid(), name,age}
this.props.createAddPersonAction(person)
}
render() {
return (
<div>
<h1>我是person组件</h1>
<div>我拿到的count值:{this.props.count}</div>
<input ref={c=>this.name=c} type="text" placeholder='输入名字'/>
<input ref={c=>this.age=c} type="text" placeholder='输入年龄'/>
<button onClick={this.addPerson}>添加</button>
<ul>
{this.props.person.map((item)=>{
return <li key={item.id}>{item.name}--{item.age}</li>
})}
</ul>
</div>
)
}
}
const PersonContainer = connect(
state=>{return {
count:state.count,
person:state.person
}},
{
createAddPersonAction:createAddPersonAction
}
)(Person)
export default PersonContainer
count
import React, { Component } from 'react'
import { connect } from "react-redux";
import {createIncrementAction,createDecrementAction,createIncrementActionAsync} from '../../redux/actions/count_action'
//UI组件
class Count extends Component {
increment=()=>{
const { value } = this.selectNumber;
this.props.increace(value*1)
}
//减法
decrement=()=>{
const {value} = this.selectNumber
this.props.decreace(value*1)
}
//奇数加
incrementOfOdd=()=>{
const {value} = this.selectNumber
if(this.props.count%2!==0){
this.props.increace(value*1)
}
}
//异步加
incrementOfAsync=()=>{
const {value} = this.selectNumber
this.props.increaceAsyn(value*1,1000)
}
render() {
return (
<div>
<h1>我是Coun组件</h1>
<div>我拿到的person:</div>
<ul>
{this.props.person.map((item)=>{
return <li key={item.id}>{item.name}-{item.age}</li>
})}
</ul>
<h3>当前求和为:{this.props.count}</h3>
<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.incrementOfOdd}>当前求和为奇数加</button>
<button onClick={this.incrementOfAsync}>异步加</button>
</div>
)
}
}
//容器组件
const countContainer = connect(
//传递state参数
state=>{return {count:state.count,person:state.person}},
//传递方法 react-redux自动分发
{
increace:createIncrementAction,
decreace:createDecrementAction,
increaceAsyn:createIncrementActionAsync
}
)(Count)
export default countContainer
app.js
import React, { Component } from 'react'
//渲染的是容器组件
import Count from './containers/Count/Count'
// import Test from './containers/test'
import Person from './containers/Person/Person'
export default class App extends Component {
render() {
return (
// 给容器组件传递store
<div>
<Count></Count>
<hr></hr>
<Person></Person>
</div>
)
}
}
最终效果
38 纯函数
是一类特别的函数:只要是同样的输入(实参),必定得到同样的输出(返回)。
————————————————————————————
必须遵循以下规则:
(1)不得改写参数数据
(2)不会产任何副作用,例如网络请求,输入和输出设备
(3)不能调用Date.now()或者Math.randing()等不纯的方法
————————————————————————————
redux的reducer必须是一个纯函数
39 redux扩展插件应用
安装库:npm install redux-devtools-extension
修改redux/store.js
// 该文件专门用于暴露一个store对象,整个应用只有一个store对象
//引入redux-devtools-extension
import {composeWithDevTools} from 'redux-devtools-extension'
// 引入createStore 用于创建store
// applyMiddleware:使异步action可以被接收
// combineReducers:合并reducer
import { createStore,applyMiddleware,combineReducers } from "redux";
// 引入为Count组件服务的reducer
import countReducer from './reducers/conut'
//引入person的reducer
import personReducer from "./reducers/person";
//引入 redux-thunk用于支持异步action
import thunk from "redux-thunk";
//汇总reducer
const allReducer= combineReducers({
count:countReducer,
person:personReducer
})
//暴露store
export default createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))
40 打包react项目
1.执行:npm run build
生成build文件
2.安装库:npm i serve -g
用于开启服务器
3. 执行:serve build
以build文件夹作为根路径来启动一台服务器
在工作上是用不到的
扩展1-关于setState()
(1). setState(stateChange, [callback])------对象式的setState
1.stateChange为状态改变对象(该对象可以体现出状态的更改)
2.callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
setState引起react更新的动作是异步的
(2). setState(updater, [callback])------函数式的setState
1.updater为返回stateChange对象的函数。
2.updater可以接收到state和props。
4.callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
总结:
1.对象式的setState是函数式的setState的简写方式(语法糖)
2.使用原则:
(1).如果新状态不依赖于原状态 ===> 使用对象方式
(2).如果新状态依赖于原状态 ===> 使用函数方式
(3).如果需要在setState()执行后获取最新的状态数据,
要在第二个callback函数中读取
1.setState()的第一种用法
//setState的第一种用法
add = () => {
const {count}=this.state
this.setState({count:count+1},()=>{
console.log("在回调函数中",this.state)
})
console.log("不在回调函数中",this.state)
};
输出结果:
不在回调函数中 {count: 0}
在回调函数中 {count: 1}
2.setState()的第二种用法
add=()=>{
this.setState((state,props)=>{
return {count:state.count+1}
})
}
扩展2-LazyLoad懒加载
通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
const Login = lazy(()=>import('@/pages/Login'))
通过React的Suspense来控制组件还没有加载好时的状态
<Suspense fallback={<div>loading...</div>}</Suspense>
// 引入lazy,Suspense
import React, { Component,lazy,Suspense } from "react";
import {NavLink,Route } from "react-router-dom";
//lazy和import一起使用 注册可以懒加载的组件
const Home= lazy(()=>import('./Home'))
const About= lazy(()=>import('./About'))
//创建并暴露App外壳组件s
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">
{/* 在react考路由链接切换组件 */}
<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">
{/* 注册路由 */}
{/* 使用Suspense来控制组件还没有加载好时的状态,这里最好是一个loading的组件 */}
<Suspense fallback={<h1>loading...</h1>}>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
</Suspense>
</div>
</div>
</div>
</div>
</div>
)
}
}
扩展3-Hooks
什么是Hooks?
(1). Hook是React 16.8.0版本增加的新特性/新语法
(2). 可以让你在函数组件中使用 state 以及其他的 React 特性
3个常用的hooks
(1). State Hook: React.useState()
(2). Effect Hook: React.useEffect()
(3). Ref Hook: React.useRef()
React.useState():使函数式组件能有state和修改state
export default function Demo() {
//React.useState的参数为state的初始值
//React.useState的返回值为一个数组 第一个值为state的值 第二个值为改变state的方法
const [count,setCount] = React.useState(0);
function add() {
//setState的第一种写法
setCount(count+1)
//setState的第二种写法
setCount(count=>count+1)
}
return (
<div>
<h1>现在的值为:{count}</h1>
<button onClick={add}>点我加1</button>
</div>
);
}
注意:在更新对象类型时,切记要合并旧的状态,否则旧的状态会丢失。
const [params, setParams] = useState({
rotate: 0,
color: "#000000"
});
const handleInputChange = event => {
const target = event.target;
setParams({
...params,
[target.name]: target.value
});
};
注意:因为是要修改useState处理后的值,所有需要使用useCallback来处理,其他要进行一次浅拷贝
const emailMouseLeave = useCallback((changeIndex)=>{
let result = email.concat()
result.forEach((item,index)=>{
if(index === changeIndex){
item.isHover = false
}
})
setEmail(result)
},[email])
React.useEffect():使函数式组件具备钩子函数
export default function Demo() {
const [count,setCount] = React.useState(0);
//React.useEffect可以传入两个参数
//第一个参数 :为一个函数,这个函数就相当于一个生命周期的回调函数
//第二个参数 :为一个数组,里面的值相当于监测谁
// 不写:谁都监测,
// 空数组:谁都不监测,此时第一个参数相当于componentDidMount的回调
// [count,name]:监测count和name,此时第一个参数相当于componentDidUpdate的回调
//React.useEffect 返回值为一个函数,这个函数就相当于componentWillUnmount的回调
React.useEffect(() => {
let timer=setInterval(()=>{
setCount(count=>count+1)
},1000)
return ()=>{
clearInterval(timer)
}
},[]);
//卸载组件的回调
function unMount(){
ReactDom.unmountComponentAtNode(document.getElementById('root'))
}
return (
<div>
<h1>现在的值为:{count}</h1>
<button onClick={unMount}>卸载组件</button>
</div>
);
}
卸载组件:
引入:import ReactDom from ‘react-dom’
卸载:ReactDom.unmountComponentAtNode(document.getElementById(‘root’))
React.useRef():使函数组件支持ref
export default function Demo() {
const myRef=React.useRef()
function show(){
alert(myRef.current.value)
}
return (
<div>
<input type="text" name="" id="" ref={myRef}/>
<button onClick={show}>点击按钮展示内容</button>
</div>
);
}
扩展4-Fragment
Fragment的作用?
代替div在解析的时候会忽略掉,就不会出现不必要的真实dom外层标签了
import React, { Component,Fragment } from 'react'
import Demo from './components/4_Fragment'
export default class App extends Component {
render() {
return (
<Fragment>
<Demo></Demo>
</Fragment>
)
}
}
Fragment可以接收一个参数 key,可以用于循环遍历的时候
<Fragment key={xxx}> <Demo></Demo> </Fragment>
扩展5-Context
1.Context的作用?
用于组件间的通信,常用与【祖组件】和【后代组件】之间
使用context
1.创建context容器对象,并取出Provider
const UserNameContext=React.createContext()
const {Provider,Consumer} = UserNameContext
2.渲染子组件的时候。外面要包裹Provider,通过value属性给后代传递数据
export default class A extends Component {
state={username:'tom'}
render() {
return (
<div className='parent'>
<h3>我是A组件</h3>
<h4>我的用户名是{this.state.username}</h4>
//必须是value 不允许改名
<Provider value={this.state.username}>
<B/>
</Provider>
</div>
)
}
}
3.后代读取数据
(1)方法一:仅用于类式组件
class C extends Component {
//声明接收
static contextType=UserNameContext
render() {
console.log(this)
return (
<div className='grand'>
<h3>我是B组件</h3>
<h4>我从A组件接收到的用户名是:{this.context}</h4>
</div>
)
}
}
(2)方法二:函数式组件和类式组件都可以使用
function C(){
return (
<div className='grand'>
<h3>我是B组件</h3>
<h4>我从A组件接收到的用户名是:
<Consumer>
{
value=>{
return `${value.username}`
}
}
</Consumer>
</h4>
</div>
)
}
在应用的开发中一般不使用context,一般用它来封装react插件
扩展6-PureComponent
component存在的两个问题:
(1)只要执行了setState即使不改变数据组件也会重新render。
(2)当组件重新render(),就会自动重新render子组件,即使子组件没有用父组件的任何东西的时候。
----》效率低
原因:
component中的shouldComponentUpdate()总是返回true
解决1:重写shouldComponentUpdate()
shouldComponentUpdate(nextProps, nextState) {
console.log(nextProps, nextState); //要变化成的目标props,state
console.log(this.props, this.state); //变化前的props,state
if (this.state.carName === nextState.carName) return false;
}
解决2:不用component而用purcomponent【内部实现就是重写了shouldComponentUpdate】
使用这种方法不要直接修改数据而要产生新的数据
import React, { PureComponent } from "react";
import "./index.css";
export default class Parent extends PureComponent {
state = { carName: "car1" };
changeCar = () => {
this.setState({ carName: "car2" });
};
shouldComponentUpdate(nextProps, nextState) {
console.log(nextProps, nextState); //要变化成的目标props,state
console.log(this.props, this.state); //变化前的props,state
if (this.state.carName === nextState.carName) return false;
}
render() {
return (
<div className="parent">
<h2>我是Parent</h2>
<button onClick={this.changeCar}>点我换车</button>
<h4>我的车是:{this.state.carName}</h4>
<Child carName={this.state.carName}/>
</div>
);
}
}
class Child extends PureComponent {
// shouldComponentUpdate(nextProps, nextState) {
// console.log(nextProps, nextState); //要变化成的目标props,state
// console.log(this.props, this.state); //变化前的props,state
// if (this.props.carName === nextProps.carName) return false;
// }
render() {
return (
<div className="child">
<h2>我是Child</h2>
<h4>我接到的车是:{this.props.carName}</h4>
</div>
);
}
}
扩展7-renderProps
import React, { Component } from "react";
import "./index.css";
export default class Parent extends Component {
state = { username: "tom" };
render() {
return (
<div className="parent">
<h3>我是Parent组件</h3>
<h4>我的用户名是{this.state.username}</h4>
//传参
<A render={(name) => <B name={name}/>} />
</div>
);
}
}
class A extends Component {
state = { name: "tom" };
render() {
const {name} = this.state
console.log(this.props);
return (
<div className="child">
<h3>我是A组件</h3>
//调用子组件
{this.props.render(name)}
</div>
);
}
}
//类式组件接收
class B extends Component {
render() {
return (
<div className="grand">
<h3>我是B组件</h3>
//接收
<h4>我接到的名字:{this.props.name}</h4>
</div>
);
}
}
扩展8:-ErrorBoundary错误边界
用于捕获组件生命周期产生的错误,一般是当后端返回的数据不正确的时候使用
错误边界:不要让一个组件的错误影响整个组件,使错误控制在一定的范围内
import React, { Component } from "react";
import Child from "./Child";
export default class Prrent extends Component {
//hasError:用于标识子组件是否产生错误
state={hasError:''}
//当parent的子组件出现错误的时候会调用getDerivedStateFromError并且携带错误信息
static getDerivedStateFromError(error){
console.log(error)
return {hasError:error}
}
//渲染组件出错会调用这个函数,一般用于做统计错误,反馈给服务器,用于通知编码人员进行bug修复
componentDidCatch(){
console.log('渲染组件出错')
}
render() {
return (
<div>
<h2>我是Parent组件</h2>
{this.state.hasError?<h2>出错啦...</h2>:<Child />}
</div>
);
}
}
错误边界只使用与生产环境,在开发环境下还是会报错。
扩展9-组件之间通信方式的总结
1.组件见的关系
- 父子
- 兄弟
- 子孙
2.组件之间的通信方式
props: (1)chilren props (2)render props
消息订阅-发布
集中式管理 redux
conText 生产者-消费者模式
3.比较好的搭配
- 父子组件:props
- 兄弟组件:消息订阅预发布、集中式管理
- 祖孙组件:消息订阅与发布、集中式管理、conText(开发用的少,封装插件用的多)
41 react-router6
安装:npm i react-router-dom
(41-1)router6的基本使用
默认使用(不区分大小写):<Route path=“/about” element={} />
区分大小写使用:<Route caseSensitive path=“/about” element={} />
import React from "react";
import {NavLink,Routes,Route } from "react-router-dom";
import Home from "./pages/Home";
import About from "./pages/About";
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">
{/* 在react考路由链接切换组件 */}
<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/>} />
</Routes>
</div>
</div>
</div>
</div>
</div>
);
}
对比route5:
(1)用Routes代替Switch包裹Route
(2)用<Route path="/home" element={<Home/>} />
代替<Route path="/about" component={About}/>
(41-2)router6的重定向
基本使用(默认push形式跳转):<Navigate to={‘/about’}/>
replace形式跳转 :<Navigate to={‘/about’} replace={true}/>
1.注册路由时使用
{/* 注册路由 */}
<Routes>
<Route path="/about" element={<About/>} />
<Route path="/home" element={<Home/>} />
<Route path="/" element={<Navigate to={'/about'}/>} />
</Routes>
2.路由跳转时使用
import React ,{useState}from 'react'
import {Navigate} from 'react-router-dom'
export default function Home() {
const [state,setState] = useState({num:1})
function addStateNum(){
setState({num:state.num+1})
}
return (
<div>
<h2>Home组件的内容</h2>
//值为2的时候跳转到about组件
{state.num === 2?<Navigate to='/about'/>:<h4>当前sum的值为:{state.num}</h4>}
<button onClick={addStateNum}>点我+1</button>
</div>
)
}
(41-3)router6的NavLink高亮效果
基本使用:
<NavLink className={({isActive})=>isActive?'weijiamin':'list-group-item'} to="/about"> about</NavLink>
优化:
function computedClassName({isActive}){
return isActive?'list-group-item weijiamin':'list-group-item'
}
{/* 在react考路由链接切换组件 */}
<NavLink className={computedClassName} to="/about"> about</NavLink>
<NavLink className={computedClassName} to="/home">Home</NavLink>
(41-4)router6的路由表的使用
import {useRoutes} from "react-router-dom";
路由表
const elements=useRoutes([
{path:'/about',element:<About/>},
{path:'/home',element:<Home/>},
{path:'/',element:<Navigate to={'/about'}/>},
])
<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">
{/* 注册路由 */}
{elements}
</div>
</div>
</div>
</div>
</div>
一般是把路由表的数据放在文件 src/routers/index.js 中,再在组件中引入
import Home from "../pages/Home";
import About from "../pages/About";
import { Navigate } from "react-router-dom";
const routes = [
{ path: "/about", element: <About /> },
{ path: "/home", element: <Home /> },
{ path: "/", element: <Navigate to={"/about"} /> },
];
export default routes;
import React from "react";
import {NavLink,useRoutes} from "react-router-dom";
import routes from "./routes";
export default function App() {
const elements=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">
{/* 注册路由 */}
{/* <Routes>
<Route path="/about" element={<About/>} />
<Route path="/home" element={<Home/>} />
<Route path="/" element={<Navigate to={'/about'}/>} />
</Routes> */}
{elements}
</div>
</div>
</div>
</div>
</div>
);
}
(41-5)router6嵌套路由
注册路由表
import Home from "../pages/Home";
import About from "../pages/About";
import News from "../pages/Home/News";
import Message from "../pages/Home/Message";
import { Navigate } from "react-router-dom";
import Detail from "../pages/Home/Message/Detail";
const routes = [
{
path: "/about",
element: <About />,
},
{
path: "/home",
element: <Home />,
children: [
{
path: "news",
element: <News />,
},
{
path: "message",
element: <Message />,
children: [
{
// 声明接收params参数
path: "detail/:id/:title/:content",
element: <Detail />,
},
],
},
],
},
{
path: "/",
element: <Navigate to={"/about"} />,
},
];
export default routes;
import React from 'react'
import {NavLink,Outlet} from 'react-router-dom'
<div>
<h2>Home组件的内容</h2>
<div>
<div className="list-group">
<NavLink className="list-group-item" to="/home/news">about</NavLink>
<NavLink className="list-group-item" to="/home/message">about</NavLink>
</div>
<div>
{/* 指定路由组件呈现的位置 */}
<Outlet/>
</div>
</div>
</div>
(41-6)router6路由传参
1.传递params参数:
声明传递params参数
<NavLink to={`detail/${item.id}/${item.title}/${item.content}`}>{item.title}</NavLink>
声明接收params参数
{
path: "message",
element: <Message />,
children: [
{
// 声明接收params参数
path: "detail/:id/:title/:content",
element: <Detail />,
},
],
},
接收params参数
1.传递search参数:
声明传递search参数:
<NavLink to={`detail?id=${item.id}&title=${item.title}&content=${item.content}`}>{item.title}</NavLink>
声明接收search参数:
{
path: "message",
element: <Message />,
children: [
{
// 声明接收params参数
path: "detail",
element: <Detail />,
},
],
},
接收search参数:
setSearch的用法:
import React from "react";
import { useSearchParams } from "react-router-dom";
export default function Detail() {
const [search,setSearch] = useSearchParams()
return (
<div>
<ul>
<li>
<button onClick={()=>{setSearch('id=11&title=哈哈&content=呜呜')}}>点我更新收到的serch参数</button>
</li>
{/* 调用serrch身上的get方法来获值 */}
<li>{search.get('id')}</li>
<li>{search.get('title')}</li>
<li>{search.get('content')}</li>
</ul>
</div>
);
}
3.传递state参数
声明传递state参数:
<NavLink to="detail" state={{id:item.id,title:item.title,content:item.content}}>
{item.title}
</NavLink>
声明接收state参数:
{
path: "message",
element: <Message />,
children: [
{
// 声明接收params参数
path: "detail",
element: <Detail />,
},
],
},
接收state参数:
(41-7)编程式路由导航
可以用于路由组件和非路由组件
import { NavLink,Outlet,useNavigate} from "react-router-dom";
const navigate = useNavigate()
function toDeatil(item){
//第一个参数是要跳转的路径、第二个参数是一个配置对象
navigate('detail',{
replace:false,
//目前只能携带state参数 如果要携带其他类型的参数,拼在路径中
state:{
id:item.id,
title:item.title,
content:item.content
}
})
}
function goForward(){
navigate(1)
}
function goBack(){
navigate(-1)
}
<button onClick={()=>{toDeatil(item)}}>点我查看详情</button>
<button onClick={goForward}>前进</button>
<button onClick={goBack}>后退</button>
(41-8)Hook-useInRouterContext
用于判断当前组件是否处于
<Router>
的上下文环境中,如果是,则返回true否则返回false
import { useInRouterContext} from "react-router-dom";
console.log(useInRouterContext())
(41-9)Hook-useNavigationType
作用:用于返回当前的导航类型(用户是如何来到当前页面的)
返回值:POP、PUSH、REPLACE
备注:POP是指在浏览器中直接打开了这个路由组件(刷新页面)
import { useNavigationType} from "react-router-dom";
console.log(useNavigationType())
(41-10)Hook-useOutlet
作用:用于返回当前组件中渲染的嵌套路由
说明:
如果嵌套的路由组件还没有挂载,则返回null
如果嵌套的路由组件已经挂载,则展示嵌套的路由对象
import { useOutlet} from "react-router-dom";
console.log(useOutlet())
(41-11)Hook-useResolvePath()
作用:给一个url值,解析其中的:path,search,hash值
import { useResolvePath} from "react-router-dom";
console.log(useResolvePath('/user?id=88&name=777'))
输出内容:
{hash: “”
pathname: “/user”
search: “?id=88&name=777”}