------------------------------------入门--------------------------------------------------
一.react的概述
1.react是什么
React是一个用于构建用户界面的Javascript库。
用户界面:前端HTML界面。react主要是用来写HTML页面,或构建web应用。
如果从MVC的角度看,react仅仅是视图层(V),也就是负责视图的渲染,而并非提供了完整的M和C的功能。
2.react的特点
声明式
只需要描述UI(HTML)看起来是什么样,就像写HTML一样。
react负责渲染UI,并在数据变化时更新UI基于组件
学习一次,随处使用
可以开发web,开发移动端原生应用,可以开发VR(虚拟现实)应用
二.安装
1.安装命令
npm i react react-dom
- react包是核心,提供创建元素,组件等功能
- react-dom包提供DOM相关功能等
2.使用
1 引入js文件
<script src="./node_modules/react/umd/react.development.js"></script>
<script src="./node_modules/react-dom/umd/react-dom.development.js"></script>
2.创建react元素(知道)
三个参数:元素名称,元素属性,元素的子节点(第3个参数及其以后的参数)
const title = React.createElement('h1', null, 'hello react',
React.createElement('span', null, '我是span'))
3 渲染react元素
两个参数:要渲染的react元素,挂载点
ReactDOM.render(title,document.getElementById('root'))
三.脚手架
1.初始化命令:(推荐)
npx create-react-app my-app
1.其他的初始化命令:
npm init react-app my-app
yarn create react-app my-app
yarn 是Facebook发布的包管理器,可以看做是npm的替代品,功能与npm相同。
2.npx命令的介绍:
以前是先安装脚手架包,再使用这个包中提供的命令
现在是无需安装脚手架包,就可以直接使用这个包提供的命令。
npm极大地提升了我们安装和管理包依赖的体验,在npm的基础之上,npx让npm包中的命令行工具和其他可执行文件在使用上变得更加简单。它极大地简化了我们之前使用纯粹的npm时所需要的大量步骤。
2.启动项目
在项目的根目录执行命令
npm start
3.使用
1)导入react和react-dom两个包
2)调用React.createElement()方法创建react元素
3)调用ReactDOM.render()方法渲染react元素到页面中
index.js
// 1.导入react
import React from 'react'
import ReactDOM from 'react-dom'
// 2.创建react元素
const title=React.createElement('h1',null,'我是新来的!')
//3.渲染react元素
ReactDOM.render(title,document.getElementById('root'))
四.JSX
JSX的特点:
1)JSX是react的核心内容
2)JSX表示在JS代码中写HTML结构,是React声明式的体现
3)使用JSX配合嵌入式的JS表达式、条件渲染、列表渲染,可以描述任意UI结构
4)推荐使用className的方式给JSX添加样式
5)React完全利用JS语言自身的能力来编写UI,而不是造轮子增强HTML功能
1 JSX的基本使用
1)createElement()的问题
- 繁琐不简洁
- 不直观,无法一眼看出所描述的结构
- 不优雅,用户体验不爽
而JSX用来代替,并且解决这些问题
2)JSX简介
JSX是Javascript XML的简写,表示在Javascript代码中写XML(HTML)格式的代码
优势:声明式语法更加直观,与HTML结构相同,降低了学习成本,提高开发效率
JSX是React的核心内容
为什么脚手架中可以使用JSX语法?
1.JSX不是标准的ECMAScript语法,它是ECMAScript的语法扩展
2.需要使用babel编译处理后,才能在浏览器环境中使用
3.create-react-app 脚手架中已经默认有该配置,无需手动配置
4.编译JSX语法的包为:@babel/preset-react。
3)JSX使用步骤
1)使用JSX语法创建react元素
const title=<h1>hello JSX</h1>
2)使用ReactDOM.render()方法渲染react元素到页面中
ReactDOM.render(title,document.getElementById('root'))
4)JSX的注意点
1)React元素的属性名使用驼峰命名
2)特殊属性名:class - >calssName,for - >htmlFor,tabindex - > tabIndex
const title=<h1 className="title">hello JSX </h1>

