Skip to content

组件深入

生命周期

什么是生命周期?

事物从生到死的过程,我们称之为生命周期

什么是生命周期方法

事物从生到死的过程中,在特定时间节点调用的方法,我们称之为生命周期方法

React组件生命周期方法

组件从生到死的过程,在特定的时间节点调用的方法,我们称之为组件的生命周期方法

  • constructor生命周期的作用

    • 通过props接收父组件传递过来的数据
    • 通过this.state初始化内部的数据
    • 通过bind为时间绑定示例(this) this.myClick = this.btnClick.bind(this)
  • render 生命周期的作用

    • 返回组件的结构
  • componentDidMount 生命周期的作用

    • 依赖于DOM的操作可以在这里进行
    • 在这里发送网络请求(官方推荐)
    • 可以在此处添加一些订阅
  • componentDidUpdate 生命周期的作用

    • 可以在此对更细之后的组件进行操作
  • componentWillUnmount 生命周期的作用

    • 在此方法中执行必要的清理操作
    • 例如,清理定时器,取消或清除网络请求
    • 清理在componentDidMount()中创建的订阅

示例:

jsx

import React from "react";
class Home extends React.Component{
    constructor(props) {
        super(props);
        console.log('挂载时 - 创建组件');
        this.state = {
            count:0
        }
    }
    render() {
        console.log('挂载时 - 渲染组件');
        return(
            <div>
                <p>Home</p>
                <p>{this.state.count}</p>
                <button onClick={()=>{this.btnClick()}}>按钮</button>
            </div>
        )
    }

    btnClick(){
        this.setState({
            count:1
        })
    }

    componentDidUpdate(prevProps, prevState, snapshot) {
        console.log('更新时-更新完成');
    }

    componentDidMount() {
        console.log('挂载时 - 渲染完成');
    }

    componentWillUnmount() {
        console.log('卸载时');
    }
}

class App extends React.Component{

    constructor(props) {
        super(props);
        this.state = {
            flag : true
        }
    }

    render() {
        return(
           <div>
               {this.state.flag && <Home />}
               <button onClick={()=>{this.btnClick()}}>点击</button>
           </div>
        )
    }

    btnClick(){
        this.setState({
            flag:!this.state.flag
        })
    }
}

export default App

生命周期的其他方法

  • getDerivedStateFromProps

会在调用 render 方法之前调用, 并且在初始挂载及后续更新时都会被调用。 它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。

  • shouldComponentUpdate

判断 React 组件的输出是否受当前 state 或 props 更改的影响

  • getSnapshotBeforeUpdate

可以拿到最后更新之前的数据的地方

diff算法

React渲染流程

  • 执行render方法

  • 将JSX转换成createElement

  • 执行createElement创建虚拟DOM,得到虚拟DOM树

组件更新流程

  • props或state发生改变

  • render方法重新执行

  • 将JSX转换成createElement

  • 利用createElement重新生成新的虚拟DOM树

  • 新旧虚拟DOM通过diff算法进行比较

  • 每发现一个不同就生成一个mutation

  • 根据mutation更新真实DOM

React-diff算法

  • 只会比较同层的元素

  • 在diff算法中,默认在同层比较的时候,只会同位置的比较

  • 在diff算法中,如果比较的是相同类型的元素,那么就记录变化

  • 在diff算法中,如果比较的是不同类型的元素,那么直接删除以前的,使用新的

  • 在diff算法中,如果上一层是不同类型的元素,那么下一层不会进行比较,而是直接使用新的

列表渲染优化

在渲染列表的过程中,因为react的diff算法只会比较同层同位置的数据,所以在渲染列表的过程中,元素插入位置的不同,会导致一定的渲染问题

示例:

源数据:

jsx

<ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
</ul>

在后面插入一个元素

jsx

<ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
</ul>

因为会对同层同位置的元素进行比较,所以比较的顺序如下

同层元素比较

只会创建一个mutation对象

如果在前面插入一个元素

jsx

<ul>
    <li>0</li>
    <li>1</li>
    <li>2</li>
    <li>3</li>
</ul>

同上会对同层同位置的元素进行比较,默认会创建四个mutation对象,这样会拖慢渲染性能

如何解决当前性能的问题:

给每个子结点一个特殊的key做标识,就可以实现如下代码:

jsx

import React from "react";

