一. 创建类组件
import React from 'react';
class B extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>hi</div>
)
}
}
export default B;
二. 外部数据props
给B组件传props
一个父组件给子组件传props,通常是把自己的内部数据传进去
class Parent extends React.Component {
constructor(props){
super(props)
this.state = {name:'frank'}
}
onClick = ()=>{}
render(){
return <B name={this.state.name} onClick={this.onClick}>hi</B>
}
}
传进去的props会被包装成一个对象,{name:'frank',onClick:...,children:'hi'}
子组件接收props
class B extends React.Component {
constructor(props) {
super(props);
}
render(){
return <div onClick={this.props.onClick}>
{this.props.name}
<div>
{this.props.children}
</div>
</div>
}
}
注意:constructor初始化函数里边,如果还需要写其他东西,比如this.state等,那就必须把这三行写全,必须把props参数写在那。
还有一种情况是,如果没有其他代码需要写在constructor里,那么初始化props可以不写,直接使用this.props.xxx
总结
props的作用:
接受外部数据,而且只能读,不能写。一般外部数据由父组件传递。
还可以接受外部函数,一般也是父组件的函数,会在恰当的时机,调用该函数。
三. 生命周期钩子
1. constructor
初始化props,初始化state.如果只需要初始化props,可以不写constructor
React组件在创建的时候,会调用这个函数
2. shouldComponentUpdate
用途:在里边做一些判断,返回false,就是告诉React阻止UI更新。返回true,就是不阻止更新,可以更新。
示例:
class App extends React.Component {
constructor(props){
super(props)
this.state={
n:1
}
}
onClick=()=>{
this.setState(state=>({n:state.n+1}))
this.setState(state=>({n:state.n-1}))
}
render(){
console.log('render被调用了');
return (
<div className="App">
App n:{this.state.n}
<button onClick={this.onClick}>+1-1</button>
</div>
);
}
}
假设有这样一种情况:点击按钮后,对n做了一系列操作,最后发现n的值没变,还是1。(用先+1,再-1模拟)
点击按钮,发现页面上的n没变,还是1.这是没问题的。因为n的值没变。
但是,log打印了两次。为什么n的值没变,还是调用了render呢?
虽然n的值前后都是1,但是{n:1}和{n:1}是两个不同的对象,地址不同。所以React认为数据变了,就去调用了render函数。然后生成了新的虚拟DOM。对比前后两个虚拟DOM,发现n的值都是1,没有不同的地方,就没有更新UI。但是render确是被调用了
所以其实从调用render往后,生成新DOM,对比,发现没有不同,停止更新,这几步都是多余的。是数据的地址变了,让React误以为数据变了,但是最后又发现没变,所以中间的那些步骤就多余了。
能不能在n的值没变的情况下,就不让render被调用了呢?shouldComponentUpdate 钩子可以做到
shouldComponentUpdate(nextProps, nextState, nextContext) {
if(nextState.n===this.state.n){
return false
}else{
return true
}
}
直接在class下边定义。默认传进去三个参数,按顺序:新的props,新的state。
在这个函数里判断:如果新的n和当前的n一样,就返回false,不用更新了。(手动告诉React,数据没变,不要去render了)这时再点击按钮,就没有log了,说明render没有被调用了。
面试问:shouldComponentUpdate有什么用? 它让我们可以手动判断是否要进行组件更新。我们可以根据场景灵活地设置返回值,以避免不必要的更新。
React.PureComponent代替React.Component
上边的shoule钩子还要自己手动判断,有点麻烦,于是React内置了这个功能 ,叫做React.PureComponent。在class extends后边继承它,就相当于should的功能 。PureComponent 会在 render 之前对比新 state 和旧 state 的每一个 key,以及新 props 和旧 props 的每一个 key。
如果所有 key 的值全都一样,就不会 render;如果有任何一个 key 的值不同,就会 render。
class App extends React.PureComponent {}
3. render
创建虚拟DOM,展示视图 return <div></div>
return 的元素,只能有一个根元素。如果有两个根元素,要么用一个div包起来,要么用<React.Fragment></React.Fragment>包起来。
他们的区别是:<div>会被渲染到页面中,而<React.Fragment></React.Fragment>不会被渲染。
render(){
return (
<React.Fragment className="App">
App n:{this.state.n}
<button onClick={this.onClick}>+1-1</button>
<div>hi</div>
</React.Fragment>
);
}
把这些渲染到root里,发现页面的html是这样的:

