高阶组件:接收函数作为输入,或者输出另一个函数的一类函数,被称作高阶函数。对于高阶组件,它描述的便是接受React组件作为输入,输出一个新的React组件的组件。即高阶组件通过包裹(wrapped)被传入的React组件,经过一系列处理,最终返回一个相对增强(enhanced)的React组件,供其他组件调用。
什么时候使用高阶组件:在React开发过程中,发现有很多情况下,组件需要被"增强",比如说给组件添加或者修改一些特定的props,一些权限的管理,或者一些其他的优化之类的。而如果这个功能是针对多个组件的,同时每一个组件都写一套相同的代码,明显显得不是很明智,所以就可以考虑使用HOC。
react-redux 的 connect 方法就是一个 HOC ,他获取 wrappedComponent ,在 connect 中给 wrappedComponent 添加需要的 props。
// define
const withTitle = WrappedComponent => class extends React.Component {
render() {
return (
<WrappedComponent
{...this.props}
/>
)
}
}
// use
@withTitle
class Demo extends React.Component {
render() {
// ...
}
}
上面语法等同于 const EnhanceDemo = withTitle(Demo)
如果 Demo 是 stateless 组件,则只能 const EnhanceDemo = withTitle(Demo)
这种写法
因为 withTitle
返回一个匿名组件,所以在调试的时候:

可以加一个 displayName
解决:
const withTitle = WrappedComponent => class extends React.Component {
static displayName = `HOC(${WrappedComent.displayName})`
render() {
return (
<WrappedComponent
{...this.props}
/>
)
}
}