3)没有子节点的React元素可以使用 />结束,单标签的形式
下面的demo就是span里面没有子节点
const title=<h1>hello JSX <span/> </h1>
4)推荐:使用小括号包裹JSX,从而避免JS中的自动插入分号陷阱
const title=(
<h1 className="title">hello JSX1234 </h1>
)
2 JSX中使用Javascript表达式
嵌入式JS表达式,数据存储在JS中
语法:{JavaScript表达式}
注意:语法中是单大括号,不是双大括号
const name='Jack'
const title=(
<h1 className="title">hello JSX ,{name} </h1>
)
ReactDOM.render(title,document.getElementById('root'))
3 JSX的条件渲染
条件渲染:根据条件渲染特定的JSX结构
可以使用 if/else或三元运算符或逻辑与运算符来实现
if/else
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'))
三元运算符
const loadData=()=>{
return isLoading ? (<div>loading...</div>):(<div>数据加载完成,此处显示加载后的数据</div>)
}
逻辑与运算符效果和上面的两种不太一样,但可以实现要么显示,要么隐藏的效果
const loadData=()=>{
return isLoading &&(<div>loading</div>)
}
4 JSX的列表渲染
- 使用数组的
map()方法 - 渲染列表的时候,应该添加key属性,key属性的值要保持唯一
- 原则:map()遍历谁,就给谁添加key属性
- 尽量避免使用索引号作为key
const song=[
{id:1,name:'痴心绝对'},
{id:2,name:'像我这样的人'},
{id:3,name:'南山南'},
]
const list=(
<ul>
{song.map(item=><li key={item.id}>{item.name}</li>)}
</ul>
)
ReactDOM.render(list,document.getElementById('root'))
5.JSX的样式处理
1)行内样式————style
外面的花括号表示的是JSX语法,里面的花括号表示的是对象
const list=(
<h1 style={{color:'red',background:'pink'}}>JSX的样式处理</h1>
)
2)类名————className(推荐)
import './css/index.css'
const list=(
<h1 className="title" style={{color:'red',background:'pink'}}>JSX的样式处理</h1>
)
五.react组件
1.简单介绍
组件是react的一等公民,使用react就是在使用组件
特点:可复用,独立,可组合
2.react组件的两种创建方式
1) 使用函数创建组件
① 特点:
函数组件:使用JS的函数(或箭头函数)创建的组件
特点:
- 函数名称必须以大写字母开头,因为react是根据这个区分是组件还是普通的react函数
- 函数组件必须有返回值,否则会报错。返回值表示该组件的结构,没值就返回null。
如果返回值为Null,表示不渲染任何内容
② 使用:
- 用函数名作为组件的标签名
- 组件标签可以是单标签也可以是双标签
function Hello(){
return (
<div>这是我的第一个函数组件</div>
)
}
ReactDOM.render(<Hello />,document.getElementById('root'))
或者箭头函数的写法:
const Hello=()=> <div>这是我的第er个函数组件</div>
ReactDOM.render(<Hello />,document.getElementById('root'))
2) 使用类创建组件
类组件:使用ES6的class创建的组件
特点:
- 类名称也必须以大写字母开头
- 类组件应该继承React.Component父类,从而可以使用父类中提供的方法或属性
- 类组件必须提供render()方法,且该必须有返回值,表示该组件的结构
class Hello extends React.Component{
render(){
return (
<div>这是我的第一个类组件</div>
)
}
}
ReactDOM.render(<Hello />,document.getElementById('root'))
3)抽离为独立的js文件
组件作为一个独立的个体,一般都会放到一个单独的js文件中
步骤:
1)创建一个js文件
2)导入React
3)创建组件
4)导出组件
5)在其他js文件中引入上面创建好的的组件,并且渲染
3.React事件处理
1)事件绑定
- React事件绑定语法与DOM事件语法相似
- 语法:
on + 事件名称 = {事件处理程序},比如:onClick={()=>{}} - 注意:React事件采用
驼峰命名法,比如:onMouseEnter,onFocus
类组件的事件处理:
class App extends React.Component{
handleClik(){
console.log('我被点了')
}
render(){
return(
<button onClick={this.handleClik}>点我,点我</button>
)
}
}
ReactDOM.render(<App />,document.getElementById('root'))
函数组件的事件处理:
function App(){
function handleClick(){
console.log('我又被电了')
}
return (
<button onClick={handleClick}>点我</button>
)
}
ReactDOM.render(<App />,document.getElementById('root'))
2)事件对象
可以通过事件处理程序的参数获取到事件对象,React中的事件对象叫做合成事件(对象)
合成事件:兼容所有的浏览器,无需担心跨浏览器兼容问题
class App extends React.Component{
handleClick(e){
e.preventDefault();
console.log('事件对象',e)
}
render(){
return (
<a href='http://itcast.cn' onClick={this.handleClick}>传智播客</a>
)
}
}
4.有状态组件和无状态组件
状态(state)就是数据
函数组件——无状态组件
函数组件没有自己的状态,只负责静态数据的展示类组件——有状态组件
类组件有自己的状态,负责更新UI,让页面动起来
5.组件中的state和setState
1)state的基本使用
- 状态即数据,是组件内部的私有数据,只能在组件内部使用
- state的值是对象,表示一个组件中可以有多个数据

- 获取state的值
this.state
class App extends React.Component{
//第一种写法:
// constructor(){
// super()
// this.state={
// count:0
// }
// }
//另外一种写法:
state={
count:20
}
render(){
return (
<div>
<h1>计数器:{this.state.count}</h1>
</div>
)
}
}
2)setState修改状态
- 语法:
this.setState({要修改的数据}) - 注意:不要直接修改state中的值,这是错误的!!!
- 作用:1.修改state,2.更新UI
- 思想:数据驱动视图

按钮控制数字的变化:
class App extends React.Component{
state={
count:0
}
render(){
return (
<div>
<h1>计数器:{this.state.count}</h1>
<button onClick={()=>{
this.setState({
count:this.state.count + 1
})
}}>+1</button>
</div>
)
}
}
3)从JSX中抽离事件处理程序
将逻辑抽离到单独的方法中,保证JSX结构的清晰
class App extends React.Component{
state={
count:0
}
onIncrement(){
this.setState({
//这里的this为undefined
count:this.state.count + 1
})
}
render(){
return (
<div>
<h1>计数器:{this.state.count}</h1>
<button onClick={this.onIncrement}>+1</button>
</div>
)
}
}
报错:事件处理程序中this的值为undefined
解决:如何将this指向组件实例(render方法中的this即为组件实例)
4)事件绑定this指向

