这几天在看React,学习的路线就是先看了油管上国外Mosh的React Tutorial系列的视频(建议直接英文字幕甚至不用开,有些词汇更能达意),接着我看了胡子课堂的Reactjs小册子,你会发现有了Vue的基础和JS较好的基础之后看React真的过渡起来很快,这篇文章旨在记录下学习React高阶组件的一些特性。
高阶组件
高阶组件就是一个函数,传给它一个组件,它返回一个新的组件。
const NewComponent = higherOrderComponent(OldComponent)
上一篇文章中我写到了装饰者模式,也就是赋能篇,是不是很像!
重要的事情再重复一次,高阶组件是一个函数(而不是组件),它接受一个组件作为参数,返回一个新的组件。这个新的组件会使用你传给它的组件作为子组件,我们看看一个很简单的高阶组件:
import React, { Component } from 'react'
export default (WrappedComponent) => {
class NewComponent extends Component {
// 可以做很多自定义逻辑
render () {
return <WrappedComponent />
}
}
return NewComponent
}
现在看来好像什么用都没有,它就是简单的构建了一个新的组件类 NewComponent
,然后把传进入去的 WrappedComponent
渲染出来。但是我们可以给 NewCompoent
做一些数据启动工作:
import React, { Component } from 'react'
export default (WrappedComponent, name) => {
class NewComponent extends Component {
constructor () {
super()
this.state = { data: null }
}
componentWillMount () {
let data = localStorage.getItem(name)
this.setState({ data })
}
render () {
return <WrappedComponent data={this.state.data} />
}
}
return NewComponent
}
现在 NewComponent
会根据第二个参数 name
在挂载阶段从 LocalStorage 加载数据,并且 setState
到自己的 state.data
中,而渲染的时候将 state.data
通过 props.data
传给 WrappedComponent
。
这个高阶组件有什么用呢?假设上面的代码是在 src/wrapWithLoadData.js
文件中的,我们可以在别的地方这么用它:
import wrapWithLoadData from './wrapWithLoadData'
class InputWithUserName extends Component {
render () {
return <input value={this.props.data} />
}
}
InputWithUserName = wrapWithLoadData(InputWithUserName, 'username')
export default InputWithUserName
假如 InputWithUserName
的功能需求是挂载的时候从 LocalStorage 里面加载 username
字段作为 <input />
的 value
值,现在有了 wrapWithLoadData
,我们可以很容易地做到这件事情。
只需要定义一个非常简单的 InputWithUserName
,它会把 props.data
作为 <input />
的 value
值。然把这个组件和 'username'
传给 wrapWithLoadData
,wrapWithLoadData
会返回一个新的组件,我们用这个新的组件覆盖原来的 InputWithUserName
,然后再导出去模块。
别人用这个组件的时候实际是用了被加工过的组件:
import InputWithUserName from './InputWithUserName'
class Index extends Component {
render () {
return (
<div>
用户名:<InputWithUserName />
</div>
)
}
}
根据 wrapWithLoadData
的代码我们可以知道,这个新的组件挂载的时候会先去 LocalStorage 加载数据,渲染的时候再通过 props.data
传给真正的 InputWithUserName
。
如果现在我们需要另外一个文本输入框组件,它也需要 LocalStorage 加载 'content'
字段的数据。我们只需要定义一个新的 TextareaWithContent
:
import wrapWithLoadData from './wrapWithLoadData'
class TextareaWithContent extends Component {
render () {
return <textarea value={this.props.data} />
}
}
TextareaWithContent = wrapWithLoadData(TextareaWithContent, 'content')
export default TextareaWithContent
写起来非常轻松,我们根本不需要重复写从 LocalStorage 加载数据字段的逻辑,直接用 wrapWithLoadData
包装一下就可以了。
我们来回顾一下到底发生了什么事情,对于 InputWithUserName
和 TextareaWithContent
这两个组件来说,它们的需求有着这么一个相同的逻辑:“挂载阶段从 LocalStorage 中加载特定字段数据”。
如果按照之前的做法,我们需要给它们两个都加上 componentWillMount
生命周期,然后在里面调用 LocalStorage。要是有第三个组件也有这样的加载逻辑,我又得写一遍这样的逻辑。但有了 wrapWithLoadData
高阶组件,我们把这样的逻辑用一个组件包裹了起来,并且通过给高阶组件传入 name
来达到不同字段的数据加载。充分复用了逻辑代码。
到这里,高阶组件的作用其实不言而喻,其实就是为了组件之间的代码复用。组件可能有着某些相同的逻辑,把这些逻辑抽离出来,放到高阶组件中进行复用。高阶组件内部的包装组件和被包装组件之间通过 props
传递数据。