class Home extends React.Component{
    constructor(props) {
        super(props);
        this.state = {
            arr:['鲁班','虞姬','火舞']
        }
    }
    render() {
        return(
            <div>
                <ul>
                    {
                        this.state.arr.map(v=>{
                            return(<li key={v}>{v}</li>)
                        })
                    }
                </ul>
            </div>
        )
    }

}
class App extends React.Component{
    constructor(props) {
        super(props);
    }

    render() {
        return(
          <div>
              <Home/>
          </div>
        )
    }
}
export default App

注意点

必须保证key是唯一的不然会报错

组件性能优化

类组件性能优化

在只改变父组件的数据不改变子组件的数据的时候,不仅仅父组件会重新渲染,子组件也会跟着重新渲染,这样就会导致一定的性能问题。

解决方案:

让组件继承于PureComponent,让React自动帮助我们实现是否需要重新渲染数据

实现代码:

jsx

import React from "react";

// class Home extends React.Component{
class Home extends React.PureComponent{
    constructor(props) {
        super(props);
        this.state = {
            arr:['鲁班','虞姬','火舞']
        }
    }
    render() {
        console.log("子组件render");
        return(
            <div>
                <ul>
                    {
                        this.state.arr.map(v=>{
                            return(<li key={v}>{v}</li>)
                        })
                    }
                </ul>
            </div>
        )
    }

}

class App extends React.Component{
    constructor(props) {
        super(props);
        this.state = {
            name:'test'
        }
    }

    render() {
        console.log("父组件render");
        return(
          <div>
              <p>{this.state.name}</p>
              <Home/>
              <button onClick={()=>{this.btnClick()}}>按钮</button>
          </div>
        )
    }

    btnClick(){
        this.setState({
            name:'sss'
        })
    }
}

export default App

函数式组件性能优化

  • 没有继承关系

  • 没有生命周期方法

那么如何优化函数式组件的性能问题呢?

在React中提供了一个函数方法React.memo(),这个方法,内部已经对组件的性能做了一定的优化

示例:

jsx

import React from "react";

// function Home(){
//     console.log("子组件render");
//     return(
//         <div>
//             <p>Home</p>
//         </div>
//     )
// }

const PurHome = React.memo(function (){
    console.log("子组件render");
    return(
        <div>
            <p>Home</p>
        </div>
    )
})

class App extends React.Component{
    constructor(props) {
        super(props);
        this.state = {
            name:'test'
        }
    }

    render() {
        console.log("父组件render");
        return(
          <div>
              <p>{this.state.name}</p>
              <PurHome/>
              <button onClick={()=>{this.btnClick()}}>按钮</button>
          </div>
        )
    }

    btnClick(){
        this.setState({
            name:'sss'
        })
    }
}

export default App

性能优化注意点

  • state注意点

永远不要直接修改state中的数据,更新数据必须要通过setState传递一个新的值。

造成的问题:

如果直接修改state中的值,然后再调用setState(this.state),如果我们继承的是PureComponent,会造成界面永远无法更新的情况

示例:

jsx
import React from "react";

class Home extends React.PureComponent{
    constructor(props) {
        super(props);
    }
    
    render() {
        console.log("子组件render");
        return(
            <div>
                <p>Home</p>
            </div>
        )
    }

}

class App extends React.Component{
    constructor(props) {
        super(props);
        this.state = {
            name:'test'
        }
    }

    render() {
        console.log("父组件render");
        return(
          <div>
              <p>{this.state.name}</p>
              <Home/>
              <button onClick={()=>{this.btnClick()}}>按钮</button>
          </div>
        )
    }

    shouldComponentUpdate(nextProps, nextState, nextContext) {
        console.log(this.state, nextState);// 值永远都是相同的不会更新界面
        if (this.state.name !== nextState.name){
            return true
        }
        return false
    }

    btnClick(){
        this.state.name = "newName";
        this.setState(this.state);
        // this.setState({
        //     name:'sss'
        // })
    }
}

export default App


组件的Ref

字符串(不推荐)

通过ref='字符串'再通过this.refs.字符串(这种方式将会被废弃,不推荐使用)

jsx

import React from "react";

class App extends React.Component{
    constructor(props) {
        super(props);
        this.state = {
            name:'test'
        }
    }
    render() {
        return(
          <div>
              <p ref={'box'}>我是段落</p>
              <button onClick={()=>{this.btnClick()}}>按钮</button>
          </div>
        )
    }

