React--高阶组件(HOC)

高阶函数

一个函数执行的返回结果还是一个函数时,它就是一个高阶函数,如下

function debounce (fn, delay = 500, context) {
  let timer = null
  return function (...args) {
    if (timer) {
      clearTimeout(timer)
    }
    timer = setTimeout(() => {
      fn.apply(context, args)
      timer = null // 闭包释放
    }, delay)
  }
}

防抖函数执行后,返回一个新的函数,新函数内部通过闭包获取实参及变量timer

高阶组件(HOC)

高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式

函数执行后返回的是一个组件,该函数就是一个高阶组件,如下:

function logProps (WrappedComponent) {
  return class extends React.Component {
    componentDidUpdate(oldProps) {
      console.log('oldProps: ', oldProps)
      console.log('newProps: ', this.props)
    }

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

该函数的作用是,为一个组件打印props(通过注册componentDidUpdate钩子函数,在组件活动周期,父级传递的props变化重新渲染组件,会执行该钩子函数),功能并不实用,仅举例子

使用HOC可以很好地解决横切关注点问题

示例

官网链接:https://react.docschina.org/docs/higher-order-components.html

使用高阶组件根据横切关注点分离代码,优化后如下:

// 评论列表
class CommontList extends React.Component {
  render() {
    return (
      <ul>
        {this.props.data.map(commont => (<li key={commont}>{commont}</li>))}
      </ul>
    )
  }
}
// 博客文章
class BlogPost extends React.Component {
  render() {
    return (
      <ul>
        {this.props.data.text}
      </ul>
    )
  }
}

function withSubscription(WrappedComponent, selectData) {
  return class extends React.Component {
    constructor(props) {
      super(props)
      this.handleDataChange = this.handleDataChange.bind(this)
      this.state = {
        data: selectData(DataSource, this.props)
      }
    }

    componentDidMount() {
      DataSource.addChangeListener(this.handleDataChange)
    }
  
    componentWillUnmount() {
      DataSource.removeChangeListener(this.handleDataChange)
    }
  
    handleDataChange() {
      console.log(`${WrappedComponent.name ? WrappedComponent.name : 'Component' } change!`)
      this.setState({
        data: selectData(DataSource, this.props)
      })
    }

    render() {
      return <WrappedComponent data={this.state.data} {...this.props} />
    }
  }
}


// 使用【订阅】高阶组件封装
const SubscribeBlogPost = withSubscription(
							BlogPost,
							(DataSource, props) => DataSource.getBlogPost(props.id))
							
const SubscribeCommontList = withSubscription(
							CommontList,
							DataSource => DataSource.getComments())

export default class Home extends React.Component {
  state = {
    blogId: 1,
    commontInput: '',
  }

  componentDidMount () {
  }

  handleCommontInput(e) {
    this.setState({
      commontInput: e.target.value
    })
  }

  addComment() {
    DataSource.commonts = [...DataSource.getComments(), this.state.commontInput]
    this.setState({
      commontInput: ''
    })
  }

  render () {
    return (
      <div>
        <h1>Home</h1>
        <div>
          <input value={this.state.commontInput} onInput={(e) => this.handleCommontInput(e)} />
          <button onClick={() => this.addComment()}>添加评论</button>
        </div>
        <SubscribeCommontList />
        <hr />
        <SubscribeBlogPost id={this.state.blogId} />
      </div>
    )
  }
}

解释如下:

  1. DataSource是全局的数据源,一方面提供组件需要的数据,另一方面在数据变化后,执行通过DataSource.addChangeListener()方法绑定的订阅更新函数,在数据变化时,DataSource内部会自动调用所有的监听函数;
  2. 定义了一个CommentList组件和一个BlogPost组件,它们都会接收一个data属性(this.props.data)而该属性的数据类型,则完全由高阶组件withSubscription的第二个参数selectData函数确定
  3. 高阶组件withSubscription内部初始化会调用一次selectData()获取数据,当数据源DataSource变化后,DataSource trigger所有的监听函数,也就是handleDataChange()方法,setState({ data: selectData(DataSource, this.props}),更新了高阶组件的状态后,其被包裹的组件也会刷新

使用建议

  • 高阶组件应当是一个纯函数,不要掺杂其他副作用
  • 不要改变原始组件,使用组合
    • 高阶组件的分离横切关注点应该颗粒化更好,以方便组件的复用和维护
    • 更不要修改原始传入的组件特性,应当使用组合包装,例如:在上文【订阅】高阶组件中继续添加打印props的功能
function logProps (WrappedComponent) {
  return class extends React.Component {
    componentDidUpdate(oldProps) {
      console.log('oldProps: ', oldProps)
      console.log('newProps: ', this.props)
    }

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

const LogPropsSubscribeBlogPost = logProps(withSubscription(
									BlogPost,
									(DataSource, props) => DataSource.getBlogPost(props.id)))
const LogPropsSubscribeCommontList = logProps(withSubscription(
									CommontList,
									DataSource => DataSource.getComments()))
  1. 上例需要打印props是新的需求,虽然也可以在withSubscription高阶组件内直接修改,毕竟不冲突,但是如果那样加了之后,**打印props**就和【订阅】功能绑定在一起了,所以最好分开;
  2. 方便复用高阶组件,没有修改原组件

你可能感兴趣的:(React,react.js,javascript,前端)