JSX是什么
JSX是一种像下面这样的语法:类似的在js中嵌入html的代码
1. const element = <h1>Hello, world!</h1>;
2. render(){
return (
<h1>Hello, world!</h1>;
)
}
3. jsx直接可以看作是一个变量
它是一种JavaScript语法扩展,在React中可以方便地用来描述UI。
本质上,JSX为我们提供了创建React元素方法(React.createElement(component, props, ...children))的语法糖(syntactic sugar)
const element = <h1>Hello, world!</h1>;//可以将element看作是一个值,可以像普通的js变量一样引用
上面的代码实质上等价于:
var element = React.createElement(
"h1",
null,
"Hello, world!"
);
jsx语法写在这里面
<script type='text/babel'>
</script>
jsx语法规则
jsx语法:
1、定义虚拟DOM时,不能用引号包含,如果需要换行,需要使用()将html代码进行包含
2、html标签中需要混入js表达式时,需要使用{}进行包括 {}里面写js代码 {{name:""}}这表示是js对象
3、html标签可以直接添加样式class属性,但是指定的属性名是className 而不是class
4、添加内联样式:不能直接写style标签来指定样式,
需要使用style={{属性:"值1",fontSize:"12px"...}} 其中css样式是多个单词需要使用小驼峰命名
第一层{}表示里面的是js代码,第二层{}表示js对象
如果属性是多个单词组成的需要使用小驼峰命名
5、jsx语法的创建的虚拟DOM只能有一个根元素标签啊
6、html标签必须要有闭合<input type="text" />
7、jsx创建虚拟DOM时不能直接写自定义的html标签
8、标签的首字母
(1).若小写字母开头,则将该标签转为html中同名元素,若html中无该标签对应的同名元素,则报错。
(2).若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。
{}中只能写js表示达式,而不能写js语句
什么是js表达式?什么是js语句?
js表达式:在js中具有返回值的
例如:1.const a=0; 此时的a就是表达式
2.console.log("")
3.a+b
4.数组的操作.map() 也是具有返回值的
5。const func=function(){}
js语句:
js判断语句:if判断语句
js循环语句:for循环语句
js开关语句:switch()
jsx嵌入数据
■JSX嵌入变量:{js代码变量}
情况-- :当变量是Number. String. Array类型时,可以直接显示
情况二:当变量是null、undefined、 Boolean类型时 ,内容为空;
如果希望可以显示null、undefined. Boolean ,那么需要转成字符串 转换的方式有很多,比如toString方法和空字符串拼接, String(变量)等方式;
情况三:对象类型不能作为jsx的子元素( not valid as a React child )
state = {
firend: {
name: "szxx",
age: 12
}
}
<h1>{this.state.friend}</h1>//不会显示 会报错
jsx绑定属性
render() {
const { href } = this.state
// jsx写在return里面
return (
<div>
{/* 属性={js代码} 不需要加引号 */}
<h1 title={this.state.title} className={this.state.clas}>我是标题</h1>
<img src={this.state.imgSrc} alt="" />
<a href={href} target="_blank">百度</a>
<button onClick={this.handlerClick}>点击</button>
</div>
)
}
className:是元素标签的class属性
htmlFor:是label标签的for属性
jsx绑定事件处理函数
jsx中标签绑定事件处理函数会出现的第一个问题是this指向问题
如果事件绑定的是一个普通的函数
handleClick(){
console.log("点击事件", this.state.msg);
}
<button onClick={this.handleClick}>点击</button>
就会产生this指向问题:
此时的handleClick函数内部的this是undefined
为啥会指向的是undefined?
因为onClick={this.handleClick}将普通函数绑定给onClick不是我们自己调用,而是react的onClick事件进行回调
react调用我们绑定的普通函数时,给普通函数绑定的this是undefined =》onClick.call(undefined) 开启了严格模式,this指向的是undefined
普通函数给调用,this指向的就是谁
解决这个this指向问题的方法有四种:
1.<button onClick={this.handleClick.bind(this)}>点击</button>在jsx语法的作用域中的this指向的是当前组件实例对象
2.在组件类的构造函数中提前给普通事件处理函数绑定上this
constructor() {
// 解决事件绑定函数的this指向问题,解决方法2:
this.handleClick = this.handleClick.bind(this)
}
3.改造普通函数为箭头函数+赋值语句的形式
箭头函数+赋值语句可以解决事件处理函数中this指向问题 解决方法3
handleClick = () => {//箭头函数没有this,this会向上层查找this
onsole.log("点击事件", this.state.msg);
}
4.给事件绑定一个箭头函数,触发事件就会执行这个箭头事件函数,箭头事件函数中的this指向的就是当前的类组件对象
handleClick(){
console.log("点击事件", this.state.msg);
}
<button onClick={() => { console.log(this); }}>点击</button>
<button onClick={() => { this.handleClick() }}>点击</button>
第四种解决方法还有一个好处是:给事件处理函数传递参数方便
用的最多的是第三种和第四种方法
事件处理函数传参
不传参:
<button onClick={() => { this.handlerClick() }}>点击</button>
handlerClick(event) {//事件处理函数默认接收一个参数:事件处理对象,如果在调用事件处理函数时,传递了参数,就会默认覆盖掉,事件对象
console.log("事件对象", event);
}
//需要事件对象的话,就需要在调用事件处理函数时,传递事件对象给事件处理函数,事件处理函数使用形参进行接收
传递参数,不传递事件处理对象(不传递事件处理对象就没有事件处理对象)
<button onClick={() => { this.handlerClick(参数) }}>点击</button>
handlerClick(参数) {
}
传递参数和事件对象
<button onClick={(事件对象) => { this.handlerClick(参数,事件对象) }}>点击</button>
handlerClick(参数,事件对象) {
}
条件渲染:
某些情况下,界面的内容会根据不同的情况显示不同的内容,或者决定是否渲染某部分内容:
在vue中,我们会通过指令来控制:比如v-if、v-show ;
在React中,所有的条件判断都和普通的JavaScript代码-致;
通过if判断,进行条件渲染:用于逻辑复杂的渲染
state = {
isLogin: false
}
render() {
// 通过if判断
let element = null
if (this.state.isLogin) {
element = <h1>已登录</h1>
} else {
element = <h1>未登录</h1>
}
return (
<div>
{element}
</div>
)
}
通过三目运算符进行条件渲染:用于逻辑简单的渲染
<button>{this.state.isLogin ? "退出" : "登录"}</button>
逻辑&& 进行条件渲染
<h2>{this.state.isLogin && "登录后可查看"}</h2>
上面代码都是v-if的效果,控制元素是被渲染到页面
下面的代码时v-show的效果,控制元素是否隐藏,元素还是会被添加到页面上
<h2 style={{ display: this.state.isLogin ? "block" : "none" }}>登录后可查看</h2>
列表渲染
在React中并没有像Vue模块语法中的v-for指令,而且需要我们通过JavaScript代码的方式组织数据,转成JSX :
很多从Vue转型到React的同学非常不习惯,认为Vue的方式更加的简洁明了;
但是React中的JSX正是因为和JavaScript无缝的衔接,让它可以更加的灵活;
常用的就是列表的一些函数,map,filter等进行渲染
jsx的本质
jsx的本质是React.createElement(标签名,{属性1:值,...},标签内容)的语法糖(简便写法代替复杂的写法,但是最终所有的简便写法最终还是会被转换成复杂的写法)
所有的jsx代码最终都会转成React.createElemet
使用jsx创建虚拟dom:jsx创建dom元素非常简单,就直接写原生的html就行,jsx底层是使用的是原生js创建虚拟dom的方式
使用jsx创建虚拟DOM:const VDOM = (<h1 id="h1"><span>hello jsx</span></h1>)
使用原生的React创建虚拟dom:const VDOM = React.createElement("h1", { id: "title" }, React.createElement("p", { className: "cls" }, "hahahah"))
jsx->babel->React.createElement
babel的作用就是将jsx转换成React.createElement
React.createElement创建出的是一个ReactElement的js对象树
js对象树就是虚拟DOM
// ReactDOM.render函数就将createElement生成的js对象树(虚拟DOM转成页面上的真实DOM)
ReactDOM.render(VDOM, document.getElementById('app'))
React渲染过程
编写jsx代码->交给babel转成->React.createElememt()->createElememt创建出虚拟DOM对象->交给ReactDOM.render函数渲染成真实的DOM
为什么要使用虚拟DOM?
1.虚拟DOM设计的核心就是用高效的js操作,来减少低性能的DOM操作,以此来提升网页性能。(虚拟DOM是在内存中进行编译执行的,所以速度快)
2.原有的开发模式很难追踪到状态的改变,不便于进行调试。虚拟DOM可以借助于一些浏览器插件进行追踪状态
3.真实DOM操作性能非常低
3.1 创建DOM的过程复杂
3.2 DOM操作会引起浏览器重绘和回流(重新加载页面)
4.虚拟DOM将编程从命令式编程转到了声明式编程上
虚拟DOM是一种编程理念。虚拟DOM是统一存储在内存中,并且它是一个简单的js对象,通过ReactDOM.render统一的将虚拟DOM一次性的渲染到页面,使虚拟DOM与真实DOM同步。这个过程叫做协调
脚手架的安装
npm install -g create-react-app
创建项目
create-react-app 项目名称
运行项目
cd 项目根目录
yarn start
项目目录结构
React脚手架项目目录结构
根目录:
|-- node_modules 项目依赖包 由npm进行管理
|-- public ---- 静态资源文件夹 ---公共文件,里边有公用模板和图标等一些东西
|-- favicon.icon ------ 网站页签图标
|-- index.html -------- 主页面,承装各个组件 整个项目只有一个html文件,单页面应用
|-- logo192.png ------- logo图
|-- logo512.png ------- logo图
|-- manifest.json ----- 应用加壳的配置文件,网页移动端
|-- robots.txt -------- 爬虫协议文件
|-- src ---- 源码文件夹
|-- components 自定义模块文件夹,将页面中的不同的组件放到这个文件夹中 --存放一般组件的地方
|-- 组件1的文件夹 组件文件一般以大写字母开头
|-- 组件1.jsx 组件可以使用jsx为后缀也可以是js为后缀,以jsx为后缀以区分普通的js文件,组件文件名可以是index.jsx,如果是以index的文件名在导入组件时直接到入到当前组件文件的上一层的文件夹名称即可
|-- 样式文件.module.css 或者 样式文件.css 以样式文件.moudle.css表示css文件可以进行模块化导入
|-- 字体文件,音视频文件、...
|-- 组件文件夹2
|-- pages ---存放路由组件的地方
|-- App.css -------- App组件的样式
|-- App.jsx --------- App组件 主组件,所有其他的组件都要在这里进行引入
|-- App.test.js ---- 用于给App做测试
|-- index.css ------ 全局样式
|-- index.js ------- 入口文件 引入核心库App.js 程序的入口
|-- logo.svg ------- logo图
|-- reportWebVitals.js --- 页面性能分析文件(需要web-vitals库的支持)
|-- setupTests.js ---- 组件单元测试的文件(需要jest-dom库的支持)
|-- readme.md --项目介绍自定义一些项目信息以及简单使用
|-- package.json 项目的配置信息文件,包信息文件
---dependencies下管理生产环境所用的包及版本
---devDependencies里得插件只用于开发环境,不用于生产环境
---name项目名称
---version项目版本
---scripts项目启动等功能设置
|-- package-lock.json ---上线锁定版本信息
|-- .gitignore ---这个是git的选择性上传的配置文件,比如一会要介绍的node_modules文件夹,就需要配置不上传。
|-- yarn.lock 真实的版本依赖版本
React的组件化
React的组件相对于Vue更加的灵活和多样,按照不同的方式可以分成很多类组件:
根据组件的定义方式,可以分为:函数组件(Functional Component )和类组件(Class Component) ;
根据组件内部是否有状态需要维护,可以分成:无状态组件(Stateless Component )和有状态组件(Stateful Component) ;
根据组件的不同职责,可以分成:展示型组件(Presentational Component)和容器型组件(Container Component) ;
类组件
■ 类组件的定义有如下要求:
0 组件的名称是大写字母开头(无论类组件还是函数组件)
0 类组件需要继承自 React.Component 或 React.PureComponent
0 类组件必须实现 render 函数,并且也必须要有 return
0 在jsx语法中,如果将html的标签大写,那么jsx不会解析默认的html标签,而是把这个html标签当作一个组件
在普通的网页中,可以将html默认的标签可以大写,但是在jsx中,就会被认为是组件
■ 使用class定义一个组件:
0 constructor是可选的,我们通常在constructor中初始化一些数据;
0 this.state中维护的就是我们组件内部的数据;
0 render()方法是class组件中唯- -必须实现的方法;
函数式组件:
1.函数式组件里面没有this,没有自己的ref
2.函数式组件里面不能保存状态
3.没有生命周期钩子函数,后面可以借助于
render函数的返回值类型
1.React元素 (React.createElement对象)
2.数组,返回数组时里面可以返回多个根元素
3.返回一个片段
4.Portals
5.返回一个字符串和数值类型:渲染成文本节点
6.返回布尔类型和null:什么都不渲染
react生命周期-旧
prevState:上次更新之前的state值
nextState:是state更新之后的state
prevProps:props变化之前的props
nextProps:propx变化之后的props
挂载阶段
1. 挂载阶段的钩子---由ReactDOm.render函数触发
constructor() 构造器也是钩子函数:是第一个被调用的回调函数,初始化类组件的生命周期函数
componentWillMount 钩子:组件被挂载之前被调用 第二个被调用的钩子 【新版本废弃了】 UNSAFE_componentWillMount
render() componentWillMount组件被调用完毕后,然后然后调用render对组件进行渲染,setState执行也会引起render函数的执行
componentDidMount() 组件挂载到页面后render执行完毕后执行的钩子 ---常用,例如:在这个生命周期函数中开启定时器,发送网络请求,订阅消息
卸载阶段
2. 卸载组件阶段---由ReactDOM.unmountComponentAtNode函数触发
componentWillUnmount() 销毁组件之前执行的钩子 执行次数:1 ---常用,例如:在这个生命周期函数中关闭定时器,取消订阅
卸载组件:ReactDOM.unmountComponentAtNode(DOM容器)
更新阶段
- 组件更新阶段的钩子—由this.setState(),或,父组件中render函数执行,导致子组件重新渲染,或,forceUpdate()强制更新.触发更新阶段的钩子函数
3.1 以this.setState()为起点的更新阶段的钩子函数:
shouldComponentUpdate(nextProps, nextState) 执行setState()后第一个执行的函数,表示是否允许组件发送更新,根据它的返回值决定,默认返回true
nextProps, nextState再当前钩子函数中都是最新的,而this.state和this.props访问到的都是更新之前的数据,由此可以来判断当前组件所依赖的数据是否发生变化,来决定组件是否需要发生重新渲染
可以根据nextProps来判断,父组件传递给我的props是否发生变化,来决定当前子组件是否需要渲染
可以根据nextState来判断,当前组件中所依赖的state中的某个数据是否发生变化,来决定当前组件是否需要重新渲染
因为react父组件的重新渲染会导致其所有子组件的重新渲染,这个时候其实我们是不需要所有子组件都跟着重新渲染的,因此需要在子组件的该生命周期中做判断
UNSAFE_componentWillUpdate(nextProps, nextState)
render()
componentDidUpdate(prevprops,prevState) 这个钩子函数执行完毕说明页面已经是更新之后的结果了
3.2 以this.forceUpdate()为起点的强制更新组件:强制更新:不修改state中的数据,使组件发生更新.不受shouldComponentUpdate的返回值约束
UNSAFE_componentWillUpdate()
render()
componentDidUpdate(prevprops,prevState)
3.3 以父组件render为起点更新组件
componentWillReceiveProps(nextProps) 父组件通过属性传递的方式传递给子组件,当父组件更新这个属性时,就会执行这个函数。 componentWillReceiveProps在初始化render的时候不会执行,它会在Component接受到新的状态(Props)时被触发,[这个钩子已经被废弃了]
可以在组件内使用this.props获取到之前的状态
componentWillReceiveProps在初始化render的时候不会执行,它会在Component接受到新的状态(Props)时被触发,一般用于父组件状态更新时子组件的重新渲染。
UNSAFE_componentWillReceiveProps
shouldComponentUpdate(nextProps, nextState) 可以根据一些情况来决定当前是否允许被更新
UNSAFE_componentWillUpdate(nextProps, nextState)
render()
componentDidUpdate(prevprops,prevState)
强制更新forceUpdate与普通setState更新区别
this.setState()需要多经历一个钩子函数的限制 只有shouldComponentUpdate的返回值为true,才会发生更新
this.forceUpdate()直接强制发生更新,不需要经过shouldComponentUpdate
react生命周期-新
新版18.x 新版17.x 旧版16.x的生命周期函数的区别:
旧版本中的三个钩子函数名称发生变化
1.componentWillReceiveProps
2.componentWillMount
3.componentWillUpdate --三个钩子函数在新版本中已经废弃
这个三个钩子函数在新版本中名称前面需要加上 UNSAFE_
1. UNSAFE_componentWillReceiveProps
2. UNSAFE_componentWillMount
3. UNSAFE_componentWillUpdate
如果不加,也不会报错,只会报警告
在新版本中又提出两个新的钩子函数
1.getDerivedStateFromProps
React生命周期的命名一直都是非常语义化的,这个生命周期的意思就是从props中获取state,可以说是太简单易懂了。可以说,这个生命周期的功能实际上就是将传入的props映射到state上面。
2.getSnapshotBeforeUpdate
这两个钩子函数很少使用
挂载阶段
constructor()
static getDerivedStateFromProps(nextProps,prevState) 父组件更新props并且getDerivedStateFromProps返回一个对象时触发
当父组件修改了props属性,并且子组件中的getDerivedStateFromProps返回一个state对象时,就会触发这个函数执行,
接收两个参数,第一个是父组件传递过来的props,第二个参数是,当前组件的上一个state值,
使用场景:组件中的所有的state任何时候都取决于props时,可以使用这个钩子
如果当前getDerivedStateFromProps返回的是一个null,表示不更新将props映射到state,或者父组件的
getDerivedStateFromProps
getDerivedStateFromProps必须返回一个,才会被回调
1.返回 null //默认返回
2.返回 props //将接收到的props作为state使用,及state的值在任何时候都取决于props,props不会覆盖掉原有的state
getDerivedStateFromProps会在挂载阶段和更新阶段执行
render()
componentDidMount(prevprops,prevState)
更新阶段
就是在旧版本的更新阶段中:在render与componentDidUpdate之间新加了一个钩子 getSnapshotBeforeUpdate,更新阶段其他钩子没有变
getSnapshotBeforeUpdate(prevProps, prevState,snapshotValue) 这个钩子是更新完成之前调用,新的内容还没有渲染到页面上
必须返回一个快照snapshot 或者 null 返回值会传递给componentDidMount 的第三个参数,snapshotValue
这个钩子主要用于:在页面发生更新之前,获取一些DOM消息,如滚动条的位置消息等
完整流程:
constrcutor() 初始化组件及状态
getDerivedStateFromProps()传入的props映射到state上面
UNSAFE_componentWillMount() 组件即将挂载到页面
render() 将组件渲染到页面
componentDidMount() 组件被挂载到页面
UNSAFE_compomentWillReceiveProps(nextProps) 父组件更改props,触发当前组件的UNSAFE_compomentWillReceiveProps调用
getDerivedStateFromProps() 传入的props映射到state上面
shouComponentUpdate() 表示是否允许组件发送更新
render()
getSnapshotBeforeUpdate()
UNSAFE_componentWillUpdate() 组件即将更新
组件之间的嵌套
App组件是根组件,融入其他的子组件
父子组件之间的通信:
父组件->子组件:通过props
类组件:
在父组件中给子组件设置属性标签 xxxx="yyyy"
在子组件中通过this.props.xxx来接收
函数式组件:
通过function Son(props){} 形式参数props来接收父组件传递过来的数据
子组件->父组件:通过回调函数+props
类组件:
在父组件中提前定义好一个函数,然后通过props将这个函数传递给子组件,
在子组件中得到父组件中函数的引用,然后在子组件中执行这个函数,那么父组件中的这个函数就会发生回调执行
父组件给深层子组件传递数据-Context上下文对象
Context 通过组件树提供了一个传递数据的方法,从而避免了在每一个层级手动的传递 props 属性。
两种方式:
1. 复杂写法:一层一层的传递
2. context实现全局数据共享
2.1 首先创建 Context 对象 构建一个上下文对象
const Context对象 = React.createContext({
xxx:"数据的默认值" //Provider 指定的 value 获取不到时,就使用这个默认的值
})
Context 对象包含两个组件:
Provider :数据的提供者(定义数据)==》父级
Consumer :数据的消费者(需要数据)==》子级
2.2 给类式子组件传递数据
2.2.1
<Context对象.Provider value={需要共享的数据键值对}> //被Provider包裹的所有的子代元素都可以拿到指定的value
<需要数据的子组件></需要数据的子组件> 需要数据的子组件这个组件的子组件也可以获取到数据
</Context对象.Provider>
此时就将数据传递给Context对象.Provider包裹组件的所有子组件了
2.2.2 在子类组件中获取共享数据
给需要数据的组件添加上一个类属性
在类外面: 子类组件的类名.contextType=Context对象 (这个是必须的)
或者
在类内部:static contextType = Context对象
此时在需要数据的组件中通过this.context 就可以访问到Provider指定的value
或者使用 Consumer 进行包裹
2.3 给函数式子组件或类式子组件传递数据,在函数式组件中使用context的数据
<Context对象.Provider value={ 需要共享的数据键值对 }>
{
value=>{
return (
value
)
}
}
</Context对象.Provider>
function DemoComponent() {
return (
<ContextObj.Consumer> //Consumer的子级必须是一个函数,传递的数据将作为参数传递给这个函数,函数的返回值就是要渲染的模板
{
value => { //value是 <Context对象.Provider value={需要共享的数据键值对}>指定的数据
return (
<div>
<h2>DemoComponent</h2>
<p>{value.aData}</p>
</div>
)
}
}
</ContextObj.Consumer>
)
}
对props数据类型进行限定
通过prop-types进行进行限制
import PropTypes from "prop-types"
给类添加数据(在类的外部)
类组件名称.propTypes = {
props属性:PropTypes.限制数据类型
}
// 设置props默认数据
类组件名称.defaultProps = {
props属性:默认值
}
简写形式:(在类的内部)
static propTypes = {//添加类属性
props属性:PropTypes.限制数据类型
}
// 设置props中属性接收的默认值的
static defaultProps = {
sex: "男"
}
支持的限制类型:
optionalArray: PropTypes.array,
optionalBool: PropTypes.bool,
optionalFunc: PropTypes.func,
optionalNumber: PropTypes.number,
optionalObject: PropTypes.object,
optionalString: PropTypes.string,
optionalSymbol: PropTypes.symbol,
react 实现插槽
1.第一种方式:通过prpos.children来接收标签中自定义的数据
<组件标签>
....乱七八糟的数据==>this.props.childrend 传递给子组件
</组件标签>
组件标签之间的一些乱七八的数据都会传递给这个组件标签的 this.props.children中进行保存
其中:一个节点作为children数组的一个元素, this.props.children只适用于接收单个数据标签情况,组件标签中的乱起八糟的数据一旦多了,就很难统一维护
2.第二种方式:通过props传递参数的形式
jsx定义的html标签可以看作是一个变量数据,可以通过props进行传递
实现步骤:
在父组件中:
将jsx 定义的html标签作为变量传递给子组件
<NavTab2
leftSlot={<div>返回</div>}
rightSlot={<div>用户中心</div>}
centerSlot={<div>标题</div>}
></NavTab2>
在NavTab2组件中:
class NavTab2 extends React.Component {
render() {
const { leftSlot, rightSlot, centerSlot } = this.props
return (
<div className="box">
<div className="left-box">
{leftSlot}
</div>
<div className="center-box">
{centerSlot}
</div>
<div className="right-box">
{rightSlot}
</div>
</div>
)
}
}
setState()
setState 是 Component 原型对象上的方法
通过setState修改的state数据,才会引起页面的重新渲染
直接修改state中的数据,不会引起页面的重新渲染,但是state中的数据还是会发生变化的
setState会调用render函数进行页面的渲染
用法一:
this.setState({
state定义的字段:修改值
})
setState异步更新
1.为什么setState要设置成异步的?
■setState设计为异步,可以显著的提升渲染性能;
0如果每次调用setState瞬间进行一次更新 ,那么意味着render函数会被频繁调用,界面重新渲染,这样效率是很低的;
0最好的办法应该是获取到多个更新,之后进行批量更新;
■如果同步更新了state ,但是还没有执行render函数,(render函数执行需要一定的时间)那么state和props不能保持同步;
0state和props不能保持一致性,会在开发中产生很多的问题;
2。拿到setState异步更新后的最新的数据
方式一:
this.setState({键值对修改state},回调函数)
this.setState执行完毕后,在后面的回调函数中可以访问到最新的state
方式二:
this.setState会触发render函数的执行 rander函数会触发componentDidUpdate()函数的执行,所以也可以在componentDidUpdate()函数中获取到最新的state
state同步更新
3.1.将setState放入到定时器中执行
setTimeout(()=>{
this.setState({键值对修改state})
console.log(this.state);//此时获取的是最新的数据
},0)
3.2将setState放入到原生的DOM时间监听中执行
componentDidMount() {
document.getElementById("btn").addEventListener("click", () => {
this.setState({
count: "hhhhhh",
})
console.log(this.state); //此时获取的是最新的state,state更改完毕后立刻可以获取到最新的数据
})
}
React提供的事件绑定不是DOM原生事件绑定,事件对象也不是原生的DOM事件对象
React提供的事件是react自己合成的事件,在react自己的合成事件中,setState更新是异步的
在定时器和原生的DOM事件中,setState是同步的
setState数据合并
state = {
msg: "hello",
info: "world"
}
setState({
msg:"sasa"
})
最终的结果是:
state = {
msg: "sasa",
info: "world"
}
这就是数据合并,内部原理是实现了:Object.assign({},this.state,{msg:"sasa"})
Object.assign({},目标数据,要修改的数据) ==》会将第二个第三个参数合并到第一个对象中,如果值一致,就替换掉目标数据中的值
setState接收函数 来解决setState本身合并
如果写多个异步setState,那么react会将多个setState合并成一个setState执行,也就是说,只会执行一个setState
但是有时候我门想要实现多个setState执行的效果
那么就可以给setState传递一个函数
setState((prrvState,props)=>{ //prrvState是修改前的state
return {
state中的字段:修改值
}
})
setState((prrvState,props)=>{
return {
state中的字段:修改值
}
})
setState((prrvState,props)=>{
return {
state中的字段:修改值
}
})
this.setState((prevState, props) => {
return {
count: prevState.count + 1
}
})
React更新机制
react渲染流程:jsx->虚拟DOM(内存中运行)->真实DOM
react更新机制:props/state发生改变->render函数重新执行->产生一个新的虚拟DOM树->新旧虚拟DOM树进行diff算法对比->计算出差异,将变化的地方重新渲染,没变化的地方进行复用->更新真实DOM
React Diff算法
情况一:对比不同类型的元素,当节点为不同的元素,React会拆卸原有的树,重新建立起新的树
旧的DOM树: 新的DOM树:
<span> <div>
<组件1></组件1> <组件1></组件1>
</span> </div>
此时就不会复用组件1,而是将整个节点重新渲染
情况二:当对比的两个元素的类型相同时,React会保留DOM节点,仅对比属性和修改属性
情况三:默认情况下,递归DOM节点的子元素,react会同时遍历新旧虚拟DOM树,当产生差异时,会产生一个mutation
此时就出现了key的作用
key值的作用主要用来减少没必要的diff算法的对比,对于同一个组件或者节点来说,只要父节点状态或属性发生改变,该组件就会进行diff
对比,即使该组件没有发生改变;如果为该组件引入key值,在进行diff对比前先做一次校验,判断该组件是否需要diff对比,也可以判断该
组件是直接更新操作还是销毁或者新建操作,从而提高了diff算法的效率;特别在渲染同级同结构的组件们时,key可以为它们加上了身份的
标志,在rerender时,可以通过key来判断该组件是否已经存在,是否需要跟新或者销毁,新建等操作,提高了diff算法在同级节点上的操作。
原理: 因为在reactelement中有一个属性是key,该属性默认是为空值,所以一般情况下,只要组件不加上key值,react是不会去校验组件
的key,而是直接采用diff算法进行对比,一旦组件加上了key值,react就会在渲染时对该组件的身份进行校验,首先校验新旧组件的key值
是不是一致,不一致的话,该组件直接销毁,然后在新建该组件;如果一致,则比较组件的属性是否发生变化,如果发生变化,则采用diff算
法进行对比,然后得出差异对象,如果属性没发生变化,则认为该组件不需要改变;
解决:父组件中的数据发生变化,导致父组件中所有的子组件都会发生重新渲染(render函数都会被调用一遍) 或者 组件中没有依赖state中某个数据,但是这个数据发生setState修改后,也会重新渲染整个组件
解决方式1:【比较麻烦】使用shouldComponentUpdate进行前后数据的对比
shouldComponentUpdate(nextProps, nextState) 执行setState()后第一个执行的函数,表示是否允许组件发送更新,根据它的返回值决定,默认返回true
nextProps, nextState再当前钩子函数中都是最新的,而this.state和this.props访问到的都是更新之前的数据,由此可以来判断当前组件所依赖的数据是否发生变化,来决定组件是否需要发生重新渲染
可以根据nextProps来判断,父组件传递给我的props是否发生变化,来决定当前子组件是否需要渲染
可以根据nextState来判断,当前组件中所依赖的state中的某个数据是否发生变化,来决定当前组件是否需要重新渲染
因为react父组件的重新渲染会导致其所有子组件的重新渲染,这个时候其实我们是不需要所有子组件都跟着重新渲染的,因此需要在子组件的该生命周期中做判断
解决方式2:
让类组件继承PureComponent 不继承Component
React.PureComponent 与 React.Component 几乎完全相同,但 React.PureComponent 通过props和state的浅对比来实现 shouldComponentUpate()。
PureComponent优势:不需要开发者自己实现shouldComponentUpdate,就可以进行简单的判断来提升性能。
PureComponent缺点:可能会因深层的数据不一致而产生错误的否定判断,从而shouldComponentUpdate结果返回false,界面得不到更新。
区别点:PureComponent自带通过props和state的浅对比来实现 shouldComponentUpate(),而Component没有。
深层比较,耗费性能
PureComponent只能解决类组件,解决不了函数式组件
解决函数式组件:被重复渲染(render函数被重复执行)
import {memo} from "react"
memo的使用:
const 新的组件 = React.memo(组件)
可以将一个函数式组件传递给memo,生成一个新的组件,然后去渲染这个新的组件
react修改数据的两种方式:
1.直接修改state,
this.state.xxx=值,这种方式可以把state修改掉,但是react检测不到,不会渲染到页面上,最主要的是,在PureComponent和shouldComponentUpdate中检测不到state发生变化
2.使用setState进行修改
注意点:
setState({
state字段:值
})
这个值:不能与state字段内存地址一致,如果state字段是一个引用类型的,那么值就不能与state字段的内存地址一致,否则在PureComponent和shouldComponentUpdate中检测不到state发生变化
如果你的当前类继承的是Component类,那么就不会有这个问腿,因为Component没有实现shouldComponentUpdate
数组的push与concat方法的区别是:
push,不会改变原数组的内存地址,而是直接在源数组上添加元素
concat 会直接返回一个新的数组,
state
和渲染无关的状态尽量不要放在 state 中来管理
通常 state 中只来管理和渲染有关的状态 ,从而保证 setState 改变的状态都是和渲染有关的状态。这样子就可以避免不必要的重复渲染。其他和渲染无关的状态,可以直接以属性的形式保存在组件中,在需要的时候调用和改变,不会造成渲染。
避免不必要的修改,当 state 的值没有发生改变的时候,尽量不要使用 setState 。虽然 shouldComponentUpdate 和 PureComponent 可以避免不必要的重复渲染,但是还是增加了一层 shallowEqual 的调用,造成多余的浪费。
state中存储的是”不可变的数据“ https://www.jianshu.com/p/1e7e956ec1ee
全局事件传递
全局事件传递:可以实现跨组件之间的通信
安装:npm install events
导入:在main.js中
import {EventEmitter} from "events"
创建全局事件对象
const EventBus= new EventEmitter()
发射事件:
EventBus对象.emit("自定义事件名称",参数数据1,...) 参数数据一般会以参数的形式传递给addListener监视事件的回调函数
监听事件:
EventBus对象.addListener("监听的自定义事件名称",回调函数)
监听事件一般放在:componentDidMount中
取消事件监听:
EventBus对象.removeListener("监听的自定义事件名称") 取消某一个
EventBus对象.removeAllListeners() 取消全部
事件销毁一般写在,componentWillUnmount()中
ref的使用:
ref就相当于一个标签的标识,但是请勿过渡使用ref
ref给jsx中的html标签添加一个属性
此时的组件实例对象中refs就会多一个属性{键:值} 键是ref的值, 值是ref绑定的元素节点对象
方式一:字符串形式【不推荐】
设置ref
<h1 ref="xxx"></h1>
获取h1 真实DOM对象
this.refs.xxx
方式二:createRef对象 容器【推荐】
1.创建createRef对象
import {createRef} from "react"
在构造器中:
创建容器:this.自定义容器名称=createRef()
2.将元素添加到容器
<h1 ref={this.自定义容器名称}></h1>
3.获取元素DOM
this.自定义容器名称.current
方式三:回调函数
回调函数:1.自己定义的函数 2.不自己手动调用 3.最终执行了
ref绑定的回调函数由react来进行回调
书写形式:
<标签 ref={(参数element)=>{回调函数体}}>
参数:是当前元素
回调函数体:里面一般写this.ref别名=参数
简写形式:<标签 ref={当前真实DOM元素=>this.当前元素自定义的标识=当前真实DOM元素}>
this.当前元素的标识 就挂载到了当前组件实例对象上
此时获取到refs中的元素标签值:
this.当前元素的标识
ref类型
1.ref绑定在html元素上,返回的ref类型就是DOM
2.ref绑定在类组件上,返回的ref类型是组件对象
3.ref绑定在函数式组件上,就没有ref,不能绑定给函数式组件,函数式组件没有实例,绑定之后就会报错
受控组件与非受控组件
受控组件与非受控组件都与表单有关系
■在React中, HTML表单的处理方式和普通的DOM元素不太-样:表单元素通常会保存在一些内部的state.
受控组件:就是将表单元素中的值(数据)绑定到state中进行管理,实现双向数据绑定
state={
username:""
}
{/* 此时的input就是一个受控组件 */}
用户名:<input type="text" id="username" value={this.state.username} onChange={e => this.handlerChange(e)} />
非受控组件:
在大多数情况下,我们推荐使用 受控组件 来处理表单数据。
在一个受控组件中,表单数据是由 React 组件来管理的。
另一种替代方案是使用非受控组件,这时表单数据将交由 DOM 节点来处理。
高阶函数
高阶函数:满足下面的一个条件就称为高阶函数
1。若A函数,接收的参数是一个函数,那么A函数就是高阶函数
2。若A函数,函数的返回是一个函数,那么A函数就是高阶函数
高阶函数有哪些?
Promise、setTimeout、与数组相关的方法
高阶组件(高阶组件不是一个组件,而是一个函数)
高阶组件是参数为组件,返回值为新组件的函数,本质上是一个类工厂(class factory),可以批量创建类组件和函数组件,并加以统一的管理,节省代码
function highComponent(Comp) {
return class NewComponent extends React.PureComponent {
render() {
return <Comp></Comp>
}
}
}
const HighComponent = highComponent(App)
组件的名称可以通过:displayName来修改
组件名称.displayName="sasa"
高阶组件的意义:
代码复用,逻辑抽象,抽离底层准备(bootstrap)代码
渲染劫持
State 抽象和更改
Props 更改
React内置函数高阶组件 React.forwardRef
<Profile ref={this.ProfileRef}></Profile>
// forwardRef高阶组件:接受一个组件,返回一个新组件
const Profile = Reacrt.forwardRef(function (props,ref) {//ref接收的就是ProfileRef
return (
<div ref={ref}>
Profile
</div>
)
})
函数式组件默认不能接收和设置ref属性,但是经过高阶组件forwardRef的处理,此时函数式组件就可以接收到ref
ref以形式参数的形式,传递到函数式组件内部,然后就可以给函数式组件内部的元素进行绑定
Portals的使用
Portals:把父组件的某个子组件渲染到页面中其它盒子(非父组件子树)里
■某些情况下 ,我们希望渲染的内容独立于父组件,甚至是独立于当前挂载到的DOM元素中(默认都是挂载到id为root的DOM元素上的)。
■Portal提供了-种将子节点渲染到存在于父组件以外的DOM节点的优秀的方案:
0第一个参数( child )是任何可渲染的React子元素,例如一个元素,字符串或fragment ;
0第二个参数( container )是一一个DOM元素;
ReactDOM.createPortal(child, 容器); -可以将child子元素渲染到当前组件的父组件的之外的容器中
class App extends React.Component {
render() {
return (
<div>
{/* model自定义组件里面存放的内容时独立于App组件的 */}
<Model>
<Home></Home>//将home渲染到app2盒子中
</Model>
</div>
)
}
}
class Model extends React.PureComponent {
render() {
return (
ReactDOM.createPortal(this.props.children, document.getElementById("app2"))
)
}
}
React.Fragment的使用
方式1:
<React.Fragments></React.Fragments> ==>类似于小程序中的<block></block>标签的作用,这个标签不会渲染到页面
<React.Fragment>
<h2>1111</h2> ==>到时候只有h2渲染到页面
</React.Fragment>
方式2:
<>
<h2>1111</h2> ==>到时候只有h2渲染到页面
</>
React.StrictMode 开启组件的严格模式
<React.StrictMode>
组件标签
</React.StrictMode>
被React.StrictMode组件包裹的组件会触发额外的检查和警告,只会在开发阶段生效
检查的内容:
1.识别组件中不安全的生命周期
2.识别过时的ref(字符串形式)
3.react会将组件中的constructor会被调用两次
4.检查过期的Api
react样式
内联样式
■内联样式是官方推荐的-种css样式的写法:
style接受一个采用小驼峰命名属性的 JavaScript对象,,而不是CSS字符串;
并且可以引用state中的状态来设置相关的样式;
<h2 style={{ fontSize: "30px", color: this.styleReact.color }}>App组件</h2>
优点:
内联样式之间不会发生样式冲突,可以动态根据state决定样式
缺点:
内联样式写法过于复杂,没有代码提示。。。。
外联样式
在项目中,每个组件都会放入到components文件夹中,然后每个组件都会创建一个当前组件的文件夹
文件夹中可以包含,index.jsx--》组件文件 style.css-->组件样式文件
然后通过es6语法引入样式
缺点:样式命名会发生冲突,导致样式重叠
css 模块化
React已经内置好了css 模块化开发
.css/.less/.scss等样式文件都修改成.module.css/ .module.less/.module.scss等;
之后就可以引用并且进行使用了;
1.创建模块化文件
将原本的css文件修改为 xxx.module.css
xxx.module.css中的内容:
.类选择器{
color:red;
}
2.模块化引入
import xxx from "xxx.module.css"
使用:
<h2 className={xxx.类选择器}>App组件</h2>
优点:解决css变量作用域
缺点:类选择器不能使用"-"连接符,只能使用小驼峰命名 .homeStyle{}
不能动态改变类样式
css in js
"CSS-in-JS” 是指一种模式,其中CSS由JavaScript生成而不是在外部文件中定义;
支持 css-in-js 的库?
yarn add styled-components
使用步骤:
1.在组件的文件夹中创建一个style.js文件
import styled from "styled-components"
2.使用
在style.js中:
const 组件=styled.标签元素`
css样式
.类选择器{
....css样式
&:hover{
}
...
}
`
在组件中:
import React, { PureComponent } from 'react'
import HomeStyleComponent from './style' //具有样式的组件
export default class ReactStyle01 extends PureComponent {
render() {
return (
<HomeStyleComponent>
<h2 className="title">样式测试</h2>
<div>
<p>样式测试2</p>
</div>
</HomeStyleComponent>
)
}
}
这个组件渲染出来后,根元素是指定的标签元素
标签元素上会增加上两个class属性,都是随机的
所以一般将这个组件作为组件的根标签,就可以直接在 const 组件=styled.标签元素``中写样式了
styled.html标签元素 相当于是一个函数,函数返回一个组件,组件标签的根元素是指定的html标签元素,这个元素会增加一个随机的样式class属性,class的样式就是css样式
``相当于是调用上面的函数,给上面的函数 styled.html标签元素 传递css样式
styled-components也可以进行样式嵌套使用,类似于less
styled-components 的语法:
1.styled-components创建出来的组件:具有props穿透
const XXXinpCompoent = styled.input.attrs({属性})`
css样式
`
<XXXinpCompoent type="password"></XXXinpCompoent> ==>可以直接作为input框进行使用,最终渲染出来的元素也是input,
type="password"也是直接给input设置的
.attrs({属性})也是可以直接给input设置属性
const XXXinpCompoent = styled.input.attrs({
placeholder:"xxxx", //定义的css属性直接会生效
变量:"值"//定义的变量需要在${props=>props.变量}中进行使用
})`
css样式
css属性:${props=>props.变量} ==>props是attrs中定义的所有的属性,
`
2.动态设置css样式:
将组件的state作为attrs中的变量,传递给${props=>props.变量}进行使用
<XXXinpCompoent type="password" 变量={this.state.state中的属性}></XXXinpCompoent>
变量={this.state.state中的属性}这个变量会与attrs中的变量进行合并传递给${props=>props.变量}进行使用
3.继承
const 样式组件2 = style(继承某个样式组件1)`
自己组件的内容
`
组件2会把组件1的所有的样式和组件特性都会继承过来
// 设置的默认按钮
const BtnStyleCompoent = styled.button`
padding: 10px;
border: 1px skyblue solid;
`
// 当前这个样式组件,会继承BtnStyleCompoent他的所有的样式
const TestBtnStyleComponent = styled(BtnStyleCompoent)`
background-color: green;
color:white;
`
4.主题
import ThemeProvider from "styled-components"
<ThemeProvider theme={{变量:"值",。。。}}>//这个标签不会渲染到页面
</ThemeProvider>
theme会传递给 所有的样式组件的 props.theme
const XXXinpCompoent = styled.input.attrs({
、、变量
})`
css属性:${props=>props.theme.变量} ==>使用 <ThemeProvider theme={{变量:"值",。。。}}>全局共享的变量
`
添加className属性
1.普通
<h1 className="sasa sasasa sasxcas"></h1>
2. 根据三元运算符动态绑定class
<h1 className={"sasa "+(true?"xasxa":"")}></h1>
3. 数组的方式
<h1 className={["aaa","ccc",(true?"xasxa":"")].join(" ")}></h1>
4.借助第三方库
安装:
yarn add classnames
引入
import classNames from "classnames"
classnames其实是一个函数
使用:
<h2 className={classNames("foo", 'asxcas', 'xcasxcas')}>测试classnames</h2> ==>类似于第一种普通写法
<h2 className={classNames({ "active": true, "hadColor": false },"csssass","title")}>测试2</h2> ==>类似于第二种:可以动态绑定class,与vue中class的绑定是一样的
const 变量="xxxx"
<h2 className={classNames("foo", 变量)}>测试3</h2>
AntDesign组件库
■AntDesign的兼容性:
现代浏览器和IE11 (需要polills )。
支持服务端渲染。
Electron
安装:
yarn add antd -->主库
yarn add @ant-design/icons -->图标库
yarn add moment -->处理日期
引入:
import { Button } from "antd"
import { PoweroffOutlined } from '@ant-design/icons';
import "antd/dist/antd.css" -->antd的样式文件 【这个时在入口文件中最好要导入】
import moment from "moment"
moment库的使用:https://blog.csdn.net/WuLex/article/details/80402036?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162875329116780265439214%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=162875329116780265439214&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-80402036.pc_search_similar&utm_term=moment%28%29&spm=1018.2226.3001.4187
生成时间戳:moment().valueOf()
格式化时间:moment().format()
craco:需要对antd的主题进行修饰时,或者对项目的webpack进行配置时使用craco
https://ant.design/docs/react/use-with-create-react-app-cn
craco可以管理react项目。对react项目进行配置
"scripts": {
"craco-start": "craco start",
"craco-build": "craco build",
"craco-test": "craco test",
"eject": "react-scripts eject"
},
craco配置启动、编译、打包react项目
通过 yarn start --》执行的就是craco start--》好处是:他会像vue一样读取根目录下的craco.config.js配置文件(craco.config.js就是craco的配置文件,类似vue中的vue.config.js),
最终与底层的webpack配置文件进行合并,方便我们配置项目
安装 yarn add @craco/craco
运行项目 yarn craco-start
使用craco的配置文件更改antd的主题 https://ant.design/docs/react/use-with-create-react-app-cn
安装:yarn add craco-less
将原本导入的index.js下的 import "antd/dist/antd.css"
换成:import "antd/dist/antd.less"
在craco.config.js中写入:
const CracoLessPlugin = require('craco-less');
module.exports = {
plugins: [
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
lessOptions: {
modifyVars: { '@primary-color': '#1DA57A' },//'#1DA57A'重新指定antd的主题颜色
javascriptEnabled: true,
},
},
},
},
],
};
使用craco 配置项目的目录的别名,方便导入
yarn add @craco/craco
在项目的根目录下:创建 craco.config.js
写入:
const path = require("path")
const resolve = dir => path.resolve(__dirname, dir)
module.exports={
webpack: {
alias: {
"@": resolve("src"),
"components": resolve("src/components")
}
},
}
封装 axios
一般在componentDidMount这个生命周期中发送网路请求
axios的基本使用
1. axios({
url:"url地址",
method:"GET/POST",
params:{} //get参数,
data:{} //POST参数
}).then((res)=>{
请求成功的回调
}).catch(err=>{
请求失败的回调
})
2.
异步拿到请求之后的结果
axios.get(url,{
params:{ //get参数,
name:"xxx"
}
}).then((res)=>{
请求成功的回调
}).catch(err=>{
请求失败的回调
})
axios.post(url,{post参数},配置}).then((res)=>{
请求成功的回调
}).catch(err=>{
请求失败的回调
})
axios函数返回的是一个Promise对象
async与await 可以将异步请求的代码转换为同步代码
async 异步函数(){
const 请求成功数据=await axios.get()
}
async与await 获取请求失败的数据
try{
const 请求成功数据=await axios.get()
}catch(err){
错误处理
}
axios.all() 合并多个请求
axios.all([{请求1},{请求2.}。。]).then(res=>{多个请求完成之后回调}).catch(err=>{只要一个请求失败就会回调失败的函数})
{请求1}:{url:"",params:{get请球参数},method:"GET"}
只有请球1与请球2的都完成后才会进行回调
返回成功的结果是一个数组,[{请球1的数据},{请球2的数据}]
axios的配置信息
请球配置选项:https://zhuanlan.zhihu.com/p/88497407
默认全局配置
axios.defaults.baseURL="https://zhuanlan.zhihu.com/" 之后的url就可以不用写https://zhuanlan.zhihu.com/
axios.defaults.timeout=3000 请求超时
axios.defaults.headers={} 配置请求头
创建不同的axios实例
const axiosObj1=axios.create()
const axiosObj2=axios.create()...
不同的实例对应不同的服务器,如果有多个不同的服务器url地址不同,就可以创建多个实例,每个实例都有自己的独有的配置,
const axiosObj1=axios.create({
baseURL:"https://zhuanlan.zhihu.com/" 之后的url就可以不用写https://zhuanlan.zhihu.com/
timeout:3000 请求超时
headers:{} 配置请求头
})
axios配置参数的优先级
axios.get({参数配置})>默认的default>创建实例的中的配置
axios拦截器
请求拦截:axios.interceptors.request.use(回调函数1,回调函数2)
axios.interceptors.request.use((config) => { // 请求发送成功后的回调函数
// 可以在这 发送网络请求期间做一些事
// 1.可以在发送请求时,显示loading加载动画
// 2.验证用户是否登录,来决定是否允许用户请求当前url
// 3.对请求参数进行一些修改
return config //必须返回请求的配置对象
}, (err) => {// 请求失败的回调函数
})
响应拦截
axios.interceptors.response.use((res) => {//响应数据成功调用
// 响应数据之前做一些事 res是响应体数据
//1. return res.data 简化响应数据
//2. 关闭loading动画加载效果
return res //必须返回网络请求的结果
}, (err) => {//响应发送错误时,调用
if (err && err.response.status) {
// 判断错误类型
}
return err //返回响应失败的数据
})
axios二次封装
在src/network/request.js
import axios from "axios";
import config from './config.js'
// axios.create({axios的配置选项})
const axiosIntance = axios.create(config) //创建axios实例
// 这里也可以进行请求的拦截
// 请求拦截:axiosIntance的请求发送都会经过这个钩子
axiosIntance.interceptors.request.use(config => {
console.log("请求成功");
return config
})
export default axiosIntance
配置文件:src/network/config.js
// 发送请求相关的配置文件
// 配置根基url
const devBaseURL = "http://httpbin.org" //开发环境使用的api
const proBaseURL = "http://xxxx.org"//生产环境使用的api
const baseURL = process.env.NODE_ENV === "development" ? devBaseURL : proBaseURL
// 配置超时时间
const timeout = 5000
export default {
baseURL,
timeout
}
在组件中使用:
improt request from “src/network/request.js”
request.get()/post() ...
react动画
借助:react-transition-group
安装: yarn add react-transition-group
react-transition-group提供四个组件
1.Transition
2.CSSTransition 只有被CSSTransition包裹的元素才会有相应的动画效果
<CSSTransition in={this.state.isShow} classNames="xxxx" timeout={3000} appear>
要添加动画的组件
</CSSTransition>
CSSTransition的属性:
1. in={true/false} 由in来控制CSSTransition包裹的组件的动画结束与开始
2. classNames="xxxx" 被CSSTransition包裹的组件会有三个动画阶段 xxxx-enter xxxx-exit xxxx-appear
in=true => xxxx-enter -> xxxx-enter-active -> xxxx-enter-done
进入动画之前 进入动画执行过程 进入动画执行之后
in=fasle xxxx-exit -> xxxx-exit-active -> xxxx-exit-done
离开动画之前 离开动画执行过程 离开动画执行之后
xxxx-appear -> xxxx-appear-active ->xxxx-appear-done
第一次出现之前 出现之后 隐藏之后
3.timeout={500ms} 控制多久后执行unmountOnExit
4.unmountOnExit={true} 动画退出后组件CSSTransition卸载
5.appear 在页面第一次加载的时候,添加动画
xxxx-appear
6。组件CSSTransition动画的生命周期 这些都是给标签添加的属性
onEnter={ele => { console.log(ele, "进入状态"); }}
onEntering={ele => { console.log(ele, "正在执行动画"); }}
onEntered={ele => { console.log(ele, "完成动画"); }}
onExit={ele => { console.log(ele, "开始离开"); }}
onExiting={ele => { console.log(ele, "正在离开动画"); }}
onExited={ele => { console.log(ele, "离开动画"); }}
3.SwitchTransition 两个组件显示和隐藏切换时,使用该组件,需要包裹CSSTransition组件 CSSTransition组件的key来控制两个组件的切换
<SwitchTransition mode="in-out"> mode:控制动画执行的类型:mode="in-out" 先隐藏 后续组件再显示 mode="out-in" 先进入新组件,然后再隐藏
<CSSTransition timeout={300} key={this.state.isOn ? "on" : "off"} classNames="switch">
<button onClick={e => { this.setState({ isOn: !this.state.isOn }) }}>{this.state.isOn ? "on" : "off"}</button>
</CSSTransition>
</SwitchTransition>
4. 将多个动画组件包裹在其中, -般用于列表中元素的动画;可以包裹多个元素
<TransitionGroup>
{
this.state.names.map((item, index) => {
return (
<CSSTransition key={index} classNames="group" timeout={300}>
<p >{item}</p>
</CSSTransition>
)
})
}
</TransitionGroup>
js纯函数
函数式编程中有纯函数的概念,js符合函数式编程范式
判断是否为纯函数的两个指标
1、函数的返回结果只依赖于它的参数。
2、不改变函数体外部数据、函数执行过程里面没有副作用。
React 非常灵活,但它也有一个严格的规则:所有 React 组件都必须像纯函数一样保护它们的 props 不被更改
不纯的函数:不纯的函数会产生副作用
1.引用外部变量【DOM,ajax,。。】,依赖外部变量
常见的副作用操作:
1.修改DOM【DOM】
2.修改全局变量【Windows】
3.ajax请求【依赖全局】
4.计时器【依赖window】
5.存储相关
一句话:与函数外部进行交互操作的都称呼为副作用
redux
redux 是 js 应用的可预测状态的容器。 可以理解为全局数据状态管理工具(状态管理机),用来做组件通信等。(管理项目的状态)
redux的作用:集中式管理react应用中多个组件共享的状态
什么情况下需要使用redux←
1.某个组件的状态,需要让其他组件可以随时拿到(共享)。。
2.一个组件需要改变另-个组件的状态(通信)。
3.总体原则: 能不用就不用,如果不用redux使得程序过于复杂,比较吃力才考虑使用。
redux的三大原则
■单一数据源
0整个应用程序的state被存储在一颗object tree中,并且这个object tree只存储在-个store中:
0 Redux并没有强制让我们不能创建多个Store ,但是那样做并不利于数据的维护;
0单-的数据源可以让整个应用程序的state变得方便维护、追踪、修改;
■State是只读的
0唯一修改State的方法- 定是触发action ,不要试图在其他地方通过任何的方式来修改State :
O这样就确保了View或网络请求都不能直接修改state ,它们只能通过action来描述自己想要如何修改state ;
0这样可以保证所有的修改都被集中化处理,并且按照严格的顺序来执行,所以不需要担心race condition (竟态)的问题; .
■使用纯函数来执行修改
0通过reducer将旧state和actions联系在- -起 ,并且返回-个新的State :
口随着应用程序的复杂度增加,我们可以将reducer拆分成多个小的reducers ,分别操作不同state tree的一部分;
0但是所有的reducer都应该是纯函数,不能产生任何的副作用;
redux三大核心
1.action:负责描述要修改的状态数据的{type:动作类型,data:修改的数据} 的对象,交给store负责调度reducer进行状态的修改
通过action修改状态数据,是可追踪的,可预测的;要通过本地或远程组件更改状态,需要分发一个action;
actions中描述要修改的state状态数据和操作的数据方法类型,传递给store,store派发action给reducer进行状态的操作
action可以是一个普通的对象 也可以是 一个函数
action是一个对象时,此时就是同步action
action是一个函数时,此时就是异步action 实现异步action需要借助redux-thunk库(redux-thun中间件:用于支持异步action)和react.applyMiddleware 中间件实现
在store/index.js文件
import thunk from "redux-thunk"
import {applyMiddleware} from "react"
const store = createStore(countReducer, applyMiddleware(thunk))
2.reducer:用于初始化状态,根据action的描述进行状态数据的修改
3.action发出了做某件事的请求,只是描述了要做某件事,并没有去改变state来更新界面,reducer就是根据action的type来处理不同的事件,去改变state;
Reducer 是一个纯函数,它接受 Action 和前一个 State 作为参数,返回一个新的 State。
Reducer 的作用:用于初始化状态,修改状态数据
store:store就是把action和reducer联系到一起的对象,store本质上是一个状态树,保存了所有对象的状态。任何UI组件都可以直接从store访问特定对象的状态。
redux执行流程
1.组件利用store自带的dispatch方法,发出一个action。
2.redux接收到action,将业务分发给对应的reducer处理。
3.reducer处理完毕后,返回处理后的state对象。
4.redux接收到新的state对象后,通知store.subscribe()函数执行。
store
一个对象,保存着对redux中的操作方法,包括dispatch(派发action),subscribe(订阅redux中的状态变化)和getState(获得状态)。store是只读的,Redux 应用有且只有一个单一的 store。
action
主要有两个作用:
用type字段描述我们准备执行的动作,通知reducer进行相应的处理;
向reducer传递数据,它是store数据的唯一来源。
action可以是一个对象:同步action
action可以是一个函数,异步action
reducer
也有两个作用:
分发action,根据action中type的不同,处理不同的事件;
返回新的state。
redux三大原则
单一数据源:所有状态都保存在单一的store中
state是只读的:不能直接对store进行修改,只能用新的store替换旧的store
使用纯函数来执行修改:reducer是只读的
react实现redux
1.index.js
创建一个store对象,并且将reducer与store进行绑定,到时候就可以通过store.dispatch({})直接访问到对应的reducer
import {createStore} from "redux"
import actionCreators from "./actionCreators"
const store=createStore(actionCreators)
export default store
2.reducer.js
Reducer 是一个纯函数,它接受 Action 和前一个 State 作为参数,返回一个新的 State。
const initState = 0 //组件的初始化状态
function countReducer(prevState = initState, action) { //prevState初始化的时候是undefined
const { type, data } = action
// 根据不同的type进行不同的操作
switch (type) {
case INCREMENT:
return prevState + data
case DECREMENT:
return prevState - data
default: //初始化
return prevState //返回值必须与上次的返回值的内存地址不同
}
}
export default countReducer
3.actionCreators.js
actions中描述要修改的state状态数据,传递给store,store派发action给reducer进行状态的操作
export const createIncrementAction = data => ({ type: INCREMENT, data })
store的API
store.getState() 获取store返回的State
store.dispatch(action对象) 派发action,相当于去执行reducer函数
store.subscribe(回调函数) 订阅state状态数据是否发生变化,一旦发生变化就执行回调函数
一般 store.subscribe(回调函数) 写在index.js入口文件中或者组件的componentDidMount(){}钩子函数中,在回调函数中进行this.setState({})=>执行render函数重新渲染页面,因为reducer改变state状态数据,不会引起页面的重新渲染
this.unsubscribe = store.subscribe(回调函数) 返回一个函数,用于取消订阅,一般写在componentWillUnmount(){this.unsubscribe()}钩子函数中,
注意:在原生的redux修改store中的状态的数据,页面不会发生自动的刷新,需要借助于store.subscribe(回调函数)来订阅状态的数据的变化,一般数据发生变化,就可以通过手动的方式:this.setState({})来刷新页面,不然页面不会自己动态的发生更新
但是在react-redux中就会将此问题解决掉,不用我们自己手动监听数据的变化
redux的目录结构
|--- src
|--- store
|--- index.js 创建store
|--- constant.js 定义常量
|--- actions 创建action的文件夹
|--- count.js count组件的action
|--- person.js person组件的action
|--- reducers 创建reducer的文件夹
|--- count.js count组件的reducer
|--- person.js person组件的reducer
|--- index.js 汇总reducer的文件
安装:yarn add redux
react-redux
在react-redux看来组件分为两种类型:1.容器组件,2.UI组件
其中容器组件包含UI组件,redux和容器组件进行交流,容器组件通过porps传递状态给UI组件
redux->react-redux->容器组件->UI组件
components文件夹存放的是ui组件(负责页面展示的组件)
container文件夹存放的是容器组件
安装:
yarn add react-redux
使用:
入口文件中
import { Provider } from "react-redux" //此处使用Provider包裹App组件:作用是让App所有的后代组件都能够通过props接收到store
<Provider store={store}>
<App/>
</Provider>
在需要使用redux的组件中
import { increment } from '../store/actionCreator';//action
import { connect } from 'react-redux';//connect根据UI组件生成一个容器组件
function Home(props) {//UI组件
console.log(props);
return (
<div>
{props.count}
<button onClick={e => props.increment()}>+2</button>
</div>
)
}
/*映射redux中的state状态数据 当前组件需要依赖哪些state数据 mapStateToProps是一个函数,返回值是一个对象,整个对象会传递给props*/
const mapStateToProps = state => ({ count: state.count }) //这个state是redux状态总对象
/*映射redux中操作state的方法 当前组件需要依赖的dispatchmapDispatchProps可以是一个函数,函数的返回值是一个对象,对象的会传递给props */
const mapDispatchProps = dispatch => {//这个dispatch是redux传递过来的
return {
increment: function (data) {==>调用:this.props.increment(data)
dispatch(increment(data))
}
}
}
export default connect(mapStateToProps, mapDispatchProps)(Home)
简化:mapDispatchProps,简化之后mapDispatchProps可以是一个对象
cosnt mapDispatchProps= { ==> 调用:this.props.increment(data)==>这个实际上调用的是一个action的函数,返回一个action对象,react-redux就会默认的将这个action函数绑定到dispatch进行执行,就不用手动进行dispatch()
increment:incrementAction
}
react-redux中两个最重要的函数 connect和Provider
connect:
// connect() 返回的是一个高阶组件,connect()函数接收两个参数:第一个参数:mapStateToProps【映射state给UI组件通过props传递】,第二个参数:mapDispatchToProps【映射dispatch给UI组件通过props传递】
// connect(mapStateToProps,mapDispatchToProps)(UI组件) 返回的是一个类组件(加工后的组件) connect()返回的是一个高阶组件 connect()()执行高阶组件 高阶组件需要一个组件为参数,返回一个新的组件
mapStateToProps: 当前组件需要依赖哪些state数据
mapDispatchToProps:当前组件需要依赖的dispatch
Provider: 使用Provider包裹App组件:作用是让App所有的后代组件都能够通过props接收到store
异步action:函数式的action就是异步的
需要借助中间件:redux-thunk
安装:yarn add redux-thunk
在store/index.js
import {applyMiddleware} from "redux"
import thunk from "redux-thunk" //中间件
//applyMiddleware(中间件1,中间件2....) //应用中间件
//createStore第二个参数就是应用中间件
createStore(reducer,applyMiddleware(中间件1,中间件2....))
在action.js中
export const asyncRequestSwipe = (url) => {
return (dispatch,getState) => { //异步action返回的一个函数:在这个函数中执行异步操作
axios.get(url).then(res => {
dispatch(requestSwipe(res.data.message))
})
}
}
当action返回的为一个函数时:这个函数会收受到了两个参数:dispatch用于调用reducer ,getState用于获取state数据
redex多个组件数据共享
在reducers/index.js 整合reducer
通过 import {combineReducers} from "redux"
const allReducer=combineReducers({
xx:reducer1,
yy:reducer2,
key:value
})
combineReducers作用是合并多个reducer, combineReducers返回的对象(store的statte总对象)可以作为createStore的第一个参数
combineReducers接收一个对象,这个对象就是redux中需要保存的state总状态对象
合并后的store中保存的数据结构是:
{
reducer1的别名:reducer1中的state的数据
reducer2的别名:reducer2中的state的数据
}
之后访问state的话,通过store.getState().reducer1的别名 或者 store.getState().reducer2的别名
react路由
react-router-dom
1. react的一个插件库。
2. 专用来实现一个SPA应用。
3. 基于react的Web项目基本都会用到此库。
前端路由的原理:
简单来说,前端路由器监听 浏览器地址栏路由地址变化 然后展示路由对应的组件
基本使用
安装:yarn add react-router-dom 《Web》
所有的路由组件标签都需要包含在Router标签里面
improt { BrowserRouter,HashRouter } from "react-router-dom" 路由有两种模式:history和hash模式(对应:BrowserRouter/HashRouter),要说明使用的是那种模式的路由
<BrowserRouter>
...
</BrowserRouter>
<HashRouter>
...
</HashRouter>
一般这两个组件给App组件包裹就行
使用:Api路由组件
import {Link,Route,Switch} from "react-router-dom" 在react中靠路由链接是实现切换路由
1.Link:编写路由链接
<Link className="" to="path路径">文本</Link>
Link默认渲染成a标签
to与to绑定的值,被转换成a的href属性
2.Route:注册路由 (相当于vue中的router-view路由占位)
<Route path="与Link标签的to一致" component={组件名称}>文本</Route>
3.NavLink:也是编写路由链接的(Link的升级版)
<NavLink activeClassName="指定动态追加的类名(默认是active)" to="path路径">文本</NavLink>
NavLink的特点:点击哪个NavLink,这个NavLink就会追加上一个active的类名
4.Switch: 一个路由只匹配一个组件
<Switch> //使用Switch将所有的注册组件包裹起来,匹配上一个路由,就不会在继续匹配
<Route path="/home" component={Home}>文本</Route>
<Route path="/home" component={Test}>文本</Route> //此时就只会展示第一个Home组件
</Switch>
注意点:
所有的Api路由组件都需要被单独的一个BrowserRouter或HashRouter,进行包裹,
如果一个应用种出现多个BrowserRouter或 HashRouter,那么就是说明使用了多个不同路由对象,一个表示一个路由对象
二次封装 NavLink
export default class MyNavLink extends PureComponent {
render() {
return (
<div>
<NavLink {...this.props} activeClassName="test-active" className="publicClass"></NavLink>
</div>
)
}
}
使用封装后的NavLink
<MyNavLink to="/home">Home</MyNavLink>
<MyNavLink to="/about">About</MyNavLink>
组件标签之间的内容会被放到props中的children属性中存储
例如: <组件1>hello world</组件>
获取hello world:通过this.props.children
在react中组件标签的都会有一个默认的属性:children
<组件1 children="xxx"></组件> children可以指定组件标签之间的内容
一个路由只匹配一个组件
<Route path="/home" component={Home}>文本</Route>
<Route path="/home" component={Test}>文本</Route>
当一个路由匹配上多个组件,当访问这一个路由时,这些组件都会被展示出来
此时就会出现一个效率问题:当路由地址发生变化后,路由器会将所有的Route的path都匹配一遍.将路由匹配成功的组件渲染出来
解决此效率问题: 一个路由匹配一个组件,当匹配到路由时,就不往后再进行匹配
import {Switch} from "react-router-dom"
<Switch> //使用Switch将所有的注册组件包裹起来,匹配上一个路由,就不会在进行匹配
<Route path="/home" component={Home}>文本</Route>
<Route path="/home" component={Test}>文本</Route> //此时就只会展示第一个Home组件
</Switch>
路由的精准匹配与模糊匹配
1.路由默认的是:模糊匹配
Link去匹配Route
<Link to="/home/a/b">文本</Link>
<Route path="/home" component={Home}>文本</Route> 此时还是会展示出Home组件
<Link to="/home">文本</Link>
<Route path="/home/a/b" component={Home}>文本</Route> 此时不会展示出Home组件
2.精准匹配:谨慎使用 exact={true}
<Link to="/home">文本</Link>
<Route exact={true} path="/home/a/b" component={Home}>文本</Route>
路由重定向
import {Rediract} from "react-router-dom"
如果在注册路由中,没有匹配到任何的Router,那么就会跳转到Rediract指定的路径对应的组件
<Rediract to="path路径"></Rediract>
嵌套路由
编写路由
<MyNavLink to="/父级路由/news">news</MyNavLink>
注册路由
<Route path="/父级路由/news" component={News}></Route>
注册子路由时,要在前面加上父路路由的path
路由的匹配时按照注册路由的顺序进行的,从一开始的第一个路由匹配到最后一个路由
路由传参
1.params类型:http://localhost:3000/home/params参数
编写路由时:设置要携带传递的数据
<Link style={{ color: "skyblue" }} to={`/home/message/detail/${item.id}/${item.title}`}>{item.title}</Link>
注册路由组件时:设置接收数据的字段
<Route path="/home/message/detail/:id/:title" component={MessageDetail}></Route>
在MessageDetail组件中接收参数数据 传递给组件的props
this.props.match.params
2.search类型:http://localhost:3000/home?name=zs&age
编写路由时:设置要携带传递的数据
<Link style={{ color: "skyblue" }} to={`/home/message/detail?id=${item.id}?title=${item.title}`}>{item.title}</Link>
注册路由组件时:search无需声明接收 正常注册路由即可
<Route path="/home/message/detail" component={MessageDetail}></Route>
在MessageDetail组件中接收参数数据 传递给组件的props
this.props.location.search 返回的一个urlcode字符串,需要借助quertstring工具库进行解析转换成js对象,然后获取数据进行使用
3.传递state参数:
params参数与search参数都会在地址栏中展示出来
statr参数不会显示在地址栏中
编写路由时:to要求是一个对象 to={{pathname:"指定路由",state:{state参数}}}
<Link style={{ color: "skyblue" }} to={{ pathname: "/home/message/detail", state: { id: item.id, title: item.title } }}>{item.title}</Link>
注册路由组件时:state无需声明接收 正常注册路由即可
<Route path="/home/message/detail" component={MessageDetail}></Route>
获取state参数
const { id, title } = this.props.location.state
所有的路由组件的props都会接收到以下参数:
history:
go: ƒ go(n)
goBack: ƒ goBack()
goForward: ƒ goForward()
push: ƒ push(path, state)
replace: ƒ replace(path, state)
location:
pathname: "/about"
search: ""
state: undefined
match:
params: {}
path: "/about"
url: "/about"
路由的两种跳转模式
push模式:栈结构,先进后出 【默认模式】
replace模式:替换当前的栈顶的localtion记录
开启replace模式:<Link replace={true}></Link>
编程式导航
this.props.history.push("path/${id}/${params}")
this.props.history.replace("path?id=${id}")
this.props.history.replace("path",{state参数})
this.props.history.go(n)
在普通组件中使用路由的Api
默认在普通组件中不能使用路由的Api,但是可以借助于withRouter函数
withRouter(组件) ==》此时被绑定的组件上就多出了路由的Api
withRouter(组件)还是会返回一个新组件
在普通组件的文件中:
import {withRouter} from "react-router-dom"
class Header extends Component{}//一般组件
export default withRouter(withRouter)
简单来说:withRouter函数可以加工一般组件,让一般组件具有路由组件所特有的Api
BrowserRouter与HashRouter的区别
1.底层原理不一样:
BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。
HashRouter使用的是URL的哈希值。
2.ur1表现形式不一-样
Brows erRouter的路径中没有# ,例如: localhost :3000/ demo/test
HashRouter的路径包含#,例如: localhost : 3000/#/demo/test
3.刷新后对路由state参数的影响
(1) . BrowserRouter没有任何影响,因为state保 存在history对象中。
(2) . HashRouter刷新后会导致路由state参数的丢失。
4.备注:HashRouter可以用于解决一些路径错误相关的问题。HashRouter
5.HashRouter的url路径中的#后面的内容不会作为请求路由发送给后端服务器,并且hashRouter的兼容性好于BrowserRouter
BrowserRouter的url路径会作为请求url发送给后端服务器,需要后端服务器进行配合
##react-router-config=>类似于vue的路由写法 20 34:21 https://www.npmjs.com/package/react-router-config
yarn add react-router-config --save
// 编写路由的映射关系
export const routes = [
{
path: "/home",
exact: true,
component: Home,
routes:[
{
path:"/home/aaa/"
component:HomeSon
}
]
},
]
import {renderRoutes} from "react-router-config"
import routes from "./router/index.js"
{renderRoutes(routes)} //相当于 router-view的作用
{renderRoutes(this.props.route.routes)}
实现路由的跳转
<NavLink activeClassName="指定动态追加的类名(默认是active)" to="path路径">文本</NavLink>
NavLink的特点:点击哪个NavLink,这个NavLink就会追加上一个active的类名
路由组件与普通组件
写法不同:
一般组件:<Demo/>
路由组件:<Route path="/demo" component={Demo}/> 靠路由匹配上然后展示的组件
文件存放位置不同:
一般组件:components
路由组件:pages
接收到的props不同:
一般组件:写组件标签时传递了什么,就能收到什么
路由组件:
接收到三个固定的属性
history:
go: ƒ go(n)
goBack: ƒ goBack()
goForward: ƒ goForward()
push: ƒ push(path, state)
replace: ƒ replace(path, state)
location:
pathname: "/about"
search: ""
state: undefined
match:
params: {}
path: "/about"
url: "/about"
Hook
React 16.8新增特性 Hook可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
class组件存在的问题
1。业务增加,代码变得的复杂。
2。this指向问题
3。组件的状态难以复用
简单总结hooks:
0它可以解决上述问题
0它可以让我们在不编写class的情况下使用state以及其他的React特性; hook基于函数
0但是我们可以由此延伸出非常多的用法,来让我们前面所提到的问题得到解决;
0hook就是一个js函数,可以帮助我们钩入react及其生命周期函数
Hook 就是 JavaScript 函数,但是使用它们会有两个额外的规则:
只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。(还有一个地方可以调用 Hook —— 就是自定义的 Hook 中,我们稍后会学习到。)
hooks函数要放到组件的最顶层进行调用
Hook与Hooks
➢Hook指的类似于useState,useEffect这样的函数
➢Hooks是对这类函数的统称;
useEffect、useMemo、useCallback都是自带闭包的
1.useState Hook
useState可以在函数组件中添加state而不必转换成class组件
useState接受唯一一个参数 ,在第一次组件被调用时使用来作为初始化值。( 如果没有传递参数,那么初始化值为 undefined )
useState返回的是一个数组 ,我们可以通过数组的解构,来完成赋值会非常方便。[state.setState]
初始化状态
improt {useState} from "react"
cosnt [state,setXxx]=useState(state状态数据) / useState(函数) 传递的参数是一个函数的话 函数的返回值会作为state
取出状态数据:state
修改state数据:setXxx(newState) 或 setXxx(回调函数)
注意点:setstate修改的state不能与上一次的引用地址相同,否则无法完成修改
setXxx(newState) 接收一个变量:那么setXxx(newState就是异步更新state,无法立即获取到更新之后的变量state
setXxx(newState=>{})可以接收一个函数,函数接收一个参数:最新的State, 函数的返回值作为修改后的state的值,此时的setXxx就是同步执行的
setstate接收的如果是一个函数,那么setstate()就可以同时被执行多次,
setstate接收的如果是一个值,那么setstate()就不能被同时执行多次,setstate()就会合并成一次被执行
setXxx一旦完成修改操作,就会完成一次更新组件操作,
2.useEffect Hook - 执行副作用操作
useEffect可以完成class组件中的生命周期的功能,类组件中的生命周期函数都在effect中有体现
useEffect应用场景1:
useEffect(()=>{
可以在useEffect里面做一些副作用的操作
})
useEffect的执行时机:
1.初次渲染组件时会被调用 --componnentDidMount
2.更新组件完成后被调用 -- componentDIdUpdate
可以简单的理解为useEffect讲componentDidMount与ComponentDidUpdate做了简单的合并
真实DOM构建完成之后【组件渲染完成之后】执行 useEffect 不会阻塞DOM的更新
useLayoutEffeact 会在内容更新到DOM上之前执行,会阻塞DOM的更新
componentDidMount与ComponentDidUpdate是在真实DOM构建完成之前被调用
useEffect返回值函数
可以完成清除 effect的副作用,通常,组件卸载时需要清除 effect 创建的诸如订阅或计时器 ID 等资源。要实现这一点,useEffect 函数需返回一个清除函数。
useEffect(()=>{
return ()=>{//函数执行时机:初次渲染页面->render->effect->发生更新->render->清除函数->effect
}
})
清除函数的执行时机:
1.组件发生更新:初次渲染页面->render->effect->发生更新->render->清除函数->effect
2。组件卸载之后
useEffect是异步的
第一个参数:回调函数默认的执行时机,是当页面中DOM每次挂载完毕后和页面发生更新完成后都会调用这个函数
回调函数的返回值也是一个回调函数,返回值回调函数的执行时机:【默认的执行时机:当组件销毁和组件发生更新时就会调用返回值函数】
只让返回值回调函数在组件被卸载的时候被触发:给useEffect加上第二个参数,[],表示useEffect不依赖任何数据
第二个参数:[回调函数依赖的数据],或者 []
只有当数组中指定的数据发生变化时,才会调用第一个参数指定的回调函数
如果数组啥也不写,就放一个空数组在第二个参数的位置,那么useEffect只会在初始化页面的时候被调用一次,组件卸载的时候会调用回调函数的返回值函数,之后就不会被调用,表示回调函数不依赖任何数据
第二个参数也可以用于监听某个状态的改变,一旦依赖的数据发生变化,就会调用useEffect
useEffect(()=>{
订阅操作
return ()=>{
取消订阅操作
}
},[])
3.useContext Hook
useContext可以实现父组件跨层级传递props数据给子组件
const value=useContext(context对象)
useContext函数接受一个参数:context对象
useContext函数返回值:Provider的value指定的值
可以快速的取出Context传递的数据
使用步骤:
1.在父组件中创建context上下文对象
import {createContext} from "react"
export context xxContext=createContext()
2.在父组件中讲需要数据的组件使用 xxContext.Provider进行包裹
<xxContext.Provider value={传递的数据 }>
子组件
</xxContext.Provider>
3。在子组件中引入父组件创建的context对象
import {xxContext} from "父组件“
improt {useContext} from "react"
cosnt value=usecontext(xxContext)
4.useRedecer Hook
useRedecer式useState的一种替代方案,它更适合用于管理包含多个子值的 state 对象。
import {useReducer} from "react"
const [state数据,xxxDispatch]=useReducer(操作state的纯函数reducer,初始化数据initState)
操作state的纯函数reducer
function reducer(state,action){
switch(action.type){
case "":
return state的操作
case ”“:
return state的操作
default:
return state
}
}
使用reducer来操作state【当state为对象的时候】
xxxDispatch({type:"",data}) dispatch派发action调用reducer函数
5.useCallback Hook --缓存一个函数
useCallback目的是为了进行性能优化,组件化有一个问题,就是父组件依赖的状态发生变化后,父组件会发生重新渲染,此时也会引起子组件不要的重新渲染
在组件中:如果组件重新发生渲染,组件内的函数都i会被重新执行调用【每次的函数都是执行的不同的引用】
useCallback应用场景:
有一个父组件,其中包含子组件,子组件接收父组件一个函数作为props;通常而言,如果父组件更新了,子组件也会执行更新;【如果子组件接收的props始终指向的是同一引用(props的值不会发生变化),子组件在父组件中也不会随着父组件发生更新】
但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件,如果这个useCallback函数的第二个参数,指定的依赖没有发生变化,那么这个函数就会被缓存起来【这个函数始终指向的就是同一个依赖,不会重新发生调用,改变指向】
这样,子组件接收到的props就是指向同一引用,子组件就能避免不必要的更新。
const 返回一个函数=useCallback(()=>{
默认情况下这个回调函数在每一render后都会被销毁,然后重新创建,useCallback包裹后,就会被缓存起来
},[依赖的状态])
[这个函数会根据依赖的状态的值是否发生变化,然后决定是否重新执行],函数重新执行,那么函数的引用就会发生变化,一旦函数的引用发生变化,再将这个变化的函数作为props传递给子组件,那么子组件间就会发生一些无畏的渲染
6.useMemo --缓存的是一个值
const 回调函数的值=useMemo(回调函数,[依赖的状态])
useMemo与useCallback的道理一样
7.类组件的Ref
react获取dom的唯一方式
还是使用渲染的方式createRef的方式获取DOM
7.useRef 函数组件的Ref
使用useRef
使用方式:
import {useRef} from "react"
const xxxRef=useRef() //返回的 ref 对象在组件的整个生命周期内保持不变。
<input ref={xxxRef}>
<组件 ref={xxxRef}>==》ref获取的是类组件的实例对象 ,但是不能直接给函数式组件绑定ref
获取DOM:xxxRef.current
7.转发ref,给函数式子组件元素绑定ref
import {forwardRef} from "react"
const 新的函数式组件=forwardRef((props,父组件传递过来的ref)=>{ //函数式组件
return (
<div></div>
)
})
被forwardRef包裹的函数式组件还可以接收第二个参数:ref,是父组件给函数式子组件绑定ref值
然后子组件接受到这个ref,可以通过ref={接收的ref} 给自己的DOM元素绑定上,然后就可以在父组件中访问到子组件中的DOM
8.useImperativeHandle Hooks useImperativeHandle相当于是重构ref的current属性值
useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值,【父组件给函数式子组件中的元素绑定ref后,父组件可以通过ref获取到这个元素的全部,
如果不想这样,而只是想通过自定义的暴露当前元素的某些属性或方法给父组件,就可以使用seImperativeHandle,useImperativeHandle可以重新指定ref.current的值】
useImperativeHandle 应当与 forwardRef 一起使用:useImperativeHandle应当放在forwardRef函数体内部
forwardRef((props,父组件传递过来的ref)=>{
useImperativeHandle(父组件传递过来的ref,回调函数,[依赖数据])
})
useImperativeHandle第一个参数:准备重构那个ref,
useImperativeHandle第二个参数:接收一个函数,函数的返回值应为一个对象,返回值就作为ref的current对象的值
useImperativeHandle(iptRef, () => {
return {
focus: () => {
SoniptSelfRef.current.focus()
}
}
})
在父组件中通过 iptRef.current.focus()调用的是focus: () => { 这个函数
iptSelfRef.current.focus()
}
focus: () => { 这个函数
iptSelfRef.current.focus()
}
在这个函数内部使用子组件自己的ref进行绑定元素
9.useLayoutEffect Hooks
这个是用在处理DOM的时候,当你的useEffect里面的操作需要处理DOM,并且会改变页面的样式,就需要用这个,
否则可能会出现出现闪屏问题, useLayoutEffect里面的callback函数会在DOM更新完成后立即执行,
但是会在浏览器进行任何绘制之前运行完成,阻塞了浏览器的绘制.
useLayoutEffect与useEffect用法一致
10.自定义hook
它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。通过自定义 Hook,可以将组件逻辑提取到可重用的函数中。
只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用
自定义hooks函数:可以使用react提供的hooks,而普通函数就不能使用hook,自定义hook函数名称前面一定要加use
自定义hooks函数
function useXxxx(){//在个函数中就可以使用hook,让后实现代码的复用
useEffect(() => {
console.log(`${data}创建完毕或发生更新`);
return () => {
console.log(`${data}卸载`);
}
}, [])
....
}
useXxxx()调用
Fiber
Fiber 是 React 16 中新的协调引擎。它的主要目的是使 Virtual DOM 可以进行增量式渲染