react 高级特性整理

2021-06-19  本文已影响0人  miao8862

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

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

函数组件

类组件和函数组件对比

两者的选择:

非受控组件

非受控组件,就是不受组件内部state控制的组件,这时表单数据将交由 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

使用场景:

受控和非受控选择:

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

使用场景:

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使用

异步组件加载

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>
}

简单总结一下:

上一篇下一篇

猜你喜欢

热点阅读