美文网首页
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 高级特性整理

    这一篇会整理一些react常见的高级特性以及它们的应用场景: 函数组件 非受控组件 protals context...

  • Java高级特性整理

    Java高级特性 反射 使用场景 功能 获取Class对象方式 判断是否为类的实例 创建实例 newInstanc...

  • 最新web前端相关课程学习链接

    js基础篇 js进阶篇 js高级篇 vue基础篇 vue高级篇 react基础 react高级 Nodejs基础 ...

  • 使用Netty,我们到底在开发些什么?

    您可能感兴趣的文章: 大数据成神之路系列:Java高级特性增强-集合Java高级特性增强-多线程Java高级特性增...

  • React 高阶组件

    高阶组件是一种组件复用的高级技巧,是基于React的组合特性而形成的一种设计模式。高阶组件(Higher Orde...

  • React特性精华

    以下内容是我在学习和研究React时,对React的特性、重点和注意事项的提取、精练和总结,可以做为React特性...

  • 再聊react hook

    React Hook是React函数式组件,它不仅仅有函数组件的特性,还带有React框架的特性。所以,官网文档多...

  • 状态逻辑复用:React Hooks 与 Vue Functio

    React Hooks 是 React16.8 引入的新特性,支持在类组件之外使用 state、生命周期等特性。 ...

  • 高级特性

    1.切片:取一个list或tuple的部分元素是非常常见的操作,比如说取list中的前n个元素,我们用循环来实现这...

  • 高级特性

    1.切片(Slice)# python提供了切片(Slice)操作符,能大大简化取数据操作L = ['aa','b...

网友评论

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

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