一、两种方式创建 Class 组件
1、ES 5(过时)
const Demo = React.createClass({
render(){
return <div>hi</div>
}
})
ES 5 不支持 class,所以使用了这种方式。
2、ES 6
class Demo extends React.Component{
constructor(porps){
super(props)
}
render(){
return <div>hi</div>
}
}
二、props 属性/外部数据
读props
使用组件时传入外部数据:
<Demo name={x} onClick={fn}>hi</Demo>
其中的name、onClick就是外部数据,它们会被包装成对象:{name: x, onClick: fn, children: 'hi'},传入类组件constructor的props参数,并且成为this.props。
写props
!!不准改写props!!
原则:外部数据应该由数据的主人进行更改
相关钩子
componentWillReceiveProps钩子
- 当组件接收新的
props时,触发此钩子 - 已被弃用,更名为
UNSAFE_componentWillReceiveProps,不要使用
props的作用
- 接收外部数据
- 只能读不能写
- 外部数据由父组件传入
- 接收外部函数
- 在合适的时机调用函数
- 函数一般是父组件的函数
三、state & setState 内部数据
改写state用setState函数
steState的用法
setState可接收两个参数,即this.setState(???, fn)
(1)第一个参数
第一个参数有两种写法:
- 传入全新的
state
this.setState({x: this.state.x + 1})
必须要传入一个全新的对象,不要改动旧的state
``setState会自动将旧state`的第一级属性合并
- 传入一个回调函数
this.setState(state => {return {x: state.x + 1}})
setState会调用函数,传入旧的state,将返回值作为新的state。
【注】:
setState是异步的,它不会立刻更新state,在当前代码运行完毕后,才会更新state,然后触发UI更新。基于此,推荐使用第二种调用方法。
(2)第二个参数
第二个参数是一个函数,在state成功更新后会被调用,很少使用。
四、常用生命周期
1、constructor
创建组件时执行
用途:
-
初始化
props、state -
用于 bind this:
constructor(){
// ...
this.onClick = this.onClick.bind(this)
}
- 可不写(初始化
state必须写)
2、shouldComponentUpdate
在数据更新时执行
2-1 用法
- 返回true,不阻止UI更新
- 返回false,阻止UI更新
2-2 用途
它允许我们手动决定是否要进行组件更新,可以根据应用场景灵活设置返回值,以避免不必要的更新
2-3 案例
若state由{n:1}变为{n:1}(新的对象),虽然两个对象看似一样,但它们是不同的对象,React会启动更新UI的流程,执行render(),经对比发现两个虚拟DOM没有不同,于是不更新视图,但render却实实在在执行了,此时可以用钩子函数阻止启动更新UI的流程。
【例】
shouldComponentUpdate(newProps, newState){ //调用时会依次传入新props、新state
if(newState.n === this.state.n){
return false
}else{
return true
}
}
2-4 改进:React.PureComponent
React内置了自动进行上述比较的功能,用React.PureComponent代替React.Component即可
class App extends React.PureComponent{
//...
}
当state或props更新时,会自动对新旧state、props的第一级属性进行比较,若完全一样,则不触发UI更新。只要有任何一个属性不同,就会触发。
3、render
生成虚拟DOM
3-1 用法
render的返回值有要求:
- 只能有一个根元素
- 若有多个根元素,要用
<React.Fragment>包围,可以简写为<>
<React.Fragment>
<div>元素一</div>
<div>元素二</div>
</React.Fragment>
//等价于:
<>
<div>元素一</div>
<div>元素二</div>
</>
3-2 技巧
render中可以使用各种语句结构
if...else
render(){
let message
if(this.state.n % 2===0){
message = <div>偶数</div>
}else{
message = <span>奇数</span>
}
return (
<>
{message}
<button >+1</button>
</>
)
}
?:表达式
render(){
return (
<>
{this.state.n % 2 === 0? <div>偶数</div>: <span>奇数</span>}
<button>+1</button>
</>
)
}
循环结构
render中使用循环,需借助数组:
render(){
let result = []
for(let i=0;i<this.state.arr.length;i++){
result.push(<div key={i}>{this.state.arr[i]}</div>)
}
return result
}
最常用的是借助数组的map方法:
render(){
return this.state.arr.map(item=><div key={item}>{item}</div>)
}
【注】:每次循环得到的结果必须要有key 属性
4、componentDidMount
在元素渲染到页面后执行,通常是一些依赖于DOM的操作,只在首次渲染后执行
4-1 用途
(1)【例】:获取页面中div的宽度
【方法一】通过id获取div
componentDidMount(){
const div = document.getElementById('xxx')
const {width} = div.getBoundingClientRect()
this.setState({width})
}
当div渲染到页面中时,执行函数,获取宽度值放在this.state.width上
【方法二】通过React虚拟DOM的ref属性获取div
constructor(){
//...
this.divRef = React.createRef()
}
componentDidMount(){
const div = this.divRef.current
const {width} = div.getBoundingClientRect()
this.setState({width})
}
render(){
return <div ref={this.divRef}>Hello</div>
}
(2)在此处发起加载数据的AJAX请求(官方推荐)
5、componentDidUpdate
在视图更新后执行,首次渲染不会执行
用途:
-
此处也可以发起AJAX请求,但用于更新数据。例如,用户ID改变,重新请求新ID的信息。
-
在此处setState会引起无限循环,要使用if进行条件判断
-
若
shouldComponentUpdate返回false,则此钩子不触发
传参:
依次为prevProps、prevState、snapshot(暂时忽略)
6、componentWillUnmount
组件将要被移出页面并被销毁时执行
用途:
销毁在mount时创建的一些内容,释放内存,例如:
- 在
Mount里监听了window scroll,就要在unmount里取消监听 - 在
Mount里创建了Timer,就要在unmount里取消Timer - 在
Mount里创建了AJAX请求,就要在unmount里取消请求
(实际上,可以不用理会这些)
7、生命周期总结
- 首次渲染:
constructor ----> render ----> 更新UI ----> componentDidMount
- 再次渲染:
props改变/setState()/forceUpdate() ----> shouldComponentUpdate ----> render ----> 更新UI ----> componentDidUpdate
- 销毁:
componentWillUnmount