React中的高阶组件&Protals&Fragment

94 阅读7分钟

高阶函数

定义

  • 接收一个或多个函数作为输入
  • 输出一个组件

在js中常见的高阶函数有 map filter forEach reduce 函数

高阶组件 (Higher-Order Components,简称为 HOC)

定义

1.高阶组件的参数是一个组件 返回值为新组件的一个函数

  • 高阶组件本身不是一个组件 而是一个函数 返回一个组件
  • 高阶组件就是对传入的组件进行拦截 处理一些特殊逻辑 再把传入组件输出出去
  • 高阶组件的参数本身也是一个组件
  • 高阶组件并不是ReactApi的一部分 它只是基于React的组合特征而形成的一种设计模式

总结:React高阶组件是一种用于复用组件逻辑的高级技巧。高阶组件本身并不是React API的一部分,它们只是一种基于React的组合特性而形成的设计模式。高阶组件是参数为组件,返回值为新组件的函数

  1. 比如在Redux中的connect
  2. 比如在Redux中的withRouter
  3. export default connect(fn1,fn2)(Home) 作用就是 将Redux中的数据 插入到Home组件的props中
import React, { PureComponent } from 'react'

// 定义一个高阶组件
function hoc(Cpn) {
  // 1.定义类组件
  class NewCpn extends PureComponent {
    render() {
      return <Cpn name="123"/>
    }
  }
    // 可以修改组件的名称
  NewCpn.displayName='newName'
  return NewCpn

  // 定义函数组件
  // function NewCpn2(props) {

  // }
  // return NewCpn2
}

class HelloWorld extends PureComponent {
  render() {
    return <h1>Hello World</h1>
  }
}

const HelloWorldHOC = hoc(HelloWorld)

export class App extends PureComponent {
  render() {
    return (
      <div>
        <HelloWorldHOC/>
      </div>
    )
  }
}

export default App

高阶组件的应用场景

应用一:props的增强

当父组件给他的子代/孙代 来传递数据的时候 可以使用高阶组件进行输入组件的包裹 达到输出的组件也会有父组件传递的参数

import { createContext } from "react"
import ThemeContext from './context/theme_context'

// 定义高阶组件
function withTheme(OriginComponment) {
  return (props) => {
    return (
      <ThemeContext.Consumer>
        {
          value => {
          // 把自身的props 和Context 传递的值 都放到了组件props
            return <OriginComponment {...value} {...props}/>
          }
        }
      </ThemeContext.Consumer>
    )
  }
}

export default withTheme



定义父组件
import React, { PureComponent } from 'react'
import ThemeContext from './context/theme_context'
import Product from './pages/Product'

export class App extends PureComponent {
  render() {
    return (
      <div>
        <ThemeContext.Provider value={{color: "red", size: 30}}>
          <Product/>
        </ThemeContext.Provider>
      </div>
    )
  }
}


子组件使用
import React, { PureComponent } from 'react'
import ThemeContext from '../context/theme_context'
import withTheme from '../hoc/with_theme'

export class Product extends PureComponent {
  render() {
    const { color, size } = this.props
    return (
      <div>
        <h2>Product: {color}-{size}</h2>
      </div>
    )
  }
}
// 因为导出的时候 用高阶组件包裹了原始组件 所以在原始组件内部可以使用props
export default withTheme(Product)


渲染判断健全

大多数页面都是需要用户登录成功后才可以访问 如果没登录成功 需要返回首页


// 高阶组件
function loginAuth(OriginComponent) {
  return props => {
    // 从localStorage中获取token
    const token = localStorage.getItem("token")

    if (token) {
      return <OriginComponent {...props}/>
    } else {
      return <h2>请先登录, 再进行跳转到对应的页面中</h2>
    }
  }
}

export default loginAuth

//子组件
import React, { PureComponent } from 'react'
import loginAuth from '../hoc/login_auth'

export class Cart extends PureComponent {
  render() {
    return (
      <h2>Cart Page</h2>
    )
  }
}
export default loginAuth(Cart)

父组件
import React, { PureComponent } from 'react'
import Cart from './pages/Cart'

export class App extends PureComponent {
  constructor() {
    super()

    // this.state = {
    //   isLogin: false
    // }
  }

  loginClick() {
    localStorage.setItem("token", "coderwhy")
    // this.setState({ isLogin: true })
    // 可以使用setState来进行重新渲染 也可以用forceUpdate方法进行强制重新渲染
    this.forceUpdate()
  }

  render() {
    return (
      <div>
        App
        <button onClick={e => this.loginClick()}>登录</button>
        <Cart/>
      </div>
    )
  }
}

export default App

应用三 生命周期的劫持

可以劫持通用的生命周期 在生命周期中执行自己的逻辑

import { PureComponent } from "react";

function logRenderTime(OriginComponent) {
  return class extends PureComponent {
  // 这个生命周期已经在16.3不建议使用了 可以在constructor中定义开始时间
    UNSAFE_componentWillMount() {
      this.beginTime = new Date().getTime()
    }
  
    componentDidMount() {
      this.endTime = new Date().getTime()
      const interval = this.endTime - this.beginTime
      console.log(`当前${OriginComponent.name}页面花费了${interval}ms渲染完成!`)
    }

    render() {
      return <OriginComponent {...this.props}/>
    }
  }
}

export default logRenderTime

//子组件
import React, { PureComponent } from 'react'
import logRenderTime from '../hoc/log_render_time'

export class Detail extends PureComponent {

  render() {
    return (
      <div>
        <h2>Detail Page</h2>
        <ul>
          <li>数据列表1</li>
          <li>数据列表2</li>
          <li>数据列表3</li>
          <li>数据列表4</li>
          <li>数据列表5</li>
          <li>数据列表6</li>
          <li>数据列表7</li>
          <li>数据列表8</li>
          <li>数据列表9</li>
          <li>数据列表10</li>
        </ul>
      </div>
    )
  }
}