<React.Fragment></React.Fragment>可以缩写成:<></>
render里可以写任何JS代码,if...else... , ? : ,&&
但是render里不能直接写for循环,因为render是需要return 的,如果在for循环里写return ,就只能循环第一次,就直接退出循环了。
可以借助数组,循环的时候,把结果push进数组,循环结束后,return 数组。
或者array.map()
class App extends React.PureComponent {
constructor(props){
super(props)
this.state={
n:1,
arr:[1,2,3]
}
}
render(){
return (
this.state.arr.map(item=><div key={item}>{item}</div>)
);
}
}
所有循环都需要一个不重复的key
4. componentDidMount
componentDidMount()会在组件挂载后(插入 DOM 树中,出现在页面)立即调用。依赖于 DOM 节点的初始化应该放在这里。如需通过网络请求获取数据,此处是实例化请求的好地方(发起加载数据的AJAX请求)。当然,首次渲染会执行这个钩子
应用场景:在页面渲染一个div,同时展示这个div的宽度。
解析:要展示一个div的宽度,前提是它已经出现在页面里了,才能去获取。如果在constructor里获取,肯定是拿不到的,因为constructor是在元素出现在内存后被调用的,此时还没被挂载到页面里。所以需要使用该钩子,在组件挂载后,去获取div的宽度
class App extends React.PureComponent {
constructor(props){
super(props)
this.state={
n:1,
width:undefined
}
}
componentDidMount(){
const div=document.querySelector('#xxx')
const width=div.getBoundingClientRect().width
this.setState({width:width})
}
render(){
return (
<div id={'xxx'}>
hello world {this.state.width}px
</div>
);
}
}
访问页面里的DOM节点的第二种方式-Refs
上边我们通过id访问DOM节点。React提供了一种访问页面里的DOM节点的方式-Refs
class App extends React.PureComponent {
divRef=undefined // 可以先声明一下,表示我之后要动态创建Ref
constructor(props){
super(props)
this.state={
width:undefined
}
this.divRef=React.createRef() // 1. 创建Refs
}
componentDidMount(){
const div=this.divRef.current // 3. current属性访问节点
const width=div.getBoundingClientRect().width
this.setState({width:width})
}
render(){
return (
<div ref={this.divRef}> // 2. 通过ref属性,关联到元素上
hello world {this.state.width}px
</div>
);
}
}
创建的时候,divRef是绑定到this上,也就是实例上的,而不是state上。
5. componentDidUpdate(prevProps, prevState, snapshot)
会在UI更新后会被立即调用。首次渲染不会执行此方法(因为没更新)。
如果要发起AJAX请求,除了可以在componentDidAmount里请求,还可以在这个钩子里也可以发起AJAX请求,不过是用于更新数据的。官方文档:
如果你对更新前后的 props 进行了比较,也可以选择在此处进行网络请求。(例如,当 props 未发生变化时,则不会执行网络请求)。
componentDidUpdate(prevProps) {
// 典型用法(不要忘记比较 props):
if (this.props.userID !== prevProps.userID) {
this.fetchData(this.props.userID);
}
}
就是说,假如用户的id变了,那就需要发起一个网络请求获取新的用户信息,这时网络请求就要在componentDidUpdate里做,因为视图更新了才知道用户信息变了。
6. componentWillUnmount()
组件将要被移出页面(取消挂载)然后销毁(从内存消失)时执行。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等。
总结
分阶段看一下这些钩子会在什么时候执行,以及他们的执行顺序:

constructor() 初始化state,创建元素
shouldComponentUpdate() return false阻止更新
render() 会创建虚拟DOM
componentDidMount() 组件已经出现在页面
componentDidUpdate() 组件已经更新
componentWillUnmount() 组件将要死掉之前