目录
第一章:React入门
1.1 React简介
1.1.1 官网
1.1.2 介绍描述
1. 用于动态构建用户界面的 JavaScript 库(只关注于视图)
2. 由Facebook开源
1.1.3 React的特点
- 声明式编码
- 组件化编码
- React Native 编写原生应用
- 高效(优秀的Diffing算法)
1.1.4 React高效的原因
- 使用虚拟(virtual)DOM, 不总是直接操作页面真实DOM。
- DOM Diffing算法, 最小化页面重绘。
1.2 React的基本使用
1.2.1 相关js库
- react.js:React核心库。
- react-dom.js:提供操作DOM的react扩展库。
- babel.min.js:解析JSX语法代码转为JS代码的库。
1.2.2 创建虚拟DOM的两种方式
1. 纯JS方式(一般不用)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Document</title>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript">
// 1.创建虚拟DOM
// const VDOM = React.createElement(标签名,标签属性,标签内容)
const VDOM = React.createElement('h1', { id: 'title', class: 'abc' }, React.createElement('span', {}, 'Hello, React'))
// 2.渲染虚拟DOM到页面
ReactDOM.render(VDOM, document.getElementById('test'))
</script>
</body>
</html>
2. JSX方式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Document</title>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<!-- 此处写babel表示里面写的是jsx,通过bable翻译成js -->
<script type="text/babel">
// 1.创建虚拟DOM
// 此处不要加引号,因为不是字符串
const VDOM = (
<h1>
<span>Hello,React</span>
</h1>
)
// 2.渲染虚拟DOM到页面
// ReactDom.render(虚拟DOM, 容器)
ReactDOM.render(VDOM, document.getElementById('test'))
</script>
</body>
</html>
1.2.3 虚拟DOM与真实DOM
1. React提供了一些API来创建一种 “特别” 的一般js对象
const VDOM = React.createElement('xx',{id:'xx'},'xx')
上面创建的就是一个简单的虚拟DOM对象
2. 虚拟DOM对象最终都会被React转换为真实的DOM
3. 我们编码时基本只需要操作react的虚拟DOM相关数据, react会转换为真实DOM变化而更新界。
4. 关于虚拟DOM:
- 本质是Object类型的对象(一般对象)
- 虚拟DOM比较‘轻’,真实DOM比较‘重’,因为虚拟DOM是React内部在用,无需真实DOM上那么多的属性
- 虚拟DOM最终会被React转化为真实DOM,呈现在页面上
1.3 React JSX
1.3.1 JSX
1. 全称: JavaScript XML
2. react定义的一种类似于XML的JS扩展语法: JS + XML本质是
React.createElement(component, props, ...children)方法的语法糖
3. 作用: 用来简化创建虚拟DOM
(1). 写法:var VDOM = <h1>Hello JSX!</h1>
(2). 注意1:它不是字符串, 也不是HTML/XML标签
(3). 注意2:它最终产生的就是一个JS对象
4. 标签名任意: HTML标签或其它标签
5. 标签属性任意: HTML标签属性或其它
6. 基本语法规则
jsx语法规则:
1.定义虚拟DOM时,不要写引号
2. 标签中混入JS表达式时要用{}
3. 样式的类名制定不要用class,要用className
4. 内联样式,要用style={{key:value}}的形式去写
5. 只有一个根标签
6. 标签必须闭合
7. 标签首字母
(1).若小写字母开头,则将该标签转为html中同名元素,若html中无改标签对应的同名元素,则报错
(2).若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错
7. 一定要注意区分:【js语句(代码)】与【js表达式】
1). 表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方, 下面这些都是表达式:
(1). a
(2). a+b
(3). demo(1)
(4). arr.map()
(5). function test() {}
2). 语句(代码),下面这些都是语句(代码):
(1). if() {}
(2). for() {}
(3). switch() {case:xxxx}
8. babel.js的作用
- 浏览器不能直接解析JSX代码, 需要babel转译为纯JS的代码才能运行
- 只要用了JSX,都要加上type="text/babel", 声明需要babel来处理
1.3.2 渲染虚拟DOM(元素)
1. 语法: ReactDOM.render(virtualDOM, containerDOM)
2. 作用: 将虚拟DOM元素渲染到页面中的真实容器DOM中显示
3. 参数说明
参数一: 纯js或jsx创建的虚拟dom对象
参数二: 用来包含虚拟DOM元素的真实dom元素对象(一般是一个div)
1.4 模块与组件、模块化与组件化的理解
1.4.1 模块
1. 理解:向外提供特定功能的js程序, 一般就是一个js文件
2. 为什么要拆成模块:随着业务逻辑增加,代码越来越多且复杂。
3. 作用:复用js, 简化js的编写, 提高js运行效率
1.4.2 组件
1. 理解:用来实现局部功能效果的代码和资源的集合(html/css/js/image等等)
2. 为什么要用组件: 一个界面的功能更复杂
3. 作用:复用编码, 简化项目编码, 提高运行效率
1.4.3 模块化
当应用的js都以模块来编写的, 这个应用就是一个模块化的应用
1.4.4 组件化
当应用是以多组件的方式实现, 这个应用就是一个组件化的应用
第二章:React面向组件编程
2.1 基本理解和使用
2.1.1 函数式组件:
执行了ReactDOM.reander(<MyComponent />......)之后发生了什么?
1.React解析组件标签,找到了MyComponent组件
2.发现组件是使用函数定义的,随后调用该函数,将render返回的虚拟DOM转为真实DOM,随后呈现在页面中
......
<script type="text/babel">
// 1.创建函数式组件
function MyComponent() {
return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
}
// 2.将组件渲染到页面
ReactDOM.render(<MyComponent/>, document.getElementById('test'))
/*
执行了ReactDOM.reander(<MyComponent />......)之后发生了什么?
1.React解析组件标签,找到了MyComponent组件
2.发现组件是使用函数定义的,随后调用该函数,将render返回的虚拟DOM转为真实DOM,随后呈现在页面中
*/
</script>
......
2.1.2 类式组件:
<script type="text/babel">
// 1.创建类式组件
class MyComponent extends React.Component {
render() {
// render是放在哪里的? —— MyComponent的原型对象上,供实例使用
// render中的this是谁?—— MyComponent的实例对象 <=> MyComponent组件实例对象
console.log('render中的this:', this);
return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>
}
}
// 2.渲染组件到页面
ReactDOM.render(<MyComponent/>, document.getElementById('test'))
/*
执行了ReactDOM.reander(<MyComponent />......)之后发生了什么?
1.React解析组件标签,找到了MyComponent组件
2.发现组件是使用类定义的,然后new出来该类的实例,并通过该实例调用到原型上的render方法
3.将render返回的虚拟DOM转为真实DOM,随后呈现在页面中
*/
</script>
2.1.3 注意
- 组件名必须首字母大写
- 虚拟DOM元素只能有一个根元素
- 虚拟DOM元素必须有结束标签
2.2 组件三大核心属性1: state
2.2.1 理解
1. state是组件对象最重要的属性, 值是对象(可以包含多个key-value的组合)
2. 组件被称为"状态机", 通过更新组件的state来更新对应的页面显示(重新渲染组件)
2.2.2 强烈注意
1. 组件中render方法中的this为组件实例对象
2. 组件自定义的方法中this为undefined,如何解决?
- 强制绑定this: 通过函数对象的bind()
- 箭头函数
3. 状态数据,不能直接修改或更新
2.2.3 案例
1. 在构造器中定义state,是个对象,里面定义属性
2. state上的属性不能直接修改,状态必须通过setState进行更新,且更新是合并,不是替换
3. 类中的方法默认开启了局部的严格模式,所以方法中的this为undefined
4. 通过this.changeWeather = this.changeWeather.bind(this)方法,将原型上的方法改变this指向后赋值给组件实例对象,保证this的指向是正确的
5. 构造器调用几次 —— 1次,render调用几次? —— 1+n次 1是初始化的那次 n是状态更新的次数,方法调用几次? —— 点几次调用几次
......
<script type="text/babel">
// 1.创建组件
class Weather extends React.Component {
// 构造器调用几次 —— 1次
constructor(props){
console.log('constructor');
super(props)
// 初始化状态
this.state = {isHot: true, wind: '微风'}
// 解决changeWether中this指向问题
this.changeWeather = this.changeWeather.bind(this)
}
// render调用几次? —— 1+n次 1是初始化的那次 n是状态更新的次数
render(){
console.log('render');
const {isHot, wind} = this.state
return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>
}
// changeWeather调用几次? —— 点几次调用几次
changeWeather(){
// changeWeather放在哪里? —— Weather的原型对象上,供实例使用
// 由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用
// 类中的方法默认开启了局部的严格模式,所以changeWeather中的this为undefined
console.log('changeWeather');
// 获取原来的isHot值
let isHot = this.state.isHot
// 状态必须通过setState进行更新,且更新是合并,不是替换
this.setState({isHot: !isHot})
// 严重注意,state上的属性不能直接修改,下面这行代码就是直接修改
// this.state.isHot = !isHot
}
}
// 2.渲染组件到页面
ReactDOM.render(<Weather/>, document.getElementById('test'))
</script>
.......
2.2.4 简写方式
1. 直接将状态state放在外面
2. 自定义方法用赋值语句+箭头函数来定义,此时的this指向就是组件实例对象
......
<script type="text/babel">
// 1.创建组件
class Weather extends React.Component {
// 初始化状态
state = {isHot: true, wind: '微风'}
render(){
const {isHot, wind} = this.state
return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>
}
// 自定义方法——要用赋值语句的形式+箭头函数
changeWeather = () => {
const isHot = this.state.isHot
this.setState({isHot: !isHot})
}
}
// 2.渲染组件到页面
ReactDOM.render(<Weather/>, document.getElementById('test'))
</script>
......
2.3 组件三大核心属性2: props
2.3.1 理解
1. 每个组件对象都会有props(properties的简写)属性
2. 组件标签的所有属性都保存在props中
2.3.2 作用
1. 通过标签属性从组件外向组件内传递变化的数据
2. 注意: 组件内部不要修改props数据
2.3.3 编码操作
1. 内部读取某个属性值
this.props.name
2. 对props中的属性值进行类型限制和必要性限制
第一种方式(React v15.5 开始已弃用):
Person.propTypes = {
name: React.PropTypes.string.isRequired,
age: React.PropTypes.number
}
第二种方式(新):使用prop-types库进限制(需要引入prop-types库)
Person.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number.
}
3. 扩展属性: 将对象的所有属性通过props传递,只有在组件标签中才能使用
<Person {...person}/>
4. 默认属性值:
Person.defaultProps = {
age: 18,
sex:'男'
}
5. 组件类的构造函数
constructor(props){
super(props)
console.log(props)//打印所有属性
}
2.3.4 案例
<body>
<!-- 准备好一个容器 -->
<div id="test1"></div>
<div id="test2"></div>
<div id="test3"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<!-- 引入prop-types,用于对组件标签属性进行限制 -->
<script src="../js/prop-types.js"></script>
<script type="text/babel">
// 1.创建组件
class Person extends React.Component{
render(){
// console.log(this);
const {name, sex, age} = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age + 1}</li>
</ul>
)
}
}
// 对标签属性进行类型、必要性的限制
Person.propTypes = {
name: PropTypes.string.isRequired, // 限制name必传,且为字符串
sex: PropTypes.string, // 限制sex为字符串
age: PropTypes.number, // 限制age为数值
speak: PropTypes.func // 限制speak为函数
}
// 制定默认标签属性值
Person.defaultProps = {
sex: '男', // sex默认值为男
age: 18 // age默认值为18
}
// 2. 渲染组件到页面
ReactDOM.render(<Person name="tom" speak={speak} />, document.getElementById('test1'))
ReactDOM.render(<Person name="jerry" sex="女" age={19} />, document.getElementById('test2'))
const p = {name: 'jack', age: 18, sex: '男'}
ReactDOM.render(<Person {...p} />, document.getElementById('test3'))
function speak(){
console.log('讲话');
}
</script>
</body>
2.3.5 简写
将对标签属性进行类型、必要性的限制以及制定默认标签属性值放在类中,使用static修饰
<body>
<!-- 准备好一个容器 -->
<div id="test1"></div>
<div id="test2"></div>
<div id="test3"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<!-- 引入prop-types,用于对组件标签属性进行限制 -->
<script src="../js/prop-types.js"></script>
<script type="text/babel">
// 1.创建组件
class Person extends React.Component{
// 对标签属性进行类型、必要性的限制
static propTypes = {
name: PropTypes.string.isRequired, // 限制name必传,且为字符串
sex: PropTypes.string, // 限制sex为字符串
age: PropTypes.number, // 限制age为数值
speak: PropTypes.func // 限制speak为函数
}
// 制定默认标签属性值
static defaultProps = {
sex: '男', // sex默认值为男
age: 18 // age默认值为18
}
render(){
// console.log(this);
const {name, sex, age} = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age + 1}</li>
</ul>
)
}
}
// 2. 渲染组件到页面
ReactDOM.render(<Person name="tom" speak={speak} />, document.getElementById('test1'))
ReactDOM.render(<Person name="jerry" sex="女" age={19} />, document.getElementById('test2'))
const p = {name: 'jack', age: 18, sex: '男'}
ReactDOM.render(<Person {...p} />, document.getElementById('test3'))
function speak(){
console.log('讲话');
}
</script>
2.4 组件三大核心属性3: refs与事件处理
2.4.1 理解
组件内的标签可以定义ref属性来标识自己
2.4.2 编码
1. 字符串形式的ref
<input ref="input1"/>
2. 回调函数形式的ref
<input ref={(c)=>{this.input1 = c}}
3. createRef创建ref容器
myRef = React.createRef()
<input ref={this.myRef}/>
2.4.3 事件处理
1. 通过onXxx属性指定事件处理函数(注意大小写)
- React使用的是自定义(合成)事件, 而不是使用的原生DOM事件 —— 为了更好地兼容性
- React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) —— 为了更高效
2. 通过event.target得到发生事件的DOM元素对象 ——不要过度使用ref
2.4.4 案例
1. 字符串形式的ref
<script type="text/babel">
// 创建组件
class MyComponent extends React.Component {
showData = () =>{
const {input1} = this.refs
alert(input1.value)
}
showData2 = () =>{
const {input2} = this.refs
alert(input2.value)
}
render(){
return (
<div>
<input ref="input1" type="text" placeholder="点击按钮提示数据" />
<button ref="btn" onClick={this.showData}>点我提示左侧数据</button>
<input onBlur={this.showData2} ref="input2" type="text" placeholder="失去焦点提示数据" />
</div>
)
}
}
// 渲染组件到页面
ReactDOM.render(<MyComponent/>, document.getElementById('test'))
</script>
2. 回调函数形式的ref
<script type="text/babel">
// 创建组件
class MyComponent extends React.Component {
showData = () =>{
const {input1} = this
alert(input1.value)
}
showData2 = () =>{
const {input2} = this
alert(input2.value)
}
render(){
return (
<div>
<input ref={c => this.input1 = c} type="text" placeholder="点击按钮提示数据" />
<button onClick={this.showData}>点我提示左侧数据</button>
<input ref={c => this.input2 = c} onBlur={this.showData2} type="text" placeholder="失去焦点提示数据" />
</div>
)
}
}
// 渲染组件到页面
ReactDOM.render(<MyComponent/>, document.getElementById('test'))
</script>
3. createRef创建ref容器,失去焦点使用的是event.target获取DOM对象
<script type="text/babel">
// 创建组件
class MyComponent extends React.Component {
// React.createRef 调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是“专人专用”的
myRef = React.createRef()
showData = () =>{
alert(this.myRef.current.value)
}
showData2 = () =>{
alert(event.target.value);
}
render(){
return (
<div>
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据" />
<button onClick={this.showData}>点我提示左侧数据</button>
<input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据" />
</div>
)
}
}
// 渲染组件到页面
ReactDOM.render(<MyComponent/>, document.getElementById('test'))
</script>
使用回调形式和createRef形式的ref都可以实现上面的效果
2.5 收集表单数据
2.5.1 理解
包含表单的组件分类
- 受控组件
- 非受控组件
2.5.2 非受控组件
现取现用就是“非受控”,下面这段代码当提交的时候通过ref获取input中的值,刚拿到就直接使用,所以是“非受控”
<script type="text/babel">
// 创建组件
class Login extends React.Component{
handleSubmit = (event) => {
event.preventDefault()
const {username, password} = this
alert(`你输入的用户名是:${username.value},你输入的密码是${password.value}`)
}
render(){
return (
<form onSubmit={this.handleSubmit}>
用户名:<input ref={c => this.username = c} type="text" name="username" placeholder="请输入用户名" />
密码:<input ref={c => this.password = c} type="text" name="password" placeholder="请输入密码" />
<button>登录</button>
</form>
)
}
}
// 渲染组件到页面
ReactDOM.render(<Login/>, document.getElementById('test'))
</script>
2.5.3 受控组件
随着输入维护状态就是“受控”,下面这段代码,两个input中的值都绑定了onChange事件,随着value的改变,state中的状态发生改变,维护状态,所以是“受控”
<script type="text/babel">
// 创建组件
class Login extends React.Component{
// 状态
state = {
username: '', // 用户名
password: '' // 密码
}
// 保存用户名到状态中
saveUsername = (event) => {
this.state.username = event.target.value
}
// 保存密码到状态中
savePassword = (event) => {
this.state.password = event.target.value
}
// 表单提交的回调
handleSubmit = (event) => {
event.preventDefault()
const {username, password} = this.state
alert(`你输入的用户名是:${username},你输入的密码是${password}`)
}
render(){
return (
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveUsername} type="text" name="username" placeholder="请输入用户名" />
密码:<input onChange={this.savePassword} type="text" name="password" placeholder="请输入密码" />
<button>登录</button>
</form>
)
}
}
// 渲染组件到页面
ReactDOM.render(<Login/>, document.getElementById('test'))
</script>
2.5.4 扩展1 —— 使用高阶函数和函数柯里化
1. 高阶函数:如果一个函数符合下面两个规范中的任何一个,那该函数就是高阶函数
- 若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数
- 若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数
常见的高阶函数有:Promise、setTimeout、arr.map()等等
2. 函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式
上面那段代码如果在真正的表单提交的是可能会有很多要收集的数据,此时会产生大量重复的代码,可以进行函数的柯里化
将所有input的onChange事件都改为saveFormData函数,这个函数返回值也是一个函数,所以是一个高阶函数
通过将修改的状态的参数传过去,修改对应的状态,最终效果也是和上面是一样的
<script type="text/babel">
// 创建组件
class Login extends React.Component{
// 状态
state = {
username: '', // 用户名
password: '' // 密码
}
// 保存表单数据到状态中
saveFormData = (dataType) => {
return (event) => {
this.setState({[dataType]: event.target.value})
}
}
// 表单提交的回调
handleSubmit = (event) => {
event.preventDefault()
const {username, password} = this.state
alert(`你输入的用户名是:${username},你输入的密码是${password}`)
}
render(){
return (
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveFormData('username')} type="text" name="username" placeholder="请输入用户名" />
密码:<input onChange={this.saveFormData('password')} type="text" name="password" placeholder="请输入密码" />
<button>登录</button>
</form>
)
}
}
// 渲染组件到页面
ReactDOM.render(<Login/>, document.getElementById('test'))
</script>
2.5.5 扩展2 —— 不适用函数柯里化
上面这个案例也可以不使用函数柯里化来实现,将onChange中的函数体定义为一个箭头函数,保证它不会立刻调用,并且在这个函数中调用saveFormData函数,修改状态
<script type="text/babel">
// 创建组件
class Login extends React.Component{
// 状态
state = {
username: '', // 用户名
password: '' // 密码
}
// 保存表单数据到状态中
saveFormData = (dataType, event) => {
this.setState({ [dataType]: event.target.value })
}
// 表单提交的回调
handleSubmit = (event) => {
event.preventDefault()
const {username, password} = this.state
alert(`你输入的用户名是:${username},你输入的密码是${password}`)
}
render(){
return (
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={(event) => { this.saveFormData('username', event) }} type="text" name="username" placeholder="请输入用户名" />
密码:<input onChange={(event) => { this.saveFormData('password', event) }} type="text" name="password" placeholder="请输入密码" />
<button>登录</button>
</form>
)
}
}
// 渲染组件到页面
ReactDOM.render(<Login/>, document.getElementById('test'))
</script>
2.6 组件的生命周期
2.6.1 理解
- 组件从创建到死亡它会经历一些特定的阶段。
- React组件中包含一系列勾子函数(生命周期回调函数), 会在特定的时刻调用。
- 我们在定义组件时,会在特定的生命周期回调函数中,做特定的工作。
2.6.2 案例
这个案例中<h2>标签中的内容的透明度逐渐减小到0,再一下子返回1,当点击按钮后,销毁组件
在state中定义opacity给h2设置透明度,在组件挂载完毕时创建一个定时器,每隔200毫秒透明度减小0.1,在组件即将被销毁时清除定时器
<script type="text/babel">
// 创建组件
// 生命周期回调函数 <=> 生命周期钩子函数 <=> 生命周期函数 <=> 生命周期钩子
class Life extends React.Component {
state = { opacity: 1}
Death = () => {
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
// 组件挂载完毕
componentDidMount(){
this.timer = setInterval(() => {
// 获取原状态
let {opacity} = this.state
// 减小0.1
opacity -= 0.1
if(opacity <= 0){
opacity = 1
}
// 设置新的透明度
this.setState({ opacity })
}, 200)
}
// 组件将要卸载
componentWillUnmount(){
clearInterval(this.timer)
}
// 初始化渲染、状态更新
render(){
console.log('render');
return (
<div>
<h2 style={{opacity: this.state.opacity}}>React学不会怎么办?</h2>
<button onClick={this.Death}>不活了</button>
</div>
)
}
}
// 渲染组件
ReactDOM.render(<Life/>, document.getElementById('test'))
</script>
2.6.3 生命周期流程图(旧)
在这个案例中,Count组件随着生命周期的进行,打印相应的内容
当点击卸载组件按钮时,触发componentWillUnmount钩子,表示组件即将被卸载
点击强制更新状态按钮时,触发force函数,此时不需要经过shouldComponentUpdate钩子
在A组件中使用B组件,父传子props属性时B组件会触发componentWillReceiveProps钩子,但是当页面初次渲染完毕时不会调用,当A组件传给B组件的props更新的时候才调用这个钩子
<body>
<!-- 准备好一个容器 -->
<div id="test"></div>
<div id="test1"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
// 创建组件
class Count extends React.Component{
// 初始化状态
state = {count: 0}
// 构造器
constructor(props){
super(props)
console.log('Count---constructor');
}
// +1按钮的回调
add = () => {
const {count} = this.state
this.setState({count: count + 1})
}
// 卸载组件的回调
death = () => {
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
// 强制更新组件的回调
force = () => {
this.forceUpdate()
}
// 组件即将挂载
componentWillMount(){
console.log('Count---componentWillMount');
}
// 组件挂载完毕
componentDidMount(){
console.log('Count---componentDidMount');
}
// 组件即将被卸载
componentWillUnmount(){
console.log('Count---componentWillUnmount');
}
// 控制组件更新的“阀门”
shouldComponentUpdate(){
console.log('Count---shouldComponentUpdate');
return true
}
// 组件将要更新的钩子
componentWillUpdate(){
console.log('Count---componentWillUpdate');
}
// 组件更新完毕的钩子
componentDidUpdate(){
console.log('Count---componentDidUpdate');
}
render(){
console.log('Count---render');
const {count} = this.state
return (
<div>
<h2>当前求和为:{count}</h2>
<button onClick={this.add}>+1</button>
<button onClick={this.death}>卸载组件</button>
<button onClick={this.force}>不更改任何状态中的数据,强制更新一下</button>
</div>
)
}
}
class A extends React.Component{
state = {carName: '奔驰'}
changeCar = () => {
this.setState({carName: '宝马'})
}
render(){
return (
<div>
<h2>我是A组件</h2>
<button onClick={this.changeCar}>换车</button>
<B carName={this.state.carName}></B>
</div>
)
}
}
class B extends React.Component{
// 组件将要接收新的props的钩子
componentWillReceiveProps(props){
console.log('B---ComponentWillReceiveProps', props);
}
// 控制组件更新的“阀门”
shouldComponentUpdate(){
console.log('B---shouldComponentUpdate');
return true
}
// 组件将要更新的钩子
componentWillUpdate(){
console.log('B---componentWillUpdate');
}
// 组件更新完毕的钩子
componentDidUpdate(){
console.log('B---componentDidUpdate');
}
render(){
return (
<div>
<h2>我是B组件,接收到的车为{this.props.carName}</h2>
</div>
)
}
}
// 渲染组件
ReactDOM.render(<Count/>, document.getElementById('test'))
ReactDOM.render(<A/>, document.getElementById('test1'))
</script>
</body>
1. 初始化阶段: 由ReactDOM.render()触发---初次渲染
- constructor()
- componentWillMount()
- render()
- componentDidMount() ====> 常用,一般在这个钩子里做一些初始化的事,例如:开启定时器、发送网络请求、发布订阅
2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发
- shouldComponentUpdate()
- componentWillUpdate()
- render() ====> 必须使用的一个
- componentDidUpdate()
3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
- componentWillUnmount() ====> 一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
2.6.4 生命周期流程图(新)
1. 初始化阶段: 由ReactDOM.render()触发---初次渲染
- constructor()
- getDerivedStateFromProps
- render()
- componentDidMount()
2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发
- getDerivedStateFromProps
- shouldComponentUpdate()
- render()
- getSnapshotBeforeUpdate
- componentDidUpdate()
3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
- componentWillUnmount()
2.6.5 重要的钩子
- render:初始化渲染或更新渲染调用
- componentDidMount:开启监听, 发送ajax请求
- componentWillUnmount:做一些收尾工作, 如: 清理定时器
2.6.6 即将废弃的钩子
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
现在使用会出现警告,下一个大版本需要加上UNSAFE_前缀才能使用,以后可能会被彻底废弃,不建议使用。
2.6.7 新增加的两个钩子
- getDerivedStateFromProps
- getSnapshotBeforeUpdate
这两个钩子很少使用
2.7 虚拟DOM与DOM Diffing算法
2.7.1 基本原理图
2.7.2 经典面试题
1. react/vue中的key有什么作用?(key的内部原理是什么?)
2. 为什么遍历列表时,key最好不要用index?
1. 虚拟DOM中key的作用:
1)简单的说:key是虚拟DOM对象的标识,在更新显示时key起着极其重要的作用
2)详细的说:当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】
随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
a:旧虚拟DOM中找到了与新虚拟DOM相同的key:
(1)若虚拟DOM中内容没变,直接使用之前的真实DOM
(2)若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
b:旧虚拟DOM中未找到与新虚拟DOM相同的key
根据数据创建新的真实DOM,随后渲染到页面
2.用index作为key可能会引发的问题:
a:若对数据进行:逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实DOM更新 ==> 界面效果没有问题,但效率低
b:如果结构中还包含输入类的DOM:会产生错误DOM更新 ==> 界面有问题
c:注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的
3. 开发中如果选择key?
a:最好使用每条数据的唯一表示作为key,比如id、手机号、身份证号、学号等唯一值
b:如果确定只是简单的展示数据,用index也是可以的