export default logRenderTime(Detail)

父组件
import React, { PureComponent } from 'react'
import Detail from './pages/Detail'

export class App extends PureComponent {
  render() {
    return (
      <div>
        <Detail/>
      </div>
    )
  }
}


高阶组件的缺陷

在早期的React有提供组件之间的复用就是用的Mixin

Mixin是一种代码复用的方式,它通过将可复用的代码块注入到一个类中来实现代码复用。Mixin是一种在多重继承中复用代码的方式,但是在JavaScript中,由于没有多重继承,所以Mixin通常是通过将一个对象的属性复制到另一个对象中来实现的。

Mixin弊端

  1. 可能会互相依赖 互相耦合 不便于代码维护
  2. 不同的Mixin中的方法可能会有冲突
  3. Mixin非常多时,组件处理起来会比较麻烦,甚至还要为其做相关处理,这样会给代码造成滚雪球式的复杂性

HOC的弊端

  1. HOC需要在原组件上进行包裹或者嵌套,如果大量使用HOC,将会产生非常多的嵌套,这让调试变得非常困难;
  2. HOC可以劫持props,在不遵守约定的情况下也可能造成冲突 (原始组件本身props自带name。但是被HOC劫持做了修改)

React和Mixin的对比

在React中,高阶组件和Mixin都可以用于代码复用,但是它们的实现方式不同。高阶组件是一个函数它接受一个组件作为参数,并返回一个新的组件。Mixin是一个对象,它包含了一些可复用的代码块,这些代码块可以被注入到一个组件中

Hooks的出现,是开创性的,它解决了很多React之前的存在的问题

比如this指向问题、比如hoc的嵌套复杂度问题等等

Protals

Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案:

  • 第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或 fragment;
  • 第二个参数(container)是一个 DOM 元素;
import React, { PureComponent } from 'react'
import { createPortal } from "react-dom"
import Modal from './Modal'

export class App extends PureComponent {
  render() {
    return (
      <div className='app'>
        <h1>App H1</h1>
        // 他并没有重新渲染一个新的div标签 而是找到特定的元素下面 进行插入
        {
          createPortal(<h2>App H2</h2>, document.querySelector("#why"))
        }

        {/* 2.Modal组件 封装组件 */}
        <Modal>
          <h2>我是标题</h2>
          <p>我是内容, 哈哈哈</p>
        </Modal>
      </div>
    )
  }
}

export default App

fragment

在之前的开发中,我们总是在一个组件中返回内容时包裹一个div元素: 如果不需要有一个根的话 可以使用Fragment 来进行一个包裹(类似于Vue中的template) Vue3+TypeScript

React还提供了Fragment的短语法:

  1. 它看起来像空标签 <> </>;
  2. 但是如果我们需要在Fragment中添加key,那么就不能使用短语法

在vue3中 代码可以不需要一个根元素 在内部也是使用的是Fragment

import React, { PureComponent, Fragment } from 'react'

export class App extends PureComponent {
  constructor() {
    super() 

    this.state = {
      sections: [
        { title: "哈哈哈", content: "我是内容, 哈哈哈" },
        { title: "呵呵呵", content: "我是内容, 呵呵呵" },
        { title: "嘿嘿嘿", content: "我是内容, 嘿嘿嘿" },
        { title: "嘻嘻嘻", content: "我是内容, 嘻嘻嘻" },
      ]
    }
  }

  render() {
    const { sections } = this.state

    return (
      <>
        <h2>我是App的标题</h2>
        <p>我是App的内容, 哈哈哈哈</p>
        <hr />

        {
          sections.map(item => {
            return (
              <Fragment key={item.title}>
                <h2>{item.title}</h2>
                <p>{item.content}</p>
              </Fragment>
            )
          })
        }
      </>
    )
  }
}

export default App

StrictMode 严格模式

是一个用来突出显示应用程序中潜在问题的工具

  • 与 Fragment 一样,StrictMode 不会渲染任何可见的 UI
  • 他是作用于后代元素出发额外的警告和检查
  • 严格模式紧紧作用于开发环境 不会影响生产构建
import React, { PureComponent, StrictMode } from 'react'
// import { findDOMNode } from "react-dom"
import Home from './pages/Home'
import Profile from './pages/Profile'

export class App extends PureComponent {
  render() {
    return (
      <div>
      // Home开启了严格模式
        <StrictMode>
          <Home/>
        </StrictMode>
        // Profile没开启严格模式
        <Profile/>
      </div>
    )
  }
}

export default App

严格模式检查的是什么

  1. 一些已经废弃/不安全的生命周期 UNSAFE_componentWillMount
  2. 使用过时的ref <h2 ref="title">Profile Title</h2> this.$refs.title
  3. 检查意外的副作用
  • 这个组件的constructor会被调用两次;
  • 这是严格模式下故意进行的操作,让你来查看在这里写的一些逻辑代码被调用多次时,是否会产生一些副作用;
  • 在生产环境中,是不会被调用两次的;
  • 在React18之后 使用ReactDevTool 插件时候。第二次打印出来的结果 会偏灰色
  1. 使用废弃的findDOMNode方法
    在之前的React API中,可以通过findDOMNode来获取DOM,不过已经不推 荐使用了。
  2. 检测过时的context API
    早期的Context是通过static属性声明Context对象属性,通过getChildContext返回Context对象等方式来使用Context的

**总结:**一般情况下 如果不引用第三方库api的话 可以开启严格模式 但是因为有一些三方库的api并不符合代码规范 但是还要使用的话。就不建议开启严格模式