高阶组件(Higher Order Component) 不属于 React API 范畴, 但是它在 React 中也是一种实用的技术, 它可以将常见任务抽象成一个可重用的部分
。这个小节算是番外篇, 会结合 cpreact(前文实现的类 react 轮子) 与 HOC 进行相关的实践。
它可以用如下公式表示:
y = f(x),// x: 原有组件// y: 高阶组件// f():
f()
的实现有两种方法, 下面进行实践。
属性代理是装饰器模式的一种运用, 通过装饰器函数给原来函数赋能。下面例子在装饰器函数中给被装饰的组件传递了额外的属性 { 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 } />)}}}@ppHOCclass 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 } />)}}}@ppDecorateclass 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 表单中实现伪双向绑定的效果。
继承反转的核心是: 传入 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>)}}}}@iiHOCclass B extends Component {render() {return (<span>Inheritance Inversion</span>)}}
在这个 demo 中, 在 HOC 内实现了渲染劫持, 页面上最终显示如下:
可能会有疑惑, 使用
属性代理
的方式貌似也能实现渲染劫持呀, 但是那样做没有继承反转
这种方式纯粹。