const withTitle = title => WrappedComponent => class extends React.Component {
render() {
return <div>
<div>
{ title || '我是标题'}
</div>
<WrappedComponent {...this.props}/>
</div>
}
}
@withTitle('test title')
class A extends React.Component {
constructor(props){
super(props)
}
render(){
return(
<h3>{this.props.children}</h3>
)
}
}
相对 HOC 来说,父组件可以做什么,不可以做什么?我们详细地总结一下:
- 渲染劫持 (在 Inheritance Inversion 一节讲到)
- 操作内部 props (在 Inheritance Inversion 一节讲到)
- 提取 state。但也有它的不足。只有在显式地为它创建钩子函数后,你才能从父组件外面访问到它的 props。这给它增添了一些不必要的限制。
- 用新的 React 组件包裹。这可能是唯一一种父组件比 HOC 好用的情况。HOC 也可以做到。
- 操作子组件会有一些陷阱。例如,当子组件没有单一的根节点时,你得添加一个额外的元素包裹所有的子组件,这让你的代码有些繁琐。在 HOC 中单一的根节点会由 React/JSX语法来确保。
- 父组件可以自由应用到组件树中,不像 HOC 那样需要给每个组件创建一个类。
一般来讲,可以用父组件的时候就用父组件,它不像 HOC 那么 hacky,但也失去了 HOC 可以提供的灵活性。
之所以被称为 Inheritance Inversion 就是因为 WrappedComponent 被 Hoc 继承了,而不是 WrappedComponent 继承 Hoc。
// II 最简实现
function iiHOC(WrappedComponent) {
return class Enhancer extends WrappedComponent {
render() {
return super.render()
}
}
}
Inheritance Inversion 允许 HOC 通过 this 访问到 WrappedComponent,意味着它可以访问到 state、props、组件生命周期方法和 render 方法。
至于为什么能够继承 WrappedComponent 的 state、props、lifecycle、render,详见 es2015 的 class 语法
HOC 控制着 WrappedComponent 的渲染输出,可以用它做各种各样的事
通过渲染劫持你可以:
- 在由 render输出的任何 React 元素中读取、添加、编辑、删除 props
- 读取和修改由 render 输出的 React 元素树
- 有条件地渲染元素树
- 把样式包裹进元素树(就像在 Props Proxy 中的那样)
// demo1: 条件渲染
// 当 this.props.loggedIn 为 true 时,这个 HOC 会完全渲染 WrappedComponent 的渲染结果。(假设 HOC 接收到了 loggedIn 这个 prop)
function iiHOC(WrappedComponent) {
return class Enhancer extends WrappedComponent {
render() {
if (this.props.loggedIn) {
return super.render()
} else {
return null
}
}
}
}
// demo2: 修改由 render 方法输出的 React 组件树
// 如果 WrappedComponent 的输出在最顶层有一个 input,那么就把它的 value 设为 “may the force be with you”
function iiHOC(WrappedComponent) {
return class Enhancer extends WrappedComponent {
render() {
const elementsTree = super.render()
let newProps = {};
if (elementsTree && elementsTree.type === 'input') {
newProps = {value: 'may the force be with you'}
}
const props = Object.assign({}, elementsTree.props, newProps)
const newElementsTree = React.cloneElement(elementsTree, props, elementsTree.props.children)
return newElementsTree
}
}
}
HOC 可以读取、编辑和删除 WrappedComponent 实例的 state,如果你需要,你也可以给它添加更多的 state。
export function IIHOCDEBUGGER(WrappedComponent) {
return class II extends WrappedComponent {
render() {
return (
<div>
<h2>HOC Debugger Component</h2>
<p>Props</p> <pre>{JSON.stringify(this.props, null, 2)}</pre>
<p>这里的 this.state 竟然是 WrappedComponent 的 state</p>
<p>State</p><pre>{JSON.stringify(this.state, null, 2)}</pre>
{super.render()}
</div>
)
}
}
}
hoc 有两种实现方式:
- Props Proxy(PP): HOC 操作 WrappedComponent 的 props
- Inheritance Inversion(II): HOC 继承 WrappedComponent
// PP 最简实现
function ppHOC(WrappedComponent) {
return class PP extends React.Component {
render() {
return <WrappedComponent {...this.props}/>
}
}
}
- 操作 props
- 通过 refs 访问组件实例
- 提取 state
- 用其他元素包裹 WrappedComponent
可以读取、添加、编辑、删除传给 WrappedComponent 的 props。
function ppHOC(WrappedComponent) {
return class PP extends React.Component {
render() {
const newProps = {
user: currentLoggedInUser
}
return <WrappedComponent {...this.props} {...newProps}/>
}
}
}
function PPHOC(WrappedComponent) {
return class PP extends React.Component {
proc(wrappedComponentInstance) {
wrappedComponentInstance.xxx() // 调用组件实例的 xxx 方法
}
render() {
const props = Object.assign({}, this.props, {ref: this.proc.bind(this)}) // 合并 props
return (
<div>
<WrappedComponent {...props} />
</div>
)
}
}
}
@PPHOC
class Example extends React.Component {
constructor(props) {
super(props)
}
xxx () {
console.log('this is xxx method')
}
render() {
return (
<div>
<h2>
Wrapped Component
</h2>
</div>
)
}
}
通过传入 props 和回调函数把 state 提取出来
function ppHOC(WrappedComponent) {
return class PP extends React.Component {
constructor(props) {
super(props)
this.state = {
name: ''
}
this.onNameChange = this.onNameChange.bind(this)
}
onNameChange(event) {
this.setState({
name: event.target.value
})
}
render() {
const newProps = {
name: {
value: this.state.name,
onChange: this.onNameChange
}
}
return <WrappedComponent {...this.props} {...newProps}/>
}
}
}
@ppHOC
class Example extends React.Component {
render() {
return <input name="name" {...this.props.name}/>
}
}
这个 input 会自动成为受控input
用来封装样式、布局等
function ppHOC(WrappedComponent) {
return class PP extends React.Component {
render() {
return (
<div style={{display: 'block'}}>
<WrappedComponent {...this.props}/>
</div>
)
}
}
}
逻辑复用