react
react的基本使用
- 初始化项目: npx creact-react-app hkzf-mobile
- 启动项目:在项目目录执行命令:yarn start
React的安装
安装命令:npm i react react-dom (同时安装两个包)
- react包是核心,提供创建元素、组件等功能
- react-dom包提供DOM相关功能等
React的使用
-
引入react和react-dom两个js文件(注意引入顺序,先是react后是react-dom)
<script src="./node_modules/react/umd/react.development.js"></script> <script src="./node_modules/react/umd/react-dom.development.js"></script>
-
创建React元素(有三个参数)
React.createElment()
- 参数一:元素名称
- 参数二:元素属性
- 参数三及其以后的参数:元素的子节点
-
渲染React元素到页面中
ReactDOM.render()
- 参数一:要渲染的react元素
- 参数二:挂载点
//注意大小写问题 <div id="root"></div> <script> // 这里的React由react包提供 conse title = React.createElement('h1',null,'Hello React') //这里的ReactDom由react-dom包提供 ReactDOM.render(title,document.getElementById('root')) </script>
-
null
react脚手架的基本使用
使用React脚手架初始化项目
-
初始化项目,命令:npx create-react-app my-app //create-react-app //脚手架的名称 my-app是项目名称
-
启动项目,在项目根目录执行命令:npm start
npx命令介绍
- npm v5.2.0 引入的一条命令
- 目的:提升包内提供的命令行工具的使用体验
- 原来:先安装脚手架包,再使用这个包中提供的命令
- 现在:无需安装脚手架包,就可以直接使用这个包提供的命令
初始化项目
- 推荐使用:npx create-react-app my-app
- npm init react-app my-app
- yarn creact react-app my-app
react脚手架中使用react
-
导入react和react-dom两个包
import React from 'reacr' import ReactDOM from 'react-dom'
-
调用React.createElment()方法创建react元素
-
调用ReactDOM.render()方法渲染react元素到页面中 //开发的是web应用,不同的应用导入不同的渲染函数
react基础阶段总结
JSX
JSX的基本使用
- createElement()的问题
- 繁琐不简洁
- 不直观,无法一眼看出所描述的结构
- 不优雅,用户体验不爽
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s9rOSgjI-1642920168810)(D:\资料\web笔记\react\image\笔记\image-20211217153347234.png)]
- JSX简介
- JSX是JavaScript XML的简写,表示在JavaSript代码中写XML(HTML)格式的代码
- 优势:声明式语法更加直观、与HTML结构相同,降低了学习成本、提升开发的效率
- JSX是React的核心内容
- 使用JSX步骤
- 使用JSX语法创建react元素
- const title =
Hello JSX
- const title =
- 使用ReactDOM.render()方法渲染react元素到页面中
- 渲染创建好的React元素
- ReactDOM.render(title,root)
- 使用JSX语法创建react元素
为什么脚手架中要使用JSX语法
- JSX不是标准的ECMAScrpt语法,它是ECMAScript的语法扩展
- 需要使用babel编译处理后,才能在浏览器环境中使用
- create-react-app脚手架中已经默认有babel配置了,无需手动配置
- 编译JSX语法的包为:@/babel/preset-react
JSX的注意点
-
React元素的属性名使用驼峰命名法
-
特殊属性名:class->className、 for->htmlFor、 tabindex->tabIndex
-
没有子节点的React元素可以用 /> 结束
-
推荐:使用小括号包裹JSX,从而避免JS中的自动插入分号陷阱
-
//使用小括号包裹JSX const dv = ( <div>hello JSX</div> )
-
在JSX中使用JavaScript表达式
-
嵌入JS表达式
-
数据存储在JS中
-
语法:{JavaScript表达式}
-
注意:语法是单大括号,不是双大括号
-
const name = 'hauge' const dv = ( <div>你好,我叫{name}</div> )
-
JSX的条件渲染
- 场景:loading效果
- 条件渲染:根据条件渲染特定的JSX结构
- 可以使用if/else 或 三元运算符或逻辑运算符来实现
-
import React from 'react' import ReactDOM form 'react-dom' const isLoading = true const loadData = () => { if(isLoading) { return <div>loading...</div> } return <div>数据加载完成</div> } const title = ( <h1> 条件渲染 {loadData} </h1> ) ReactDOM.render(title,document.getElementById('root'))
JSX的列表渲染
-
如果要渲染一组数据,应该使用数据的map()方法 //map() 映射
-
注意:渲染列表是应该添加key属性,key属性的值要保证唯一
-
原则:map()遍历创建什么元素,就给什么元素添加key属性
-
注意:尽量避免使用索引号作为key
-
null
-
const songs = [ {id:1,name:'li'}, {id:2,name:'hua'}, {id:3,name:'ge'} ] const list = ( <ul> {songs.map(item=><li key={item.id}>{item.name}</li>)} </ul> )
JSX的样式处理
-
行内样式 —style
第一个花括号是使用表达式 第二个花括号是一个对象 <h1 style={{color:'res',backgroundColor:'skyblue'}}> JSX的样式处理 </h1>
-
类名–className(推荐)
<h1 className = "title"> JSX的样式处理 </h1>
JSX阶段性总结
- React完全利用JS语言自身的能力来编写UI,而不是造轮子增强HTML功能,React没有指令一说,就连渲染列表都是使用map()
React组件基础学习
- 能够使用函数创建组件
- 能够使用class创建组件
- 能够给React元素绑定事件
- 能够使用state和setState()
- 能够处理事件中的this指向问题
- 能够使用受控组件方式处理表单
React组件介绍
- 组件是React的一等公民,使用React就是用组件
- 组件表示页面中的部分功能
- 组合多个组件实现完整的页面功能
- 特点:可复用、独立、可组合
组件的两种创建方式(1函数组件)
-
函数组件: 使用JS的函数 (或箭头函数)创建的组件
-
约定1:函数名称必须以大写字母开头
-
约定2:函数组件必须有返回值,表示该组件的结构
-
如果返回值为null,表示不渲染任何内容 // 如果不想返回任何东西,就return null
function Hello () {
return (
<div>函数创建组件</div>
)
}
-
渲染函数组件:用函数名作为组件标签名
ReactDOM.render(<Hello />, document.getElmentById('root'))
组件的两种创建方式(2类创建组件)
-
类组件:使用ES6的class创建的组件
-
约定1:类名称也必须以大写字母开头
-
约定2:类组件应该继承React.Component父类,从而可以使用父类中提供的方法或属性
-
约定3:类组件必须提供==render()==方法
-
约定4:render()方法必须有返回值,表示该组件的结构
-
class Hello extends React.Component { render(){ return <div> 类创建组件 </div> } } ReactDOM.render(<Hello />, document.getElmentById('root'))
组件的两种创建方式(3抽离为独立js文件)
-
步骤
- 创建Hello.js
- 在Hello.js中导入React
- 创建组件(函数创建或者类创建)
- 在Hello.js中导出该组件
- 在index.js中导入Hello组件
- 渲染组件
-
// Hello.js 组件 //导入React import React from 'react' class Hello extends React.Component { render(){ return (R <div>Hello 组件</div> ) } } // 导出该组件 export default Hello // index.js // 导入Hello组件 import Hello from './Hello' // 渲染导入的Hello组件 ReactDOM.render(<Hello />,root)
React事件处理(事件绑定)
事件绑定1
-
React事件绑定语法与DOM事件语法相似
-
语法:on+事件名称 = {事件处理程序},比如:onClick={() => {}}
-
注意:React事件采用驼峰命名法,比如:onMouseEnter、onFocus
-
//类组件绑定事件 class App extends React.Component { handleClick() { console.log('单击事件触发了') } render() { return ( <button onClick={this.handleClick}></button> ) } }
-
// 函数组件绑定事件 (没有this) function App(){ function handleClick() { consle.log('函数组件绑定事件') } return ( <button onClick={handleClick}></button> ) }
事件对象
-
可以通过事件处理程序的参数获取到事件对象
-
React中的事件对象叫做:合成事件(对象)
-
合成事件:兼容所有的浏览器,无需担心跨浏览器兼容性问题
-
function handleClick(e) { e.preventDefault() // 阻止点击a标签自动跳转 /阻止默认浏览器行为 console.log('事件对象',e) } <a onClick = {handleClick}> 点我,不会跳转页面</a>
有状态组件和无状态组件
- 函数组件又叫无状态组件,类组件又叫做有状态组件
- 状态(state)即数据
- 函数组件没有自己的状态,只负责数据显示(静)
- 类组件有自己的状态,负责更新UI,页面“动”起来// 比如计数器增加页面显示,此功能就要使用有状态组件来完成
state和setState()
state的基本使用
-
状态(state)即数据,
-
组件内部的私有数据,只能在组件内部使用
-
state的值是对象,表示一个组件中可以有多个数据
-
通过this.state来获取状态 b
-
class Hello extends React.Component { constructor(){ super() //初始化state this.state = { count:0 } } rende() { return ( <div>有状态组件{this.state.count}</div> ) } } // 简化写法 class Hello extends React.Component { //初始化state state = { count:0 } rende() { return ( <div>有状态组件{this.state.count}</div> ) } }
setState()修改状态
-
状态是可变的
-
语法:this.setState({要修改的数据})
-
注意:不要直接修改state中的值,这是错误的!!!
-
setState()作用:1.修改state,2.更新UI
-
思想:数据驱动视图
-
// 正确的 this.setState({ count:this.state.count+1 }) //错误的 this.state.count += 1
组件中的state和setState() (2从JSX中抽离事件处理程序)
class Hello extends React.Component {
state = {
count:0
}
//事件处理程序
onIncrement(){
console.log('事件处理程序的this',this) // this为undefind
//报错:TypeError:Cannot read property 'setState' of undefind,因为this为undefind
this.setState({
count:this.state.cound + 1
})
}
render() {
return (
//<button onClick={this.onIncrement}>点击+1</button>
//这里之所以没有报错是因为使用了箭头函数,箭头函数本身没this的,这里的this会往外部环境找,也就是render方法,render里面的this是可以获取到setState的即也就是组件实例
<button onClick={()=>{
this.setState({
count:this.state.cound + 1
})
}}>点击+1</button>
)
}
}
- JSX中掺杂过多JS逻辑代码,会显得非常的混乱
- 推荐:将逻辑抽离到单独的方法中,保证JSX结构清晰
- 原因:事件处理程序中this的值为undefined
- 希望:this指向组件实例(render方法中的this即为组件实例)
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Dik2EnMK-1642920168815)(D:\资料\web笔记\react\image\笔记\image-20211220181534485.png)]
- 注意:TypeError:Cannot read property ‘setState’ of undefind //无法读取undefind的属性“setState”
- setState是this读取的也就是this出问题了,tihs为undefind
- 箭头函数自身是没有this的,
- null
事件绑定this指向
事件绑定this指向(1箭头函数)
-
利用了箭头函数自身不绑定this的特点
-
render()方法中的this为组件实例,可以获取setState()
-
箭头函数里面的this是由外部环境决定的、外部环境的this是谁,箭头函数的this就是谁
-
class Hello extends React.Component { state = { count:0 } //事件处理程序 onIncrement(){ //这里this是谁调用指向谁,(因为是箭头函数调用)此处是render方法里面的this调用的,指向render也就是组件实例,所以是可以拿到setState的 this.setState({ count:this.state.cound + 1 }) } render() { return ( //箭头函数中的this指向外部环境,此处为:render()方法的this <button onClick={()=>{this.onIncrement()}>点击+1</button> ) } }
事件绑定this指向(2bind)
-
Function.prototype.bind()
-
利用ES5中的bind方法,将事件处理程序中的this与组件实例绑定到一起
-
class Hello extends React.Component { //constructor里面的this也是指向实例的 constructor(){ super() state = { count:0 } this.onIncrement = this.onIncrement.bind(this) } //事件处理程序 onIncrement(){ this.setState({ count:this.state.cound + 1 }) } render() { return ( <button onClick={this.onIncrement}>点击+1</button> ) } }
事件绑定this指向(3class的实例方法)
-
利用箭头函数形式的class实例方法
-
注意:改语法是实验性语法,但是,由于babel的存在可以直接使用
-
class Hello extends React.Component { state = { count:0 } //事件处理程序 //这里自身就是一个箭头函数,所以这里的this会执行实例的 onIncrement = () => { this.setState({ count:this.state.cound + 1 }) } render() { return ( <button onClick={this.onIncrement}>点击+1</button> ) } }
事件绑定this指向总结
-
推荐:使用class的实例方法
-
class Hello extends React.Component { onIncrement = () => { this.setState({...}) } }
-
-
箭头函数
-
<button onClick={()=>this.onIncrement()} />
-
-
bind
-
constructor(){ super() this.onIncrement = this.onIncrment.bind(this) }
-
表单处理
表单处理(1受控组件概念)
-
HTML 中的表单元素是可输入的,也就是有自己的可变状态
-
而,React中可变状态通常保存在state中,并且只能通过setState()方法来修改
-
React将state与表单元素值value绑定到一起,由state的值来控制表单元素的值
-
受控组件:其值受到React表单控制元素
-
<input type='text' value={this.state.txt} />
表单处理(2受控组件使用步骤)
-
在state中添加一个状态,作为表单元素的value值(控制表单元素值的来源)
-
给表单元素绑定change事件,将表单元素的值设置为state的值(控制表单元素值的变化)
-
state = { text: ''} handelChange = e=> { this.setState({ text:e.target.value }) } <input type='text' value={this.state.txt} //onChange={e => this.setState({text:e.target.value})} onChange={this.handelChange} />
表单处理(3受控组件的示例)
- 文本框、富文本框、下拉框 操作value属性
- 复选框 操作checked属性
表单处理(4多表单元素优化)
多表单元素优化
问题:每个表单元素都有一个单独的事情处理程序,处理太繁琐了
多表单元素优化步骤
-
给表单元素添加name属性,名称与state相同
-
根据表单元素类型获取对应的值 (value/checked)
-
在change事件处理程序中通过[name]来修改对应的state
-
//根据表单元素类型获取值 state = { txt:'', isChecked:false } const value = target.type === 'checkbox' ? target.checked : target.value //若三元运算符看,可使用if if(target.type === 'checkbox') { const value = target.checked } else { const value = target.value } // 根据name设置对应的state this.setState ({ [name]:value }) <input type="test" name="txt" value={this.state.txt} onChange={this.hangdleForm} > // 复选框 <input type="checkbox" name="isChecked" checked={this.state.isChecked} onChange={this.hangdleForm} >
表单处理(5非受控组件)
-
说明:借助于ref,使用原生的DOM方式来获取表单元素值
-
ref的作用:获取DOM或组件
使用步骤
-
调用React.createRef()方法创建一个ref对象
-
constructor(){ super() this.txtRef = React.creactRef() }
-
-
将创建好的ref对象添加到文本框中
-
<input type = "text" ref={this.txtRef}>
-
-
通过ref对象获取到文本框的值 (别忘了current)
-
Console.log(this.txtRef.current.value)
-
-
React组件基础总结
- 组件的两种创建方式:函数组件和类组件
- 无状态(函数)组件,负责静态结构展示
- 有状态(类)组件,负责更新UI,让页面动起来
- 绑定事件注意this的指向问题
- 推荐使用受控组件来处理表单
- 完全利用JS语言的能力创建组件,这是React的思想
React组件基础综合案例
案例需求分析
渲染评论列表
渲染暂无评论并优化代码
获取评论信息
发表评论-1
发表评论-2边界情况处理
React组件进阶学习
- 能够使用props接收数据
- 能够实现父子组件之间的通讯
- 能够实现兄弟组件之间的通讯
- 能够给组件添加props校验
- 能够说出生命周期常用的钩子函数
- 能够知道高阶组件的作用
组件通讯介绍
组件的props基本使用
组件是封闭的,要求接收外部数据应该通过props来实现
props的作用:接收传递给组件的数据
传递数据:给组件标签添加属性
接收数据:函数组件通过参数props接收数据,类组件通过this.props接收数据
-
//函数组件 //接收数据 const Hello = props => { console.log(props) return ( <div> 接收到数据:{props.name}</div> ) } //类组件 //接收数据 class Hello extends React.Component { render(){ return ( <div> 接收到数据:{this.props.age}</div> ) } } //传递数据 <Hello name="jack" age={18}>
组件的props特点
-
可以给组件传递任意类型的数据
-
props是只读的对象,只能读取属性的值,无法修改对象
-
注意:使用类组件时,如果写了构造函数,应该将props传递给super(),否则,无法在构造函数中获取到props!
-
class Hello extends React.Component { constructor(props) { //推荐将props传递给父类构造函数 super(props) } render() { return ( <div>接收的数据:{this.props.age}</div> ) } }
props深入
children属性/类似vue的插槽
-
children属性:表示组件标签的子节点。当标签组件有子节点时,props才有children属性
-
children属性与普通的props一样,值可以是任意值(文本、React元素、组件、甚至是函数)
-
function Hello (props) { return ( <div> 组件的子节点:{props.childen} </div> ) } <Hello>我是子节点</Hello>
props校验
问题
- 对于组件来说,props是外来的,无法保证组件使用者传入什么格式的数据
- 如果传入的数据格式不对,可能会导致组件内部报错
- 关键问题:组件的使用者不知道明确的错误原因
解决方案使用props校验
-
props校验:允许在创建组件的时候,就指定props的类型、格式等
-
作用:捕获使用组件时因为props导致的错误,给出明确的错误提示,增加组件的健壮性
-
App.propsType = { colos:PropType.array }
-
明确的错误提示如下:
- Warning: Failed prop type: Invalid prop ‘colors’ of type ‘number’ supplied to ‘App’, expected ‘array’
- 译:不合法的‘colors’值,'colors’值是number类型的,但是期望得到的是Array类型
使用步骤
-
安装包prop-types (yarn add prop-types/ npm i props-types)
-
导入prop-types包
-
使用==组件名.propTypes = {}==来给组件的props添加校验规则
-
校验通过PropsTypes对象来指定
-
import PropTypes from 'prop-types' function App (props) { return ( <H1>Hi,{props.colors}</H1> ) } //添加props校验 App.propType = { //约定colors属性为array类型 //如果类型不对,则报出明确错误,便于分析错误原因 colors: PropType.array }
props校验-约束规则
约束规则
-
常见类型:array、bool、func、number、objec、string
-
React元素的类型:element
-
必填项: isRequired
-
特定结构的对象: shape({})
-
//常见类型 optionalFunc:PropTypes.func //必填(类型后面加isRequired) requiredFunc:PropTypes.func.isRequired // 特定结构的对象 optionalObjectWithShape: PropTypes.shape({ color: PropTypes.string, fontSize:PropTypes.number })
默认值
-
使用==组件名.defaultProps = {}==来给组件的props设置默认值
-
App.defaultProps = { colors:'pink' }
组件通讯的三种方式
父组件传递数据给子组件
-
父组件提供要传递的state数据
-
给子组件标签添加属性,值为state中的数据
-
子组件中通过props接收父组件中传递的数据
-
//父组件 class Parent extends React.Conponent { state = {lastName : 'li'} render() { return ( <div> 传递数据给子组件: <Child name={this.state.lastName} /> </div> ) } }
-
//子组件 function Child(props) { return ( <div>子组件接收数据:{props.name} <div/> ) }
子组件传递数据给父组件
思路:利用回调函数,父组件提供回调,子组件调用,将要传递的数据作为回调函数的参数
-
父组件提供一个回调函数(用于接收数据)
-
将该函数作为属性的值,传递给子组件
-
子组件通过props调用回调函数
-
将子组件的数据作为参数传递给回调函数
-
注意:回调中this指向的问题
-
// 父组件 class Parent extends React.Component { //提供回调函数,用来接收数据 getChildMsg = (data) => { console.log('接收到子组件中传递过来的数据',data) } render() { return ( <div> 子组件:<Child getMsg = {this.getChildMsg} /> </div> ) } }
-
// 子组件 class Child extends React.Component { state = {childMsg:'React'} handleClick = () => { //子组件调用父组件中传递过来的回调函数 /调用父组件的子组件的getMsg属性函数 this.props.getMsg(this.state.childMsg) } render() { return ( <button onClick={this.handleClick}>点我,给父组件传递数据</button> ) } }
兄弟组件通讯
-
将共享状态提升到最近的公共父组件中,由公共父组件管理这个状态
-
思想:状态提升
-
公共父组件职责:1.提供共享状态,2.提供操作共享状态的方法
-
要通讯的子组件只需通过props接收状态或操作状态的方法
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GAru3cQS-1642920168818)(D:\资料\web笔记\react\image\笔记\image-20211221172730338.png)]
-
然后就是正常的子传父,父传子操作了
-
class Counter extends React.Component { //提供共享状态 state = { count:0 } //提供修改状态的方法 onIncrnment = () => { this.setState({ count:this.state.count+1 }) } render(){ return ( <div> <Child1 count= {this.state.count} /> <Child2 onIncrement= {this.onIncrnment} /> </div> ) } } const Child1 = props => { return ( <h1>计数器:{props.count}</h1> ) } const Child2 = props => { return ( <button onClick={()=>props.onIncrnment()}>点击+1</button> ) }
Contex的基本使用
思考:App组件要传递数据给Child组件,该如何处理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MzTrUIAQ-1642920168821)(D:\资料\web笔记\react\image\笔记\image-20211221180359807.png)]
-
更好的姿势:使用Context
-
作用:跨组件传递数据(比如:主题,语言等)
使用步骤
-
调用React.createContext()创建Provider(提供数据)和Consumer(消费数据)两个组件。
-
const {Provider,Consumer} = React.createContext()
-
-
使用Provider组件作为父节点
-
<Provider> <div className='App'> <Child1 /> </div> </Provider>
-
-
设置value属性,表示要传递的数据
-
<provider value="pink">
-
-
使用Consume组件接收数据
-
data就是上面value传递过来的数据“pink” <Consumer> {data => <span>data参数表示接收到的数据--{data}</span>} </Consumer>
-
-
总结
- 如果两个组件是远方亲戚(比如,嵌套多层)可以使用Context实现组件通讯
- Context提供了两个组件:Provider和Consumer
- Provider组件:用来提供数据
- Consumer组件:用来消费数据
-
组件的生命周期
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1XjRhTfk-1642920168823)(D:\资料\web笔记\react\image\笔记\image-20211224152740122.png)]
概述
只有类组件才有生命周期
- 意义:组件的生命周期有助于理解组件的运行方式、完成更复杂的组件功能、分析组件错误原因等
- 组件的生命周期:组件从创建到被挂载到页面中运行、再到组件不用是卸载的过程
- 生命周期的每个阶段总是伴随着一些方法调用,这些方法就是生命周期的钩子函数
- 钩子函数的作用:为开发人员在不同阶段操作组件提供了时机
组件的生命周期三个阶段
- 每个阶段的执行时机
- 每个阶段钩子函数的执行顺序
- 每个阶段钩子函数的作用
创建时(挂载阶段)
-
执行时机:组件创建时(页面加载时)
-
执行顺序
constructor() -> render() -> componentDidMount()
-
import React from 'react' import ReactDom from 'react-doom' class App extends React.Component { constrctor(props) { super(propd) console.warn('生命周期钩子函数:constrctor') } render(){ // 不能再render里面调用setState,会进入递归更新,因为setState会更新状态也会渲染UI //错误写法 // this.setState({ // count:this.state.count+1 //}) console.warn('生命周期钩子函数:render') return null } componentDidMount(){ console.warn('生命周期钩子函数:componentDidMount') } } //constrctor、render、componentDidMount
-
钩子函数 触发时机 作用 constructor 创建组件时,最先执行 1.初始化state。2.为事件处理程序绑定this render 每次组件渲染都会触发 渲染UI(注意:==不能调用setState()==会进入死循环) componentDidMount 组件挂载(完成DOM渲染)后 1.发送网络请求2.DOM操作
更新时(更新阶段)
-
执行时机:
- setState()
- forceUpdae() //强制执行更新
- 组件接收到新的props
-
说明:以上三者任意一种变化,组件就会重新渲染
-
执行顺序:
- render() -> componentDidUpdate()
-
钩子函数 触发时机 作用 render 每次组件渲染都会触发 渲染UI(与挂载阶段是同一个render) componentDidUpdate 组件更新(完成DOM渲染)后 1.发送网络请求2.DOM操作3.注意:如果要setState()必须放在一个if条件中 -
//componentDidUpdate参数是上一次的props componentDidUpdate(prevProps){ console.log('上一次的props:',prevProps,'当前的props:',this.props) //正确调用 setState() // 做法:比较更新前后的props是否相同,来决定是否重新渲染组件 if(prevProps.count !== this.props.count) { this.setState({}) //发送请求的代码也要写在这里(因为发送请求的话,一般最后都会更新state) } }
卸载时(卸载阶段)
-
执行时机:组件从页面中消失
-
钩子函数 触发时机 作用 componentWillUnmount 组件卸载(从页面中消失) 执行清理工作(比如:清理定时器等)
-
-
//子组件 //因为他们都是使用同一个this。所以把timerId交给this class Counter entends React.Component { componentDidMount() { //开启定时器 this.timerId = setInterval(()=>{ console.log('定时器正在执行~') }) } render(){ return ( <H1> hellog </H1> ) } componentWillUnmount(){ clearInterval(this.timerId) } }
不常用钩子函数介绍
render-props和高阶组件概述
- 思考:如果两个组件中的部分功能相似或相同,该如何处理
- 处理方式:复用相似的功能(联想函数封装)
- 复用什么?:1.state 2.操作state的方法(组件状态的逻辑)
- 两种方式:1.render props模式 2.高阶组件(HOC)
- 注意:这两种方式不是新的API,而是利用React自身特点的编码技巧,演化而成的固定模式(写法)
props模式(1思路分析)
-
思路:将要复用的state和操作的state的方法封装到一个组件中
-
问题1.如何拿到组件中复用的state?
-
在使用组件时,添加一个值为函数的prop,通过函数参数来获取(需要组件内部实现) //(个人理解类似子传父)
-
问题2:如何渲染UI
-
使用该函数的返回值作为要渲染的UI内容(需要组件)
-
<Mouse render={mouse => {}} />
-
<Mouse render = {mouse => { <p>鼠标当前的位置 {mouse.x},{mouse.y}</p> }}/>
props模式(2使用步骤)
-
创建Mouse组件,在组件中提供复用的状态逻辑代码(1.状态 2.操作状态的方法)
-
将要复用的状态作为props.render(state)方法的参数,暴露到组件外部
-
使用props.render()的返回值作为要渲染的内容
-
class Mouse extends React.Component { state = { x:'', y:'' } // 鼠标移动事件的处理程序 handleMouseMove = e => { this.setState ({ x=e.clientX, y=e.clientY }) } //监听鼠标移动事件 componentDidMount(){ window.addEventListener('mousemove',this.handleMouseMove) } render(){ //把状态暴露到组件外部 //这里没有渲染任何结构 return this.props.render(this.state) } }
-
//这里的mouse参数就是组件暴露的状态 // 这里的函数返回值就是要渲染的UI <Mouse render={(mouse) => <p>鼠标的位置 {mouse.x},{mouse.y}} />
-
//react react脚手架导入图片资源 import img from './image/cat.png' <Mouse render={ mouse => { return <img src={img} alt="猫" style={{ position:'absolut', top:mouse.y, left:mouse.x }} /> }} />
-
null
props模式(3演示Mouse组件的复用)
- Mouse组件负责:封装复用的状态逻辑代码(1.状态 2.操作状态的方法)
- 状态:鼠标坐标(x,y)
- 操作状态的方法:鼠标移动事件
- 传入的render prop负责:使用复用的状态来渲染UI结构
props模式(4children代替render属性)
用children更清晰
-
注意:并不是该模式叫render props就必须使用名为 render的prop,实际上可以使用任意名称的prop
-
把prop是一个函数并且告诉组件要渲染什么内容的技术叫做:render props模式
-
推荐:使用children代替render属性
-
//使用render <Mouse render={(mouse) => <p>鼠标的位置 {mouse.x},{mouse.y}} /> //使用children <Mouse> {({x,y}) => <p>鼠标的位置是{x},{y}</p>} </Mouse> // 组件内部: this.props.children(this.state)
-
props模式(5代码优化)
-
推荐:给render props模式添加props校验
-
应该在组件卸载时解除mousemove事件绑定
-
Mouse.propTypes { chidlren:PropTypes.func.isRequired } //如果是render的话也要对应的给render做校验 Mouse.propTypes { render:PropTypes.func.isRequired }
-
componentWillUnmount(){ window.removeEventListener('mousemove',this.handleMouseMove) }
高阶组件
介绍
-
目的:实现状态逻辑复用
-
采用包装(装饰)模式,比如说:手机壳
-
手机:获取保护功能
-
高阶组件就相当于手机壳,通过包装组件,增强组件功能
思路分析
-
高阶组件(HOC,Hight-OrderComponnet)是一个函数,接收要包装的组件,返回增强后的组件
-
高阶组件内部创建一个类组件,在这个类组件中提供复用的状态逻辑代码,通过prop将复用的状态传递给被包装组件WrappedComponent
-
const EnhancedComponent = withHOC(WrappedComponent)
-
//高阶组件内部创建的类组件 class Mouse extends React.Component { render(){ return <WrappedComponent {...this.state} /> } }
-
使用步骤
-
创建一个函数,名称约定以with开头
-
指定函数参数,参数应该以大写字母开头(作为要渲染的组件)
-
在函数内部创建一个类组件,提供复用的状态逻辑代码,并返回
-
在该组件中,渲染参数组件,同时将状态通过prop传递给参数组件
-
调用该高阶组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面中
-
//创建高阶组件函数 //withMouse 高阶组件函数 //WrappedComponent 要被渲染的组件 function withMouse(WrappedComponent) { // 该组件提供复用的状态逻辑 class Mouse extends React.Component { state = { x:0, y:0 } handleMouseMove = e => { this.setState({ x:e.clientX, y:e.clientY }) } //控制鼠标状态的逻辑 componentDidMount(){ window.addEventListener('mousemove',this.handleMouseMove) } render (){ return <WrappedComponent {...this.state}></WrappedComponent> } } return Mouse }
-
// Mouse组件的render方法中 return <WrappedComponent {...this.state} />
-
//使用高阶组件 //创建组件 const MousePosition = withMouse(Position) //渲染组件 <MousePosition />
设置displayName
-
使用高阶组件存在的问题:得到的两个组件名称相同
-
原因:默认情况下,React使用组件名称作为displayName
-
解决方法:为高阶组件 设置displayName便于调试是区分不同的组件
-
displayName的作用:用于设置调试信息(ReactDeveloper Tools信息)
-
设置方式
-
Mouse.displayName = `withMouse${getDisplayName(WrappedConent)}` funciton getDisplayName(WrappedComponent) { return WrappedComponent.displayName || WrappedComponent.name || 'Component' }
传递props
-
问题:props丢失
-
原因:高阶组件没有往下传递props
-
解决方式:渲染WrappedComponent时,将state和this.props==一起传递给组件
-
传递方式:
-
<WrappedComponent {...this.state} {...this.props}>
-
React组件进阶性总结
React原理揭秘
- 能够知道setState()更新数据是异步的
- 能够知道JSX语法的转化过程
- 能够说出React组件的更新机制
- 能够对组件进行性能优化
- 能够说出虚拟DOM和Diff算法
setState()方法的说明
更新数据
-
setState()是异步更新数据的
-
注意:使用该语法时,后面的setState()不要依赖于前面的setState()
-
可以多次调用setState(),但只会触发一次重新渲染(render函数只会触发一次)
-
this.state = {count:1} this.setState({ count:this.state.count + 1 //再执行 }) console.log(this.state.count) //1 先执行
推荐语法
-
推荐:使用setState((state,props) => {}) 语法
-
参数state:表示最新的state
-
参数props:表示最新的props
-
这个也是异步更新state
-
this.setState((state,props) => { return { count:state.count + 1 } }) console.log(this.state.count) //1
第二个参数
-
场景:在状态更新(页面完成重新渲染)后立即某个操作
-
this.setState( (state,props) => {}, //第一个参数 () => {console.log('这个回调函数会在状态更新后立即执行')} //第二个参数 )
JSX语法的转化过程
- JXS仅仅是createElement()方法的语法糖(简化语法)
- JSX语法被@babel/preset-react插件编译为createElement()方法
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hGuzsXkX-1642920168828)(D:\资料\web笔记\react\image\笔记\image-20211227175716677.png)]
组件更新机制
- ==setState()==的两个作用:1.修改state;2.更新组件(UI)
- 过程:父组件重新渲染时,也会渲染子组件。但只会渲染==当前组件子树(当前组件及所有子组件)
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GWYz4Kt6-1642920168830)(D:\资料\web笔记\react\image\笔记\image-20211228100228710.png)]
组件性能优化
减轻state
state数据越多到时候渲染的性能就越低
- 减轻state:只存储根组件渲染相关的数据(比如:count/列表数据/loading等)
- 注意:不用做渲染的数据不要放在state中,比如定时器id等
- 对于这种需要在多个方法中用到的数据,应该放在this中
避免不必要的重新渲染
-
组件更新机制:父组件更新会引起子组件也被更新,这种思路很清晰
-
问题:子组件没有任何变化时也会重新渲染
-
如何避免不必要的重新渲染呢?
-
解决方式:使用钩子函数 shouldComponentUpdate(nextProps,nextState)
-
作用:通过返回值决定该组件是否重新渲染,返回true表示重新渲染,false表示不重新渲染
-
触发时机:更新阶段的钩子函数,组件重新渲染前执行(shouldComponentUpdate -> render)
-
calss Hello extends Component { shouldComponentUpdate(nextProps,nextState){ //nextprops,:最新的props // nextState:最新的状态 //根据条件,决定是否重新渲染组件 return false } render (){ return } }
避免不必要的重新渲染-随机数案例1-
随机生成10以内的数,如果这次和上次是重复的则不必重新渲染
calss Hello extends Component {
state={
count:0
}
handleClick = ()=> {
this.setState(()=>{
return {
count:Mate.floor(Math.random()*10)
}
})
}
shouldComponentUpdate(nextProps,nextState){
//nextprops,:最新的props
// nextState:最新的状态
//if(nextState.count === this.state.count) {
// return true
//}
//return false
return nextState.count === this.state.count
}
render (){
return (
<div>
<p>随机数:{this.state.count}</p>
<button onClick={this.handleClick}>重新生成</button>
</div>
)
}
}
纯组件-shallow compare
-
说明:纯组件内部的对比是shallow compare(浅层对比)
-
对于引用类型来说:只比较对象的引用(地址)是否相同
-
注意:stete或props中的属性值为引用类型时,应该创建新数据,不要直接修改原数据!(示例)
-
//正确的做法:创建新对象, (新的对象和原来的对象的地址不同 const newObj = {...this.state.obj,number:Math.floor(Math.random()*10)} this.setState (() => { return { obj:newObj } })
//假如是数组
//不要用数组的push / unshift 等直接修改当前数组的方法
//而应该用 concat 或 slice 等这些返回新数组的方法
this.setState ({
list: […this.state.list, {新数据}]
})
- ```js
//错误做法 :直接修改原始对象中的属性
const newObj = this.state.obj
newObj.number = Math.floot(Math.random() * 10)
this.setState (() => {
return {
obj:newObj
}
})
虚拟DOM和Diff算法
- React 更新视图的思想是:只要state变化就重新渲染视图
- 特点:思路非常清晰
- 问题:组件中只有DOM元素需要更新时,也得把整个组件的内容重新渲染到页面中?不是
- 理想状态:部分更新,只更新变化的地方
- 问题:React是如何做到部分更新的?虚拟DOM配合Diff算法
- NULL
虚拟DOM:本质上就是一个JS对象,用来描述你希望在屏幕上看到的内容(UI)
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wNWxpWNo-1642920168832)(D:\资料\web笔记\react\image\笔记\image-20211228165817263.png)]
执行过程
-
初次渲染时,React会根据初始state(Model),创建一个虚拟DOM对象(数)
-
根据虚拟DOM生成真正的DOM,渲染到页面中
-
当数据变化后(setState()),重新根据新的数据,创建新的虚拟DOM对象(树)
-
与上一次得到的虚拟DOM对象,使用Diff算法对比(找不同),得到需要更新的你内容
-
最终,React只将变化的内容更新(patch)到DOM中,重新渲染到页面 //patch(打补丁)
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xO17n2Ex-1642920168833)(D:\资料\web笔记\react\image\笔记\image-20211228171911378.png)]
-
白话:初始渲染时,React会根据初始state状态,去生成一个虚拟dom对象(其实就是一个js对象),然后根据虚拟DOM对象去重新生成真正的浏览器DOM对象,渲染到页面中,这样就能看到这个组件初次渲染的内容了。当我们在React中调了setState()进行更新之后,那么首先状态会发生更新,状态一发生更新,那么React内部就会根据最新的状态生成一个新的虚拟DOM对象,很明显这个新的虚拟DOM就会反映状态的一个变化,所以新的虚拟DOM对象也是和上次的虚拟DOM对象是存在差异的,这样的话我们就得到了,新的和旧的两个虚拟DOM了,接下来通过Diff算法来对比新、旧两个虚拟DOM对象,这样的话就能找到他们两个虚拟DOM的不同地方了,找到这个不同的地方之后,最后只把不同的地方对应的那个DOM来进行重新渲染一下,那么就可以看到页面中某一部分发生变化了,变化其实是只把需要更新的地方进行了更新,不需要更新的内容是没有发生过变化的,这样就做到了部分更新
-
React通过了虚拟DOM和Diff算法来高效的更新
虚拟DOM和Diff算法(代码演示)
- 组件render()调用后,根据状态和JSX结构生成虚拟DOM对象
React原理揭秘总结
- 工作角度:应用第一,原理第二
- 原理有助于更好地理解React的自身运行机制
- setState()异步更新数据
- 父组件更新导致子组件更新,纯组件提升性能 / 钩子函数 shouldComponentUpdate(nextProps,nextState)
- 思路清晰简单为前提,虚拟DOM和Diff保效率
- 虚拟DOM →state+JSX
- 虚拟DOM的真正价值从来都不是性能
- 虚拟DOM最大的好处:脱离了浏览器的环境束缚,虚拟DOM其实就是个js对象,哪里可以运行js哪里就可以运行react
React路由基础
译:
- pathname: //to属性:浏览器地址栏中的pathname(location.pathname) 页面一
- React路由介绍
- 路由的基本使用
- 路由的执行过程
- 编程式导航
- 默认路由
- 匹配模式
React路由介绍
现代的前端应用大多数都是SPA(单页面应用程序),也就是只有一个HTML页面的应用程序。因为它的用户体验更好、对服务器的压力更小,所以更受欢迎。为了有效的使用单个页面来管理原理多个页面的功能,前端路由应运而生。
- 前端路由的功能:让用户从一个视图(页面)导航到另一个视图(页面)
- 前端路由是一套映射规则,在React中,是URL与组件的对应关系
- 使用React路由简单来说,就是配置路径和组件(配对)
React路由基本使用
-
安装:yarn add react-router-dom
-
导入路由的三个核心组件:Router/Route/Link
//BrowserRouter 取别名为 Router import {BrowserRouter as Router, Route, Link} from 'react-router-dom'
-
使用Router组件包裹整个应用(重要)
<Router> <div className="App"> //...省略内容 </div> </Router>
-
使用Link组件作为导航菜单(路由入口)
<Link to="/first">页面一</Link>
-
使用Route组件配置路由规则和要展示的组件(路由出口)
const First = () => <p>页面一的内容</p> <Router> <div className = "App"> <Link to="/first">页面一</Link> <Route push="/first" component={Fisrt}> </div> </Router>
-
null
-
import React from 'react' import ReactDom form 'react-dom' //react-router-dom 的基本使用 //导入组件: import {BrowserRouter as Router,Route,Link} from 'react-router-dom' const First = () => <p>页面一的内容</p> //使用router包裹整个应用 const App = () => { <Router> <div> <h1>Router路由基础</h1> //指定路由入口 <Link to="/first">页面一</Link> //指定路由出口 <Route path="/first" component={First} /> </div> </Router> } ReactDOM.render(<App />,document.getElementById('root'))
React路由基本使用(常用组件说明)
-
Router组件:包裹整个应用,一个React应用只需要使用一次
-
两种常用Router: HashRouter和BrowserRouter
-
HashRouter:使用URL的哈希值实现(localhost:3000/#/first)
-
(推荐)==BrowserRouter:==使用H5的history API实现(localhost:3000/first)
//使用HashRouter import {HashRouter as Router,Route,Link} from 'react-router-dom' //使用BrowserRouter import {BrowserRouter as Router,Route,Link} from 'react-router-dom'
-
Link组件:用于指定导航链接(a标签)
//to属性:浏览器地址栏中的pathname(location.pathname) <Link to="/first">
-
Route组件:指定路由展示组件的相关信息
//path属性:路由规则 //component属性:展示的组件 //Router组件写在哪,渲染出来的组件就展示在哪 <Route path="/first" component={Fisrt}></Route>
-
null
React路由执行过程
- 点击Link组件(a标签),修改了浏览器地址栏中的url
- React路由监听到地址栏url的变化
- React路由内部遍历所有Route组件,使用路由规则(path) 与pathname进行匹配 / location.pathname
- 当路由规则(path)能够匹配地址栏中的pathname是,就展示该Route组件的内容
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R44k8tlX-1642920168836)(D:\资料\web笔记\react\image\笔记\image-20211229163313420.png)]
编程式导航
-
场景:点击登录按钮,登录成功后,通过代码跳转到后台首页,如何实现?
-
编程是导航:通过JS代码来实现页面跳转
-
history是React路由提供的,用于获取浏览器历史记录的相关信息
-
==push(path):==跳转到某个页面,参数path表示要跳转的路径
-
go(n):前进或后退到某个页面,参数n表示前进或后退页面数量(比如:-1表示后退到上一页)
class Login extends Component { handleLogin = () => { // ... this.props.history.push('/home') } render(){...省略其他代码} }
默认路由
-
问题:现在的路由都是点击导航菜单后展示的,如何在进入页面的时候就展示呢?
-
默认路由:表示进入页面时就会匹配的路由
-
默认路由path为:/
-
<Route path="/" component={Home} />
匹配模式(1模糊匹配模式)
- 问题:当Link组件的to属性为“/login”时,为什么默认路由也被匹配成功?
- 默认情况下,React路由是模糊匹配模式
- 模糊匹配规则:只要pathname以path开头就会匹配成功
<Link to="/login">登录成功</Link>
<Route path="/" component={Home} /> //匹配成功
//path 代表Route组件的path属性
//pathname 代表Link组件的to属性(也就是locatio.pathname)
path | 能够匹配的pathname |
---|---|
/ | 所有pathname |
/first | /first 或/first/a 或/first/a/b/… |
匹配模式(2精确匹配)
-
问题:默认路由任何情况下都会展示,如何避免这种问题?
-
给Route组件添加exact属性,让其变为精确匹配模式
-
精确匹配:只有但path和pathname完全匹配时才会展示该路由
-
推荐:给默认路由添加exact属性
//此时,该组件只能匹配pathname="/"这一种情况 <Route exact path="/" component={...}>
React路由基础总结
- React路由可以有效的管理多个视图(组件)实现SPA
- Router组件包裹整个应用,只需使用一次
- Link组件是入口,Route组件是出口
- 通过props.history实现编程式导航
- 默认模糊匹配,添加exact变精确匹配
- React路由的一切都是组件,可以像思考组件一样思考路由
项目
项目准备
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cfZ8JjgR-1642920168837)(D:\资料\web笔记\react\image\笔记\image-20211230170936995.png)]
组件库antd-mobile
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VxNBq8AY-1642920168839)(D:\资料\web笔记\react\image\笔记\image-20211230172501453.png)]
配置基础路由
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nSeNshKV-1642920168840)(D:\资料\web笔记\react\image\笔记\image-20211230172945578.png)]