美文网首页
react 高级特性整理

react 高级特性整理

作者: miao8862 | 来源:发表于2021-06-19 14:44 被阅读0次

    这一篇会整理一些react常见的高级特性以及它们的应用场景:

    • 函数组件
    • 非受控组件
    • protals
    • context
    • 异步加载组件
    • shouldComponentUpdate 优化
    • pureComponent 和 memo优化
    • 了解Immutable概念

    还有一部分关于组件公共逻辑抽离的高级特性,由于篇幅太长,我会另写一篇来介绍:

    • HOC 高阶组件
    • Render Props

    函数组件

    • 纯函数,输入props,输出JSX
    • 没有实例,没有生命周期,不包含state状态
    • 不能扩展其它方法
    类组件和函数组件对比

    两者的选择:

    • 如果仅是视图展示,没有状态的话,逻辑简单,建议使用函数组件;
    • 如果要定义内部状态,逻辑比较复杂,可能用到生命周期的话,建议使用类组件

    非受控组件

    非受控组件,就是不受组件内部state控制的组件,这时表单数据将交由 DOM 节点来处理:

    • 初始值由defaultValuedefaultChecked来使用state赋值
    • 但表单内容修改后,对应的state值不会修改,因为没有通过onChange等事件回传
    • 要拿到表单内容修改的值,会使用refref通过createRef来创建)来获取对应dom,然后获取对应的值
    import React, {Component} from 'react'
    // // class 类组件
    class NonFormInput extends Component {
      constructor(props) {
        super(props)
        this.state = {
          name: '小花', 
          flag: true
        }
        // 创建ref,react要通过createRef来创建,不能像vue一样直接使用字符串
        this.nameInputRef = React.createRef()
        this.fileInputRef = React.createRef()
      }
      render() {
        const {name, flag} = this.state
        return <div>
          {/* 
            使用defaultValue赋初始值 
            ref的作用就是用来标识dom的,如vue中的ref="xxx"
          */}
          <input defaultValue={name} ref={this.nameInputRef}/>
          {/* this.state.name不会随着表单内容改变 */}
          <span>state.name:{name}</span>
          <br/>
          <button onClick={this.alertName}>alert name</button>
    
          <hr/>
          <input type="file" ref={this.fileInputRef}/>
          <button onClick={this.alertFile}>alert file</button>
        </div>
      }
      alertName = () => {
        // ref指代的dom元素,<input value="小花">
        console.log(this.nameInputRef.current)  
        // value值
        alert(this.nameInputRef.current.value)  
      }
      alertFile = () => {
        const ele = this.fileInputRef.current
        console.log(ele.files[0].name)
      }
    }
    export default NonFormInput
    
    image.png

    使用场景:

    • 必须手动操作Dom元素,setState实现不了的
    • 常见的有文件上传<input type=file>,因为它的值只能由用户设置,不能通过代码控制
    • 某些富文本编辑器,需要传入dom元素

    受控和非受控选择:

    • 优先使用受控组件,符合react设计原则
    • 必须操作dom时,再使用非受控组件

    Portals

    Portals是将组件渲染到指定到dom元素上,可以是脱离父组件甚至是root根元素,放到其以外的元素上,类似vue3 teleport的作用

    先看下未使用Portals样子:

    // ProtalsDemo.js
    import React, {Component} from 'react'
    
    class ProtalsDemo extends Component {
      constructor(props) {
        super(props)
      }
      render() {
        return <div className="model">
          {/* this.props.children等于vue中的slot插槽 */}
          {this.props.children}
        </div>
      }
    }
    export default ProtalsDemo
    
    // 在index.js引入组件
    import React from 'react';
    import ReactDOM from 'react-dom';
    import './index.css';
    import reportWebVitals from './reportWebVitals';
    import ProtalsDemo from './advance/ProtalsDemo'
    ReactDOM.render(
      <React.StrictMode>
        <ProtalsDemo>
          model内容
        </ProtalsDemo>
      </React.StrictMode>,
      document.getElementById('root')
    );
    reportWebVitals();
    
    正常组件的层级

    使用Portals后:
    需要使用ReactDOM.createPortal创建protal对象,它有两个参数:1. 要改变位置的组件, 2. 要改变的目标dom位置

    // ProtalsDemo.js
    
    import React, {Component} from 'react'
    // 需要先引入 ReactDOM
    import ReactDOM from 'react-dom'
    class ProtalsDemo extends Component {
      constructor(props) {
        super(props)
      }
      render() {
        // 使用 Portal 将组件放在指定的dom元素上
        // 两个参数:第一个为要显示组件
        // 第二个为要放至的dom元素位置
        return ReactDOM.createPortal(
          <div className="model">
            {this.props.children}
          </div>,
          document.body
        )
      }
    
    }
    export default ProtalsDemo
    
    portal

    使用场景:

    • 父级元素 overflow:hidden,遮挡子元素的展示
    • 父组件的z-index值太小,导致子组件内容被遮挡
    • fixed布局的组件需要放在body第一层级的,比如上面例子的弹窗

    context

    上下文context用于向每个组件传递一些公共信息,当组件嵌套层级过深时,使用props传递太过麻烦,使用redux又太过度设计,这时就会使用context来传递,它的作用类似于vue中的provide inject的作用
    它的使用方法如下:

    1. 创建一个自定义的context上下文对象,比如ThemeContext
      这个对象是祖先子孙组件中的枢纽,所有组件要通过它来进行通信
    // contextType.js
    import React from 'react'
    // 1. 通过createContext创建一个`context`对象
    // theme: 是自定义的上下文名称
    // ThemeContext: 是自定义的上下文对象,是后续祖先孙子组件的中间承接者,所以要导出方便子孙组件使用
    export const ThemeContext = React.createContext('theme')
    
    1. 在父组件中引入刚定义的上下文对象ThemeContext,并使用ThemeContext.Provide组件包裹所有子孙组件,并在其value属性上设置要共享的状态
    // Fahter.js
    import React from 'react'
    // 导入context上下文对象
    import { ThemeContext } from './contextType'
    import Son from './Son'
    
    export default class Father extends React.Component {
      constructor(props) {
        super(props)
        // 2. 最外层组件定义要共享的变量,比如这里共享主题颜色 themeColor
        this.state = {
          themeColor: 'light'
        }
      }
      render() {
        let { themeColor } = this.state
        // 3. 由最外层组件使用上下文变量ThemeContext,通过Provide提供要共享的数据value
        return <ThemeContext.Provider value={themeColor}> 
          <div>
            这是父组件的内容内容内容
            <Son />
            <button onClick={this.changeTheme}>改变主题</button>
          </div>
        </ThemeContext.Provider>
      }
      changeTheme = () => {
        this.setState({
          themeColor: 'dark'
        })
      }
    }
    
    1. 在子组件中使用时,类组件函数组件使用context对象的方式是不一样的,下面我使用两个组件例子来说明,子组件使用类组件,孙组件中使用函数组件

    2. 子组件(类组件)使用context

      • 导入上下文对象ThemeContext
      • 给类组件设置当前组件的contextType,指明这个组件要共享的上下文对象:Son.contextType = ThemeContext
      • 通过this.context获取父组件传的共享状态并使用
    import React from 'react'
    // 4. 子组件中导入上下文对象
    import { ThemeContext } from './contextType'
    // 导入孙子组件
    import Grandson from './Grandson'
    
    class Son extends React.Component {
      render() {
        // 6. 通过this.context获取共享数据
        const theme = this.context
        // 7. 在子组件中正常使用即可      
        return <div>
            这是子组件的内容,从父组件中获取的共享数据为: {theme}
            <Grandson />
          </div>
      }
    }
    
    // 5. 类组件设置当前组件的contextType,指明这个组件要共享的上下文对象
    Son.contextType = ThemeContext
    
    export default Son
    
    1. 孙组件(函数组件)中使用
      • 导入上下文对象ThemeContext
      • 使用上下文对象的Consumer组件,通过回调函数方式来获取对应的共享状态
    // 8. 孙组件中导入上下文对象
    import { ThemeContext } from './contextType'
    
    export default function Grandson(props) {
    
      // 9. 函数组件没办法从this中获取context,所以要借助上下文对象ThemeContext的Consumer来获取
      return <ThemeContext.Consumer>
        { value => <p>这是孙子函数组件,从Father组件中获取到共享数据: {value}</p> }
      </ThemeContext.Consumer>
    }
    
    context使用
    context使用

    异步组件加载

    • React.lazy
      React.lazy通常会和Suspense结合,来达成异步加载的效果,它类似vue3 defineAsyncComponent的作用;
    import React,{ Component, Suspense } from 'react';
    // 异步导入组件
    const AsyncComp = React.lazy(() => import('./FormInput'))
    class SuspenseDemo extends Component {
      render() {
          // fallback代表异步操作之前的展示效果
         return <Suspense fallback={<div>Loading...</div>}>
            {/* 这里是异步引入的组件 */}
            <AsyncComp/>
          </Suspense>
      }
    }
    
    export default SuspenseDemo;
    

    shouldComponentUpdate 优化

    shouldComponentUpdatereact的一个生命周期,顾名思义,就是用于设置是否进行组件更新,常用的场景是用来优化子组件的渲染
    SCU默认返回true,即react默认重新渲染所有子组件,当父组件内容更新时,所有子组件都要更新,无论这子组件内容是否有更新;
    我们可以在子组件的shouldComponentUpdate生命周期中设置,只有当子组件某些状态(注意这里最好是用不可变状态来判断,否则性能优化代价太大)发生更新时,我们才返回true让其重新渲染,从而提升渲染性能;否则返回false,不渲染

    shouldComponentUpdate(nextProps, nextState) {
         // 只有父组件xxx状态改变时,当前子组件才重新渲染
        if(nextProps.xxx !== this.props.xxx) {
          return true;
        }
        return false;
      }
    

    因为这个讲起来篇幅太长,这里不再扩展,想具体了解的,可以参考 shouldComponentUpdate

    PureComponent 和 memo

    PureComponentmeno其实就是react内部提供的具有SCU浅比较优化的Component组件,PureComponent(纯组件)针对的是类组件的使用方式,而meno针对的是函数组件的使用方式,当props或者state改变时,PureComponent将对propsstate进行浅比较,如果有发生改变的话,则重新渲染,否则不渲染。

    注意,使用PureComponentmeno的前提是,使用不可变值的状态,否则这个浅比较是起不到优化作用的

    对大部分需求来说,PureComponentmeno已经能满足性能优化的需求了,但这要求我们设计的数据层级不要太深,且要使用不可变量

    PureComponent的使用非常简单,就是把React.Component换成React.PureCompoennt就可以了,它会隐式在SCU中对propsstate进行浅比较。

    // 改为PureComponent
    import React, { PureComponent } from 'react';
    export default class PureCompDemo extends PureComponent {
      // ...
    }
    

    memo的用法,稍微麻烦一些,需要自己手写一个类似的scu浅拷贝的方法,然后通过React.memo将这个方法应用到函数组件返回:

    import React from 'react'
    // 要使用的函数组件
    function Mycomponent(props) {
      console.log('render')
      return <p>{props.name}</p>
    }
    
    // 需要自己手写一个类似scu的方法
    function areEqual(preProps, nextProps) {
      // console.log(preProps.name, nextProps.name)
      if(preProps.name !== nextProps.name) {
        return true;
      }
      return false;
    }
    
    // 通过memo将手写的SCU使用到函数组件中
    export default React.memo(Mycomponent, areEqual)
    

    了解 Immutable

    前面我们多次提到Immutable不可变值的理念,但是是怎么使用的呢?

    Immutable顾名思义,就是不可改变的值,它是一种持久化数据。一旦被创建就不会被修改。修改Immutable对象的时候返回新的Immutable。但是原数据不会改变。使用旧数据创建新数据的时候,会保证旧数据同时可用且不变,同时为了避免深度复制复制所有节点的带来的性能损耗,Immutable使用了结构共享,即如果对象树种的一个节点发生变化,只修改这个节点和受他影响的父节点,其他节点则共享。

    Immutable其实不单react中可以使用,在其它地方也可以使用,只不过它和react的理念十分紧密,所以通常会结合起来一起使用和说明。

    先看下它的基本使用:
    npm i immutable

    import immutable from "immutable";
    
    export default function ImmutableDemo() {
      let map = immutable.Map({
        name: '小花',
        age: 3
      })
      console.log(map)  // Map {size: 2, ...}
      // map原对象永远不会改变,只有创建新对象
      let map1 = map.update('name', (val) => '小小')
      return <div>
        <p>{map.get('name')}</p>
        <p>{map.get('age')}</p>
        <p>{map1.get('name')}</p>
      </div>
    }
    

    简单总结一下:

    • 是不可变值的
    • 基于共享数据(但不是深拷贝),速度好
    • 有一定的学习和迁移成本,按需使用

    相关文章

      网友评论

          本文标题:react 高级特性整理

          本文链接:https://www.haomeiwen.com/subject/pekfyltx.html