①箭头函数
利用箭头函数自身不绑定this的特点
class App extends React.Component{
state={
count:0
}
onIncrement(){
this.setState({
count:this.state.count + 1
})
}
render(){
return (
<div>
<h1>计数器:{this.state.count}</h1>
<button onClick={()=>this.onIncrement()}>+1</button>
</div>
)
}
}
②Function.prototype.bind()
利用ES5的bind方法,将事件处理程序中的This与组件实例绑定到一起
class App extends React.Component{
state={
count:0
}
constructor(){
super()
//this绑定到onIncrement方法,绑定一次,后面就不用管了
this.onIncrement=this.onIncrement.bind(this)
}
onIncrement(){
this.setState({
count:this.state.count + 1
})
}
render(){
return (
<div>
<h1>计数器:{this.state.count}</h1>
<button onClick={this.onIncrement}>+1</button>
</div>
)
}
}
③class实例的方法
利用箭头函数形式的class实例方法
注意:该语法是实验性的语法,但是由于babel的存在可以直接使用
class App extends React.Component{
state={
count:0
}
onIncrement=()=>{
this.setState({
count:this.state.count + 1
})
}
render(){
return (
<div>
<h1>计数器:{this.state.count}</h1>
<button onClick={this.onIncrement}>+1</button>
</div>
)
}
}
6.表单处理
1)受控组件
①概念:
其值受到react控制的表单元素。
②使用场景:
- HTML中的表单元素是可输入的,也就是有自己的可变状态
- 而React中可变状态通常保存在state中,并且只能通过setState()方法来接收
也就是说 html可维护自己的可变状态,而react有希望所有的可变状态在state中,导致不一致。
所以React将state于表单元素值value绑定到一起,由state的值来控制表单元素的值
③步骤:
1.在state中添加一个状态,作为表单元素的value值(控制表单元素值的来源)
2.给表单元素绑定change事件,将表单元素的值设置为state的值(控制表单元素值的变化)
demo:数据实时变化响应:
class App extends React.Component{
state={
txt:''
}
handleChange=(e)=>{
this.setState({
txt:e.target.value
})
}
render(){
return (
<div>
<div>{this.state.txt}</div>
<input type="text" value={this.state.txt} onChange={this.handleChange}></input>
</div>
)
}
}
输入框,文本框,下拉框 操作value属性
复选框 操作checkbox属性
④多表单元素优化
每个表单元素都有一个单独的事件处理程序太繁琐,那么如何使用一个事件处理程序 同时处理多个表单元素?
给表单元素添加name属性,名称与state相同,目的:
a.根据表单元素类型获取对应值
b.在change事件处理程序中通过[name]来修改对应的state
class App extends React.Component{
state={
txt:'',
content:'',
city:'',
isChecked:true
}
handleChange=(e)=>{
//获取当前DOM对象
const target = e.target
//根据类型获取值
const value = target.type === 'checkbox'?target.checked:target.value
//获取name
const name = target.name
this.setState({
[name]:value
})
}
render(){
return (
<div>
{/* 文本框 */}
<input name="txt" type="text" value={this.state.txt} onChange={this.handleChange}/> - <span>{this.state.txt}</span>
<br/>
{/* 富文本框 */}
<textarea name="content" type="text" value={this.state.content} onChange={this.handleChange}></textarea> - <span>{this.state.content}</span>
<br/>
{/* 下拉框 */}
<select name="city" value={this.state.city} onChange={this.handleChange}>
<option value="shanghai">上海</option>
<option value="beijin">北京</option>
<option value="guangzhou">广州</option>
</select>
- <span>{this.state.city}</span>
<br/>
{/* 复选框 */}
<input type="checkbox" name="isChecked" checked={this.state.isChecked} onChange={this.handleChange}/> - <span>{this.state.isChecked}</span>
</div>
)
}
}
2)非受控组件
①作用
- 借助于ref,使用原生DOM方式来获取表单元素值。
- ref的作用:获取DOM或组件
②使用步骤
获取input输入框的值:demo
class App extends React.Component{
constructor(){
super()
//创建ref
this.txtRef=React.createRef()
}
//获取文本框的值
getTxt=(e)=>{
console.log(this.txtRef.current.value)
}
render(){
return (
<div>
<input type='text' ref={this.txtRef}/>
<button onClick={this.getTxt}>获取文本框的值</button>
</div>
)
}
}
------------------------------------进阶--------------------------------------------------
六.组件通讯
1.组件的props
作用:接受攒底给组件的数据
如何传:给组件标签添加属性
如何接受:函数组件通过参数props接收数据,类组件通过this.props接收数据
函数组件:
const Hello = props=>{
console.log(props)
return(
<div>
<h1>props:{props.name}---{props.age}</h1>
</div>
)
}
ReactDOM.render(<Hello name='jack' age={19} />,document.getElementById('root'))
类组件
class Hello extends React.Component{
render(){
return(
<div>
<h1>props:{this.props.name}-{this.props.age}</h1>
</div>
)
}
}
//如果穿的是费字符串的数据,就用花括号包裹下。这里就是传数字18,如果用字符串传递就是字符串18
ReactDOM.render(<Hello name='rose' age={18} />,document.getElementById('root'))
2.props的特点
1)可以传递任意数据类型
const Hello = props=>{
console.log('props',props)
props.fn()
return(
<div>
<h1>props:</h1>{props.tag}
</div>
)
}
ReactDOM.render(<Hello
name='rose'
age={18}
colors={['red','green','blue']}
fn={()=>console.log('这是一个函数')}
tag={<p>这是一个标签</p>}
/>,
document.getElementById('root'))
2)props是只读的对象
只能读取属性的值,无法修改对象
3)注意:
使用类组件时,如果写了构造函数,应该将props传递给super,否则,无法在构造函数中获取到props
class Hello extends React.Component{
constructor(props){
super(props)
console.log(props)
}
render(){
return(
<div>
<h1>props:{this.props.name}-{this.props.age}</h1>
</div>
)
}
}
ReactDOM.render(<Hello name='rose' age={18} />,document.getElementById('root'))
3.组件通讯的三种方式
1)父组件 - > 子组件
1.父组件提供要传递的state数据
2.给子组件标签添加属性,值为state中的数据
3.子组件中通过props接受父组件中传递的数据
class Parent extends React.Component{
state={
lastName:'王'
}
render(){
return(
<div className='parent'>
父组件
<Child name={this.state.lastName}></Child>
</div>
)
}
}
const Child=(props)=>{
return (
<div>
<p>子组件,接受父组件的数据:{props.name}</p>
</div>
)
}
ReactDOM.render(<Parent />,document.getElementById('root'))
2)子组件 -> 父组件
1.父组件提供一个回调函数(勇于接受数据)
2.将该函数作为属性的值,传递个子组件
3.子组件通过props调用回调函数
4.将子组件的数据作为参数传递给回调函数
class Parent extends React.Component{
state={
lastName:'王'
}
getChildMsg=data=>{
console.log('接受了子组件传递过来的数据:',data)
}
render(){
return(
<div className='parent'>
父组件
<Child getMsg={this.getChildMsg}></Child>
</div>
)
}
}
class Child extends React.Component{
state={
msg:'刷痘印'
}
handleClick=()=>{
this.props.getMsg(this.state.msg)
}
render(){
return (
<div>
<p>子组件:</p>
<button onClick={this.handleClick}>点我,给父组件传递数据</button>
</div>
)
}
}
ReactDOM.render(<Parent />,document.getElementById('root'))
3)兄弟组件
思想状态:状态提升
将共享状态提升到最近的公共组件中,有公共父组件管理这个状态
公共父组件职责:1.提供共享状态;2.提供操作共享状态的方法
要通讯的子组件只需要通过props接受状态或操作状态的方法
class Counter extends React.Component{
state={
count:0
}
onIncrement= ()=>{
this.setState({
count:this.state.count+1
})
}
render(){
return(
<div>
<Child1 count={this.state.count}/>
<Child2 onIncrement={this.onIncrement}/>
</div>
)
}
}
const Child1 = (props)=>{
return <h1>计数器:{props.count}</h1>
}
const Child2 = (props)=>{
return <button onClick={()=>{props.onIncrement()}}>+1</button>
}
ReactDOM.render(<Counter />,document.getElementById('root'))
4.context
1)使用场景
跨组件传递数据
2)使用步骤:
1.调用React.createContext()创建Provider提供数据和Consumer(消费数据)两个组件
2.使用Provider组件作为父节点
3.设置value属性,表示要传递的数据
4.调用Consumer组件接收数据
//创建context得到得两个组件
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></SubNode>
</div>
)
}
const SubNode=props=>{
return(
<div className='subNode'>
<Child></Child>
</div>
)
}
const Child=props=>{
return <div className='child'>
<Consumer>
{
data=><span>我是子节点 -- {data}</span>
}
</Consumer>
</div>
}
ReactDOM.render(<App />,document.getElementById('root'))
5.props的深入
1)children属性
表示组件标签的子节点,当组件标签有子节点时,props就会有该属性
children属性与普通的props一样,值可以是任意值(文本,React元素,组件,甚至是函数)
const App = props=>{
console.log(props)
return(
<div>
<h1>组件标签的子节点:</h1>
{props.children}
</div>
)
}
ReactDOM.render(<App>我是子节点</App>,document.getElementById('root'))
2)props校验
Ⅰ.出现原因:
对于组件来说,props是外来的,无法保证组件使用者传入什么格式的数据
如果传入的数据格式不对,肯会导致组件内部报错
关键问题:组件的使用者不知道明确的错误原因
Ⅱ.使用场景:
允许在创建组件的时候,就指定props的类型,格式等
Ⅲ.作用:
对于组件来说,props是外来的,无法保证组件使用者传入什么格式的数据
如果传入的数据格式不对,肯会导致组件内部报错
捕获使用组件时因为props导致的错误,给出明确的错误提示,增加组件的健壮性

