React组件常见设计模式

498 阅读6分钟
  1. render props pattern
  2. compound component pattern
  3. context compound component pattern
  4. custom hook pattern
  5. state reducer pattern
  6. props getter pattern

render props pattern

核心思想:将一个函数作为prop 传递给组件,该函数返回一个能够渲染组件的React 元素

示例

import React from "react";

class MouseTracker extends React.Component{
  constructor(props){
    super(props)
    this.state={
      x:0,
      y:1
    }
  }

  handleMouseMove=(event)=>{
    this.setState({
      x:event.clientX,
      y:event.clientY
    })
  }

  render(){
    return(
      <div onMouseMove={this.handleMouseMove}>
        {this.props.render(this.state)}
      </div>
    )
  }
}

function App(){
  return (
    <MouseTracker render={(mouse)=>(
      <h1>The mouse position is({mouse.x},{mouse.y})</h1>
    )}/>
  )
}


在上面这个示例中,创建了一个MouseTracker组件,通过render prop将一个函数传递给子组件,该函数接收MouseTracker组件的状态作为参数,并返回一个能够渲染组件的React元素

在App组件中,将MouseTracker组件作为子组件,并传递一个返回h1元素的函数作为render prop,这样当用户移动鼠标时,MouseTracker组件会调用传递的函数,并将当前鼠标位置作为参数传递给它,该函数返回的h1元素将作为子组件渲染到MouseTracker组件中

Render Props模式允许将任意组件作为子组件,所以我们可以将任意组件作为子组件传递给父组件,并在其中使用父组件的状态,这种模式非常适合用于共享代码和逻辑,以及在组件之间传递数据和状态的情况下使用

compound component pattern

核心思想:将多个组件作为子组件传递给一个父组件,并通过父组件来管理它们之间的关系 示例

import React from 'react'

class Accordion extends React.Component{
  static defaultProps={
    activeIndex:0
  }

  state={
    activeIndex:this.props.activeIndex
  }

  handleItemClick=(index)=>{
    this.setState({
      activeIndex:index
    })
  }

  render(){
    const children=React.Children.map(this.props.children,(child,index)=>{
      return React.cloneElement(child,{
        isActive:index===this.state.activeIndex,
        onItemClick:()=>this.handleItemClick(index),
      })
    })
    return <div>{children}</div>
  }
}

function AccordionItem({isActive,onItemClick,title,children}){
  return(
    <div>
      <h2 onClick={onItemClick}>{title}</h2>
      {isActive&&<div>{children}</div>}
    </div>
  )
}

function Demo01(){
  return(
    <Accordion>
      <AccordionItem title='item 1'  >Item1 content</AccordionItem>
      <AccordionItem title='item 2' >Item2 content</AccordionItem>
      <AccordionItem title='item 3' isActive={true}>Item3 content</AccordionItem>
    </Accordion>
  )
}

export default Demo01

在这个示例中,创建了一个Accordion组件,该组件通过多个AccordionItem子组件来实现,Accordion组件通过React.Children.map方法遍历子组件,并为每个子组件传递一个isActive prop和一个onItemClick prop,isActive prop用于确定该子组件是否处于活动状态,onItemClick prop用于处理子组件的点击事件,AccordionItem组件接收这些props并根据需要来显示或隐藏内容

在App组件中,我们将Accordion组件作为子组件使用,并传递多个AccordionItem组件作为其子组件,当用户点击AccordionItem组件时,Accordion组件会更新其状态,并通过isActive prop将活动状态传递给每个AccordionItem组件

这种模式非常适合用于创建多个关联组件的组合组件,例如菜单、选项卡和表单等

context compound component pattern

核心思想:将共享状态存储在一个Context中,并通过多个子组件来访问该Context 示例

import React from "react";

const AccordionContext=React.createContext()


class Accordion extends React.Component{
  static defaultProps={
    activeIndex:0
  }

  state={
    activeIndex:this.props.activeIndex
  }

  handleItemClick=(index)=>{
    this.setState({
      activeIndex:index
    })
  }

  render(){
    return(
      <AccordionContext.Provider value={{
        activeIndex:this.state.activeIndex,
        onItemClick:this.handleItemClick
      }}>
        <div>{this.props.children}</div>
      </AccordionContext.Provider>
    )
  }
}


function AccordionItem({ title, children }) {
  return (
    <AccordionContext.Consumer>
      {({ activeIndex, onItemClick }) => (
        <div>
          <h2 onClick={() => onItemClick(activeIndex === title ? null : title)}>
            {title}
          </h2>
          {activeIndex === title && <div>{children}</div>}
        </div>
      )}
    </AccordionContext.Consumer>
  );
}
  
