一、React 入门
1.1 特点
高性能、声明式、组件化、单向响应的数据流、JSX扩展、灵活
1.2 React初体验
<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>01.React初体验</title>
<!--1、引入react核心文件-->
<script src="./js/react.development.js"></script>
<!--2、引入react-dom文件-->
<script src="./js/react-dom.development.js"></script>
</head>
<body>
<!--3、创建容器元素-->
<div id="root"></div>
<script>
//4、通过ReactDOM来创建挂载的根容器
const root = ReactDOM.createRoot(document.querySelector('#root'));
//5、通过root.render来指定呈现的内容
root.render('尚硅谷');
</script>
</body>
1.3 注意事项
- root.render可以使用多次,但最后一次render会将之前的覆盖掉。
- 不要同时挂载相同的容器(会报错)
- 在一个页面中,可以挂载多个不同的容器
- render中
可以渲染
的内容类型(数组类型、数值类型、字符串类型) - render中
不可以渲染
的内容类型,会报错 (对象和函数不能渲染,但是函数中可以return让render能渲染的内容类型,json对象字符串可以渲染) - render中
不会报错,但不会渲染内容
的类型
//5、通过root.render来指定呈现的内容
//5.1 undefined类型
//root.render(undefined);
//5.2 空
//root.render();
//5.3 null
//root.render(null);
//5.4 true
//root.render(true);
//5.5 false
root.render(false);
- 不建议将根容器指定为body元素或html元素(会报错)
二、虚拟DOM
2.1 创建
语法:
*React.createElement(标签名,标签属性对象,值)*
标签属性:需要传入一个对象,设置:键值对,不设置:
空对象{}或者null
- 注意:如果想要创建的标签中嵌套其他子标签时
语法:
*React.createElement(标签名,标签属性,子节点1,子节点2...)*
同级标签
*React.createElement(标签名,标签属性,React.createElement(标签名,标签属性.'内容')*
子标签
-
包含多个元素:从第三个参数开始即是包裹的内容。或将第三个参数设置为数组,
如果第三个参数设置为数组,数组元素需要增加唯一
key属性
。
2.2 JSX语法
2.2.1 jsx基本语法
//6、render可以接收虚拟DOM,会将虚拟DOM转为真实Dom
//6.1 渲染指定元素
// root.render(<div>我是一个DIV</div>)
一个元素包裹多个元素
如果元素是单标签,必须闭合
如果元素较多可以换行,建议这样做添加 ()
JSX中标签必须闭合,单双标签都必须
JSX有且只能是一个根元素
可以使用包裹标签:包裹标签不会被编译(不会渲染),只是起到包裹的作用。
<React.Fragment></React.Fragment>
2.2.2 注释
必须使用 {} 包裹
- 单行注释
{ // 我是注释 }
- 多行注释
{ /* 添加多行注释*/ }
2.2.2 插值表达式
语法 {}
{指定动态的属性值和标签体文本
}中填写变量值。
- 可以是任意基本类型数据值
- 如果{}内的是用双引号,单引号,反引号包裹,则是字符串;
- 如果{}内是数字,即是要输出该数字;
- 如果{}内是undefined,null,true,false
不会进行渲染
。
- 可以是一个 JS 数组,会直接进行展开渲染(不包含逗号 ),
不允许是对象
- 可以是 JS 的表达式,
不允许在{}内写JS语句
- 可以是 react 元素对象
2.3.4 条件渲染
在React中实现条件渲染的方式有以下三种:
-
if…else
当判断的条件较多时,我们还可以采用函数的方式
-
三元表达式
-
逻辑短路
注意: 只适用于只在一种情况下才有界面的情况(也就是说当条件满足时即有界面否则没有,并非二选一)
-
表达式1 && 表达式2
如果表达式1对应的boolean为true, 返回表达式2的值
如果表达式1对应的boolean为false, 返回表达式1的值
-
表达式1 || 表达式1
如果表达式1对应的boolean为true, 返回表达式1的值
如果表达式1对应的boolean为false, 返回表达式2的值
-
const root = ReactDOM.createRoot(document.querySelector("#root"));
const vDom = isLogin && <h2>恭喜您,登录成功</h2>;
root.render(vDom);
2.3.5 属性渲染
作用:为属性指定数据,可以直接使用{},不需要加双引号
constimgUrl = "https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png"
const w = 110;
<img src={imgUrl} width={w} />
2.3.6 列表渲染
react中可以将数组中的元素依次渲染到页面上,可以直接往数组中存储react元素,推荐使用数组的 map 方法
注意:必须给列表项添加唯一的 key 属性, 推荐使用id作为key, 尽量不要用index作为key
- 数组是可以被直接展开的
- 数组中的元素也可以是JSX
- 如果使用map,map接收的是一个箭头函数,{}:如果要返回内容需要使用return,()返回的内容
2.3.7 样式渲染
-
行内样式(style)
style属性值是一个对象,
带有"-"的属性名需要写成驼峰命名
当属性值单位为px的时候,且值为number类型,可以省略单位,直接写成数值
<div style={{width: 100,height: '100px', background: 'red',marginLeft: '100px'}}>尚硅谷</div>
-
非行内样式(className)
<p className="box">尚硅谷</p>
2.3.7 关于事件
- 绑定事件
React 元素的事件处理和 DOM 元素的很相似,但是有一点语法上的不同:
React 事件的命名采用小驼峰式(camelCase)
,而不是纯小写。比如:onClick、onFocus 、onMouseEnter
使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串
const div = <div onClick={事件处理函数}></div>
- 事件对象
React 根据 W3C 规范来自定义的合成事件, 与原生事件不完全相同
-
处理好了浏览器的兼容性问题
-
阻止事件默认行为不能使用 return false, 必须要调用: event.preventDefault()
-
有自己特有的属性, 比如: nativeEvent – 原生事件对象
-
<input>
的 change 监听在输入过程中触发, 而原生是在失去焦点才触发原理:内部绑定的是原生 input 事件
在事件对象中属性虽然很多,但是一点要记住的有两点:
- 1、实际发生事件的目标对象(由于最终虚拟DOM要转换成真实DOM,所以e.target获取的都是真实DOM):e.target
console.log(e.target)
console.log(e.target.innerHTML) - 2、关于事件默认行为的取消:
e.preventDefault()
三、React组件
1.1 函数组件
-
定义
要求组件名称的首字母需要大写
function 组件名称(){ // 返回一段jsx的视图结构 return <div></div> }
-
调用
-
<组件名称></组件名称>
或
-
<组件名称 />
-
- 组件名首字母必须大写. 因为react以此来区分组件元素/标签 和 一般元素/标签
- 组件内部如果有多个标签,必须使用一个根标签包裹.只能有一个根标签
- 必须有返回值.返回的内容就是组件呈现的结构, 如果返回值为 null,表示不渲染任何内容
- 会在组件标签渲染时调用, 但不会产生实例对象, 没有状态
1.2 类组件
- 定义
class 类组件名 extends React.Component{
render(){
return <div></div>
}
}
-
解释
- extends:继承
- React.Component: react提供的一个类组件的基础类(父类)
-
注意
- render必须是一个方法
- return的必须是组件类的视图结构
-
特点
- 类组件有自己的生命周期和状态数据
-
调用
-
<组件名称></组件名称>
或
-
<组件名称 />
-
* 使用类式组件的注意事项:
* 1、组件名首字母大写
* 2、每一个类式组件的继承父类都是React.Component
* 3、类式组件中必须要有render方法,通过render方法来进行返回虚拟DOM元素对象
* 4、类式组件调用时相当于ES6中的new 类()的写法,产生组件实例化对象,
* 在组件实例化对象中可以使用像state、props、ref等属性
* 5、在return返回的虚拟DOM元素对象中必须要有一个绝对唯一的根标签,
如果在return后面换行书写,需要添加一个()
* 6、想要给组件实例化对象身上添加初始化的属性,则可以使用constructor构造器,
如果没有明确写出,则系统会默认分配一个空的构造器
如果明确写出构造器,则在构造器中的第一句话需要调用super()方法
* 7、如果类中既有constructor,又有render方法,
则先执行constructor,在执行render,且这两个方法都是自动执行
* 8、其中的this均指向类组件的实例化对象
四、React组件实例的三大核心属性
2.1 state
值是一个对象(可以包含多个key-value)
基本使用
-
初始化 state 两种方法
- 构造器中:
this.state = {xxx: 2}
- 类体中:
state = {xxx: 2}
- 构造器中:
-
读取state数据
- this.state.xxx
-
更新state数据
- 不能直接更新state数据
- 必须
this.setState({ 要修改的属性数据 })
异步方法
-
state中的事件绑定
注意:
在虚拟DOM元素身上绑定事件的时候,事件名遵循小驼峰写法。
事件函数直接使用{函数名}的写法,不能像原生JS中写入一个字符串,
而且也不要在{}中直接调用函数,这样会脱离事件,页面加载后直接运行函数内容。
- 类中方法的this
问题: 类中定义的事件回调方法中
this
是undefined
, 无法更新state原因:
事件回调都不是组件对象调用的, 都是事件触发后,直接调用的,
class中所有方法都使用严格模式, 所以方法中的this就是undefined
解决办法1 - 包裹箭头函数
原因: render中的this是组件对象, 处理函数是我们通过组件对象来调用的解决办法2 - bind绑定this
原因: 构造器中的this是组件对象, 将处理函数通过bind绑定为了组件对象解决办法3 - 箭头函数
原理: 利用bind给事件回调绑定this为组件对象(render中的this)
选择:
- 一般用
箭头函数
方式, 编码简洁- 如果要传递特定的数据, 选择用
包裹箭头函数
方式
问题1:如果state中的不光有isGood一个属性状态,那么在修改的时候属性是合并还是替换呢??
答:
state中进行修改状态的时候,其实是一种合并属性,而并不是替换属性;
说白了就是替换同名的属性,不同名的仍然保留。
问题2:类中的构造器被调用了几次??
答:
当new一个类的时候,就会产生一个实例对象,当在new的过程中代码中出现了构造器就会执行,
所以构造器执行了几次,需要看页面中有几个类的组件实例,有几个组件实例,那么构造器就会执行几次。
问题3:类中的render被调用了几次??
答:1 + n次
1次是初始化时候的次数,n是状态更新的次数
问题4:类中的changeMood被调用了几次??
答:事件发生几次,这个函数就会执行几次
问题5:类中添加构造器的作用是什么??
答:需要借助构造器进行初始化状态,还可以解决this指向问题
2.2 props
- 介绍
props
是 『properties』 的缩写, 也是类式组件实例对象的属性
- 作用
如果想要在组件的外部向组件的内部传数据,那么就可以利用props
-
语法: <组件 属性名=“属性值”/>
在类组件中使用this.props.data就可以取得属性名中的值
在函数组件中使用props.data就可以取得属性名中的值注意:其中属性名中可以任意指定但是组件中的和获取props要对应好了
-
批量传递props 使用
...扩展运算符
语法:<组件 {...obj}/>
2.2.1 props中的children属性
props.children可以获取组件标签中间的内容
//单标签
<Student {...item} } children = 值/>
//双标签
<Student>值 通过props.children获取<Student/>
2.2.2 props 的特点
-
可以给组件传递任意类型的数据
-
props 是只读的对象,只能读取属性的值,不要修改props
-
可以通过
...
运算符来将对象的多个属性分别传入子组件 -
如果父组件传入的是动态的 state 数据, 那一旦父组件更新 state 数据, 子组件也会更新
2.3 refs
refs 提供了一种允许我们访问 DOM 节点或在 render 方法中创建的 React 元素的写法。
ref是虚拟DOM对象的一个属性。
作用:方便在组件中获取真实DOM对象
refs的语法场景有三种:字符串形式、回调函数形式、createRef形式
- 字符串形式的ref【最简单,但是不推荐】
语法:<标签 ref=“名称”>
通过this.refs对象来获取值
- 回调函数形式的ref【官方推荐】
语法:
<标签 ref={currentNode => this.名称 = currentNode}>
获取:this.名称
回调函数中接受一个参数,参数名可以自定义,表示的是当前的节点对象。
ref的回调函数写法:ref={形式参数名=>this.属性名 = 形式参数名}
其中形式参数名在函数身上可以自定义,表达当前所在的虚拟DOM元素对象
this后面的属性名则直接添加在类组件实例化对象身上
ref属性只有值为字符串的形式的时候,才会需要使用this.refs的写法
除此之外后两种ref的用法都会直接添加在类的组件实例化对象身上
思考:ref的回调函数具体执行了几次??
摘自官方文档:
如果
ref
回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数null
,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,
所以 React 清空旧的 ref 并且设置新的。
通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,
但是大多数情况下它是无关紧要的。
- createRef的使用【
官方最推荐的
】
语法:
React.createRef()
<script type="text/babel">
class Demo extends React.Component{
/**
* React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是"专人专用"的
* **/
myRef = React.createRef();// {current:null}
myRef2 = React.createRef();
render(){
return (
<div>
<input ref={this.myRef} type="text"/>
<button onClick={this.ClickFun}>提示输入数据</button>
<input ref={this.myRef2} onBlur={this.BlurFun} type="text"/>
</div>
)
}
ClickFun = ()=>{
alert(this.myRef.current.value);
}
BlurFun = ()=>{
alert(this.myRef2.current.value);
}
}
//渲染组件到页面
ReactDOM.render(<Demo/>,document.getElementById("app"))
</script>
五、React中的事件处理
虽然之前咱们可以使用ref来标识想要的节点对象,但是官网提示【勿过度使用 Refs】,可以通过event.target得到发生事件的DOM元素对象,一般用于当发生事件的元素恰好是你要操作的元素就可以避免使用ref。
总结:
通过onXxx属性指定事件处理函数(注意大小写)
React使用的是自定义(合成)事件, 而不是使用的原生DOM事件
React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)
通过
event.target得到发生事件的DOM元素对象
六、非受控组件与受控组件
- 非受控组件
表单项不与state数据相向关联, 需要手动读取表单元素的值
借助于 ref获取真实DOM,在通过value获得输入值
,使用原生 DOM 方式来获取表单元素值
非受控组件: 表单项不与 state 数据相向关联, 需要手动读取表单元素的值
- 受控组件
组件中的表单项根据state状态
数据动态初始显示和更新显示, 当用户输入时实时同步到状态数据中
也就是实现了页面表单项与 state 数据的双向绑定
实现方式
- 在 state 中添加一个状态,作为表单元素的value值(控制表单元素值的来源)
- 给表单元素绑定 change 事件,将 表单元素的值 设置为 state 的值(控制表单元素值的变化)
{/* react中虚拟DOM中的name的值一定是和state属性相关的值 */}
<p>用户名:<input type="text" name='user' value={user} onChange={this.changeForm} /></p>
{/* a是咱们自定义的属性名,后期获取的时候需要使用getAttribute()方法 */}
<p>用户名:<input type="text" a='user' value={user} onChange={this.changeForm} /></p>
{/* 在DOM元素对象身上还可以通过data-a的形式添加,后期获取的时候使用dataset.a */}
<p>用户名:<input type="text" data-a='user' value={user} onChange={this.changeForm} /></p>
changeForm = (e) => {
this.setState({
[e.target.name]: e.target.value
[e.target.getAttribute('a')]: e.target.value
[e.target.dataset.a]: e.target.value
})
}
七、高阶函数
本身也是一个函数
特点:接收函数类型的参数或者
返回一个新函数
function fn(f){
return ()=>{
}
}
fn1(()=>{})
const p = new Promise((resolve,reject)=>{});
then(value=>{},reason=>{})
p.catch(reason=>{})
setTimeout(()=>{}) setInterval(()=>{})
let arr = [1,2,3];
arr.foreach(()=>{})
arr.map((item,index)=>{})
函数类型参数:Promise、then、catch、setTimeout、setInterval、forEach、map、filter、every、some等
返回函数:闭包函数、bind
function fun(a, b) {
function fun1() {
a++;
return a;
}
return fun1;
}
fun(10,20);
<p>用户名:<input type="text" value={user} onChange={this.saveData('user')} /></p>
saveData = (type) => {
return (e) => {
this.setState({
[type]: e.target.value
})
}
}
八、React脚手架
1、 脚手架的安装与启动
- 全局安装
npm i -g create-react-app
- 切换目录
create-react-app 目录名称
- 进入目录
cd 目录名称
- 启动项目
npm start
2、脚手架目录说明
- public 脚手架服务的网站根目录(静态资源目录)
- index.html :webpack打包html的模板文件
- src 源代码开发的目录
- index.js 打包的入口文件
- .gitignore git的配置忽略文件
3、 props 校验 (了解)
允许在创建组件的时候,就指定 props 的类型、格式等
作用:捕获使用组件时因为props导致的错误,给出明确的错误提示,增加组件的健壮性
实现方式:
- 导入 prop-types 包 (脚手架自带,无需额外安装)
- 使用propTypes来给组件的props添加校验规则
4、 props 设置类型和设置默认值
作用:给 props 设置默认值,在未传入 props 时生效
在class类中实现方式:
import React, { Component } from 'react'
//1、导包
import types from 'prop-types'
export default class Stu extends Component {
//2、在类内使用静态属性 propTypes 来约束
//注意:这个属性不能修改
static propTypes = {
name: types.string.isRequired,
age: types.number,
hobby: types.array
}
//3、为传入的属性设置默认值
static defaultProps = {
name: '李四',
age: 24
}
render() {
let { name, age, hobby } = this.props;
return (
<div>
<p>姓名:{name}</p>
<p>年龄:{age}</p>
<p>爱好:{hobby.join(',')}</p>
</div>
)
}
}
5、函数式组件中使用props校验
App.jsx
import Stu from './Stu'
export default function App() {
//声明一个对象
let info = {
// name: '张三',
age: 23,
hobby: ['吃饭', '睡觉', '打豆豆']
}
return (
<div>
<Stu {...info} />
</div>
)
}
Stu.jsx
//1、导包
import PropTypes from 'prop-types'
//2、为Stu设置propTypes属性
Stu.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
hobby: PropTypes.array
}
Stu.defaultProps = {
name: '王二麻子'
}
export default function Stu(props) {
let { name, age, hobby } = props;
return (
<div>
<p>姓名:{name}</p>
<p>年龄:{age}</p>
<p>爱好:{hobby.join('-')}</p>
</div>
)
}
九、React组件的生命周期
组件对象从创建到死亡所经历的特定的阶段
React组件对象包含一系列钩子函数(生命周期回调函数),在特定的时刻调用
我们在定义组件时,在特定的生命周期回调函数中做特定的工作
全称:生命周期函数或者是生命周期钩子,其中主要研究的就是组件生命周期的三个阶段
概念:生命周期函数指在某一时刻组件会自动调用执行的函数
本质
就是组件内的一些方法
,当然在这里咱们讨论的是类式组件
,因为函数式组件中有相应的Hook函数
特点
就是能够自动执行
其中包括ComponentDidMount()
、ComponentDidUpdate()
、ComponentWillUnmount()
1.1 挂载阶段
流程: constructor ==> render ==> componentDidMount
子父类组件父组件渲染:父构造->父render->子构造->子render->子完成挂载->父完成挂载
父constructor =>父render=>子constructor =>子render=>子componentDidMount===>父componentDidMount
触发: ReactDOM.render(): 渲染组件元素
-
constructor: 创建组件时,最先执行
一般用于:
-
- 初始化state
-
-
为事件处理程序绑定this
this.xx = this.xx.bind(this)
-
-
-
render: 每次组件渲染都会触发
- 注意: 不能在render中调用setState()
-
componentDidMount: 组件挂载(完成DOM)渲染后
注意:
这个生命周期钩子从虚拟DOM转化成真实DOM之后,挂载在root元素之后,只执行这么一次
除非这个组件被卸载之后,重新挂载,还会在此执行,且还是一次
一般用于:
-
- 定时器
- 发送网络请求 axios
- 订阅频道
-
- DOM操作 (只要是组件一上来就要做的事情,都应该写在挂载成功的回调中)
-
1.2 更新阶段
componentDidUpdate: 组件更新(完成DOM渲染)后
流程: render ==> componentDidUpdate
触发: setState() , 组件接收到新的props
如果是父组件中嵌套子组件,
父组件中的状态被更新了:
父组件render->子组件render->子组件componentDidUpdate->…->父组件componentDidUpdate
子组件中的状态被更新了:那么只有这个子组件中的render->这个子组件中的componentDidUpdate执行
1.3 卸载阶段
componentWillUnmount: 组件卸载(从页面中消失) 执行清理操作
测试:当我们更改组件内容并保存之后,React会将之前的组件卸载并重新挂载一个新的组件
顺序:自己的构造->自己的render->卸载钩子函数->重新挂载
constructor->rende->componentWillUnmount->componentDidMount
父组件内容变化时触发
子组件内容变化触发:
例如:
- 清空定时器
- 取消订阅
流程: componentWillUnmount
触发: 不再渲染组件
十、Hook
- Hook 是 React 16.8 之后的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性
- Hook 也叫钩子,
本质就是函数
,能让你在函数式组件
中使用状态
和生命周期函数
等功能 - Hook 语法 基本已经代替了类组件的语法,后面的 React 项目就完全是用 Hook 语法了
5.1 useState
用来定义状态数据,可以多次调用
, 产生多个状态数据
mport React, { useState } from 'react'
export default function App() {
// console.log(React.useState);
/**
* React.useState()方法返回一个数组,
* 数组中的第一个元素记录state中的状态数据
* 数组中的第二个元素是一个方法,用来修改状态数据,类似于setState
*/
//对象解构赋值
//注意:React.useState()方法可以在导入react的时候直接做解构,后面就可以直接使用useState()方法
let [isLogin, setLogin] = useState(true);
function changeLogin() {
setLogin(!isLogin);
}
return (
<div>
<p>现在的状态:{isLogin ? '已登录' : "未登录"}</p>
{/* 按钮中直接调用函数,不需要写this */}
<button onClick={changeLogin}>修改状态</button>
</div >
)
}
5.2 useRef
功能和类式组件中的createRef()方法相似
,可以帮助我们在函数式组件中方便获取真实的DOM元素对象。
import React, { useRef } from 'react'
export default function App() {
let ipt = useRef();
let box = useRef();
let show = () => {
let v = ipt.current.value;
box.current.innerHTML += '用户名:' + v + '<br/>';
}
return (
<div>
<p>
用户名:<input type="text" ref={ipt} />
<button onClick={show}>获取</button>
</p>
<hr />
<div id="result" ref={box} style={{ width: 500, height: 500, border: '1px dashed #ccc' }}></div>
</div>
)
}
5.3 useEffect
是一个高阶函数,可以在一个组件中多次使用
,相当于componentDidMount(组件挂载完成),
componentDidUpdate(组件更新完成) 和 componentWillUnmount(组件将要卸载之前)
的组合
语法:React.useEffect(()=>{},[])
当如果不希望在组件更新时再次执行,只希望在组件挂载时执行,则需要给useEffect方法设置第二个参数为数组
数组
参数的含义:设置了哪些状态数据修改之后,才会执行回调,
分为两种情况:
- 数组的参数如果没有任何值的话,表示什么状态数据都不更新,回调函数不执行
- 当数组中传入state的状态数据,表示当这个状态数据被更新了的时候,才执行回调函数
import React, { useEffect, useState } from 'react'
export default function App() {
//声明状态
let [count, setCount] = useState(0);
let [sex, setSex] = useState(true);
//调用
//第一个参数为函数,箭头函数或匿名函数均可
//这个函数的作用等效于ComponentDidMount和ComponentDidUpdate
//这个方法可以执行多次
useEffect(function () {
console.log('我既执行ComponentDidMount效果,也执行ComponentDidUpdate效果-1');
});
useEffect(function () {
console.log('我既执行ComponentDidMount效果,也执行ComponentDidUpdate效果-2');
});
//当如果只想要ComponentDidMount效果,则需要给useEffect方法传入第二个参数,为数组
//数组的参数如果没有任何元素的话,表示不更新状态
/* useEffect(() => {
console.log('我只执行ComponentDidMount效果');
}, []) */
//如果想要某一个状态数据改变的时候,需要执行,则将这个状态数据添加即可
/* useEffect(() => {
console.log('我只执行ComponentDidMount效果');
}, [sex]) */
//如果想要模拟ComponentwillUnmount
//在脚手架中当尝试修改组件之后,react会将组件先卸载,然后再来一个新的重新挂载
useEffect(() => {
return () => {
console.log('组件将要被卸载时...')
}
})
let changeAdd = (num) => {
return () => {
count = count + num;
setCount(count);
}
}
let changeSex = () => {
setSex(!sex);
}
return (
<div>
<ul>
<li>
<p>数值:{count}</p>
<button onClick={changeAdd(10)}>新增</button>
</li>
</ul>
<ul>
<li>
<p>性别:{sex ? '男' : '女'}</p>
<button onClick={changeSex}>修改</button>
</li>
</ul>
</div>
)
}
但是使用useEffect()方法的时候有一个很重要的细节
,那就是useEffect方法的第一个函数不能是一个异步函数
也就是说,不能出现async
,原因是如果设置了async,返回值就成了promise对象,非一个函数了。
如果想要在useEffect()方法中添加异步代码,需要在函数中在单独声明一个函数
//如果想要模拟ComponentwillUnmount
//在脚手架中当尝试修改组件之后,react会将组件先卸载,然后再来一个新的重新挂载
useEffect(() => {
return () => {
async function main() {
console.log('在这里可以执行异步代码逻辑...');
}
main();
}
})
使用Hook 注意:
只在最顶层使用 Hook,不要在条件或循环中
只在React组件函数内部中调用 Hook, 不要在组件函数外部调用
十一、脚手架配置代理(proxy)的方式
CORS:
请求url:http://www.baidu.com
发送url:http://www.jd.com
response.setHeader('Access-Control-Allow-Origin','*');
-
通过express快速搭建一个服务
-
创建一个图书组件
import React, { useEffect } from 'react'
import axios from 'axios'
export default function Books() {
//在组件挂载完成之后,相当于ComponentDidMount()方法,进行ajax请求
useEffect(() => {
//ajax请求
async function main() {
let result = await axios.get('http://127.0.0.1:5000/books');
console.log(result)
}
main();
}, [])
return (
<div>Books</div>
)
}
- 在package.json文件中添加一行配置
"proxy":"http://127.0.0.1:5000"
重启服务
这一步非常重要
- 修改请求的url地址
async function main() {
let result = await axios.get('/books');
console.log(result)
}
main();
- 但是这种方式相对单一,只能配置一个代理,如果想要配置多个代理,这就不行了
- 解决办法:需要在
src
的根目录中创建一个setupProxy.js
文件,注意文件名不能修改
- 安装一个包:npm i http-proxy-middleware
//1、导包
const proxy = require('http-proxy-middleware');
//2、导出一个方法
module.exports = function (app) {
app.use(
proxy.createProxyMiddleware('/api', {
//请求的目的地址
target: 'http://127.0.0.1:5000',
//控制服务器收到的请求头中Host的值
changeOrigin: true,
//重写路径
pathRewrite: {
'^/api': ''
}
})
)
}
- 修改代码
import React, { useEffect, useState } from 'react'
import axios from 'axios'
export default function Books() {
let [booklist, setBookList] = useState([]);
useEffect(() => {
//ajax请求
async function main() {
let result = await axios.get('/api/books');
if (result.status >= 200 && result.status < 300) {
setBookList(result.data)
}
}
main();
}, [])
return (
<div>
<h3>图书列表</h3>
<hr />
<ul>
{
booklist.map(item => {
return <li key={item.id}>{item.name}</li>
})
}
</ul>
</div>
)
}
- 再次基础之上添加模糊搜索功能
- Book.jsx
import React, { useState } from 'react'
import Header from './Header/Header'
import Main from './Main/Main'
import axios from 'axios'
export default function Books() {
let [booklist, setBookList] = useState([]);
let searchBookList = async (v) => {
let result = await axios.get('/api/books', {
params: {
name: v
}
});
setBookList(result.data)
}
return (
<div>
<h3>图书列表</h3>
<hr />
<Header searchBookList={searchBookList} />
<Main booklist={booklist} />
</div>
)
}
Header.jsx
import React, { useRef } from 'react'
export default function Header(props) {
let { searchBookList } = props;
let bookname = useRef();
let searchBook = () => {
//获取文本框中的数据
let book = bookname.current.value;
if (book === '') {
alert('请输入书名');
return;
}
searchBookList(book)
//清空文本框中的数据
bookname.current.value = ''
}
return (
<div>
请输入图书名称:<input type="text" ref={bookname} />
<button onClick={searchBook}>搜索</button>
</div>
)
}
Main.jsx
import React from 'react'
export default function Main(props) {
let { booklist } = props;
return (
<ul>
{
booklist.map(item => {
return <li key={item.id}>{item.name}</li>
})
}
</ul>
)
}
十二、组件间的通信
react中的组件间的通信有三种:父子间、兄弟间、祖孙间
2.1 父子间组件通讯(props)
2.1.1 父->子
直接通过props属性向子组件传递数据,可以是一般的纯数据 <子组件 num={20}/>
也可以是父组件中的状态数据,当父组件中的状态数据更改,也会影响到子组件
2.1.2 子->父
默认情况下子是被动接收数据的一方,是没有办法直接修改父组件中的数据,但是如果父组件传递一个可以修改父
组件的状态数据的方法,通过props的形式给子组件,那么子组件修改的值是可以 影响父组件中的状态数据
2.2 祖孙间(任意后代)组件通讯(context)
2.2.1 祖-> 孙
- 调用 React. createContext() 创建 context 对象 【谁主动传递数据,就在谁的身上添加这个对象】
const context = React.createContext()
- 在
外部组件
中使用 context 上的 Provider 组件作为父节点, 使用value属性定义要传递的值
<context.Provider value={要传递的值}> 提供数据
<div className="App">
<Child1 />
</div>
</context.Provider>
- 孙组件中导入祖组件
import { context } from '../GrandFather/GrandFather'
- 孙组件接收祖组件传递的数据
- 方式1:利用Hook钩子函数中的useContext方法接收传递的数据
import React, { useContext } from 'react'
let name = useContext(context);
console.log(name);
- 方式2:利用context 上的 Consumer组件作为父节点,在其中使用{}添加回调函数写法
import React from 'react'
import { context } from '../GrandFather/GrandFather'
export default function Son() {
return (
<div style={{ width: 400, height: 200, border: '1px dashed green' }}>
<context.Consumer>
{
name => <p>姓名:{name}</p> #name可以自定义,表示提供者提供的value数据
}
</context.Consumer>
</div>
)
}
2.2.2 孙->祖
- 将祖组件中的数据做成状态数据,然后将初始状态数据以及修改状态数据通过value一并传递给孙组件
- 孙组件接收并进行修改
例如:将GrandFather组件中的数据传递给Son组件
GrandFather.jsx
import React, { createContext, useState } from 'react'
import Father from '../Father/Father'
export let context = createContext();
export default function GrandFather() {
// let name = '李四'
let [name, setName] = useState('李四');
let [age, setAge] = useState(20)
return (
<div style={{ width: 400, height: 400, border: '1px dashed #ccc' }}>
<h4>GrandFather组件</h4>
<p>姓名:{name}</p>
<p>年龄:{age}</p>
<hr />
<context.Provider value={[name, setName, age, setAge]}>
<Father />
</context.Provider>
</div>
)
}
Father.jsx
import React, { useContext } from 'react'
import Son from '../Son/Son'
import { context } from '../GrandFather/GrandFather'
export default function Father(props) {
// let { age, changeAge } = props;
let result = useContext(context);
console.log(result)
let updateAge = (num) => {
return () => {
// changeAge(20)
result[3](result[2] + num);
}
}
return (
<div style={{ width: 400, height: 300, border: '1px dashed red' }}>
<h4>Father组件</h4>
<span>年龄:{result[2]}</span>
<hr />
<button onClick={updateAge(100)}>更新年龄</button>
<hr />
<Son />
</div>
)
}
Son.jsx
// import React, { useContext } from 'react'
import { context } from '../GrandFather/GrandFather'
export default function Son() {
// let name = useContext(context);
let changeName = (f) => {
f('王五');
}
return (
<context.Consumer>
{
value => (
<div style={{ width: 400, height: 200, border: '1px dashed green' }}>
<h4>Son组件</h4>
<p>姓名:{value[0]}</p>
<button onClick={e => changeName(value[1])}>修改姓名</button>
</div >
)
}
</context.Consumer>
)
}
一般应用中不使用, 但一些插件库内部会使用context封装, 如: react-redux
2.3 兄弟间组件通讯(pub/sub)
【注意:兄弟间的组件是非嵌套式写法的组件
】
发布订阅机制: publish(发布者) / subscribe(接受者)
pubsub-js是一个用JS编写的库。
利用订阅发布模式, 当一个组件的状态发生了变化,可以通知其他任意组件更新这些变化
- 安装:
npm install pubsub-js
- 导入
import PubSub from "pubsub-js" // 导入的PubSub是一个对象.提供了发布/订阅的功能
- 在
发送消息的组件
调用
PubSub.publish('频道名称','值');
- 在
接收消息
的组件中调用
PubSub.subscribe('频道名称',(msg,data)=>{
//回调函数中的第一个参数:频道名称
//回调函数中的第二个参数:值
console.log(msg);
console.log(data);
});
- 取消订阅
PubSub.unsubscribe('频道名称');
A.jsx
import React from 'react'
import B from '../B/B'
import C from '../C/C'
export default function A() {
return (
<div>
<B />
<C />
</div>
)
}
B.jsx
import React from 'react'
import PubSub from 'pubsub-js'
export default function B() {
let sendData = () => {
PubSub.publish('keywords', '张三丰');
}
return (
<div style={{ width: 400, height: 100, border: '1px dashed #ccc' }}>
<h4>我是B组件</h4>
<button onClick={sendData}>向C组件发送数据</button>
</div>
)
}
C.jsx
import React, { useEffect, useState } from 'react'
import PubSub from 'pubsub-js'
export default function C() {
let [name, SetName] = useState('')
useEffect(() => {
PubSub.subscribe('keywords', (msg, data) => {
SetName(data);
});
return () => {
PubSub.unsubscribe('keywords');
}
}, [])
return (
<div style={{ width: 400, height: 150, border: '1px dashed red' }}>
<h3>我是C组件</h3>
<p>姓名:{name}</p>
</div>
)
}
十三、react路由版本6
1.基本使用
- 安装
npm i react-router-dom
1.1.引入BrowserRouter
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { BrowserRouter } from 'react-router-dom'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
1.2.路由中routes route Link NaLink的使用
//或者使用Link NavLink 会带激活的样式
<NavLink to="/books">图书</NavLink>
<NavLink to="/news">新闻列表</NavLink>
//使用Routes 包裹route路由
<Routes>
<Route path='/about' element={<About />} >
//二级路由写在一级路由标签中
<Route path='/about/news' element={<News />} />
<Route path='/about/message' element={<Message />} />
</Route>
<Route path='/home' element={<Home />} >
<Route path='/home/news' element={<News />} />
<Route path='/home/message' element={<Message />} />
</Route>
</Routes>
1.3.Outlet 标签
- 在Home和About中对应需要显示二级路由的位置添加一个组件标签
<Outlet/>
作用:当<Route>
产生嵌套时,渲染匹配的子路由组件。
1.4.caseSensitive路由中的大小写敏感
<Route caseSensitive path='/about' element={<About />} ></Route>
1.5.path='*'路由的统配符匹配
当在地址栏中出现不存在的路由规则进行匹配时,页面中没有任何的相关提示,这显然不太友好
我们可以单独为这样的匹配创建一个组件
<Route path='*' element={<NotFound />} />
NotFound.jsx
import React from 'react'
export default function NotFound() {
return (
<div>NotFound</div>
)
}
1.6.Navigate显示默认路由(可以理解为重定向)
- 在Routes中在添加一个路由规则
import { Navigate } from 'react-router-dom'
<Route index element={<Navigate />} />
这里的index是简写形式,如果写全是这样的 index={true}
index本身有默认名称的含义,这句话意思是当访问哪一个路由时,默认显示哪一个二级路由
注意:这句话一定要写在一级路由中
<Route path='/home' element={<Home />} >
<Route path='/home/news' element={<News />} />
<Route path='/home/message' element={<Message />} />
<Route index element={<Navigate to="/home/news" />} />
</Route>
2. 路由组件中的内置Hooks
2.1 useRoutes()
本质:是一个函数
作用:根据设置的路由表(也就是给这个函数中设置数组参数)、可以动态的创建<Routes>
和<Route>
使用:
- 导入
import { useRoutes } from 'react-router-dom'
- 配置路由表
let routeArr = useRoutes([
{
path: '/home',
element: <Home />,
children: [
{
path: 'news',
element: <News />
},
{
path: 'message',
element: <Message />
},
{
index: true,
element: <Navigate to='news' />
}
]
},
{
path: '/about',
element: <About />,
children: [
{
path: 'news',
element: <News />
},
{
path: 'message',
element: <Message />
},
{
index: true,
element: <Navigate to='news' />
}
]
},
{
path: '*',
element: <NotFound />
},
{
path: '/',
element: <Index />
}
])
- 在需要使用路由的位置调用
<div className="col-xs-6">
{routeArr}
</div>
- 考虑到后期可以单独的使用这个路由,咱们可以在src中创建一个routes目录,添加一个route.js文件
import { Navigate } from 'react-router-dom'
import About from '../pages/About/About';
import Home from '../pages/Home/Home';
import News from '../pages/News/News';
import Message from '../pages/Message/Message';
import NotFound from '../pages/NotFound/NotFound';
import Index from '../pages/Index/Index';
export default [
{
path: '/home',
element: <Home />,
children: [
{
path: '/home/news',
element: <News />
},
{
path: '/home/message',
element: <Message />
},
{
index: true,
element: <Navigate to='news' />
}
]
},
{
path: '/about',
element: <About />,
children: [
{
path: '/about/news',
element: <News />
},
{
path: '/about/message',
element: <Message />
},
{
index: true,
element: <Navigate to='news' />
}
]
},
{
path: '*',
element: <NotFound />
},
{
path: '/',
element: <Index />
}
]
- 在APP组件中导入并使用
import routess from './routes/route'
let routeArr = useRoutes(routess)
2.2 useParams()获取params数据
- 作用:返回当前匹配路由的
params
参数的对象
以图书案例为例
APP.jsx
import React from 'react'
import Books from './Component/Books/Books'
import { useRoutes } from 'react-router-dom'
import BookDetails from './Component/BookDetails/BookDetails'
export default function App() {
let routeArr = useRoutes([
{
path: '/',
element: <Books />
},
{
path: '/book/:id',
element: <BookDetails />
}
]);
return (
<div>
{routeArr}
</div>
)
}
Main.jsx
import React from 'react'
import { Link } from 'react-router-dom'
export default function Main(props) {
let { booklist } = props;
return (
<ul>
{
booklist.map(item => {
return <Link to={`/book/${item.id}`} key={item.id}><li>{item.name}</li></Link>
})
}
</ul>
)
}
BookDetails.jsx
import React, { useState } from 'react'
import { useParams } from 'react-router-dom'
import { useEffect } from 'react';
import axios from 'axios';
import './BookDetails.css'
export default function BookDetails() {
let [bookOne, setBookOne] = useState({});
//接收路由中的参数
let { id } = useParams();
useEffect(() => {
async function main() {
let { data } = await axios.get(`/api/books/${id}`);
setBookOne(data)
}
main();
}, [])
return (
<div>
<h1>{bookOne.name}</h1>
<hr />
<span className='autor'>作者:{bookOne.autor}</span>
<p>{bookOne.infro}</p>
</div>
)
}
BookDetails.css
h1 {
text-align: center;
font-size: 40px;
}
.autor {
width: 800px;
height: 30px;
margin: 20px auto;
display: block;
line-height: 30px;
}
p {
width: 800px;
margin: 20px auto;
line-height: 30px;
color: gray;
}
img {
display: block;
margin: 20px auto;
width: 540px;
height: 760px;
}
2.3 useSearchParams()获取Search数据
- 作用:用于读取和修改当前位置的 URL 中的
查询字符串
。
APP.jsx
import React from 'react'
import Books from './Component/Books/Books'
import { useRoutes } from 'react-router-dom'
import BookDetails from './Component/BookDetails/BookDetails'
export default function App() {
let routeArr = useRoutes([
{
path: '/',
element: <Books />
},
{
path: '/book',
element: <BookDetails />
},
]);
return (
<div>
{routeArr}
</div>
)
}
Main.jsx
import React from 'react'
import { Link } from 'react-router-dom'
export default function Main(props) {
let { booklist } = props;
return (
<ul>
{
booklist.map(item => {
return <Link to={`/book?id=${item.id}`} key={item.id}><li>{item.name}</li></Link>
})
}
</ul>
)
}
BookDetails.jsx
import React, { useState } from 'react'
import { useSearchParams } from 'react-router-dom'
import { useEffect } from 'react';
import axios from 'axios';
import './BookDetails.css'
export default function BookDetails() {
let [bookOne, setBookOne] = useState([]);
let [search] = useSearchParams();
let id = search.get('id');
....
}
2.4 useLocation()获取state数据
作用:获取当前 location 信息,功能类似于JS中的location对象
APP.jsx
import React from 'react'
import Books from './Component/Books/Books'
import { useRoutes } from 'react-router-dom'
import BookDetails from './Component/BookDetails/BookDetails'
export default function App() {
let routeArr = useRoutes([
{
path: '/',
element: <Books />
},
{
path: '/book',
element: <BookDetails />
},
]);
return (
<div>
{routeArr}
</div>
)
}
Main.jsx
import React from 'react'
import { Link } from 'react-router-dom'
export default function Main(props) {
let { booklist } = props;
return (
<ul>
{
booklist.map(item => {
return <Link to={`/book`} state={{ id: item.id }} key={item.id}><li>{item.name}</li></Link>
})
}
</ul>
)
}
BookDetails.jsx
import { useLocation } from 'react-router-dom'
let { state: { id } } = useLocation();
console.log(id)
2.5 useNavigate()编程式导航
作用:返回一个函数用来实现编程式导航。 可以更改页面的 URL,可以替代组件
- 导入
import { useNavigate } from 'react-router-dom'
- 调用
let navigate = useNavigate();
- 使用
<li key={item.id} onClick={() => {
navigate(`/book/${item.id}`)
//navigate(`/book/?id=${item.id}`)
//navigate(`/book,{state:{id:item.id}})
}}>{item.name}</li>
- 接收
let { id } = useParams();
console.log(id)
Main.jsx
import React from 'react'
import { useNavigate } from 'react-router-dom'
export default function Main(props) {
let { booklist } = props;
//调用useNavigate方法
let navigate = useNavigate();
return (
<ul>
{
booklist.map(item => {
return <li key={item.id} onClick={() => {
navigate(`/book/${item.id}`)
}}>{item.name}</li>
})
}
</ul>
)
}
APP.jsx
import React from 'react'
import Books from './Component/Books/Books'
import { useRoutes } from 'react-router-dom'
import BookDetails from './Component/BookDetails/BookDetails'
export default function App() {
let routeArr = useRoutes([
{
path: '/',
element: <Books />
},
{
path: '/book/:id',
element: <BookDetails />
},
]);
return (
<div>
{routeArr}
</div>
)
}
BookDetails.jsx
import React, { useState,useEffect } from 'react'
import { useParams } from 'react-router-dom'
import axios from 'axios';
import './BookDetails.css'
export default function BookDetails() {
let [bookOne, setBookOne] = useState([]);
let { id } = useParams();
useEffect(() => {
async function main() {
let { data } = await axios.get(`/api/bookOne`, {
params: { id }
});
setBookOne(data)
}
main();
}, [])
return (
<div>
<h1>{bookOne.name}</h1>
<hr />
<span className='autor'>作者:{bookOne.autor}</span>
<p>{bookOne.infro}</p>
<img src={'/image/' + bookOne.img} alt={bookOne.name} />
</div>
)
}
如果想要使用类似于请求字符串的写法,咱们还可以这样,例如:/book?id=1
Main.jsx
{
booklist.map(item => {
return <li key={item.id} onClick={() => {
navigate(`/book?id=${item.id}`)
}}>{item.name}</li>
})
}
APP.jsx
let routeArr = useRoutes([
{
path: '/',
element: <Books />
},
{
path: '/book',
element: <BookDetails />
},
]);
BookDetails.jsx
import { useSearchParams } from 'react-router-dom'
let [search] = useSearchParams();
let id = search.get('id');
console.log(id)
如果想要使用state方式传参,咱们还可以这样做
Main.jsx
{
booklist.map(item => {
return <li key={item.id} onClick={() => {
navigate('/book', {
state: {
id: item.id
}
})
}}>{item.name}</li>
})
}
APP.jsx
let routeArr = useRoutes([
{
path: '/',
element: <Books />
},
{
path: '/book',
element: <BookDetails />
},
]);
BookDetails.jsx
import { useLocation } from 'react-router-dom'
let result = useLocation();
let { id } = result.state;
console.log(id)
注:Link与navigate的选择问题
- Link更适合于简单的路径变化,而且也是一种无逻辑的组件
- navigate用于复杂的场景,进行变化
3. useNavigate路由后退与replace替换模式
在useNavigate()
方法中可以实现路由后退以及replace的替换模式
3.1 路由后退
- 导入
import { useNavigate } from 'react-router-dom'
- 调用
let navigate = useNavigate();
- 在界面中添加一个后退按钮
<div style={{ margin: 10 }}><a className="btn btn-primary" onClick={back}>后退</a></div>
let back = () => {
//功能类似于原生JS中的history.go(-1)
//后退
navigate(-1);
//前进
//navigate(1); //用的较少
}
3.2 replace替换模式
Main.jsx中的在跳转URL中添加replace
{
booklist.map(item => {
return <li key={item.id} onClick={() => {
navigate('/book', {
//路径替换
replace: true,
state: {
id: item.id
}
})
}}>{item.name}</li>
})
}
4. 路由组件的懒加载(lazyload)
问题:
所有路由组件代码是打包在一块的, 打开首页就会加载, 但我们开始只需要看到首页路由的效果,
也就是只需要执行首页路由组件代码解决:
对路由组件进行懒加载处理深入理解
对路由组件进行拆分/单独打包 => import函数动态引入
访问路由时才去后台加载对应的打包文件 => lazy函数
指定loading界面 =>
import {lazy, Suspense} from 'react'
// 懒加载动态引入组件
const About = lazy(() => import('../pages/About'))
// 路由表
{
element: <Suspense fallback={<div>正在加载中...</div>}>
<About />
</Suspense>
}
十四、redux
一.@reduxjs/toolkit使用
1.1安装
- 安装:
npm install @reduxjs/toolkit
在src目录中的store目录中的index.js中添加
1.2导入包中的对象
- 导入包中的对象
import { createSlice, configureStore } from '@reduxjs/toolkit'
1.3创建切片对象
- 创建切片对象
//这个方法需要传入一个参数是对象,在这个对象中必须要有的是name属性
const counter = createSlice({
//这个名称会作为action.type中的前缀,避免命名冲突,自动追加
name: 'count',
//初始状态数据
initialState: {
num: 10
},
//定义reducers
//定义reducer更新状态数据的函数
//而里面的方法在后期组件中执行dispatch时是作为action函数的函数名去使用的
reducers: {
//需要传入两个参数,一个是state(代理的proxy对象)、一个是action
add(state, { payload }) {
//输出
// console.log(state);
// console.log(action)
state.num += payload;
},
dec(state, { payload }) {
//输出
// console.log(state);
// console.log(action)
state.num -= payload;
}
}
});
1.4获取action函数并导出方法
- 获取action函数并导出方法
export let { add,dec } = counter.actions;
1.5创建store对象
- 创建store对象
//调用rtk的configureStore方法,
//该方法相当于集成了redux和redux-redux的createStore、combineReducers、middleware以及默认支持了Redux DevTools。
const store = configureStore({
reducer: counter.reducer
})
1.6异步使用redux
- 异步使用redux
在store/index.js文件中只需要单独声明一个方法并导出,不需要其他操作
//单独创建一个函数
export let asynctianjia = (payload) => {
return dispatch => {
//执行异步操作
setTimeout(() => {
dispatch(add(payload));
}, 1000)
}
}
1.7导出store
- 导出
export default store;
1.8获取状态数据
- 获取状态数据
console.log(store.getState())
1.9修改状态数据
- 修改状态数据
store.dispatch(add(10))
store.dispatch(dec(5))
1.10store.subscribe()来重新渲染数据
- store.subscribe()来重新渲染数据
不要忘记在主入口中重新渲染组件,因为redux只负责状态管理,不负责组件的重新渲染
import store from './store';
store.subscribe(() => {
root.render(
<>
<App />
</>
);
})
二、react-redux 使用
redux的问题:
需要自己绑定监听来重新渲染
整个组件
每次state更新, 都需要重新render所有组件(使用subscribe方法进行订阅redux状态的更新) ,
效率低,应该有用到state数据的组件需要render
解决: 使用
react-redux
库,想要使用必须下载npm i react-redux
文档: https://react-redux.js.org/
react-redux 是一个 react 插件,用来简化 react 中使用 redux
Provider 组件
:用来包裹整个 React 应用,接收 store 属性,为应用提供 state 和 更新 state 的函数。核心Hook函数
useSelector 函数
: 读取state数据useDispatch 函数
: 得到分发action的dispatch函数
2.1 使用步骤
- 下载
npm i react-redux
- 通过Provider向App及其所有后代组件提供store
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { Provider } from 'react-redux'
import store from './store';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<>
<Provider store={store}>
<App />
</Provider>
</>
);
- 修改Redux.jsx组件中的代码,进行调用Hook函数
import React from 'react'
import { add, dec, asynctianjia } from '../../store/modules/counter';
import { add as a, dec as b } from '../../store/modules/Hit'
import { useSelector, useDispatch } from 'react-redux';
//调用useSelector方法来获取redux中所有的状态数据,redux会将所获取到的状态通过state参数获取(参数名可以自定义)
let { count: { num }, hit: { hitCount } } = useSelector(state => state);
//调用useDispatch进行分发action
let dispatch = useDispatch();
let addCount = () => {
dispatch(add(1)); //省略store
}
... //后面的方法照旧
注意:在回调函数中不能调用React Hook,ReactHook必须在React函数组件或自定义ReactHook函数中调用