    btnClick(){
        // 第一种方式能获取:通过字符串的方式即将被废弃,不推荐
        let Op = this.refs.box
        Op.innerHTML = "新的段落"
        console.log(Op);
    }
}

export default App

对象(推荐)

先通过React.createRef()创建一个对象,然后将这个对象传递给ref

jsx

import React from "react";

class App extends React.Component{
    constructor(props) {
        super(props);
        this.state = {
            name:'test'
        }
        this.oPRef = React.createRef();
    }
    render() {
        return(
          <div>
              <p ref={this.oPRef}>我是段落</p>
              <button onClick={()=>{this.btnClick()}}>按钮</button>
          </div>
        )
    }

    btnClick(){
        // 使用对象的方式获取
        let oP = this.oPRef.current;
        oP.innerHTML = "对象设置新的值"
        console.log(oP);
    }
}

export default App

回调函数(推荐)

通过传递一个回调函数,然后保存回调函数参数的方式

jsx

import React from "react";

class App extends React.Component{
    constructor(props) {
        super(props);
        this.state = {
            name:'test'
        }
        this.oPRef = null;
    }
    render() {
        return(
          <div>
              <p ref={(arg)=>{this.oPRef = arg}}>我是段落</p>
              <button onClick={()=>{this.btnClick()}}>按钮</button>
          </div>
        )
    }

    btnClick(){
        // 使用对象的方式获取
        let oP = this.oPRef;
        oP.innerHTML = "对象设置新的值"
        console.log(oP);
    }
}

export default App

注意点

  • 如果获取的是原生的元素,拿到的就是元素本身

  • 如果获取的是类组件的元素,拿到的就是类组件的实例对象

  • 如果获取的是函数组件元素,什么都拿不到

Ref转发

默认情况下通过ref是拿不到数据的,如果获取某一个元素的节点信息是可以实现的,通过React.forwardRef()高阶函数来创建一个组件

jsx

import React from "react";

const About = React.forwardRef(function (props,myRef){
    return(
        <div ref={myRef}>
            <p>段落</p>
        </div>
    )
})

class App extends React.Component{
    constructor(props) {
        super(props);
        this.state = {
            name:'test'
        }
        this.oPRef = null;
        this.myRef = null;
    }
    render() {
        return(
          <div>
              <p ref={(arg)=>{this.oPRef = arg}}>我是段落</p>
              <button onClick={()=>{this.btnClick()}}>按钮</button>
              <About ref={(arg)=>{this.myRef = arg}}/>
          </div>
        )
    }

    btnClick(){
        // 使用对象的方式获取
        let oP = this.oPRef;
        oP.innerHTML = "对象设置新的值"
        console.log(oP);
    }
}

export default App

受控组件

值受到react控制的表单组件

jsx

import React from "react";

class App extends React.Component{
    constructor(props) {
        super(props);
        this.state = {
            name:'test'
        }
    }
    render() {
        return(
          <form>
              <input type='text' value={this.state.name} onChange={(e)=>{this.change(e)}}/>
          </form>
        )
    }

    change(e){
        console.log(e.target.value);
        this.setState({
            name:e.target.value
        })
    }

}

export default App

其他受控组件地址:https://react.docschina.org/docs/forms.html

受控组件处理技巧,多个组件的处理:

如果处理多个组件,可能会产生很多的冗余代码,所以需要对代码进行优化处理

示例:

jsx

import React from "react";

class App extends React.Component{
    constructor(props) {
        super(props);
        this.state = {
            name:'test',
            email:'aibayanyu@qq.com',
            phone:'13788889999'
        }
    }
    render() {
        return(
          <form>
              <input type='text' name={'name'} value={this.state.name} onChange={(e)=>{this.change(e)}}/>
              <input type='text' name={'email'} value={this.state.email} onChange={(e)=>{this.change(e)}}/>
              <input type='text' name={'phone'} value={this.state.phone} onChange={(e)=>{this.change(e)}}/>
          </form>
        )
    }
    change(e){
        this.setState({
            [e.target.name]:e.target.value
        })
    }
}

export default App

非受控组件

值不受到react控制的表单元素

示例:

jsx

import React from "react";

class App extends React.Component{
    constructor(props) {
        super(props);
        this.myRef = React.createRef();
    }
    render() {
        return(
          <form onSubmit={(e)=>{this.submit(e)}}>
              <input type='text' ref={this.myRef}/>
              <input type='submit'/>
          </form>
        )
    }

