React之组件的复用render props模式及高阶组件

组件复用

△组件复用就是将组件中相同的业务逻辑抽取出来进行封装
△复用两种东西:

  • state
  • 操作 state 的方法

△实现复用有两种方式:

  • render props 模式

  • 高阶组件(HOC)

△组件复用没有新的API,是由 React 自身特点(编码技巧)演化而来的固定模式

render props模式

1.创建鼠标位置组件

核心思想:子组件向父组件传值,父组件渲染页面时,使用子组件提供的state值

实现思路:

  • 将要操作的状态(state) 和 操作状态的方法封装到子组件中
  • render 函数中不再渲染页面,而是返回一个可供父组件调用的方法,并将state作为参数传入
  • 父组件在调用子组件时可以传入函数,函数的形参能够接收到子组件传递的state

实现步骤:

  1. 创建子组件 Mouse,在该组建中提供状态和修改状态的代码
  2. Mouse 的 render 函数不再渲染页面,而是通过props向外界提供一个函数,并将state作为参数传入
  3. 在App组件中渲染Mouse,并在Mouse中使用属性调用函数

Mouse组件 (component/mouse.js):

import React from 'react'

class Mouse extends React.Component {
    
    
  state = {
    
    
    x: 0,
    y: 0
  }
  
  moveMouse = e => {
    
    
    this.setState({
    
    
      x: e.clientX,
      y: e.clientY
    })
  }

  componentDidMount () {
    
    
    window.addEventListener('mousemove', this.moveMouse)
  }

  render () {
    
    
    // mouse组件不再渲染页面,而是向外提供一个叫做render的方法
    // 并且将 state 作为参数传入
    return this.props.render(this.state)
  }
}

export default Mouse

2.复用鼠标位置组件

实现思路: 在App组件(父组件)中调用 Mouse组件时,使用 render 方法得到Mouse组件(子组件)中提供的state,再进行页面渲染工作

App组件:

class App extends React.Component {
    
    
  render () {
    
    
    return (
      <div>
        
        <Mouse render={
    
    
           mouse => {
    
    
           	 return <div>鼠标位置: {
    
    mouse.x} - {
    
    mouse.y}</div>
           }
        }/>

        <Mouse render={
    
    
          mouse => {
    
    
            return <img src={
    
    img} alt="飞翔的大猪" style={
    
    {
    
    left: mouse.x - 50, top: mouse.y - 50}} />
        }} />
      </div>
    )
  }
}

3.children代替render props

  • return this.props.render(this.state) 中的 render 并不是固定的名称,使用什么都行
  • render props 核心思想还是利用子向父传值时候的回调函数。父组件向子组件传递一个函数,自组件执行该函数时将子组件中的state作为实参传入。
  • children 是一个更好的选择,因为组件如果写成双标签形式,标签内部会被自动认为是 children

实现方案:

  1. mouse组件中,将返回值的 this.props.render 改为 this.props.children
  2. 调用Mouse组件时,将函数执行放在Mouse组件中

mouse-1.js:

import React from 'react'

class Mouse extends React.Component {
    
    
  state = {
    
    
    x: 0,
    y: 0
  }

  moveMouse = e => {
    
    
    this.setState({
    
    
      x: e.clientX,
      y: e.clientY
    })
  }

  componentDidMount () {
    
    
    window.addEventListener('mousemove', this.moveMouse)
  }

  render () {
    
    
    // 使用 children 替换 render
    return this.props.children(this.state)
  }
}

export default Mouse

index.js:

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

import Mouse from './components/mouse-1'
import img from './assets/images/pig.jpg'
import './assets/css/pig.css'

class App extends React.PureComponent {
    
    
  // 鼠标位置
  mousePosition = data => {
    
    
    return (
      <div>鼠标位置: {
    
    data.x} - {
    
    data.y}</div>
    )
  }
  // 飞猪
  pig = data => {
    
    
    return (
      <img src={
    
    img} alt="飞猪" style={
    
    {
    
    left: data.x, top: data.y}} />
    )
  }

  render () {
    
    
    return (
      <div>
        {
    
    /* 调用组件时将函数写在组件标签之间 */}
        <Mouse>
          {
    
     this.mousePosition }
        </Mouse>
        <Mouse>
          {
    
     this.pig }
        </Mouse>
      </div> 
    )
  }
}

ReactDOM.render(<App />, document.querySelector('#app'))

高阶组件

  • 高阶组件(HOC,Higher-Order-Component) 采用 包装模式 实现组件的复用

  • 高阶组件就是一个函数,接收要包装组件,返回一个增强功能的组件