function Demo02() {
  return (
    <Accordion>
      <AccordionItem title="Item 1">Item 1 content</AccordionItem>
      <AccordionItem title="Item 2">Item 2 content</AccordionItem>
      <AccordionItem title="Item 3">Item 3 content</AccordionItem>
    </Accordion>
  );
}

export default Demo02

在这个示例中,创建了一个Accordion组件和一个AccordionContext,Accordion组件通过AccordionContext.Provider将状态和事件处理函数存储到Context中,并通过this.props.children渲染子组件,AccordionItem组件从Context中获取状态和事件处理函数,并根据需要来显示或隐藏内容

在Demo02组件中,将Accordion组件作为子组件使用,并传递多个AccordionItem组件作为其子组件,每个AccordionItem组件都从AccordionContext中获取状态和事件处理函数,并使用它们来控制其显示或隐藏内容

这种模式非常适合用于创建具有多个关联组件和共享状态的组合组件,例如菜单、选项卡和表单等

custom hook pattern

核心思想:实际上就是一个函数,该函数封装了一些通用的逻辑,可以在多个组件中使用 示例

import React,{useState} from 'react'

function useCounter(initialValue,step){
  const [count,setCount]=useState(initialValue)

  function increment(){
    setCount(count+step)
  }

  function decrement(){
    setCount(count-step)
  }

  return [count,increment,decrement]
}

function Counter(){
  const [count,increment,decrement]=useCounter(0,1)

  return(
    <div>
      <p>Count:{count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  )
}
function Demo03(){
  return(
    <div>
      <Counter />
    </div>
  )
}

export default Demo03

这种模式非常适合用于封装和共享一些通用的逻辑,例如表单处理、状态管理和数据加载等

state reducer pattern

核心思想:将状态更新逻辑封装到一个函数中,这个函数被称为reducer,并且可以被多个组件共享 示例

import React,{useReducer} from 'react'

const initialState={count:0}

function reducer(state,action){
  switch(action.type){
    case 'increment':
      return {count:state.count+1}
    case 'decrement':
      return {count:state.count-1}
    default:
      throw new Error()
  }
}

function Counter(){
  const [state,dispatch]=useReducer(reducer,initialState)

  function handleIncrement(){
    dispatch({type:'increment'})
  }

  function handleDecrement(){
    dispatch({type:'decrement'})
  }

  return(
    <div>
      <p>Count: {state.count}</p>
      <button onClick={handleIncrement}>+</button>
      <button onClick={handleDecrement}>-</button>
    </div>
  )
}

function Demo04(){
  return(
    <div>
      <Counter />
    </div>
  )
}

export default Demo04

这个示例中,创建了一个名为reducer的函数,该函数封装了计数器的逻辑,reducer函数接收两个参数:当前状态和一个表示要执行的操作的对象,在函数中,我们使用switch语句来检查操作类型并更新计数器的状态

在Counter组件中,我们使用useReducer Hook来创建计数器的状态和操作函数,并将它们存储在state和dispatch变量中,我们还创建了两个处理函数handleIncrement和handleDecrement,它们分别使用dispatch函数来执行increment和decrement操作

state reducer可以使我们更好的控制组件的状态更新流程,从而避免一些常见的错误,这种模式非常适合用于管理复杂的组件状态,例如表单处理、数据加载和多步操作等

props getter pattern

核心思想:使用一个函数来获取组件的props,然后将它们传递给子组件 示例

import React from "react";

function Button(props){
  return (
    <button onClick={props.onClick}>{props.label}</button>
  )
}

function PrimaryButton(props){
  return <Button {...props} className='primary' />
}

function Demo05(){
  function handleClick(){
    console.log('button clicked')
  }

  return(
    <div>
      <Button label='Click me' onClick={handleClick} />
      <PrimaryButton label='Click me' onClick={handleClick} />
    </div>
  )
}


export default Demo05

在这个示例中,我们创建了两个组件Button和PrimaryButton,Button组件接收onClick和label props,并将它们传递给一个<button> 元素,PrimaryButton组件则使用...props语法将所有的props传递给Button,并添加一个className='primary'的prop

在Demo05组件中,我们创建了一个handleCLick函数来处理按钮的点击事件,并将它传递给Button和PrimartButton组件,我们可以看到,PrimaryButton组件继承了Button组件的所有props,并添加了一个额外的className prop

使用Props Getter模式可以使我们更轻松地组合和重用组件,并避免一些常见的代码重复,该模式可以使我们更好的控制组件的props,并在需要时添加或修改它们,此外props getter还可以帮助我们更好的封装和隐藏组件的实现细节,从而提高代码的可维护性和可重用性,这种模式非常适合用于创建具有可重用性的UI组件库