共计 5946 个字符,预计需要花费 15 分钟才能阅读完成。
前言
高阶组件在 React 应用中,非常非常重要。空有一个想学好 React 的心,却没有一个好的教程。希望这篇文章可以帮组到你,把 React 学的更好。通过这篇文章你可以学到高阶组件的定义及原理、高阶组件在项目中的常见应用、通用高阶组件如何封装以及继承方式高阶组件和代理方式高阶组件。
搭建项目
create-react-app myapp 创建一个 react 项目,并在 src 目录下新建一个 components 放置组件 A、B、C 三个组件。
将 A 组件作为高阶组件
import React, {Component} from ‘react’
function A(WrappedComponed) {
return class A extends Component {
render() {
return (
<div className=”a-container”>
<div className=”header”>
<div> 提示 </div>
<div>x</div>
</div>
<div>
<WrappedComponed />
</div>
</div>
)
}
}
}
export default A
B 组件和 C 组件作为参数传递给 A
import React, {Component} from ‘react’
import A from ‘./A’
class B extends Component {
render() {
return (
<div>
這是组件 B
<img src={require(‘../images/B.png’)} alt=””/>
</div>
)
}
}
export default A(B)
A 组件其实就是一个 function, 通过接受一个参数返回一个 react 组件,而接收的参数又是一个组件,这就是一个简单的高阶组件。高阶组件就是接受一个组件作为参数并返回一个新组件的函数,高阶组件是一个函数,并不是一个组件。高阶组件带来的好处是多个组件都需要某个相同的功能,使用高阶组件减少重复的实现,比如我们上述的 B / C 组件都需要 A。最后效果
高阶组件的实现
一、编写高阶组件
实现一个普通组件
将普通组件使用函数包裹
二、使用高阶组件
higherOrderComponent(WrappedComponent)
@ higherOrderComponent – 装饰器模式高阶组件可以看做是装饰器模式 (Decorator Pattern) 在 React 的实现。即允许向一个现有的对象添加新的功能,同时又不改变其结构,属于包装模式 (Wrapper Pattern) 的一种
ES7 中添加了一个 decorator 的属性,使用 @符表示,可以更精简的书写。但在 create-react-app 中并不直接支持,大家可以自行 google
创建一个 D 组件
import React, {Component} from ‘react’
function d(WrappedComponent) {
return class D extends Component {
render() {
return (
<div>
這是高阶组件
<WrappedComponent />
</div>
)
}
}
}
export default d
使用装饰器 @
import React, {Component} from ‘react’
// import A from ‘./A’
import d from ‘./D’
@d
class B extends Component {
render() {
return (
<div>
這是组件 B
<img src={require(‘../images/B.png’)} alt=””/>
</div>
)
}
}
export default B
效果如下图:如果学到这里大家应该都学会了如何创建和使用高阶组件了,但是高阶组件就是这一点点知识吗?答案肯定是 NO,接下来让我们一起看看在实战中是如何应用高阶组件的。
高阶组件的应用
代理方式的高阶组件
返回的新数组类直接继承 React.Component 类,新组件扮演的角色传入参数组件的一个代理,在新组件的 render 函数中,将被包裹组件渲染出来,除了高阶组件自己要做的工作,其余功能全部转手给被包裹的组件。
代理方式的高阶组件主要有以下四个方面的运用: 操纵 prop、访问 ref、抽取状态、包装组件
操纵 prop
修改下 A 组件, 代理方式
import React, {Component} from ‘react’
export default (title) => WrappedComponent => class A extends Component {
render() {
return (
<div className=”a-container”>
<div className=”header”>
<div>{title}</div>
<div>x</div>
</div>
<div>
<WrappedComponent {…this.props}/>
</div>
</div>
)
}
}
在 B 中添加 props:
import React, {Component} from ‘react’
import A from ‘./A’
class B extends Component {
render() {
return (
<div>
這是组件 B
<br />
我的名字叫: {this.props.name}
我的年龄是:{this.props.age}
<img src={require(‘../images/B.png’)} alt=””/>
</div>
)
}
}
export default A(‘ 提示 ’)(B)
现在我们要做的是通过高阶组件对组件 B 属性进行修改。我们先添加一个性别组件。我们不在 APP.js 中通过
这样的方式将性别引入,而是在我们的高阶组件 A 中进行操作
<WrappedComponent sex={‘ 男 ’} {…this.props} />
上面讲述的是属性的增加,那么属性的删减呢
import React, {Component} from ‘react’
export default (title) => WrappedComponent => class A extends Component {
render() {
const {age, …otherProps} = this.props
return (
<div className=”a-container”>
<div className=”header”>
<div>{title}</div>
<div>x</div>
</div>
<div>
<WrappedComponent sex={‘ 男 ’} {…otherProps} />
</div>
</div>
)
}
}
这样在我们的 otherProps 中是没有 age 这个属性的,因此就达到了属性的删减。
访问 ref
我们在 C 组件中定义一个 getName 方法,
getName() {
return ‘ 我是 C 组件 ’
}
但是怎么在高阶组件 A 中调用到呢?其实 i 很简单就是在高阶组件中添加 ref
import React, {Component} from ‘react’
export default (title) => WrappedComponent => class A extends Component {
refc(instance) {
instance.getName && alert(instance.getName())
} // instanc:WrappedComponent 组件的实例
render() {
const {age, …otherProps} = this.props
return (
<div className=”a-container”>
<div className=”header”>
<div>{title}</div>
<div>x</div>
</div>
<div>
<WrappedComponent sex={‘ 男 ’} {…otherProps} ref={this.refc.bind(this)} />
</div>
</div>
)
}
}
打印的我是 C 组件其实就是我们在 C 组件中定义的 getName 方法。通过这种方法可以操作任何被包裹组件的方法,甚至操作任何一个 DOM。
抽取状态
在 B 组件中增加一个输入框
import React, {Component} from ‘react’
import A from ‘./A’
class B extends Component {
constructor(props) {
super(props)
this.state = {
value: ”
}
}
changeInput(e) {
console.log(e)
this.setState({
value: e.target.value
})
}
render() {
return (
<div>
這是组件 B
<input type=’text’ value={this.state.value} onInput={this.changeInput.bind(this)}/>
<br />
我的名字叫: {this.props.name}
我的年龄是:{this.props.age}
<br />
我的性别是:{this.props.sex}
<img src={require(‘../images/B.png’)} alt=””/>
</div>
)
}
}
export default A(‘ 提示 ’)(B)
单个组件的状态书写方式,如果很多组件都需要 input,那么就会重复代码,因此我们需要将状态抽离到高阶组件 A 中。
import React, {Component} from ‘react’
export default (title) => WrappedComponent => class A extends Component {
refc(instance) {
// instance.getName && alert(instance.getName())
}
constructor(props) {
super(props)
this.state = {
value: ”
}
}
changeInput= (e) => {
this.setState({
value: e.target.value
})
}
render() {
const {age, …otherProps} = this.props
const newProps = {
value: this.state.value,
onInput: this.changeInput
}
return (
<div className=”a-container”>
<div className=”header”>
<div>{title}</div>
<div>x</div>
</div>
<div>
<WrappedComponent {…newProps} sex={‘ 男 ’} {…otherProps} ref={this.refc.bind(this)} />
</div>
</div>
)
}
}
在 B 组件我们接受一个 newProps 状态
<input type=’text’ {…this.props}/>
回到页面,发现跟上面的是一样,这样我们就将组件的状态抽离出来了, 如果 C 组件需要 input,只需要将添加一个 input 输入框就行了。极大的简化了代码。
继承方式的高阶组件
采用继承关联作为参数的组件和返回的组件,加入传入的组件参数是 WrappedComponent, 那么返回的组件就是直接继承自 WrappedComponent
通过代码的对比,我们不难发现代理方式的高阶组件和继承方式的高阶组件的区别:
继承的类不同。代理方式继承的是 React 的 Component, 继承方式继承的则是 WrappedComponent
返回的方式不同
操纵 prop
新建一个 E 继承高阶组件
import React, {Component} from ‘react’;
const modifyPropsHOC = (WrappedComponent) => class NewComponent extends WrappedComponent {
render() {
const element = super.render()
const newStyle = {
color: element.type === ‘div’ ? ‘red’: ‘green’
}
const newProps = {…this.props, style: newStyle}
return React.cloneElement(element, newProps,element.props.children)
}
}
export default modifyPropsHOC
在 F、G 组件中使用继承组件
import React, {Component} from ‘react’
import E from ‘./E’
@E
export default class G extends Component {
render() {
return (
<p>
我是 p
</p>
)
}
}
这就是我们通过继承方式的高阶组件来操纵 props。高阶组件需要根据参数来渲染组件,不建议使用。
操作生命周期
在 G 组件中
import React, {Component} from ‘react’
import E from ‘./E’
@E
export default class G extends Component {
componentWillMount() {
alert(‘ 我是原始生命周期 ’)
}
render() {
return (
<p>
我是 p
</p>
)
}
}
在继承高阶组件 E 中修改 G 中的属性
import React, {Component} from ‘react’;
const modifyPropsHOC = (WrappedComponent) => class NewComponent extends WrappedComponent {
componentWillMount() {
alert(‘ 我是更改生命周期 ’)
}
render() {
const element = super.render()
const newStyle = {
color: element.type === ‘div’ ? ‘red’: ‘green’
}
const newProps = {…this.props, style: newStyle}
return React.cloneElement(element, newProps,element.props.children)
}
}
export default modifyPropsHOC
总结
高阶组件最大的好处就是解耦和灵活性,在 react 的开发中还是很有用的。当然这不可能是高阶组件的全部用法。掌握了它的一些技巧,还有一些限制,你可以结合你的应用场景,发散思维,尝试一些不同的用法。
你可以跟着文章尝试一遍,也可以直接 clone 项目到本地跑跑。项目地址:React-hightComponet 学习
当然也建议去慕课网观看宋老师的详细教学视频慕课网地址