1.基本使用

核心思想: 父组件向子组件传值,父组件提供state,并在调用子组件时将state传递给子组件;子组件使用porps接收state的值,进行页面渲染

实现步骤:

  1. 创建增强组件函数
    △ 创建一个函数,名称必须 以 with 开头
    △ 指定函数参数,参数名也要以大写字母开头 (实际上是组件名称)
    △ 在函数内部创建一个类组件,最后要返回该类组件
  2. 定义子组件,子组件使用 props 接收父组件的state,并进行渲染
  3. 在 App 组件中调用增强组件

代码实现
第一步:创建增强组件函数:

// 创建一个以 with 开头的函数
function withMouse (WarppedComponent) {
    
    
    class Mouse extends React.Component {
    
    
        state = {
    
    
            x: 0,
            y: 0
        }
        handleMouseMove = e => {
    
    
          this.setState({
    
    
            x: e.clientX,
            y: e.clientY
          })
        }
        componentDidMount () {
    
    
          window.addEventListener('mousemove', this.handleMouseMove)
        }

    	// render 方法中渲染子组件
    	render () {
    
    
          return <WarppedComponent {
    
    ...this.state}></WarppedComponent>
    return Mouse
}

第二步:定义子组件,子组件使用 props 接收父组件的state,并进行渲染:

class Position extends React.PureComponent {
    
    
  render () {
    
    
    return (
      // 使用 props 接收父组件传过来的 state,再进行调用
      <div>鼠标位置: {
    
    this.props.x} - {
    
    this.props.y}</div>
    )
  }
}
const PositionMouse = withMouse(Position)

第三步:在 App 组件中调用增强组件:

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

2.displayName

  • 同一个高阶组件使用两次时,在页面中会展示出两个相同的组件名,这样会造成调试不便的情况
  • 默认情况下,高阶组件会以 displayName 属性中的值作为标签名称
  • 在没有 displayName 的情况下使用 name 属性中的值作为标签名称

在这里插入图片描述
打印 Mouse 对象

function withMouse (WrappedComponent) {
    
    
  class Mouse extends React.PureComponent {
    
    
    state = {
    
     ... }
    handleMouseMove = e => {
    
     ... }
    componentDidMount () {
    
     ... }
    render () {
    
     ... }
  }
  console.log(Mouse)

  return Mouse
}

在这里插入图片描述
打印 Mouse 类能看见 name 属性,但是看不见 displayName 属性,所以标签名就使用 name 对应的值,
但是我们手动设置 displayName 后,它的优先级是高于 name 属性的

function withMouse (WrappedComponent) {
    
    
  class Mouse extends React.PureComponent {
    
    
    state = {
    
     ... }
    handleMouseMove = e => {
    
     ... }
    componentDidMount () {
    
     ... }
    render () {
    
     ... }
  }
  // 为 Mouse 增加 displayName 属性
  Mouse.displayName = 'MyCom'                         
  // 打印 Mouse 对象
  console.log(Mouse)

  return Mouse
}

Mouse 现在的结构:
在这里插入图片描述
页面上的元素:
在这里插入图片描述


高阶组件名称差异化的解决方案: 动态设置组件的 displayName

(如果组件本身有 displayName 就使用 displayName,如果没有就使用组件的 name)

3.传递props

渲染高阶组件时,如果给组件传递属性和值会丢失,因为高阶组件没有向下继续再传递值

解决方案: 渲染 WarppedComponent 时,将 state 和 props 一起传递给组件

class App extends React.PureComponent {
    
    
  render () {
    
    
    return (
      <div>
        {
    
    /* 渲染高阶组件时,向组件中传入数据 */}
        <PositionMouse a={
    
    123} />
      </div>
    )
  }
}

function withMouse (WarppedComponent) {
    
    
  class Mouse extends React.PureComponent {
    
    
    state = {
    
    ...}
    handleMouseMove = e => {
    
    ...}
    componentDidMount () {
    
    ...}

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

  Mouse.displayName = `withMouse${
      
      getDisplayName(WarppedComponent)}`

  return Mouse
}

class Position extends React.PureComponent {
    
    
  render () {
    
    
    console.log(this.props)
    return (
      <div>鼠标位置: {
    
    this.props.x} - {
    
    this.props.y}</div>
    )
  }
}

注意:

  1. render 中调用子组件,同时将 state 和 props 都传入 WarppedComponent
  2. Position 组件中的 props 即接收了Mouse组件传递的 state 又接收了props,所以在this.props中保存了

猜你喜欢

转载自blog.csdn.net/PILIpilipala/article/details/113404786