一、React的特点
- 采用组件化模式、声明式编码,提高了开发效率及组件的重复率
- 在React Native中可以使用React语法进行移动端的开发
- 使用虚拟DOM+Diffing算法,尽量减少与真实DOM的交互
<body>
<!-- 准备好一个容器 -->
<div id="test"></div>
<!-- react核心库 -->
<script src="../js/react.development.js"></script>
<!-- react扩展库 -->
<!-- 注意必须是在核心库之后引用 -->
<script src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script src="../js/babel.min.js"></script>
<!-- 一定要指明type属性,否则默认是javascript -->
<script type="text/babel">
// 注意和JavaScript中的写法不一样,这里没有引号
const VDOM = <h1>HelloReact</h1>
ReactDOM.render(VDOM, document.getElementById('test'))
</script>
</body>
二、虚拟DOM的两种创建方式
- 使用js的方式创建虚拟DOM
<body>
<!-- 准备好一个容器 -->
<div id="test"></div>
<!-- react核心库 -->
<script src="../js/react.development.js"></script>
<!-- react扩展库 -->
<!-- 注意必须是在核心库之后引用 -->
<script src="../js/react-dom.development.js"></script>
<!-- 一定要指明type属性,否则默认是javascript -->
<script>
const VDOM = React.createElement('h1',{id:'title'}, 'helloreact')
// 标签嵌套
// const VDOM = React.createElement('h1',{id: 'title'},
// React.createElement('span',{},'helloworld'))
ReactDOM.render(VDOM, document.getElementById('test'))
</script>
</body>
- 使用jsx的方式创建虚拟DOM
<body>
<!-- 准备好一个容器 -->
<div id="test"></div>
<!-- react核心库 -->
<script src="../js/react.development.js"></script>
<!-- react扩展库 -->
<!-- 注意必须是在核心库之后引用 -->
<script src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script src="../js/babel.min.js"></script>
<!-- 一定要指明type属性,否则默认是javascript -->
<script type="text/babel">
// 注意和JavaScript中的写法不一样,这里没有引号
const VDOM = (
<h1>
<span>HelloReact</span>
</h1>
)
//注意这里使用()可以保留格式
ReactDOM.render(VDOM, document.getElementById('test'))
</script>
</body>
- 当有多层标签嵌套时,此时可以看出使用jsx的写法比使用js的写法明显方便高效,因此建议使用jsx写法,避免使用js写法
三、虚拟DOM和真实DOM
关于虚拟DOM:
- 本质是Object类型的对象
- 虚拟DOM比真实DOM简洁,因为虚拟DOM是React内部在使用,无需真实DOM上那么多属性
- 虚拟DOM最终会被React转化为真实DOM,呈现在页面上
四、jsx语法规则
- 全称:JavaScript XML
- react定义的一种类似于XML的JS扩展语法:JS+XML
- 本质是React.createElement(component,props,…children)方法的语法糖
- 作用:用来简化创建虚拟DOM
- jsx语法规则:
定义虚拟DOM时,不要写引号
标签中混入JS表达式时要用{ }
样式的类名指定要用className,而不是用class
内联样式,要用style={{key: value}}的形式去写
只有一个根标签
标签必须闭合
标签首字母
(1)若小写字母开头,则将标签转为html中同名元素,若html中无对应的标签,则报错
(2)若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错
五、jsx中可以的表达式
-
{ }中会自动遍历数组中的数据
-
注意:遍历只能是数组中的数据,对象不行
-
js语句(代码)和js表达式:
一个表达式会产生一个值,可以放在任何一个需要值的地方
下面这些都是表达式:
(1)a
(2)a+b
(3)fun(1)
(4)array.map()
(5)function test (){ } — 返回值为undefined也是有返回值
下面这些都是语句(代码)
(1)if(){ }
(2)for(){ }
(3)switch(){case: } -
遍历数组数据时,每个数据都应该绑定一个唯一的key值
六、函数式组件
<body>
<div id="test"></div>
<script src="../js/react.development.js"></script>
<script src="../js/react-dom.development.js"></script>
<script src="../js/babel.min.js"></script>
<script type="text/babel">
function Demo() {
console.log(this)
//此处的this是undefined,因为babel编译后开启了严格模式
return <h3>我是用函数定义的组件,适用于简单组件的定义</h3>
}
ReactDOM.render(<Demo/>, document.getElementById('test'))
/*
执行了ReactDOM.render()之后,发生了什么?
1.React解析组件标签,找到了Demo组件
2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中
*/
</script>
</body>
注意:这里的组件名要大写,否则当小写的时候,会自动去html标签中进行查找
七、ES6中的类介绍
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
speak() {
console.log(`我的名字叫${this.name},我的年纪是${this.age}`)
}
}
const p = new Person('Tom', 18)
console.log(p);
class Student extends Person {
constructor(name, age, sno) {
super(name, age)
this.sno = sno
}
speak() {
console.log(`我的名字叫${this.name},我的年纪是${this.age},
我的学号是${this.sno}`)
}
}
const s = new Student('Tom', 18, '001')
//s.speak.call(),call函数可以改变this的指向,
//this指向call函数中的第一个参数
console.log(s);
- 总结:
类中的构造器不是必须写的,要对实例进行一些初始化的操作,如添加指定属性时才写
如果A类继承了B类,且A类中写了构造器,那么A类构造器中的super是必须要调用的
类中所定义的方法,都是放在类的原型对象上,供实例去使用
八、类式组件
- 创建类式组件时,必须继承React.Component类,类中可以没有构造器,但是必须要有render()函数
<body>
<div id="test"></div>
<script src="../js/react.development.js"></script>
<script src="../js/react-dom.development.js"></script>
<script src="../js/babel.min.js"></script>
<script type="text/babel">
//1.创建类式组件
class MyComponent extends React.Component{
//注意:这里的render和下面的只是同名,并不是指同一个
render() {
//render是放在哪里的——MyComponent的原型对象,供实例使用
//render中的this是谁——MyComponent的实例对象<=>MyComponent组件实例对象
console.log('render中的this:',this);
return <h3>我是类定义组件,适用于复杂组件的定义</h3>
}
}
//2.渲染组件到页面
ReactDOM.render(<MyComponent/>, document.getElementById('test'))
/*
执行ReactDOM.render()之后,发生了什么?
1、React解析组件标签,找到MyComponent组件
2、发现组件是使用类定义的,随后new出来该类的实例,
并通过该实例调用到原型上的render方法
3、将render返回的虚拟DOM转化为真实的DOM,呈现在页面中
*/
</script>
</body>
九、组件实例的三大核心属性——state
- 当组件中有state属性时,即为复杂组件
- state是组件对象中最重要的属性,值是对象(可以包含多个key-value的组合)
<body>
<div id="test"></div>
<script src="../js/react.development.js"></script>
<script src="../js/react-dom.development.js"></script>
<script src="../js/babel.min.js"></script>
<script type="text/babel">
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = {isHot: true}
}
render() {
const {isHot} = this.state
return (<h3>今天天气很{isHot ? '炎热' : '凉爽'}</h3>)
}
}
ReactDOM.render(<MyComponent/>, document.getElementById('test'))
</script>
</body>
十、事件绑定
- 原生的js中绑定的事件的方法有(以点击事件为例):
- 获得DOM.addEventListener(‘click’, () => { })
- 获得DOM.onclick = () => { }
- 在标签内部直接监听οnclick=“fun()”
- 在react中推荐使用在最后一种方法,即在标签中直接监听,因为前两种方法需要操作DOM,但是在react中本身没有提供操作DOM的方法,因此应该减少直接操作DOM的方法出现
<script type="text/babel">
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = {isHot: true}
}
render() {
const {isHot} = this.state
//注意:这里onClick使用驼峰写法
//使用{ }
//且定义的方法在使用时不能加()
return (<h3 onClick={click}>今天天气很{isHot ? '炎热' : '凉爽'}</h3>)
}
}
ReactDOM.render(<MyComponent/>, document.getElementById('test'))
function click () {
console.log('点击了标题');
}
</script>
十一、类中方法的this问题
class Weather extends React.Component {
constructor(props) {
super(props)
this.state = {isHot: true}
}
render() {
const {isHot} = this.state
return (<h3 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'}</h3>)
}
changeWeather() {
/*当试图在changeWeather函数中获得state时,
由于在changeWeather中this的指向为undefined,获取失败
那么为什么changeWeather中的this指向改变了呢?
原因:
1、changeWeather是放在Weather的原型对象上,供实例使用
2、changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用
3、类中的方法默认开启了局部的严格模式,所以changeWeather中的this为undefined
*/
console.log(this);
}
}
ReactDOM.render(<Weather/>, document.getElementById('test'))
- 解决方法
class Weather extends React.Component {
constructor(props) {
super(props)
this.state = {isHot: true}
this.changeWeather = this.changeWeather.bind(this)
/*
当执行到这里时,查找到原型链上的changeWeather,
并且将该函数的this绑定改变,返回一个名为长Weather的函数
前一个changeWeather是实例对象上的,后一个changeWeather是原型链上的
*/
}
十二、setState
- 在react中不能直接修改this.state属性值,即:使this.state.data = value,该写法是不合理的,这样的修改不能使页面中的数据随之发生改变
- 修改state中的属性值需要通过setState()方法
<script type="text/babel">
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = {isHot: true}
this.changeWeather = this.changeWeather.bind(this)
}
render() {
const {isHot} = this.state
return (<h3 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'}</h3>)
}
changeWeather() {
const {isHot} = this.state
//注意!!!!必须通过setState()方法进行修改
this.setState({isHot: !isHot})
console.log(isHot);
}
}
ReactDOM.render(<MyComponent/>, document.getElementById('test'))
</script>
*注意
:
- 当state中有多个属性时,更改其中的一个属性值,该操作是一种合并,并不是一种替换
- 在此过程中constructor()函数调用几次:1次
render()函数调几次:1+n次,1是初始化的那次,n是状态更新的次数- changeWeather()调几次:点击几次,调用几次
十三、state的简写
- 在前面的代码中,利用constructor()函数来解决了两个问题,此时若不写构造器,那么需要将在构造器中的两个问题解决
- 两个问题:
state中的属性值问题:
当在类中直接使用赋值语句时,该赋值语句即使不在构造器中,但是会直接出现在实例对象中,利用该性质,可解决第一个问题this的指向问题:
在第一个问题的基础上,利用赋值语句的形式,在声明自定义函数时,利用赋值语句的格式,即fun = function() { }的形式定义函数,但是此时仅仅是将定义的函数挂载到了实例对象上,this的指向问题还没有解决,将定义的形式稍加修改,改为fun = () => { },即利用箭头函数的形式定义函数。箭头函数中this执行离它最近的外层中的this,指向类中的this,即类的实例对象,故达到了修改this指向问题的目的
<script type="text/babel">
class MyComponent extends React.Component {
state = {isHot: true}
render() {
const {isHot} = this.state
return (<h3 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'}</h3>)
}
changeWeather = () => {
console.log(this);
const {isHot} = this.state
this.setState({isHot: !isHot})
}
}
ReactDOM.render(<MyComponent/>, document.getElementById('test'))
</script>
十四、组件三大核心属性——props
- 关于三点运算符的一些使用方法
let arr1 = [1, 2, 3, 4]
//展开数组
console.log(...arr1);
let arr2 = [5, 6, 7, 8]
//连接数组
let arr3 = [...arr1, ...arr2]
console.log(arr3);
//作为函数的可变参数使用
function sum(...num) {
return num.reduce((preValue, currentValue) => {
return preValue + currentValue
})
}
console.log(sum(1,2,3,4,5));
let person = {name: 'Bob', age: 18}
/*
console.log(...person);
报错,该运算符不能直接展开对象,
此写法不正确
*/
let student = {...person}
//实现的是深拷贝
student.name = 'Tom'
console.log(student);
//{name: 'Tom', age: 18}
console.log(person);
//{name: 'Bob', age: 18}
let stu = {...student, name: 'Jerry'}
//可以在复制的时候直接修改
console.log(stu);
{name: 'Jerry', age: 18}
- props用法
<script type="text/babel">
class MyComponent extends React.Component {
render() {
return (<ul>
<li>{this.props.name}</li>
<li>{this.props.age}</li>
</ul>)
}
}
const person = {name: '小明', age: 20}
// ReactDOM.render(<MyComponent name="Tom" age="18"/>, document.getElementById('test'))
ReactDOM.render(<MyComponent name="Bob" age="20"/>, document.getElementById('test'))
//该种写法必须是person属性与标签中的属性对应才可使用
//ReactDOM.render(<MyComponent name={person.name} age={person.age}/>, document.getElementById('test'))
ReactDOM.render(<MyComponent {...person}/>, document.getElementById('test'))
</script>
- 对props进行限制
- 在传递props的过程中,有时需要对数据作出一些限制,比如:数据类型、必传项、默认类型等
*规定的数据类型通过propTypes来进行设置- 默认的数据值通过defaultProps来进行设置
<script src="../js/prop-types.js"></script>
<script type="text/babel">
class MyComponent extends React.Component {
render() {
return (<ul>
<li>{this.props.name}</li>
<li>{this.props.age + 1}</li>
</ul>)
}
}
MyComponent.protoTypes = {
name: PropTypes.string.isRequired,//限制name必传,且为字符串类型
age: PropTypes.number,//限制age为number类型
speak: PropTypes.func//限制speak为函数类型
}
MyComponent.defaultProps = {//设置默认值
name: 'Tom'
}
//注意:这里使用age={20的写法,是保证了传值为number类型}
ReactDOM.render(<MyComponent name="小明" age={20} />, document.getElementById('test'))
</script>
- 注意:直接使用PropTypes.string写法时,需要引用prop-types.js文件,这是React 15.5之后的写法,在15.5前使用的是React.PropTypes.string写法(不用引入文件,15.5后弃用)
- props的简写:在前面的写法中,对props进行限制的代码部分是写在了类的外侧,在类的身上添加了propTypes和defaultProps,那么如何将这部分的代码该写到类的内部中呢?
class MyComponent extends React.Component {
//使用static修饰符,此时是在类上添加属性
static protoTypes = {
name: PropTypes.string.isRequired,//限制name必传,且为字符串类型
age: PropTypes.number,//限制age为number类型
speak: PropTypes.func//限制speak为函数类型
}
static defaultProps = {//设置默认值
name: 'Tom'
}
render() {
return (<ul>
<li>{this.props.name}</li>
<li>{this.props.age + 1}</li>
</ul>)
}
}
props是只读属性
- 构造器与props
- 在类中可以定义构造器,也可以不使用构造器,且在使用构造器时,通过super()函数传递了props参数,但是我们发现如果该参数不传,或者是根本就不写super()函数,此时的程序也不会报错,那么写和不写的区别到底在哪里呢?
- 构造器是否接收props,是否传递给super,取决于:是否希望在构造器中通过this.props的方式访问props(这种用法非常少见)
- 同时注意,通过this的方式去访问props,需要满足两个条件,一是构造器接受,二是通过super传递,若只是接收,super没有传递,则也不能通过this的方式去调用
十五、函数式组件使用props
- 在组件实例的三大属性中,函数式组件只能使用props属性(不借助其他)
- 将传递的数据作为props参数传递给function
<script src="../js/prop-types.js"></script>
<script type="text/babel">
function Person(props) {
return (<ul>
<li>{props.name}</li>
<li>{props.age + 1}</li>
<li>{props.sex}</li>
</ul>)
}
//函数式组件中无法使用static
Person.propTypes = {
name: PropTypes.string,
age: PropTypes.number
}
Person.defaultProps = {
age: 18,
sex: '男'
}
ReactDOM.render(<Person name="小明" />, document.getElementById('test'))
</script>
十六、组件实例的三大核心属性——refs
- 字符串形式的ref
<script type="text/babel">
class MyComponent extends React.Component {
btnClick = () => {
let {input1} = this.refs
alert(input1.value)
}
render() {
return (
<div>
<input type="text" name="" id="" ref="input1"/>
<button onClick={this.btnClick}>点我显示内容</button>
</div>
)
}
}
ReactDOM.render(<MyComponent />, document.getElementById('test'))
//使用字符串形式的ref影响效率
</script>
- 回调函数形式的ref
<script type="text/babel">
class MyComponent extends React.Component {
btnClick = () => {
let {input1} = this
console.log(this);
// alert(input1.value)
}
render() {
return (
<div>
//这里使用回调函数的形式
//回调函数中的形参c表示绑定ref的标签
//回调函数中的this指向类的实例对象,因此是将c重新赋值给类的实例对象中的属性
//这个属性是重新命名的input1
//即将整个标签的表示重新命名,并绑定到类的实例对象上
<input type="text" name="" id=""
ref={(c) => {this.input1 = c;console.log(c);}}/>
<button onClick={this.btnClick}>点我显示内容</button>
</div>
)
}
}
ReactDOM.render(<MyComponent />, document.getElementById('test'))
</script>
3.ref的回调次数
- 如果ref回调函数是以内联函数的方式定义的,在更新过程(即state发生变化的时候)中它会被执行两次,第一次传入参数null,然后第二次会传入参数DOM元素。这是因为在每次渲染时会创建一个新的函数实例,所以React清空旧的ref并且设置新的。通过将ref的回调函数定义成class的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的
inputContent = (c) => {
this.input1 = c
console.log(c);
}
render() {
return (
<div>
<h3>今天天气{this.state.isHot ? '炎热' : '凉爽'}</h3>
<input type="text" name="" id=""
//将ref的回调形式由内联形式改写成类绑定样式,就能解决
ref={this.inputContent}/>
<button onClick={this.btnClick}>点我显示内容</button>
<button onClick={this.changeWeather}>点我修改天气</button>
</div>
)
}
- createRef的使用
class MyComponent extends React.Component {
/*
React.createRef调用后可以返回一个容器,该容器可以存储被ref标识的节点
且该容器是专人专用的
*/
myRef = React.createRef()
btnClick = () => {
//输出结构为{current: input}
console.log(this.myRef);
alert(this.myRef.current.value)
}
render() {
return (
<div>
<input type="text" name="" id=""
ref={this.myRef} />
<button onClick={this.btnClick}>点我显示内容</button>
</div>
)
}
}
ReactDOM.render(<MyComponent />, document.getElementById('test'))
十七、React中的事件处理
- 通过onXxx属性指定事件处理函数(注意大小写)
- 这里的onXxx是自定义(合成)事件,不是原生的DOM事件——为了更好的兼容性
- React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)——为了更高效
如:在原生js中对于出现的ul>li标签来说,若为一个个li标签绑定事件,则过于麻烦,可以利用事件冒泡,直接给最外层的ul标签绑定事件
- 通过event.target得到发生事件的DOM元素对象——当要进行处理和发生的元素对象是同一个的时候,可以利用该方法减少ref的使用
- 官方推荐不要过度的使用ref
十八、非受控组件和受控组件
- 非受控组件:现用现取
<script type="text/babel">
class Login extends React.Component {
handle = (event) => {
//阻止浏览器的默认刷新事件
event.preventDefault()
console.log(this);
const {username, password} = this
alert(`输入的用户名是${username.value},密码是${password.value}`)
}
render() {
return (
//默认是GET提交,参数以query形式拼接
<form onSubmit={this.handle}>
用户名:<input ref={c => this.username = c} type="text" name="" id="" name="username"/>
密码:<input ref={c => this.password = c} type="password" name="" id="" name="password"/>
<button>登录</button>
</form>
)
}
}
ReactDOM.render(<Login/>, document.getElementById('test'))
</script>
- 这里的input都是非受控组件,非受控组件即“现用现取”
- 受控组件:随着输入维护状态
输入类的DOM,随着它的输入,将数据存储在state中,在需要的时候再在state中取出
建议使用受控组件,减少了ref
<script type="text/babel">
class Login extends React.Component {
state = {
username: '',
password: ''
}
handle = (event) => {
//阻止浏览器的默认刷新事件
event.preventDefault()
console.log(this);
const {username, password} = this.state
alert(`输入的用户名是${username},密码是${password}`)
}
saveUserame = (event) => {
this.setState({username: event.target.value})
}
savePassword = (event) => {
this.setState({password: event.target.value})
}
render() {
return (
//默认是GET提交,参数以query形式拼接
<form onSubmit={this.handle}>
用户名:<input onChange={this.saveUserame} type="text" name="" id="" name="username"/>
密码:<input onChange={this.savePassword} type="password" name="" id="" name="password"/>
<button>登录</button>
</form>
)
}
}
ReactDOM.render(<Login/>, document.getElementById('test'))
</script>
十九、高阶函数和函数的柯里化
- 高阶函数:
如果一个函数符合下面两个规范中的任一个,那么该函数就是高阶函数:
- 若函数接收一个函数作为参数
- 若函数调用的返回值是一个函数
常见的高阶函数有:Promise、setTimeout、map、reduce等等
- 函数的柯里化:
通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式
- 利用高阶函数和函数的柯里化对前面的代码进行简化:
提出疑问?
在前面的程序中,利用受控组件保存表单数据时,saveUserame和savePassword的处理部分代码相似,冗余部分太多,当表单中需要进行处理的数据越来越多时,这部分的冗余代码会随之增加,那么有什么方法能利用一个函数对这些进行统一的判断处理呢?
在绑定onChange函数时,给回调函数传入一个参数,利用该参数分辨此时进行处理的是哪个数据,但是在前面的学习中,若给onChange = {this.fun()}加上(),会直接执行该函数,将函数的返回结果直接传给onChange,而不是实现当需要时才调用
这里就利用到了高阶函数和函数的柯里化来解决这个问题
<script type="text/babel">
class Login extends React.Component {
state = {
username: '',
password: ''
}
//#region
/*
该注释部分可以折叠
*/
//#enderegion
//利用高阶函数
handle = (event) => {
event.preventDefault()
console.log(this);
const {username, password} = this.state
alert(`输入的用户名是${username},密码是${password}`)
}
//函数的柯里化
saveInfo = (dataType) => {
return (event) => {
this.setState({[dataType]: event.target.value})
}
}
render() {
return (
<form onSubmit={this.handle}>
用户名:<input onChange={this.saveInfo('username')} type="text" name="" id="" name="username"/>
密码:<input onChange={this.saveInfo('password')} type="password" name="" id="" name="password"/>
<button>登录</button>
</form>
)
}
}
ReactDOM.render(<Login/>, document.getElementById('test'))
</script>
二十、不使用柯里化的写法
- 不使用柯里化的写法,该怎样将dataType和event同时传递给saveInfo函数呢?——直接使用回调函数处理
<script type="text/babel">
class Login extends React.Component {
state = {
username: '',
password: ''
}
handle = (event) => {
event.preventDefault()
console.log(this);
const {username, password} = this.state
alert(`输入的用户名是${username},密码是${password}`)
}
saveInfo = (dataType, event) => {
this.setState({[dataType]: event.target.value})
}
}
render() {
return (
<form onSubmit={this.handle}>
用户名:<input onChange={event => this.saveInfo('usename', event)} type="text" name="" id="" name="username"/>
密码:<input onChange={event => this.saveInfo('password', event)} type="password" name="" id="" name="password"/>
<button>登录</button>
</form>
)
}
}
ReactDOM.render(<Login/>, document.getElementById('test'))
</script>
二十一、生命周期函数(钩子函数)(旧)
- 初始化阶段:
- 由ReactDOM.render()触发——初次渲染
1.constructor()
2 componentWillMount()
3 render()
4componentDidMount()——常用
:一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求
- 更新阶段(分别有三种情况):
1.第一种:通过setSate函数来更新状态,此时默认会触发render函数,在1线路中,shouldComponentUpdate函数相当于一个“开关阀”,用来控制是否允许更新状态时,调用render函数,该函数要求一定要有一个返回值,默认为true,但是当我们对该函数进行重写时,以重写的函数为主。若重写的返回值为false,则“开关阀”关闭,该线路将不再进行下去
2. 第二种:通过foreUpdate函数在状态不更新的时候强制调用了render函数,达到了不更新状态但是能重新渲染页面的效果
3. 第三种:线路线是在父子组件中的情况,父组件将自身的props传递给子组件,函数componentWillReceiveProps接收参数props,即父组件中传来的props对象,子组件通过自己的setState方法更新,此时触发的是线路3
- 卸载组件:
- 由ReactDOM.unmountComponentAtNode()触发
componentWillUnmount()——常用
:一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
- 重要的钩子:
- render:初始化渲染或更新渲染调用
- componentDidMount:开启监听,发送ajax请求
- componentWillUnmount:做一些收尾工作,如:清理定时器
- 即将废弃的钩子:
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
现在使用会出现警告,下一个版本需要加上UNSAFE_前缀才能使用,以后可能会被彻底废弃,不建议使用
//线路3,当父组件中更新state时执行
class A extends React.Component {
state = {isHot: true}
changeWeather = () => {
let {isHot} = this.state
this.setState({isHot: !isHot})
}
render() {
return (
<div>
<h3>今天天气{this.state.isHot ? '炎热':'凉爽'}</h3>
<button onClick={this.changeWeather}>修改天气</button>
<B name="Bob"/>
</div>
)
}
}
class B extends React.Component {
componentWillReceiveProps(props){
console.log('B组件',props);
}
shouldComponentUpdate() {
console.log('B中的shouldComponentUpdate');
return false
}
render() {
console.log('B中的render');
return (
<div>
我是B中的内容
</div>
)
}
}
ReactDOM.render(<A name="Tom" age={18}/>, document.getElementById('test'))
二十二、生命周期函数(新)
- 注意:要使用新的钩子函数,需要引入新的包
- getDerivedStateFromProps函数的使用:
- 该函数使用时,
需加static
- 该函数需要一个返回值,任何值都可
- 该函数接收两个参数,props和state
- 该函数是在当state的值任何时刻都取决于props时使用
- state被props影响的情况:
当return一个对象时,且该对象的属性名在state中也有,那么state中的值会以这个对象中的值为准,且不能被改变了
当组件中被传递了props且直接return props,那么会以传递的为准,但是需要注意的是:当传递的props中的name在state中存在,则替换,不存在则将props加入到state中
class Test extends React.Component{
state = {data: '今天天气很好'}
static getDerivedStateFromProps (props, state){
console.log('getDerivedStateFromProps',props,state);
//state中有data,更新
// return {data: '今天天气不好'}
return props
}
render() {
return (
<div>
<h3>{this.state.data}</h3>
</div>
)
}
}
ReactDOM.render(<Test data="Tom"/>, document.getElementById('test'))
//属性为name,state中是data,则将{name: 'Tom'}添加到state中
// ReactDOM.render(<Test name="Tom"/>, document.getElementById('test'))
- getSnapshotBeforeUpdate函数的使用:
- 和componentDidUpdate函数结合使用,getSnapshotBeforeUpdate函数是在组件更新的前一刻,将某个值传递给componentDidUpdate
- componentDidUpdate函数接收三个参数
componentDidUpdate(preProps,preState,snapshotValue)
这里的snapshotValue就是getSnapshotBeforeUpdate函数最后的返回值
- 案例:页面持续更新“新闻”,但是停留位置不随着滚动条的滚动而滚动
效果图:
class Test extends React.Component {
state = { newArr: [] }
componentDidMount() {
setInterval(() => {
const {newArr} = this.state
const news = '新闻' + (newArr.length+1)
this.setState({newArr: [news, ...newArr]})
}, 1000)
}
getSnapshotBeforeUpdate() {
return this.refs.list.scrollHeight
}
componentDidUpdate(preProps,preState,snapshotValue) {
//this.refs.list.scrollHeight - snapshotValue
//计算出每个新闻的高度
this.refs.list.scrollTop += this.refs.list.scrollHeight - snapshotValue
}
render() {
return (
<div className="box" ref="list">
{
this.state.newArr.map((item, index) => {
return <div key={index} className="content">{item}</div>
})
}
</div>
)
}
}
ReactDOM.render(<Test/>, document.getElementById('test'))