Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
142 lines (110 sloc) 4.31 KB

HOC 实践

高阶组件(Higher Order Component) 不属于 React API 范畴,但是它在 React 中也是一种实用的技术,它可以将常见任务抽象成一个可重用的部分。这个小节算是番外篇,会结合 cpreact(前文实现的类 react 轮子) 与 HOC 进行相关的实践。

它可以用如下公式表示:

y = f(x),

// x:原有组件
// y:高阶组件
// f():

f() 的实现有两种方法,下面进行实践。

属性代理(Props Proxy)

这类实现也是装饰器模式的一种运用,通过装饰器函数给原来函数赋能。下面例子在装饰器函数中给被装饰的组件传递了额外的属性 { a: 1, b: 2 }。

声明:下文所展示的 demo 均已在 cpreact 测试通过

function ppHOC(WrappedComponent) {
  return class extends Component {

    render() {
      const obj = { a: 1, b: 2 }
      return (
        <WrappedComponent { ...this.props } { ...obj } />
      )
    }
  }
}

@ppHOC
class B extends Component {
  render() {
    return (
      <div>
        { this.props.a + this.props.b } { /* 输出 3 */ }
      </div>
    )
  }
}

要是将 { a: 1, b: 2 } 替换成全局共享对象,那么不就是 react-redux 中的 Connect 了么?

改进上述 demo,我们就可以实现可插拔的受控组件,代码示意如下:

function ppDecorate(WrappedComponent) {
  return class extends Component {
    constructor() {
      super()
      this.state = {
        value: ''
      }
      this.onChange = this.onChange.bind(this)
    }

    onChange(e) {
      this.setState({
        value: e.target.value
      })
    }

    render() {
      const obj = {
        onChange: this.onChange,
        value: this.state.value,
      }

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

@ppDecorate
class B extends Component {
  render() {
    return (
      <div>
        <input { ...this.props } />
        <div>{ this.props.value }</div>
      </div>
    )
  }
}

效果如下图:

这里有个坑点,当我们在输入框输入字符的时候,并不会立马触发 onChange 事件(我们想要让事件立即触发,然而现在要按下回车键或者点下鼠标才触发),在 react 中有个合成事件 的知识点,下篇文章 onChange 事件 对 react 中的 onChange 事件为何和原生 DOM 事件中的 onchange 表现不一致进行揭秘。

顺带一提在这个 demo 中似乎看到了双向绑定的效果,但是实际中 React 并没有双向绑定的概念,但是我们可以运用 HOC 的知识点结合 setState 在 React 表单中实现伪双向绑定的效果。

继承反转(Inheritance Inversion)

继承反转的核心是:传入 HOC 的组件会作为返回类的父类来使用。然后在 render 中调用 super.render() 来调用父类的 render 方法。

《ES6 继承与 ES5 继承的差异》中我们提到了作为对象使用的 super 指向父类的实例。

function iiHOC(WrappedComponent) {
  return class extends WrappedComponent {
    render() {
      const parentRender = super.render()
      if (parentRender.nodeName === 'span') {
        return (
          <span>继承反转</span>
        )
      }
    }
  }
}

@iiHOC
class B extends Component {
  render() {
    return (
      <span>Inheritance Inversion</span>
    )
  }
}

在这个 demo 中,在 HOC 内实现了渲染劫持,页面上最终显示如下:

可能会有疑惑,使用属性代理的方式貌似也能实现渲染劫持呀,但是那样做没有继承反转这种方式纯粹。

相关链接