React
一、React概述
1.1简介
react是一个用于构建用户界面的js库
用户界面:HTML页面
React主要用来写HTML页面或者构建Web应用
如果从MVC的角度来看,React仅仅是视图层,也就是只负责视图的渲染,而并非提供了完整的M和C的功能。
React起源于Facebook的内部项目,后来又用来架设Instagram的网站,并于2013年5月开源
1.2React特点
1、声明式
只需要描述UI(html)看起来是什么样,就跟写HTMl一样(JSX)
React负责渲染UI,并在数据变化时更新UI
const jsx=<div className='app'>
<h1>hello react! 动态变化数据:{count}</h1>
</div>
2、基于组件
-
组件是react最重要的内容
-
组件表示页面中的部分内容
-
组合、复用多个组件,可以实现完整的页面功能
3、应用广
- web
- 移动端
- vr应用
…
1.3React的安装
vscode中在终端选项中新建终端
cd命令选择在当前文件夹中安装,命令:npm i react react-dom
来创建两个包
- react 包是核心,提供创建元素、组件等功能
- react-dom包提供DOM相关功能
二、React的基本使用
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J34I2z2P-1677566304596)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230215185607422.png)]
<!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="root"></div>
<!-- 1、引入js文件 -->
<script src="./node_modules/react/umd/react.development.js"></script>
<script src="./node_modules/react-dom/umd/react-dom.development.js"></script>
<script>
// 2、创建react元素
//参数一、元素名称
// 参数二、元素属性
// 参数三、元素子节点
const title = React.createElement('h1', null, 'hello React')
// 3、渲染react元素
// 参数一、要渲染的react元素
// 参数二、挂载点
ReactDOM.render(title, document.getElementById('root'))
</script>
</body>
</html>
注意:ReactDOM中DOM全大写
总结:
1、引入react文件
<script src="./node_modules/react/umd/react.development.js"></script>
<script src="./node_modules/react-dom/umd/react-dom.development.js">
之后导入react脚手架后会有更加简便的写法
2、创建react元素React.createElement()——了解
const element = React.createElement(type,props, children1,[childrenN]);
-
type:元素类型 如:h1 div p等
可以是字符串如:div p h1
也可以是自定义组件:react原生组件等
-
props表示改元素上的属性,使用对象的方式表示
{
className:‘greeting’
}
-
children:表示元素内部的内容,可有多个children,可以是文字,也可以是另一个react元素
了解JSX后会有更加简便的写法,这里只需了解不需要记忆
3、ReactDOM.render()渲染react元素——重要
ReactDOM.render(Reactele, DOMele)
Reactele:要渲染的react元素
DOMele:DOM对象,用于指定渲染到页面的DOM元素
三、React脚手架
进入正题
3.1意义
1、脚手架是开发现代Web应用的必备
2、充分利用Webpack\Babel\ESlint等工具辅助项目开发
3、零配置。无需手动配置繁琐的工具即可使用,方便更专注与项目
3.2使用React脚手架初始化项目
1、初始化项目,命令:npx create-react-app 项目名。
-
为加快初始化速度可以将npm的源进行更换,使用以下命令:
npm config set registry https://registry.npm.taobao.org
– 配置后可通过下面方式来验证是否成功
npm config get registry
– 显示出上述地址的话就是更换成功
-
可以在cmd中全局安装react脚手架,npm install -g create-react-app
之后便可直接用create-react-app 项目名 来构建react脚手架项目
2、使用cd命令转到刚刚创建的文件夹
3、启动项目,在项目根目录执行命令:npm start
4、测试,打开终端提供的网址出现启动页面则证明成功
3.3在脚手架中使用React
初始化的文件夹中public文件夹中的index.html以及src中的index.js是首页也就是之前测试用的网页可以删除或者更改
1、导入react和react-dom两个包
import React from 'react'
import ReactDOM from 'react-dom'
2、调用React.createElement()方法创建react元素(以后会用JSX创建)
3、调用ReactDOM.render()方法渲染react元素到页面中
注意:在html页面中使用alt+b显示的页面不会显示调整的内容,也就是不能直接在浏览器中打开html页面,而是需要打开npm start命令打开的页面即 http://localhost:3000
JSX
JSX不是标准的ES语法,他是ES的语法扩展
需要用babel编译处理后,才能在浏览器中使用,而脚手架中已经给了babel环境
create-react-app脚手架中已经有默认的配置,无需手动配置
编译JSX语法的包是@babel/preset-react包
四、JSX的基本使用
4.1 createElement()的问题
- 繁琐不简洁
- 不直观,无法一眼看出结构
- 不优雅,体验感差
4.2JSX简介
-
JSX是JavaScript XML的简写 表示在JavaScript代码中写入XML格式(HTML)的代码
-
优势:声明式语法更加直观,与HTML结构相同,降低了学习成本提升了开发效率
-
JSX是React的核心内容
4.3 JSX使用步骤
1、使用JSX语法创建React元素
const title=<h1>Hello JSX</h1>//创建react元素
react元素实则就是标签及其属性内容
2、使用ReactDOM.render()方法渲染react元素到页面中
ReactDOM.render(title,root)//渲染创建好的react元素
ReactDOM.render是 React 的最基本方法用于将模板转为 HTML 语言,并插入指定的 DOM 节点。一个节点只能插入一个title元素,后来插入的title元素会将其覆盖
4.4注意点
-
react元素属性名使用驼峰命名
-
特殊属性名:
class->className
for->htmlFor
tabindex->tabIndex
const title=<h1 className="title">Hello JSX</h1>
- 没有子节点的标签可以用/>结束,也就是双标签可以变成单标签
<span/>
- 最好用小括号包裹JSX,从而避免JS中的自动插入分号陷阱
const dv=(<div>hello JSX</div>)
- vscode中若有JS-CSS-HTML自动格式化插件容易导致JSX的缩进错误导致IDE报错所以要卸载禁用
4.5 JSX中使用JS表达式
嵌入JS表达式
- js表达式的数据存储在JS中
- 语法:{Js表达式}
const name='Jack'
const dv=(
<div>你好,我叫:{name}</div>
)
ReactDOM.render(dv,root)
注意:
-
{1},{‘a’},{1+4}等都是合法的
-
函数调用表达式也可以 {fn()}
-
JSX自身也是JS表达式
-
JS对象不能直接在{}中使用
-
语句不能在{}中出现比如for循环
const h1=<h1>我是h1</h1>
const dv=<div>嵌入表达式{h1}</div>
4.6 JSX的条件渲染
- 条件渲染:根据条件来渲染特定的JSX结构
const loadData=()=>{
if(isLoading){
return <div>loading...</div>
}
return <div>数据加载完成</div>
}
const title=(
<h1>条件渲染:{loadData()}</h1>
)
ReactDOM.render(title,document.getElementById('root'))
- 可以使用三元运算符来实现条件渲染
const title=(
<h1>条件渲染:{isloading?(<div>loading...</div>):(<div>数据加载完成</div>)}</h1>
)
ReactDOM.render(title,document.getElementById('root'))
-
逻辑与运算符&&
&&运算符若为true则会返回最后一个表达式值
const title=(
<h1>条件渲染:{isloading&&<div>loading...</div>}</h1>
)
ReactDOM.render(title,document.getElementById('root'))
4.7JSX列表渲染
- 如果要渲染一组数据,需要使用数组的map()方法
- map:将数组每个元素进行()中函数运算后加入新数组并返回该数组
- 注意:渲染列表时应该添加key属性,key属性的值要保证唯一
- 原则:map遍历谁就给谁添加key属性
- 避免使用索引号作为key
const songs=[
{id:1,name:'痴心绝对'},
{id:2,name:'向我这样的'}
]
const list={
<ul>
{songs.map(item=><li key={item.id}>{item.name}</li>)}
</ul>
}
4.8JSX样式处理
1、行内样式–style
const title=<h1 style={{color:'red',backgroundColor:'skyblue'}}></h1>
2、类名–className (推荐)
const title=<h1 className="title"></h1>
组件
五、React组件
5.1组件介绍
- 组件是React的一等公民,使用react就是使用组件
- 组件表示页面中的部分功能
- 组合多个组件实现完整的页面功能
- 特点:可复用、独立、可组合
5.2React组件的两种创建方式
1、使用函数创建组件
- 函数组件:使用JS的函数(或箭头函数)创建的组件
- 约定1:函数名称必须以大写字母开头
- 约定2:函数组件必须有返回值,表示组件的结构
function Hello(){
return{<div>我的第一个组件</div>}
}
//const Hello=()=><div>我的第一个组件</div>
- 渲染函数组件:直接用函数名作为组件标签名
- 组件标签可以是单标签也可以是双标签
ReactDOM.render(<Hello/>,document.getElementById('root')
2、使用类创建组件
- 类组件:使用ES6的class创建的组件
- 约定1:类名称必须以大写字母开头
- 约定2:类组件应该继承React.Component父类,从而可以使用父类中提供的方法或者属性
- 约定3:类组件必须提供render()方法
- 约定4:render()方法必须有返回值,表示该组件的结构
class Hello extends React.Component{//创建类组件
render(){
return <div>Hello!</div>
}
}
ReactDOM.render(<Hello/>,root)//渲染组件
3、将组件抽离为独立js文件
将组件单独放入一个js文件中
1、创建js文件
2、导入React
3、创建组件
4、导出该组件
5、在需要使用该组件的js文件中导入该组件
6、渲染组件
//hello.js
import React from 'react'
class Hello extends React.Component{
render(){
return <div>Hello!</div>
}
}
//导出Hello组件
export default Hello
//index.js
import Hello from './Hello'
//渲染导入的Hello组件
ReactDOM.render(<Hello/>,root)
六、React事件处理
6.1事件绑定
- React事件绑定语法和DOM事件语法类似
- 语法:on+事件名称={事件处理程序},比如: onClick={()=>{}}
- react事件采用驼峰命名
class App extends React.Component{//类组件
handleClick(){
console.log('单击事件')
}
render(){
return (
<button οnclick={this.handleClick}>点我</button>
}
)
}
function App(){//函数组件
function handleClick(){
console.log('单击事件')
}
return (
<button οnclick={handleClick}>点我</button>
)
}
6.2事件对象
- 可以通过事件处理程序的参数获取到事件对象
- react中的事件对象叫做:合成事件(对象)
- 合成事件:兼容所有浏览器,无需担心跨浏览器兼容性问题
function handleClick(e){
e.preventDefault()
console.log('事件对象',e)
}
七、有状态组件和无状态组件
- 函数组件称为无状态组件
- 类组件称为有状态组件
- 状态:即数据(state中的数据)
- 函数组件没有自己的状态,只负责数据的展示(静态)
- 类组件有自己的状态,负责更新UI,让页面“动”起来
八、组件中的state和setState
8.1state的基本使用
- 状态(state)即数据,是组件内部的私有数据,只能在组件内部使用
- state的值是对象,表示一个组件中可以有多个数据
class Hello extends React.Component{
//初始化
construct(){//构造函数
super()//es6中的要求用于继承父类构造函数的函数
//初始化state
this.state={
count:0
}
}
//初始化简化语法(推荐)
/*
state={
count:0
}
*/
render(){
return (
<div>有状态的组件,{this.state.count}</div>
)
}
}
- class中获取状态:this.state
8.2 setState()修改状态
- 状态可变
- 语法:this.setState({要修改的数据})
- 注意:不要直接修改state中的值
- setState的作用:1、修改state2、更新UI
- 思想:数据驱动视图
<button onClick={()=>{
this.setState({
count:this.state.count+1
})
}}>
点我+1</button>
九、从JSX中抽离出事件处理程序
class Hello extends React.Component{
state={
count:0
}
onIncrement(){
//this为undifined 此程序出错
this.setState({
count:this.state.count+1
})
}
render(){
return(
<div>
<h1>计数器:{this.state.count}</h1>
<button οnclick={this.onIncrement}>点我+1</button>
</div>
)
}
}
上面的代码发生了错误原因如下:
- 抽离后的this值为undefined需要重新指向
解决方法:
- 事件绑定this指向的修改
1、箭头函数绑定事件
利用箭头函数自身不绑定this的特点
class Hello extends React.Component{
state={
count:0
}
onIncrement(){
this.setState({
count:this.state.count+1
})
}
render(){//利用箭头函数更改调用时的this指向
return(
<div>
<h1>计数器:{this.state.count}</h1>
<button οnclick={()=>this.onIncrement()}>点我+1</button>
</div>react
)
}
}
2、Function.prototyoe.bind()
利用ES5中的bind方法,将事件处理程序中的this与组件实例绑定到一起
class Hello extends React.Component{
constructor(){
super()
this.onIncrement=this.onIncrement.bind(this)//将this绑定到constructor的this
state={
count:0
}
}
onIncrement(){
this.setState({
count:this.state.count+1
})
}
render(){
return(
<div>
<h1>计数器:{this.state.count}</h1>
<button οnclick={this.onIncrement}>点我+1</button>
</div>
)
}
}
3、class的实例方法
- 利用箭头函数形式的class实例方法
class Hello extends React.Component{
state={
count:0
}
onIncrement=()=>{//利用箭头函数将this指向当前实例
this.setState({
count:this.state.count+1
})
}
render(){
return(
<div>
<h1>计数器:{this.state.count}</h1>
<button οnclick={this.onIncrement}>点我+1</button>
</div>
)
}
}
十、表单处理
10.1受控组件–重点
- HTML中的表单元素是可输入的,也就是有自己的可变状态
- 而React中可变状态通常保存在state中,并且只能通过setState()改变
- React将state与表单元素值value绑定到一起,由state的值来控制表单元素的值
- 受控组件:值受到React控制的表单组件
- 总的来说就是要使组件的值能随state相应值的改变而改变,state的值也要随组件值的改变而改变,两者相互同步
绑定步骤:
1、在state中添加一个状态,作为表单元素的value值(表单绑定state)
state值的改变会使value值改变
state={txt:''}
<input type="text" value={this.state.txt}/>
2、给表单元素绑定change事件,将表单元素的值设置为state的值(state绑定表单)
当表单中的value值通过输入改变时同时也会改变state中的值
<input type="text" value={this.state.txt} onChange={e=>this.setState({txt:e.target.value})}/>
10.2多表单元素优化
优化:使用一个事件处理程序同时处理多个表单元素
步骤:
1、给表单元素添加name属性,名称与state相同(便于分辨和更改state同名的值)
<input type="text" name="txt" value={this.state.txt} onChange={this.handleForm}/>
这里名称为txt
name的一个作用是分辨表单标签,而另一个重要作用就是其命名与state中相应变量同名,以便于用标签name名与[]组合来锁定变量以更方便的改变变量的数据,也就是相当于对state中相应变量的一个索引
2、根据表单元素类型获取相应的值
3、在change事件处理程序中通过[name]来修改对应的state值
//根据表单元素类型获取值
const value=(target.type==='checkbox'?target.checked:target.value)
//根据name设置对应的state
this.setState({
[name]:value//当创建对象并将该对象的键包装在数组括号[]中时,可以动态改变其键名
})
import React from'react'
class FormHandle extends React.Component{
constructor(){
super()
this.state={
txt:'',
txtarea:''
}
}
handleForm=e=>{
//获取当前事件的Dom对象
const target=e.target;
//根据类型获取值
const value=target.type==="checkbox"
?target.checked:target.value
//获取name
const name=target.name
//设置state值
this.setState({
[name]:value//当创建对象并将该对象的键包装在数组括号[]中时,可以动态改变其键名
})
}
render(){
return (
<form>
<input type="text" name="txt" value={this.state.txt}
onChange={this.handleForm} ></input>
<textarea name="txtarea"
value={this.state.txtarea}
onChange={this.handleForm}></textarea>
</form>
)
}
}
export default FormHandle
10.3非受控组件–了解
借助于ref,使用原生DOM方式来获取表单元素值
ref:用于获取DOM或组件
步骤:
1、调用React.createRef()方法创建一个ref对象
constructor(){
super()
this.txtRef=React.createRef()
}
2、将创建好的ref对象添加到文本框中
<input type="text" ref={this.txtRef}/>
3、通过ref对象获取到文本框的值
console.log(this.txtRef.current.value)
React组件基础——案例
案例一、评论列表
Pinlun.js
import React from 'react'
class Pinlun extends React.Component{
//初始化状态
state={
comments:[
{id:1,name:'jack',content:'沙发!!!'},
{id:2,name:'rose',content:'板凳'},
{id:3,name:'tom',content:'楼主好人'}
],
userName:'',
usercontent:''
}
handleForm=(e)=>{
const target=e.target;
const {name,value}=target;
this.setState({
[name]:value
})
}
renderList=()=>{
return this.state.comments.length===0
?(<div className="no-comment">暂无评论</div>)
:(<ul>
{this.state.comments.map(item=>(
<li key={item.id}>
<h3>评论者:{item.name}</h3>
<p>{item.content}</p>
</li>
))}
</ul>)
}
addComent=(e)=>{
const {comments,userName,userContent}=this.state
const newComments=[...comments,{
id:Math.random(),
name:userName,
content:userContent}]
this.setState({
comments:newComments
})
}
render(){
return (
<div className="app">
<div>
<input className="user" type="text"
placeholder="请输入评论人" name="userName"value={this.state.userName} onChange={this.handleForm}></input>
<br/>
<textarea className="content"
cols='30'
rows='10'
placeholder="请输入评论" name="userContent"value={this.state.userContent} onChange={this.handleForm}></textarea>
<br/>
<button onClick={this.addComent}>发表评论</button>
</div>
{this.renderList()}
</div>
)
}
}
export default Pinlun
index.js
import React from'react'
import ReactDom from'react-dom'
import Pinlun from './component/Pinlun'
ReactDom.render(<Pinlun/>,document.getElementById('root'));
index.html
<!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="root"></div>
</body>
</html>
组件进阶
- 能够使用prop接受数据
- 实现父子组件通讯
- 实现兄弟组件通讯
- 给组件添加props校验
- 生命周期常用的够自函数
- 高阶组件的作用
十一、组件通讯
11.1介绍
组件是独立且封闭的单元,默认情况下,只能使用组件自己的数据,在组件化的过程中,我们将一个完整的功能拆分为多个组件,以更好的完成整个应用的功能。而在这个过程中,多个组件之间不可避免的要共享某些数据,为了实现这些功能,就需要打破组件的独立封闭性,让其与外界沟通,这个过程就是组件通讯
11.2组件的props
props:接受传递给组件的数据
步骤:
1、传递数据:给组件标签添加属性并赋值
<Hello name="jack" age={19}/>
2、接收数据:
函数组件通过参数props接收数据,
类组件通过this.props接收数据(类要额外加一个this)
function Hello(props){
console.log(props)
return (
<div>接收数据:{props.name}</div>
)
}
class Hello extends react.Component{
render(){
return {
<div>接收的数据:{this.props.age}</div>
}
}
}
- 特点:可以传递任意类型的数据
字符串、数字、数组、函数、jsx等都可以
- props里的值不能修改,只能读取
- 在 使用类组件时,如果写了构造函数,**应该将props传递给super()**否则无法在构造函数中获取props
constructor(props){//将props传递给构造函数
super(props)
}
11.2.1组件通讯的三种方式
1、父组件-》子组件
2、子组件-》父组件
3、兄弟组件之间通讯
- 父组件传递数据给子组件
1、父组件提供要传递的state数据
2、给子组件标签添加属性,值为state中的数据
3、子组件中通过props接收父组件中传递的数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-li0UekvF-1677566304597)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230225173721916.png)]
- 子组件传递数据给父组件
思路:利用回调函数,父组件提供回调,子组件调用和,将要传递的数据作为回调函数的参数
1、父组件提供一个回调函数(用于接收数据)
2、将该函数作为属性的值,传递给子组件
3、子组件通过props调用回调函数
4、将子组件的数据作为参数传递给回调函数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LYYjJta3-1677566304598)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230225221423309.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P8DlHDAZ-1677566304599)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230225221407010.png)]
- 兄弟组件通讯
①需要将他们的共享状态提升到最近的公共父组件中,由公共父组件管理这个状态,即状态提升
②公共父组件职责:1、提供共享状态2、提供操作共享状态的方法
③要通讯的子组件只需要通过props接收状态或操作状态的方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xbmb2CSb-1677566304599)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230225175109607.png)]
总结就是子组件b传递数据给父组件,父组件再传递数据给子组件a
11.3 Context
作用:跨组件传递数据
Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。
步骤:
1、调用React.createContext()来创建Provider(提供数据)和Consumer(消费数据)两个组件
const {Provider,Consumer}=React.createContext()
2、使用Provider组件作为父节点
<Provider>
<div className="App">
<Child1/>
</div>
</Provider>
3、设置value属性,表示要传递的数据,值可以是字符串、对象等,当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。
<Provider value="pink">
4、调用Comsumer组件接收数据
<Consumer>
{data=><span>接收的数据是:{data}</span>}
</Consumer>
总结:父节点用Provider提供数据,要接收数据的子节点用Consumer接收
例子:
const {Provider,Consumer}=React.createContext()
class App extends React.Component{
render(){
return(
<Provider value="pink">
<div className="app">
<Node/>
</div>
</Provider>
)
}
}
const Node=props=>{
return (
<div className="node">
<SubNode/>
</div>
)
}
const SubNode=props=>{
return(
<div className="subnode">
<Child/>
</div>
)
}
const Child=props=>{
return (
<div className="child">
<Consumer>
{
data=><span>我是子节点--{data}</span>
}
</Consumer>
</div>
)
}
十二、props深入
12.1 children属性
- children属性:表示组件标签的子节点。当组件标签有子节点时,props就会有该属性。
- 也就是说当组件为双标签时渲染时标签中间的那部分元素会被渲染到props.children的位置
function Hello(props){
return (
<div>
组件的子节点:{props.children}
</div>
)
}
ReactDOM.render(<Hello>我是文本子节点</Hello>,root)
渲染结果为:
组件的子节点:我是文本子节点
- 总结:props.children的值就是组件双标签中嵌套的元素,且children的值可以是任意值(文本、React元素、组件、甚至函数)
12.2 props校验
-
对于组件来说,props是外来的,无法保证组件使用者传入什么格式的数据
-
若传入数据格式不对,可能会导致组件内部错误
-
关键是使用者并不明确错误原因
-
因此需要有props校验
-
props校验:允许在创建组件的时候,就指定props的类型、格式等
-
作用:捕获使用组件时因为props导致的错误,给出明确的错误提示,增加组件的健壮性
步骤:
1、安装prop-types命令为:npm i props-types
2、导入prop-types包
import PropTypes from 'prop-types'
3、使用 组件名.propTypes={} 来给props添加校验规则
4、校验规则通过PropTypes对象来指定
import PropTypes from 'prop-types'
function App(props){
return(
<h1>hi,{props.colors}</h1>
)
}
App.propTypes={
//约定colors属性为array类型
//若类型不对则会报明确错误
colors:PropTypes.array
}
12.3props校验-约束规则
1、常见类型:array,bool,func,number,object,string,symbol等
2、React元素类型:element
3、必填项:isRequired
4、特定结构的对象:shape({ })
更多类型可以查看相关文档
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t9c1G7dN-1677566304600)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230226111853234.png)]
12.4props的默认值
defaultProps={}
场景:分页组件->每页显示条数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MM1u1VmF-1677566304600)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230226115618139.png)]
当props的一个值没有被传入则会使用默认值
十三、组件的生命周期
13.1 介绍
-
理解组件的生命周期有助于来理解组件的运行方式、完成更复杂的组件功能、分析组件错误原因等
-
组件的生命周期:组件从被创建到挂载到页面中运行,再到组件不用时卸载的过程
-
生命周期的每个阶段总是伴随着一些方法调用,这些方法就是生命周期的钩子函数
-
钩子函数的作用:为开发人员在不同阶段操作组件提供时机
-
只有类组件才有生命周期
13.2生命周期的三个阶段
- 每个阶段的执行时机
- 每个阶段钩子函数的执行顺序
- 每个阶段钩子的作用
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RQVqcB7A-1677566304601)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230226120513006.png)]
1、创建时(挂载阶段)
- 执行时机:组件创建时(页面加载时)
- 执行顺序
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SLAXtdFB-1677566304601)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230226120715489.png)]
钩子函数 | 触发时机 | 作用 |
---|---|---|
constructor | 创建组件时,最先执行 | 1、初始化state 2、为事件处理程序绑定this |
render | 每次组件渲染时触发 | 渲染UI,不能在render调用setState() |
componentDidMount | 组件挂载(完成DOM渲染)后 | 1、发送网络请求 2、DOM操作 |
2、更新时
更新触发条件:接收新的 props,setState(),foceUpdate()
执行顺序:
render=》componentDidUpdate
钩子函数 | 触发时机 | 作用 |
---|---|---|
render | 每次组件渲染 | 渲染UI |
componentDidUpdate | 组件更新后 | 1、发送网络请求 2、DOM操作 注意:如果要setState必须要放在一个if中,因为setState会导致组件更新导致循环递归 |
componentDidUpdate(preProps){
//比较更新前后的props中的值是否相同来决定是否重新渲染组件,前一个props可以通过prevProps来获取
if(prevProps.count!==this.props.count){
this.setState({
})
}
}
3、卸载时
执行时机:组件从页面中消失
钩子函数 | 触发时机 | 作用 |
---|---|---|
componentWillUnmount | 组件卸载 | 执行清理工作,比如清理定时器等 |
- 不常用的钩子函数–了解
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MMI1DFud-1677566304602)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230226153624068.png)]
十四、render props
14.1React组件复用概述
-
复用相似的功能(联想函数封装)
-
复用什么? 1、state2、操作state的方法
(即复用组件的状态逻辑)
-
两种方式:1、render props模式 2、高阶组件HOC
-
这两种方式不是新的API,而是利用React自身特点的编码技巧,演化而成的固定模式(写法)
-
我的理解:可以说是这个组件提供了一个接口让其它组件来共用state数据和方法
14.2render props模式
思路:将要复用的state和操作state的方法封装到一个组件中
-
如何拿到该组件中复用的state?
在使用组件时,添加一个值为函数的prop,通过函数参数来获取(需要组件内部实现)
<Mouse render={(mouse)=>{}}>
-
如何渲染任意UI?
使用该函数的返回值作为要渲染的UI内容(jsx)
<Mouse render={(mouse)=>{<p>鼠标当前位置{mouse.x},{mouse.y}</p>}}/>
- 使用步骤
1、创建Mouse组件,在组件中提供复用的状态逻辑代码(1、状态,2、操作状态的方法)
2、将要复用的状态作为props.render(state)方法的参数,暴露到组件外部(把state的值通过参数传给组件的函数render,render函数名可以更改)
3、使用props.render()的返回值作为要渲染的内容
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UzHc7h0J-1677566304602)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230226160505338.png)]
总结:1、也就是由组件本身来提供state数据和操作state数据的方法,2、再用this.props.function(this.state)将state数据暴露给组件外部,3、然后由组件标签属性来提供JSX结构负责如何渲染这个数据。
简单来说就是再组件的render()函数中的return处调用组件的属性中的自定义的返回JSX的方法来渲染该自定义的jsx,
其实就是Mouze组件提供了一个接口render使得render{}中的组件/函数能够使用其state和state方法
Mouse.js
import React from 'react'
//创建Mouse组件
class Mouse extends React.Component{
//鼠标位置state
state={
x:0,
y:0
}
handleMouseMove=e=>{
this.setState({
x:e.clientX,
y:e.clientY
})
}
//监听鼠标移动事件
componentDidMount(){
window.addEventListener('mousemove',this.handleMouseMove)
}
render(){
return this.props.Mrender(this.state)
}
}
export default Mouse
index.js
import React from'react'
import ReactDOM from'react-dom'
import Mouse from './component/Mouse'
const jsx=(
<Mouse Mrender={(state)=>{
return( <p>鼠标位置:{state.x},{state.y}</p>)
}}/>
)
ReactDOM.render(jsx,document.getElementById('root'));
总的来看如果不需要复用,按正常写则index.js中的jsx应该之间写在Mouse.js中的render函数中,让其渲染。但考虑到复用,render只将组件渲染需要用到的state数据通过函数的方式传给了组件,让组件在外部渲染时使用组件数据,且让组件属性Mrender的return来决定如何渲染,渲染怎样的jsx结构
14.3children代替render属性
并不是该模式叫做render props就必须使用名为render的prop,实际上,可以用任意名称的prop
仅仅是把prop是一个函数,并且告诉组件要渲染什么内容的技术叫做:render prop模式
- 推荐children代替render属性
<Mouse>
{({x,y})=><p>鼠标的位置是{x},{y}</p>}
</Mouse>
//组件内部
this.props.children(this.state)
其实就是把render函数写在了组件标签之间由children来传递参数
14.4代码优化
1、推荐给render props模式添加props校验
Mouse.propTypes={
render:propTypes.func.isRequired
}
2、应该在组件卸载时解除mousemove事件绑定
componentWillUnmount(){
window.removeEventListener('mousemove',this.handleMouseMove)
}
十五、高阶组件
15.1介绍
目的:实现状态逻辑复用
-
采用包装模式实现复用(函数包装组件)
-
高阶组件(HOC)是一个函数,接收要包装的组件,返回增强后的组件,也就是他的参数是一个组件而且返回值也是一个组件
-
在高阶组件内部创建一个类组件,在这个类组件中提供复用的状态逻辑代码,通过props将复用的状态传递给作为参数的组件
15.2使用步骤:
1、创建一个函数,名称约定以with开头
function withMouse(){}
2、指定函数参数,参数应该以大写字母开头(作为要渲染的组件)
function withMouse(WrappedComponent){}
3、在函数内部创建一个类组件,提供复用的状态逻辑代码,并返回
function withMouse(WrappedComponent){
class Mouse extends React.Component{}
return Mouse
}
4、在该组件中,渲染参数组件,同时将状态通过props传递给参数组件
function withMouse(WrappedComponent){
class Mouse extends React.Component{//复用组件,用于负责复用的state数据和方法
state={
.......
}
//这里省略操作state的方法
render{
//不同的jsx结构渲染
return <WrappedComponent {...this.state}/>//这里可以通过扩展运算符...来把state的数据依次转为props属性
}
}
return Mouse
}
5、调用该高阶组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面中
//调用组件
const MousePosition=widthMouse(Position)
//渲染组件
<MousePosition/>
所以高阶组件复用实质上是将组件中render函数中的jsx结构分离出来成为一个单独的组件,再利用函数的方式将这个组件作为参数与函数中的组件融合并返回一个新的组件实现复用,简单来说实质上就是两个组件的一次嵌套或者说融合
例子
Mouse.js
import React from 'react'
//创建高阶组件
function withMouse(WrappedComponent){
//创建提供复用的组件
class Mouse extends React.Component{
//鼠标位置state
state={
x:0,
y:0
}
handleMouseMove=e=>{
this.setState({
x:e.clientX,
y:e.clientY
})
}
//监听鼠标移动事件
componentDidMount(){
window.addEventListener('mousemove',this.handleMouseMove)
}
componentWillUnmount(){
window.removeEventListener('mousemove',this.handleMouseMove)
}
render(){
return <WrappedComponent {...this.state}/>//这里利用扩展运算符将state的数据依次转为props属性
}
}
return Mouse
}
export default withMouse
index.js
import React from'react'
import ReactDOM from'react-dom'
import withMouse from './component/Mouse'
const Position=props=>{//用于测试高阶组件
return (<p>
鼠标当前位置:(x:{props.x},y:{props.y})
</p>)
}
const MousePosition=withMouse(Position)//调用高阶组件(函数)
ReactDOM.render(<MousePosition/>,document.getElementById('root'));//渲染
自我总结:通过对高阶组件和render props模式的学习可以发现,组件的复用关键点都是在render函数的renturn处也就是要渲染的jsx结构,它都是被分离了出来形成了单独的可以在外部自定义的组件或者函数,而复用的部分都是state或者操作state的部分
15.3 设置displayName
- 使用高阶组件得到的不同组件的标签名相同这是一个问题
- 原因:默认情况下,Reac使用组件名称作为displayName
- 解决:为高阶组件设置displayName便于调试时区分不同组件
- displayName:用于设置调试信息
- 方式:
Mouse.displayName = `WithMouse${getDisplayName(WrappedComponent)}`
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component'
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NLzcQFd0-1677566304603)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230227094023715.png)]
15.4传递props
- 问题:props丢失
- 原因:高阶组件没有往下传递props
- 解决方式:渲染 WrappedComponent 时,将 state 和 this.props 一起传递给组件
- 传递方式:
<WrappedComponent {...this.state} {...this.props} />
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3ZCdd77i-1677566304603)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230227094519911.png)]
十六、React原理揭秘
学习目标:
- 能知道setSate()更新数据是异步的
- 能知道JSX语法的转化过程
- 能说出React组件的更新机制
- 能对组件进行性能优化
- 能说出虚拟DOM和Diff算法
1、setState()说明
1.1更新数据
-
setState()更新数据是异步的
-
在setState更新之后立即用console输出可以发现数据是之前的
-
所以注意:使用该语法时后面的setState不能依赖于前面的setState
-
可以调用多次setState但只会在最后一次调用时render更新UI,这是为了性能
1.2推荐语法
-
setState((state,props)=>{ })
-
参数
- state:表示最新的state
- props:表示最新的props
this.setState((state,props)=>{//与setState一样也是异步更新
return {
count:state.count+1
}
})
console.log(this.state.count)
- 使用该语法可以连续使用setState
- 推荐使用该方式替代之前的setState
1.3setState第二参数
- 场景:在状态更新后立即执行某个操作
- 语法:setState(update[,callback])
- 第二个参数是一个回调函数,函数会在状态更新完成且页面渲染之后立即执行,比如函数中console输出的是更新后的数据,可以操作dom
this.setState(
(state,props)=>{},
()=>{console.log('这个回调函数会在状态更新完成后立即执行')}
)
2、JSX语法转化过程
-
JSX仅仅是createElement()方法的语法糖(简化语法)
-
JSX语法被@babel/preset-react插件编译为createElement()方法
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZH57YaD3-1677566304604)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230227102228380.png)]
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rAm3v2C0-1677566304604)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230227102309634.png)]
3、组件更新机制
- setState的两个作用:1、修改state 2、更新组件(UI)
- 父组件重新渲染时,也会重新渲染子组件,但只会渲染当前组件子树(当前组件及其所有子组件)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dsPaFjE2-1677566304604)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230227103108005.png)]
4、组件性能优化
4.1减轻state
- 减轻state:只存储跟组件渲染相关的数据(比如:相关变量/列表数据/loding等)
- 不用在渲染的数据不要放在state中,比如定时器id等
- 对于这种需要在多个方法中用到的数据,应该放在对象的this中
4.2避免不必要的重新渲染
- 组件更新机制:父组件更新会引起子组件也被更新,这种思路很清晰
- 但子组件没有任何变化时也会重新渲染
- 如何避免不必要的重新渲染?
解决方法:使用钩子函数 shouldComponentUpdate(nextProps,nextState)
作用:通过返回值决定该组件是否重新渲染,返回true表示重新渲染,false表示不重新渲染
触发时机:更新阶段的钩子函数,组件重新渲染前执行(shouldComponentUpdate->render)
souldComponentUpdate(nexntProps,nextState){
console.log("最新的state",nextState)
console.log("更新前的state",this.state)
}
4.3纯组件
-
纯组件:React.PureComponent 与 React.Component功能相似
-
区别:PureComponent内部自动实现了shouldComponentUpdate钩子,不需要手动比较
-
原理:纯组件内部通过比较前后两次的props和state的值来决定是否重新渲染组件
class Hello extends React.PureComponent{
render(){
return()
}
}
-
说明:纯组件内部的对比是shallow compare (浅层对比)
-
浅层对比对于值类型来说:比较两个值是否相同(直接赋值即可,没有坑)
-
浅层对比对于引用类型来说,只比较对象的引用地址是否相同(里面可能值不同,有坑)
5、虚拟DOM和Diff算法
- React更新视图的思想是:只要state变化就重新渲染视图
- 特点:思路清晰
- 问题:组件中只有一个DOM元素需要更新时,也得把整个组件的内容重新渲染到页面中
- 理想状态:部分更新,只更新变化的地方
- 如何实现部分更新?
- 虚拟DOM配合Diff算法
虚拟DOM:本质上是一个JS对象,用来描述你希望在屏幕上看到的内容(UI)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eTz8agzh-1677566304605)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230227112441297.png)]
执行过程:
1、初次渲染时,react会根据初始state创建一个虚拟DOM对象(树)
2、根据虚拟DOM生成正真的DOM,渲染到页面中
3、当数据变化后,重新根据新数据,创建新的虚拟DOM对象
4、与上一次得到的DOM对象使用Diff算法对比,得到需要更新的内容
5、最终,React只将变化的内容更新到DOM中,重新渲染到页面
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YJs2gD30-1677566304606)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230227112604567.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jhGtppkM-1677566304606)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230227142733414.png)]
十七、React路由
1、介绍
现在前端应用大多是SPA(单页应用程序),也就是只有一个HTML页面的应用程序,因为它的用户体验更好,对服务器的压力更小,所以更受欢迎。为了有效的使用单个页面来管理原来多页面的功能,前端路由应运而生
- 前端路由的功能:让用户从一个视图(页面)导航到另一个视图
- 前端路由是一套映射规则,在React中,是URL路径与组件的对应关系
- 使用React路由简单来说,就是配置路径和组件(配对)
2、路由的基本使用
2.1使用步骤
步骤:
-
安装npm install react-router-dom
-
导入路由的三个核心组件:Router/Route/Link
import {BrowserRouter as Router,Route,Link} from 'react-router-dom'
-
使用Router组件包裹整个应用
<Router>
<div className="App">
//...省略页面内容
</div>
</Router>
- 使用Link组件作为导航菜单(路由入口)(类似超链接)
//to属性:url的pathname
<Link to="/first">页面</Link>
- 使用Route组件配置路由规则和要展示的组件(路由出口)(类似点击超链接后展示的相应页面)
const First=()=>(<p>页面一的内容</p>)
<Router>
<div className="App">
<Link to="/first">页面一</Link>
<Route path="/first" component={First}></Route>
</div>
</Router>
2.2、常用组件说明
-
Router组件:包裹整个应用,一个React应用只需要使用一次
-
两种常用Router:HashRouter和BrowserRouter
-
HashRouter:使用URL的哈希值实现(不推荐)
-
(推荐)BrowserRouter:使用H5的history API实现
-
Link组件:用于指定导航标签,最终会被编译成一个a标签,to属性表示浏览器地址栏中的pathname
-
Route组件:指定路由展示组件相关信息
//path属性:路由规则
//component属性:展示的组件
//Route组件写在哪,渲染出的组件就展示在哪
<Route path="/first" component={First}></Route>
3、路由执行过程
1、点击Link标签,修改了里卢兰其地址栏中的URL
2、React路由监听到地址栏URL的变化
3、React路由内部遍历所有Route组件,使用路由规则(path)与pathname进行匹配
4、当路由规则path能够匹配地址栏中的pathname时就展示该Route组件的内容(每一个route都会去匹配)
4、编程式导航
-
场景:点击登录按钮,登陆成功后,通过代码跳转到后台首页
-
编程式导航:通过JS代码来实现页面跳转
-
history是React路由提供的,用于获取浏览器历史记录的相关信息
-
push(path):跳转到某个页面,参数path表示要跳转的路径
class Login extends Component{
handleLogin=()=>{//编程式导航实现路由跳转
//...
this.props.history.push('/home')
}
render(){
//...
}
}
5、默认路由
- 在进入页面的时候就展示默认的组件
- 默认路由:表示进入页面时就会匹配的路由
- 默认路由path为:/
<Route path="/" component={Home}></Route>
6、匹配模式
6.1模糊匹配
-
问题:在link中用/login依然能够匹配到默认路由/
-
原因:默认情况下,React路由是模糊匹配模式
-
模糊匹配规则:只要link中的pathname(link的to属性/地址栏url的pathname)以route中path值开头,这两个就会匹配成功
比如:route中的path为/能匹配所有的pathname,/first能匹配/first,/first/a/b等
6.2精确匹配
-
如何避免默认路由在任何情况下展示
-
给Route组件添加exact属性,让其变成精确匹配模式
-
精确匹配:只要path和pathname完全匹配时才会展示该路由
<Route excat path="/" component...>
推荐:给默认路由添加exact属性
om’
+ 使用Router组件包裹整个应用
```react
<Router>
<div className="App">
//...省略页面内容
</div>
</Router>
- 使用Link组件作为导航菜单(路由入口)(类似超链接)
//to属性:url的pathname
<Link to="/first">页面</Link>
- 使用Route组件配置路由规则和要展示的组件(路由出口)(类似点击超链接后展示的相应页面)
const First=()=>(<p>页面一的内容</p>)
<Router>
<div className="App">
<Link to="/first">页面一</Link>
<Route path="/first" component={First}></Route>
</div>
</Router>
2.2、常用组件说明
-
Router组件:包裹整个应用,一个React应用只需要使用一次
-
两种常用Router:HashRouter和BrowserRouter
-
HashRouter:使用URL的哈希值实现(不推荐)
-
(推荐)BrowserRouter:使用H5的history API实现
-
Link组件:用于指定导航标签,最终会被编译成一个a标签,to属性表示浏览器地址栏中的pathname
-
Route组件:指定路由展示组件相关信息
//path属性:路由规则
//component属性:展示的组件
//Route组件写在哪,渲染出的组件就展示在哪
<Route path="/first" component={First}></Route>
3、路由执行过程
1、点击Link标签,修改了里卢兰其地址栏中的URL
2、React路由监听到地址栏URL的变化
3、React路由内部遍历所有Route组件,使用路由规则(path)与pathname进行匹配
4、当路由规则path能够匹配地址栏中的pathname时就展示该Route组件的内容(每一个route都会去匹配)
4、编程式导航
-
场景:点击登录按钮,登陆成功后,通过代码跳转到后台首页
-
编程式导航:通过JS代码来实现页面跳转
-
history是React路由提供的,用于获取浏览器历史记录的相关信息
-
push(path):跳转到某个页面,参数path表示要跳转的路径
class Login extends Component{
handleLogin=()=>{//编程式导航实现路由跳转
//...
this.props.history.push('/home')
}
render(){
//...
}
}
5、默认路由
- 在进入页面的时候就展示默认的组件
- 默认路由:表示进入页面时就会匹配的路由
- 默认路由path为:/
<Route path="/" component={Home}></Route>
6、匹配模式
6.1模糊匹配
-
问题:在link中用/login依然能够匹配到默认路由/
-
原因:默认情况下,React路由是模糊匹配模式
-
模糊匹配规则:只要link中的pathname(link的to属性/地址栏url的pathname)以route中path值开头,这两个就会匹配成功
比如:route中的path为/能匹配所有的pathname,/first能匹配/first,/first/a/b等
6.2精确匹配
-
如何避免默认路由在任何情况下展示
-
给Route组件添加exact属性,让其变成精确匹配模式
-
精确匹配:只要path和pathname完全匹配时才会展示该路由
<Route excat path="/" component...>
推荐:给默认路由添加exact属性