React
React 概述
- 用于动态构建用户界面的 JavaScript 库(只关注于视图)
- 由Facebook开源
中文官网: https://react.docschina.org/
React的特点
- 声明式编码
- 组件化编码
- React Native 编写原生应用
- 高效(优秀的Diffing算法)
React高效的原因
- 使用虚拟(virtual)DOM, 不总是直接操作页面真实DOM。
- DOM Diffing算法, 最小化页面重绘。
React的基本使用
脚手架项目中使用
安装
1 | npm i react react-dom |
react 包是核心,提供创建元素、组件等功能
react-dom 包提供 DOM 相关功能等
1 | // index.js |
1 | // App.jsx |
普通网页中使用
引入 react 和 react-dom 两个js文件
1 | <script src="./node_modules/react/umd/react.development.js"></script> |
1 | <!-- 准备好一个“容器” --> |
相关js库
- react.js:React核心库。
- react-dom.js:提供操作DOM的react扩展库。
- babel.min.js:解析JSX语法代码转为JS代码的库
创建虚拟DOM的两种方式
React Api
- React提供了一些API来创建一种 “特别” 的一般js对象
1 | // 参数一:元素名称 |
- 虚拟 DOM 对象最终都会被 React 转换为真实的 DOM
- 我们编码时基本只需要操作 react 的虚拟 DOM 相关数据, react 会转换为真实 DOM 变化而更新界
React JSX
全称: JavaScript XML
react定义的一种类似于XML的JS扩展语法: JS + XML本质是
React.createElement('component', props, ...children)
方法的语法糖作用: 用来简化创建虚拟DOM
写法:const ele =
Hello JSX!
注意1:它不是字符串, 也不是HTML/XML标签
注意2:它最终产生的就是一个JS对象
标签名任意: HTML标签或其它标签
标签属性任意: HTML标签属性或其它
基本语法规则
- 遇到 <开头的代码, 以标签的语法解析: html同名标签转换为html同名元素, 其它标签需要特别解析
- 遇到以 { 开头的代码,以JS语法解析: 标签中的js表达式必须用{ }包含
babel.js的作用
浏览器不能直接解析 JSX 代码, 需要babel转译为纯JS的代码才能运行
只要用了 JSX,都要加上 type=”text/babel”, 声明需要babel来处理
1 | const ele = <h1>Hello JSX!</h1> |
JSX
JSX 是 JavaScript XML的简写,表示在 JavaScript 代码中写 XML(HTML) 格式的代码。
优势:声明式语法更加直观、与HTML结构相同,降低了学习成本、提升开发效率
JSX 是 React 的核心内容
JSX的基本使用
使用 JSX 语法创建 react 元素
1
2// 使用 JSX 语法,创建 react 元素
const title = <h1>Hello JSX</h1>使用 ReactDOM.render() 方法渲染 react 元素到页面中
1
2// 渲染创建好的 React 元素
ReactDOM.render(title,root)为什么脚手架中可以使用 JSX 语法?
- JSX 不是标准的 ECMAScript语法,它是 ECMAScript的语法扩展。
- 需要使用 babel 编译处理后,才能在浏览器环境中使用。
- create-react-app 脚手架中已经默认有该配置,无需手动配置。
- 编译 JSX 语法的包为:@babel/preset-react。
注意点:
React元素的属性名使用驼峰命名法
特殊属性名:class -> className、for -> htmlFor、tabindex -> tabIndex。
没有子节点的React元素可以用/>结束。
推荐:使用小括号包裹 JSX,从而避免JS中的自动插入分号陷阱。
1
2
3
4// 使用小括号包裹 JSX
const dv = (
<div>Hello JSX</div>
)
JSX 中的 JavaScript 表达式
注意点:
- 单大括号中可以使用任意的 JavaScript 表达式
- JSX 自身也是 JS 表达式
- 注意:JS 中的对象是一个例外,一般只会出现在style属性中
- 注意:不能在 {} 中出现语句 ( 比如:if/for 等 )
1 | const name = 'Jack' |
JSX 的条件渲染
根据条件渲染特定的 JSX 结构
可以用 if/else 或 三元运算符 或 逻辑与运算符来实现
1 | const isLoading = true |
JSX 的列表渲染
- 如果要渲染一组数据,应该使用数组的 map() 方法
- 注意:渲染列表时应该添加 key 属性,key 属性的值要保证唯一
- 原则:map() 遍历谁,就给谁添加 key 属性
- 注意:尽量避免使用索引号作为 key
1 | const songs = [ |
JSX 的样式处理
行内样式
1
2
3<h1 style={ { color: 'red', backgroundColor: 'skyblue' } }>
JSX的样式处理
</h1>类名—className(推荐)
1
2
3<h1 className="title">
JSX的样式处理
</h1>
Reacr组件
创建和使用方式
使用函数创建组件
- 使用JS中的函数创建的组件叫做:函数组件
- 函数组件必须有返回值
- 组件名称必须以大写字母开头,React 据此区分 组件 和 普通的React元素
- 使用函数名作为组件标签名
1 | /* |
渲染函数组件:用函数名作为组件签名
组件标签可以是单标签也可以是双标签
1 | ReactDOM.render(<Hello/>,document.getElementById('root')) |
使用类创建组件
- 类组件:使用ES6的 class 创建的组件
- 约定1:类名称也必须以大写字母开头
- 约定2:类组件应该继承 React.Component 父类,从而可以使用父类中提供的方法或属性
- 约定3:类组件必须提供 render() 方法
- 约定4:render() 方法必须有返回值,表示该组件的结构
1 | /* |
抽离为独立 JS 文件
- 创建Hello.js
- 在Hello.js中导入React
- 创建组件( 函数 或 类 )
- 在 Hello.js 中导出该组件
- 在index.js中导入Hello组件
- 渲染组件
1 | // Hello.js |
1 | // index.js |
事件绑定
React 事件绑定语法与 DOM 事件语法相似
语法:on+事件名称={事件处理程序},比如:onClick={ ()=>{} }
注意:React 事件采用驼峰命名法,比如:onMouseEnter、onFocus
在函数组件中绑定事件:
1 | class App extends React.Component { |
1 | function App() { |
事件绑定 this 指向
箭头函数
- 利用箭头函数自身不绑定this的特点
- render() 方法中的 this 为组件实例,可以获取到 setState()
1
<button onClick={ () => this.onIncrement() }>+1</button>
Function.prototype.bind()
- 利用ES5中的bind方法,将事件处理程序中的this与组件实例绑定到一起
1
2
3
4
5coustructor() {
super()
this.onIncrement = this.onIncrement.bind(this)
}
// ...省略 onIncrementclass 的实例方法 (推荐)
- 利用箭头函数形式的 class 实例方法
- 注意:该语法是实验性语法,但是,由于babel的存在可以直接使用
1
2
3onIncrement = () => {
this.setState({ ... })
}
事件对象
- 可以通过事件处理程序的参数获取到事件对象
- React 中的事件对象叫做:合成事件 ( 对象 )
- 合成事件:兼容所有浏览器,无需担心跨浏览器兼容性问题
1 | class App extends React.Component { |
有状态组件和无状态组件
- 函数组件又叫做无状态组件,类组件又叫做有状态组件
- 状态 ( state ) 即数据
- 函数组件没有自己的状态,只负责数据展示 ( 静 )
- 类组件有自己的状态,负责更新UI,让页面 “动” 起来
state 的基本使用
- 状态 ( state )即数据
- 是组件内部的私有数据,只能在组件内部使用
- state 的值是对象,表示一个组件中可以有多个数据
- 通过 this.state 来获取状态
1 | class Hello extends React.Component { |
1 | class Hello extends React.Component { |
setState() 修改状态
状态是可变的
语法:this.setState({ 要修改的数据 })
注意:不要直接修改 state 中的值,这是错误的 !!!
setState() 作用:
1、修改 state
2、更新UI
思想:数据驱动视图
1 | class Hello extends React.Component { |
从 JSX 中抽离事件处理程序
- JSX 中掺杂过多 JS 逻辑代码,会显得非常混乱
- 推荐:将逻辑抽离到单独的方法中,保证 JSX 结构清晰
1 | class Hello extends React.Component { |
setState更新状态的2种写法
1 | (1). setState(stateChange, [callback])------对象式的setState |
props 的基本使用
理解
- 每个组件对象都会有props(properties的简写)属性
- 组件标签的所有属性都保存在props中
作用
- 通过标签属性从组件外向组件内传递变化的数据
- 注意: 组件内部不要修改props数据
编码操作
- 内部读取某个属性值
1 | this.props.name |
- 对props中的属性值进行类型限制和必要性限制
第一种方式(React v15.5 开始已弃用):
1
2
3
4
Person.propTypes = {
name:React.PropTypes.string.isRequired,
age:React.PropTypes.number
}
第二种方式(新):使用prop-types库进限制(需要引入prop-types库)
1 | Person.propTypes = { |
- 扩展属性: 将对象的所有属性通过props传递
1 | <Person {...person} /> |
- 默认属性值:
1 | Person.defaultProps = { |
- 组件类的构造函数
1 | coustructor(props){ |
refs 的基本使用
1.字符串形式的ref
1 | <input ref="input1" /> |
2.回调形式的ref
1 | <input ref={(c)=>{this.input1=c}} /> |
3.createRef创建ref容器
1 | myRef = React.createRef(); |
表单处理
受控组件
- HTML 中的表单元素是可输入的,也就是有自己的可变状态
- 而,React 中可变状态通常保存在state中,并且只能通过 setState() 方法来修改
- React 将 state 与表单元素值 value 绑定到一起,由 state 的值来控制表单元素的值
- 受控组件:其值受到 React 控制的表单元素
1 | <input type="text" value={ this.state.txt } /> |
步骤
- 在 state 中添加一个状态,作为表单元素的value值 ( 控制表单元素值的来源 )
- 给表单绑定 change 事件,将表单元素的值设置为 state 的值 ( 控制表单元素值的变化 )
1 | state = { txt: '' } |
1 | <input type="text" value={ this.state.txt } onChange={ e => this.setState( { txt: e.target.value } ) } /> |
多表单元素优化:
- 问题:每个表单元素都有一个单独的事件处理程序处理太繁琐
- 优化:使用一个事件处理程序同时处理多个表单元素
多表单元素优化步骤:
- 给表单元素添加name属性,名称与state相同
- 根据表单元素获取对应值
1 | <input type="text" name="txt" value={ this.state.txt } onChange={ this.handleForm } /> |
1 | handleForm = e => { |
非受控组件 (DOM方式)
- 说明:借助于 ref,使用原生DOM方式来获取表单元素值
- ref 的作用:获取 DOM 或组件
使用步骤:
调用 React.createRef() 方法创建一个 ref 对象
1
2
3
4constructor() {
super()
this.txtRef = React.createRef()
}将创建好的 ref 对象添加到文本框中
1
<input type="text" ref={ this.txtRef } />
通过 ref 对象获取到文本框的值
1
console.log(this.txtRef.current.value)
组件生命周期
生命周期的三个阶段(旧)

1. 初始化阶段: 由ReactDOM.render()触发—初次渲染
- constructor()
- componentWillMount()
- render()
- componentDidMount()
2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发
- shouldComponentUpdate()
- componentWillUpdate()
- render()
- componentDidUpdate()
3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
- componentWillUnmount()
生命周期的三个阶段(新)

1. 初始化阶段: 由ReactDOM.render()触发—初次渲染
constructor()
getDerivedStateFromProps
render()
componentDidMount()
2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发
getDerivedStateFromProps
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate
componentDidUpdate()
3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
- componentWillUnmount()
重要的勾子
- render:初始化渲染或更新渲染调用
- componentDidMount:开启监听, 发送ajax请求
- componentWillUnmount:做一些收尾工作, 如: 清理定时器
即将废弃的勾子
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
最新版本加上UNSAFE_前缀才能使用,和新钩子不能一起使用,以后可能会被彻底废弃,不建议使用。
组件通讯(props)
- 组件是封闭的,要接收外部数据应该通过 props 来实现
- props 的作用:接收传递给组件的数据
- 传递数据:给组件标签添加属性
- 接收数据:函数组件通过参数 props 接收数据,类组件通过 this.props 接收数据
1 | // 传递数据 |
1 | // 函数组件 |
1 | // 类组件 |
组件的 props
特点:
可以给组件传递任意类型的数据
props 是只读的对象,只能读取属性的值,无法修改对象
注意:使用类组件时,如果写了构造函数,应该将 props 传递给 super(),否则,无法在构造函数中获取到 props!
1
2
3
4
5
6
7
8
9
10
11class Hello extends React.Component {
// 推荐使用props作为constructor的参数:
constructor(porps) {
// 推荐将props传递给父类构造函数
super(props)
}
render() {
return <div>接收到的数据:{ this.props.age }</div>
}
}
组件之间的通讯分为 3 种:
- 父组件 -> 子组件
- 子组件 -> 父组件
- 兄弟组件
父组件传递数据给子组件
- 父组件提供要传递的state数据
- 给子组件添加属性,值为 state 中的数据
- 子组件中通过 props 接收父组件中传递的数据
1 | class Parent extends React.Component { |
1 | function Child(props) { |
子组件传递数据给父组件
思路:利用回调函数,父组件提供回调,子组件调用,将要传递的数据作为回调函数的参数。
- 父组件提供一个回调函数(用于接收数据)
- 将该函数作为属性的值,传递给子组件
1 | class Parent extends React.Component { |
1 | class Child extends React.Component { |
注意:回调函数中 this 的指向问题!
兄弟组件
- 将共享状态提升到最近的公共父组件中,由公共父组件管理这个状态
- 思想:状态提升
- 公共父组件职责:
- 提供共享状态
- 提供操作共享状态的方法
- 要通讯的子组件只需通过 props 接收状态或操作状态的方法
组件通讯(消息订阅-发布机制)
任意组件之间通讯(一般用于兄弟之间)
- 工具库: PubSubJS
- 下载: npm install pubsub-js –save
- 使用:
1 | 1. import PubSub from 'pubsub-js' //引入 |
组件通讯(Context)
理解
一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信
使用
1 | 1) 创建Context容器对象: |
1 | import React, { Component } from 'react' |
注意
在应用开发中一般不用context, 一般都用它的封装react插件
组件通讯(redux)
组件通信方式总结
方式:
props:
(1).children props
(2).render props
消息订阅-发布:
pubs-sub、event等等
集中式管理:
redux、dva等等
conText:
生产者-消费者模式
组件间的关系
父子组件:props
兄弟组件(非嵌套组件):消息订阅-发布、集中式管理
祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(用的少)
render props(标签体)
如何向组件内部动态传入带内容的结构(标签)?
Vue中:
使用slot技术, 也就是通过组件标签体传入结构 <AA><BB/></AA>
React中:
使用children props: 通过组件标签体传入结构
使用render props: 通过组件标签属性传入结构, 一般用render函数属性
children props
<A>
<B>xxxx</B>
</A>
{this.props.children}
问题: 如果B组件需要A组件内的数据, ==> 做不到
render props
<A render={(data) => <C data={data}></C>}></A>
A组件: {this.props.render(内部state数据)}
C组件: 读取A组件传入的数据显示 {this.props.data}
1 | import React, { Component } from 'react' |
错误边界(只适用类组件)
理解:
错误边界:用来捕获后代组件错误,渲染出备用页面
特点:
只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
使用方式:
getDerivedStateFromError 配合 componentDidCatch
1 | // 生命周期函数,一旦后台组件报错,就会触发 |
1 | import React, { Component } from 'react' |
组件优化(只适用类组件)
Component的2个问题
只要执行setState(),即使不改变状态数据, 组件也会重新render()
只当前组件重新render(), 就会自动重新render子组件 ==> 效率低
效率高的做法
只有当组件的state或props数据发生改变时才重新render()
原因
Component中的shouldComponentUpdate()总是返回true
解决
办法1:
重写shouldComponentUpdate()方法
比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false
办法2:
使用PureComponent
PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true
注意:
只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false
不要直接修改state数据, 而是要产生新数据
项目中一般使用PureComponent来优化
1 | import React, { PureComponent } from 'react' |
React脚手架
- 脚手架是开发现代Web应用的必备。
- 充分利用 Webpack、Babel、ESLint 等工具辅助项目开发。
- 零配置,无需手动配置繁琐的工具即可使用。
- 关注业务,而不是工具配置。
使用create-react-app创建react应用
1 | npx create-react-app 项目名称 |
npx 无需安装脚手架包,就可以直接使用这个
启动项目
1 | npm start |
入口文件
以前版本
导入 react 和 react-dom 两个包
调用 React.createElement() 方法创建react元素。
调用 ReactDOM.render() 方法渲染 react 元素到页面中。
1 | // 1 导入react |
现在版本
1 | import React from "react"; |
Hooks
1. React Hook/Hooks是什么?
1 | (1). Hook是React 16.8.0版本增加的新特性/新语法 |
2. 三个常用的Hook
1 | (1). State Hook: React.useState() |
3. State Hook
1 | (1). State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作 |
4. Effect Hook
1 | (1). Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子) |
5. Ref Hook
1 | (1). Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据 |
1 | import React from 'react' |
Fragment
使用
Fragment可以传key
1 | <Fragment key={1}><Fragment> |
作用
可以不用必须有一个真实的DOM根标签了
<></> 效果一样但是不能传key