    submit(e){
        e.preventDefault()
        console.log(this.myRef.current.value);
    }
}

export default App

高阶组件

参数为组件,返回值为新组件的函数

jsx

import React from "react";

class Home extends React.PureComponent{
    render() {
        return(
            <div>
                Home
            </div>
        )
    }
}

function enhaceComponent(WrappedComponent) {
    class AdvComponent extends React.PureComponent{
        render() {
            return(
                <div>
                    <WrappedComponent />
                </div>
            )
        }
    }
    return AdvComponent;
}

const AdvComponent = enhaceComponent(Home)

class App extends React.PureComponent{
    render() {
        return(
          <div>
            <AdvComponent/>
          </div>
        )
    }
}

export default App

应用场景

  • 增强Props,解决组件代码出现大量冗余的问题

示例:

jsx

import React from "react";

// class Father1 extends React.PureComponent{
//     render() {
//         return(
//             <div>
//                 <Son1/>
//             </div>
//         )
//     }
// }
// class Father2 extends React.PureComponent{
//     render() {
//         return(
//             <div>
//                 <Son2/>
//             </div>
//         )
//     }
// }
//
// class Son1 extends React.PureComponent{
//     render() {
//         return(
//             <div>
//                 son1
//             </div>
//         )
//     }
// }
// class Son2 extends React.PureComponent{
//     render() {
//         return(
//             <div>
//                 son2
//             </div>
//         )
//     }
// }

const MyContext = React.createContext({})
const  { Provider,Consumer } = MyContext

class Son1 extends React.PureComponent{
    render() {
        return(
            <div>
                <p>{this.props.name}</p>
                <p>{this.props.age}</p>
            </div>
        )
    }
}

class Son2 extends React.PureComponent{
    render() {
        return(
            <div>
                <ul>
                    <li>{this.props.name}</li>
                    <li>{this.props.age}</li>
                </ul>
            </div>
        )
    }
}

function EnhancedComponent (WrappedComponent){
    class Father extends React.PureComponent{
        render() {
            return(
                <Consumer>
                    {
                        (value)=>{
                            return(
                                <WrappedComponent {...value} />
                               // <WrappedComponent name={value.name} age={value.age}/>
                            )
                        }
                    }
                </Consumer>
            )
        }
    }
    return Father;
}

const Father1 = EnhancedComponent(Son1);
const Father2 = EnhancedComponent(Son2);

class App extends React.PureComponent{
    render() {
        return(
          <Provider value={{name:'test',age:19}}>
              <Father1 />
              <Father2 />
          </Provider>
        )
    }
}

export default App

上面传的是相同的数据,如何传不同的数据

通过二次传递props的数据,修改代码如下所示

jsx

<Provider value={{name:'test',age:19}}>
  <Father1 country={'中国'}/>
  <Father2 country={'韩国'}/>
</Provider>

jsx

<WrappedComponent {...value} {...this.props} />

应用场景:

  • 代码复用、增强Props、抽离State、生命周期拦截

抽离State和生命周期拦截示例:

jsx

import React from "react";
import { EventEmitter } from "events"
const eventBus = new EventEmitter();

const MyContext = React.createContext({})
const  { Provider,Consumer } = MyContext

class Son1 extends React.PureComponent{
    render() {
        return(
            <div>
                <p>{this.props.name}</p>
                <p>{this.props.age}</p>
                <p>{this.props.country}</p>
                {
                    this.props.list.map(v=>{
                        return (<p>{v}</p>)
                    })
                }
            </div>
        )
    }
}

class Son2 extends React.PureComponent{
    render() {
        return(
            <div>
                <ul>
                    <li>{this.props.name}</li>
                    <li>{this.props.age}</li>
                    <li>{this.props.country}</li>
                    {
                        this.props.list.map(v=>{
                            return (<li>{v}</li>)
                        })
                    }
                </ul>
            </div>
        )
    }
}

function EnhancedComponent (WrappedComponent){
    class Father extends React.PureComponent{
        constructor(props) {
            super(props);
            this.state = {
                list:[]
            }
        }

        componentDidMount() {
            eventBus.addListener('update',this.update.bind(this))
        }

        componentWillUnmount() {
            eventBus.removeListener('update',this.update.bind(this))
        }

        update(list){
            this.setState({
                list:list
            })
        }
        render() {
            return(
                <Consumer>
                    {
                        (value)=>{
                            return(
                                <WrappedComponent {...value} {...this.props} {...this.state} />
                            )
                        }
                    }
                </Consumer>
            )
        }
    }
    return Father;
}