Ⅳ.使用步骤:
1.安装prop-types
npm i prop-types
2.导入prop-types包
import PropTypes from 'prop-types'
3.使用组件名.propTypes={}来给组件的props添加校验规则
App.propTypes={
colors:PropTypes.array
}
注意:propTypes的大小写问题
4.校验规则通过PropTypes对象来指定
import PropTypes from 'prop-types'
const App=props=>{
const arr=props.colors
const lis=arr.map((item,index)=><li key={index}>{item.name}</li>)
return <ul>{lis}</ul>
}
App.propTypes={
colors:PropTypes.array
}
ReactDOM.render(<App colors={19}></App>,document.getElementById('root'))
如果类型传错了,那么控制台会直接提示,是类型错误导致报错的。
Ⅴ.约束规则
1.常见类型:array,bool,func,number,object,string
2.React元素类型:element
3.必填项:isRequired
4.特定结构的对象:shape({})
更多类型查询见官网:官网
3)默认值
场景:分页组件 -> 每页显示条数
作用:给props设置默认值,在未传入props的时候生效。如果传入了值,就以传入的值为主。
const App=props=>{
return <div>
<h1>此处展示props的默认值:{props.pageSize}</h1>
</div>
}
App.defaultProps={
pageSize:10
}
ReactDOM.render(<App ></App>,document.getElementById('root'))
七.组件的生命周期
1.概述
1)是什么?组件从被创建到挂载到页面中运行,再到组件不用时卸载的过程
2)意义:组件的生命周期有助于理解组件的运行方式,完成更复杂的组件功能,分析组件错误原因等
3)注意:
- 生命周期的每个阶段总是伴随着一些方法调用,这些方法就是生命周期的钩子函数
- 钩子函数的作用:未开发人员在不同阶段 操作组件 提供了时机
- 只有类组件才有生命周期