const Father1 = EnhancedComponent(Son1);
const Father2 = EnhancedComponent(Son2);

class App extends React.PureComponent{
    render() {
        return(
          <Provider value={{name:'test',age:19}}>
              <Father1 country={'中国'}/>
              <Father2 country={'韩国'}/>
              <button onClick={()=>{this.btnClick()}}>按钮</button>
          </Provider>
        )
    }

    btnClick(){
        eventBus.emit('update',['鲁班','虞姬'])
    }
}

export default App

应用场景:

权限控制

这是一个简单权限控制的界面,后面会有高级应用权限控制的方式和方法

示例:

jsx

import React from "react";

class Login extends React.PureComponent{
    render() {
        return(
            <div>
                登录页面
            </div>
        )
    }
}

class Info extends React.PureComponent{
    render() {
        return(
            <div>
                用户信息界面
            </div>
        )
    }
}

function EnhancedComponent(WrappedComponent){
    class Author extends React.PureComponent{
        render() {
            if (this.props.isLogin){
                return <WrappedComponent />
            }else {
                return <Login/>
            }
        }
    }

    return Author;
}

const AuthorInfo = EnhancedComponent(Info);

class App extends React.PureComponent{
    render() {
        return(
            <AuthorInfo isLogin={true}/>
        )
    }
}

export default App

其他内容

Portals

默认情况下,所有的组件都是渲染到root元素中的,而Portals提供了一种将组件渲染到其他元素中的能力

在index.html文件中添加如下的代码

html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
  </head>
  <body>
    <div id="root"></div>
    <div id="other"></div>
  </body>
</html>

jsx

import React from "react";
import ReactDOM from 'react-dom'

class Model extends React.PureComponent{
    render() {
        // this.props.children: 可以获取当前组件所有的子元素或者子组件
        // 接收两个参数
        // 第一个参数:需要渲染的内容
        // 第二个参数:渲染到什么地方
        return ReactDOM.createPortal(this.props.children,document.getElementById("other"))
    }
}

class App extends React.PureComponent{
    render() {
        return(
            <div id={'app'}>
                <Model>
                    <div id={'model'}>
                        Model
                    </div>
                </Model>
            </div>
        )
    }
}

export default App

Fragment

解决多层嵌套多余的问题,如果出现如下的多层嵌套的问题

html

<div id='root'>
    <div id='app'>
        <div id='home'>
            <p>内容</p>
        </div>
    </div>
</div>

有结构可以看出 apphome 是多余的,所以这时候,需要我们使用Fragment来解决这个问题

jsx

import React from "react";
const { Fragment } = React
class Home extends React.PureComponent{
    constructor(props) {
        super(props);
        this.state = {
            heroList:[
                '鲁班','虞姬','黄忠'
            ]
        }
    }
    render() {
        return(
            // 如果组件的结构比较复杂,那么只能有一个根元素
            <Fragment>
                <p>{this.state.heroList[0]}</p>
                <p>{this.state.heroList[1]}</p>
                <p>{this.state.heroList[2]}</p>
            </Fragment>
        )
    }

}

class App extends React.PureComponent{
    render() {
        return(
            // <Fragment id={'app'}>
            //     <Home/>
            // </Fragment>
            // 语法糖:这种写法是上面的写法的语法糖,如果需要给Fragment添加key,那么就不能使用语法糖
            <>
                <Home/>
                
            </>
        )
    }
}

export default App

StrictMode

  • 什么是StrictMode

开启严格模式,检查后代组件中是否存在潜在的问题

TIP

  • 和Fragment一样,StrictMode不会渲染任何UI元素
  • 仅在'开发模式'下有效

index.js

jsx

import ReactDom from "react-dom"
import React from "react";
import App from "./App";
ReactDom.render(
    <React.StrictMode>
        <App/>
    </React.StrictMode>
    ,document.getElementById("root"));

  • 主要检查什么?

  • 检查过时或废弃的属性/方法/...

  • 检查意外的副作用

    • 这个组件的constructor会被调用两次
    • 检查这里写的代码逻辑被调用多次时,是否会产生一些副作用

Released under the MIT License.