2.生命周期三个阶段
1)创建时(挂载阶段)
- 执行时机:组件创建时(页面加载时)
- 三个钩子函数:
constructor()->render()->componentDidMount()
| 钩子函数 | 触发时机 | 作用 |
|---|---|---|
constructor | 创建组件时,最先执行 | 1.初始化state; 2.为事件处理程序绑定this |
render | 每次组件渲染都会触发 | 渲染UI(注意:不能调用setState(),否则会死循环) |
| componentDidMount` | 组件挂载(wanchengDOM渲染)后 | 1.发送ajax请求(首次进入页面调用请求就是在这);2.可以进行DOM操作 |
class App extends React.Component{
constructor(){
super()
console.warn('生命周期周期钩子函数:constructor')
}
componentDidMount(){
//进行DOM操作,此外还可以在这里发送ajax请求,获取远程数据
const title = document.getElementById('title')
console.log(title)
console.warn('生命周期周期钩子函数:componentDidMount')
}
render(){
//错误演示!!!不能在render中调用setState()
// this.setState({
// count:1
// })
console.warn('生命周期周期钩子函数:render')
return(
<div>
<h1 id='title'>统计痘痘被打败的次数</h1>
<button id='btn'>打豆豆</button>
</div>
)
}
}
ReactDOM.render(<App ></App>,document.getElementById('root'))
2)更新时
①执行时机
- 调用
setState()触发更新
this.setState({
count:this.state.count+1
})
当组件接收到新的
props强制更新
this.forceUpdate()
示例:
class App extends React.Component{
constructor(){
super()
this.state={
count:0
}
}
handleClick=()=>{
//1.调用setState触发更新
this.setState({
count:this.state.count+1
})
//强制更新
this.forceUpdate()
}
render(){
return(
<div>
<Counter count={this.state.count}></Counter>
<button onClick={this.handleClick}>打豆豆</button>
</div>
)
}
}
class Counter extends React.Component{
//2.当组件接收到新的属性的时候,也会导致组件重新渲染。也就是说更新属性子组件就会重新渲染
render(){
return <h1 >统计痘痘被打败的次数:{this.props.count}</h1>
}
}
ReactDOM.render(<App ></App>,document.getElementById('root'))
②两个钩子函数
render()和componentDidUpdate()
如果在componentDidUpdate中直接调用setState()或者发送ajax请求更新状态,会导致递归更新!!!!
正确做法:需要比较前后的props是否相同,来决定是否重新重新渲染。
class App extends React.Component{
constructor(){
super()
this.state={
count:0
}
}
handleClick=()=>{
this.setState({
count:this.state.count+1
})
}
render(){
return(
<div>
<Counter count={this.state.count}></Counter>
<button onClick={this.handleClick}>打豆豆</button>
</div>
)
}
}
class Counter extends React.Component{
render(){
return <h1 >统计痘痘被打败的次数:{this.props.count}</h1>
}
componentDidUpdate(prevProps){
//componentDidUpdate进行数据的修改需要在if判断中,也就是判断props是否变化来决定是否更新
if(prevProps.count!==this.props.count){
this.setState({})
}
}
}
ReactDOM.render(<App ></App>,document.getElementById('root'))
3)卸载时
执行时机:组件从页面中消失
class App extends React.Component{
constructor(){
super()
this.state={
count:0
}
}
handleClick=()=>{
this.setState({
count:this.state.count+1
})
}
render(){
return(
<div>
{
this.state.count > 3?(
<p>痘痘被打死了!</p>
):(
<Counter count={this.state.count}></Counter>
)
}
<button onClick={this.handleClick}>打豆豆</button>
</div>
)
}
}
class Counter extends React.Component{
componentDidMount(){
//开启青石桥
this.timerId=setInterval(()=>{
console.warn('定时器开启了')
},500)
}
render(){
return <h1 >统计痘痘被打败的次数:{this.props.count}</h1>
}
componentWillUnmount(){
console.warn('被清理了')
//清理定时器
clearInterval(this.timerId)
}
}
ReactDOM.render(<App ></App>,document.getElementById('root'))
3.不常用钩子函数


八.React组件复用
- 处理方式?服用相似的功能
- 复用什么?1.
state2.操作state的方法(组件转态逻辑) - 两种方式:1.
render props模式 2.高阶组件(HOC) - 注意:这两种方式不是新的API,而是利用
React自身特点的编码技巧,演化而成的固定模式(写法)
1.render props模式
1)思路分析:
思路:将要服用的state和操作state的方法封装到一个组件中
问题1:如何拿到该组件中复用的state?
在使用组件时,添加一个值为函数的prop,通过函数参数来获取(需要组件内部实现)问题2:如何渲染任意的UI?
使用该函数的返回值作为要渲染的UI内容(需要组件内部的实现)
2)使用步骤
1.创建Mouse组件,在组建中提供复用的状态逻辑代码(1.状态 2.操作状态的方法)
2.将要复用的状态作为props.render(state)方法的参数,暴露到组件外部
3.使用props.render()的返回值作为要渲染的内容
也就是说在子组件通过this.props.render触发调用父组件传过来的render的方法,并且传参数值给父组件。
那么父组件接受子组件传过来的参数,并且调用该render方法。那么子组件只实现了逻辑代码的复用,并没有实现UI结构的复用。父组件掺入的render props负责使用复用的状态来渲染UI结构。
案例:根据鼠标展示坐标和移动端的小花猫。都是调用了Mouse这个封装的组件完成这两个功能的
import img from '../public/images/logo192.png'
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(){
//这里调用的父级的传过来的render的方法,并且传值过去
return this.props.render(this.state)
}
}
class App extends React.Component{
render(){
return(
<div>
<h1>render props模式</h1>
{/* 根据鼠标展示x,y轴坐标 */}
<Mouse
render={mouse=>{
return(
<p>
鼠标位置:{mouse.x},{mouse.y}
</p>
)
}}
/>
{/* 移动的猫 */}
<Mouse
render={mouse => {
return (
<img src={img}
alt="猫"
style={{
position: 'absolute' ,
top:mouse.y - 86,
left:mouse.x-86
}} />
)
}}
/>
</div>
)
}
}
ReactDOM.render(<App ></App>,document.getElementById('root'))
3)children代替render属性
推荐使用children代替render属性
import img from '../public/images/logo192.png'
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() {
//这里调用的父级的传过来的render的方法,并且传值过去
return this.props.children(this.state)
}
}
class App extends React.Component {
render() {
return (
<div>
<h1>render props模式</h1>
{/* 根据鼠标展示x,y轴坐标 */}
<Mouse>
{
mouse => (
<p>
鼠标位置:{mouse.x},{mouse.y}
</p>
)
}
</Mouse>
{/* 移动的猫 */}
<Mouse>
{
mouse => (
<img src={img}
alt="猫"
style={{
position: 'absolute',
top: mouse.y - 86,
left: mouse.x - 86
}} />
)
}
</Mouse>
</div>
)
}
}
ReactDOM.render(<App ></App>, document.getElementById('root'))
context用的也是children的方式
4)代码优化
1.推荐给render.props模式添加props校验
import propTypes from 'prop-types'
//添加props校验
Mouse.propTypes={
children:propTypes.func.isRequired
}
2.应该在组件卸载时解除mousemove事件绑定
//添加
componentDidMount() {
window.addEventListener('mousemove', this.handleMouseMove)
}
//解除
componentWillUnmount(){
window.removeEventListener('mousemove')
}
2.高阶组件
1)介绍
目的:实现状态逻辑复用
方式:采用包装(装饰)模式,想手机壳装饰手机一样
高阶组件(HOC,Higher-OrderComponent)是一个函数,接受要包装的组件,返回增强后的组件
高阶组件内部创建一个类组件,在这个类组件中提供复用的状态逻辑代码,通过prop将复用的状态传递给被包装组件WrappedComponent
2)使用步骤
1.创建一个函数,名称约定以with开头
2.指定函数参数,参数应该以大写字母开头(作为要渲染的组件)
3.在函数内部创建一个类组件,提供复用的状态逻辑代码,并返回
4.在该组件中,渲染参数组件,同时将状态通过prop传递给参数组件
5.调用该高阶组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面中
import img from '../public/images/logo192.png'
//创建高阶组件
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)
}
componentWillUnmount(){
window.removeEventListener('mousemove',this.handleMouseMove)
}
render(){
return <WrappedComponent {...this.state}></WrappedComponent>
}
}
return Mouse
}
//猫捉老鼠的组件:
const Cat = props=>(
<img
src={img}
alt="猫"
style={{
position:'absolute',
top:props.y - 96,
left:props.x - 96
}}
/>
)
//用来测试高阶组件
const Position = props=>(
<p>
鼠标当前位置:(x:{props.x},y:{props.y})
</p>
)
//获取增强后的组件:
const MousePosition = withMouse(Position)
//获取增强后的猫捉老鼠组件:
const MouseCat=withMouse(Cat)
class App extends React.Component{
render(){
return(
<div>
<h1>高阶组件</h1>
{/* 渲染增强后的组件 */}
<MousePosition></MousePosition>
<MouseCat></MouseCat>
</div>
)
}
}
ReactDOM.render(<App ></App>, document.getElementById('root'))
3)设置displayName
1.使用高阶组件存在的问题:得到的两个组件名称相同
2.原因:默认情况下,React使用组件名称作为displayName
3.解决方式:为高阶组件设置displayName便于调试时区分不同的组件,displayName用预设值调试信息(React Developer Tools信息)
4.设置方式:
完整示例:
import img from '../public/images/logo192.png'
//创建高阶组件
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)
}
componentWillUnmount(){
window.removeEventListener('mousemove',this.handleMouseMove)
}
render(){
return <WrappedComponent {...this.state}></WrappedComponent>
}
}
//设置displayName
Mouse.displayName = `withMouse${getDisplayName(WrappedComponent)}`
return Mouse
}
function getDisplayName(WrappedComponent){
return WrappedComponent.displayName || WrappedComponent.name || 'Component'
}
//猫捉老鼠的组件:
const Cat = props=>(
<img
src={img}
alt="猫"
style={{
position:'absolute',
top:props.y - 96,
left:props.x - 96
}}
/>
)
//用来测试高阶组件
const Position = props=>(
<p>
鼠标当前位置:(x:{props.x},y:{props.y})
</p>
)
//获取增强后的组件:
const MousePosition = withMouse(Position)
//获取增强后的猫捉老鼠组件:
const MouseCat=withMouse(Cat)
class App extends React.Component{
render(){
return(
<div>
<h1>高阶组件</h1>
{/* 渲染增强后的组件 */}
<MousePosition></MousePosition>
<MouseCat></MouseCat>
</div>
)
}
}
ReactDOM.render(<App ></App>, document.getElementById('root'))
4)传递props
问题:propsss丢失
原因:高阶组件没有往下传递
解决方式:渲染WrappedComponent时,将state和this.props一起传递给组件
传递方式:
---------------------------------------React原理揭秘--------------------------------------
九.setState()方法的说明
1.异步更新数据
- setState()是异步更新数据的
- 注意:使用该语法时,后面的
setState()不要依赖于前面的setState()
一个方法里面多次使用了setState,只会render一次
class App extends React.Component{
state={
count:1
}
handleClick=()=>{
//调用多次setState也只render一次
this.setState({
count:this.state.count +1
})
console.log(this.state.count) //1
//有于是异步,所以还没来得及更新,就进行了第二个setState,此时的count还是1
this.setState({
count:this.state.count +1 //1+1
})
console.log(this.state.count) //1
}
render(){
console.log('render')
return(
<div>
<h1>计数器:{this.state.count}</h1>
<button onClick={this.handleClick}>+1</button>
</div>
)
}
}
ReactDOM.render(<App ></App>, document.getElementById('root'))
2.推荐语法
推荐:使用setState((state,props)=>{})语法
参数:state表示最新的state,props表示最新的props
handleClick=()=>{
//state拿到的就是最新值1
this.setState((state,props)=>{
console.log(state)//最新值1
return{
count:state.count + 1
}
})
console.log('count',this.state.count) //1
//state拿到的就是最新值2
this.setState((state,props)=>{
console.log(state)//最新值2
return{
count:state.count + 1
}
})
console.log('count',this.state.count) //1
}
3.第二个参数
场景:在状态更新(页面完成重新渲染)后立即执行某个操作
语法:setState(update,callback)
这个功能和componentDidMount功能类似,可相互替代
handleClick=()=>{
this.setState((state,props)=>{
return{
count:state.count + 1
}
},
//状态更新后并且重新渲染后,立即执行
()=>{
console.log('状态更新完成',this.state.count)//2
console.log(document.getElementById('title'))
}
)
console.log(this.state.count)//1
}
十.JSX语法的转换过程
1.JSX仅仅是createElement()方法的语法糖(简化语法)
2.JSX语法被@babel/preset-react插件编译为createElement()方法
3.React元素:是一个对象,用来描述你希望在屏幕上看到的内容
十一.组件更新机制
- setState()的两个作用:1.修改state ;2.更新组件(UI)
- 过程:父组件重新渲染时,也会重新渲染子组件。但只会渲染自己组件和他的所有子组件

十二.组件性能优化
1.减轻state
state里面只存储跟组件渲染相关的数据(比如:count、列表数据、loading等)
注意:不用做渲染的数据不要放在state中,比如定时器id等,这种需要在多个方法中用到的数据,应该放在this中
2.避免不必要的重新渲染
组件的更新机制:父组件更新会引起子组件也被更新,这种思路很清晰
存在的问题:子组件如果没有任何变化时,也会重新渲染
解决:使用钩子函数shouldComponentUpdate(nextProps,nextState){},通过返回值决定该组件是否重新渲染,返回true表示重新渲染,false表示不重新渲染。nextProps最新的参数,nextState最新的状态
触发时机:更新阶段的钩子函数,组件重新渲染前执行(shouldComponentUpdate -> render)
示例1:nextState
通过nextState实现的案例,父组件调用shouldComponentUpdate方法
class App extends React.Component{
state={
number:0
}
handleClick=()=>{
this.setState(()=>{
return{
number:Math.floor(Math.random()*3)
}
})
}
shouldComponentUpdate(nextProps,nextState){
console.log('最新状态:',nextState,',当前状态:',this.state)
//原始写法
if(nextState.number === this.state.number){
return false
}
return true
//简化写法
// return nextState.number !== this.state.number
}
render(){
console.log('render')
return(
<div>
<h1>随机数:{this.state.number}</h1>
<button onClick={this.handleClick}>重新生成</button>
</div>
)
}
}
ReactDOM.render(<App ></App>, document.getElementById('root'))
示例1:nextProps
通过nextProps实现的案例,子组件调用shouldComponentUpdate方法
class App extends React.Component{
state={
number:0
}
handleClick=()=>{
this.setState(()=>{
return{
number:Math.floor(Math.random()*3)
}
})
}
render(){
return(
<div>
<NumberBox number={this.state.number}></NumberBox>
<button onClick={this.handleClick}>重新生成</button>
</div>
)
}
}
class NumberBox extends React.Component{
shouldComponentUpdate(nextProps){
return nextProps.number !== this.props.number
}
render(){
return <h1>随机数:{this.props.number}</h1>
}
}
ReactDOM.render(<App ></App>, document.getElementById('root'))
3.React组件复用
render-props和高阶组件
4.纯组件
1.纯组件内部的对比是shadow compare(浅层对比)
2.值类型和引用类型
- 值类型:比较两个值是否相同,不同的话就可以就可以渲染组件

- 引用类型:只比较对象的引用(地址)是否相同
引用传递导致只要赋值,他俩就指向同一个地址,所以他俩地址相同。导致即使状态发生了变化,但页面的数据不会重新渲染。
引用类型的解决:
state或props属性值为引用类型时,应该创建数据,不要直接修改原数据!
5.虚拟DOM和Diff算法
React更新树图的思想:只要state变化句重新渲染视图
问题:组件中只有一个DOM元素需要更新时,也得把整个组件的内容重新渲染到页面吗?
回答:不是的。
问题:React是如何做到部分更新的?
回答:虚拟DOM配合Diff算法
1)虚拟DOM:
本质上就是一个JS对象,用来描述你希望在屏幕上看到的内容(UI)。
虚拟DOM ->state+JSX

2)执行过程
1.初次渲染时,React会根据初始state(Model),创建一个虚拟DOM对象(树)。
2.根据虚拟DOM生成真正的DOM,渲染到页面中。
3.当数据变化后(setState()),重新根据新的数据,创建新的虚拟DOM对象(树)。
4.与上一次得到的虚拟DOM对象,使用Diff算法对比(找不同),得到需要更新的内容。
5.最终,React只将变化的内容更新(path)到DOM中,重新渲染到页面。
3)虚拟DOM的价值
虚拟DOM的真正价值不仅仅是性能,而是 让React脱离了浏览器环境的束缚。
因为虚拟DOM不是真正的DOM,他只是js的对象,所以只要能运行的js的地方,就能运行react。那么虚拟DOM就可以让React脱离浏览器的环境去运行,这也为React跨平台的运用提供了保障。
十三.React路由
1.使用步骤
注意:以下写法对react-router-dom ^6.0.2版本不使用,暂时没有想到解决的办法。更多参考:详情
1)安装
npm i react-router-dom
2)导入路由的核心组件:
import {BrowserRouter as Router , Route ,Link ,Routes} from 'react-router-dom'
3.Router标签是最外层包裹,
Link标签由to属性指向路由名称,
Routes标签包裹路由内容,
Route`标签指向不同的路由内容,其中path属性指向路径,element指向路由的标签
import {BrowserRouter as Router , Route ,Link } from 'react-router-dom'
const First=()=><p>页面一的内容</p>
const Home=()=><p>这是Home组件的内容</p>
const App=()=>(
<Router>
<div>
<h1>React路由基础</h1>
<Link to='/first'>页面一</Link>
<Link to='/home'>首页</Link>
<Route path='/first' component={First} />
<Route path='/home' component={Home} />
</div>
</Router>
)
ReactDOM.render(<App ></App>, document.getElementById('root'))
2.路由的执行过程
1.点击Link组件(a标签),修改了浏览器地址栏中的url
2.React路由监听到地址栏url的变化
3.React路由内部遍历所有Route组件,使用路由规则(path)与pathname进行匹配
4.当路由规则(path)能够匹配地址栏中的pathname时,就展示该Route组件的内容
3.编程式导航
问题:点击登录按钮跳转到后台首页,如何实现?
回答:通过JS代码来实现页面跳转
history是React路由提供的,用于获取浏览器历史记录的相关信息
- push (path):跳转到某个页面,参数path表示要跳转的路径
- go(n):前景或后腿到某个页面,参数n表示前进或后腿页面数量
import { BrowserRouter as Router, Route, Link} from 'react-router-dom'
class Login extends React.Component {
handleLogin=(props)=>{
//跳转到home页面
this.props.history.push('/home')
}
render() {
return (
<div>
<p>登录页面</p>
<button onClick={this.handleLogin}>登录</button>
</div>
)
}
}
const Home = (props) => {
const handleClick=()=>{
//跳转到上一页
props.history.go(-1)
}
return (
<div>
<h2>我是后台首页</h2>
<button onClick={handleClick}>返回登录页面按钮</button>
</div>
)
}
const App = () => (
<Router>
<div>
<h1>编程式导航</h1>
<Link to='/login'>去登录页面</Link>
<Route path='/login' component={Login} ></Route>
<Route path='/home' component={Home} ></Route>
</div>
</Router>
)
ReactDOM.render(<App ></App>, document.getElementById('root'))
注意:上面的路由跳转只适合react-router-dom是6以下的版本。6以上版本语法不一样,获取不到dom,暂时没有想到解决的办法。
4.默认路由
表示进入页面时就会自动匹配的路由
默认路由的path为:/
{/* 默认路由 */}
<Route path='/' component={Login} ></Route>
5.模糊匹配
1)模糊匹配模式
问题:当Link组件的to属性值为"/Login"时,为什么默认路由也会被匹配成功?也就是为什么两个路由的内容都显示?
回答:默认情况下,React路由是模糊匹配模式
模糊匹配规则:只要pathname以path开头就会匹配成功
2)精确匹配
问题:默认路由任何情况下都展示。如何避免这种问题
回答:给Route组件添加exact属性,让其变成精确匹配模式
精确匹配:只有当path和pathname完全匹配时才会展示该路由(推荐给默认路由添加exact属性)
{/* 此时,该组件只能匹配pathname='/'这一种情况 */}
<Link to='/'>去登录页面</Link>
<Route exact path='/' component={Login} ></Route>
-----------------------------项目实战-------------------------------------------