一、什么是高阶组件
⾼阶组件(HOC)是 React 中⽤于复⽤组件逻辑的⼀种⾼级技巧。HOC ⾃身不是 React API 的⼀部分,它是⼀种基于 React 的组合特性⽽形成的设计模式。 简单点说,就是组件作为参数,返回值也是组件的函数,它是纯函数,不会修改传⼊的组件,也不会使 ⽤继承来复制其⾏为。相反,HOC 通过将组件包装在容器组件中来组成新组件。HOC 是纯函数,没有副作⽤。
二、为什么使用HOC
-
抽取重复代码,实现组件复⽤:相同功能组件复⽤
-
条件渲染,控制组件的渲染逻辑(渲染劫持):权限控制。
-
捕获/劫持被处理组件的⽣命周期,常⻅场景:组件渲染性能追踪、⽇志打点。
三、HOC实现⽅式
3.1 属性代理
使⽤组合的⽅式,将组件包装在容器上,依赖⽗⼦组件的⽣命周期关系
- 返回stateless的函数组件
- 返回class组件 操作props
// 可以通过属性代理,拦截⽗组件传递过来的props并进⾏处理。
// 返回⼀个⽆状态的函数组件
function HOC(WrappedComponent) {
const newProps = { type: 'HOC' }
return props => <WrappedComponent {...props} {...newProps} />
}
// 返回一个有状态的class组件
function HOC(WrappedComponent) {
return class extends React.Component {
render() {
const newProps = { type: 'HOC' }
return <WrappedComponet {...this.props} {...newProps}>
}
}
}
- 抽象state
function HOC(WrappedComponent) {
return class extends React.Component {
constructor(props) {
super(props)
this.state = {
name: ''
}
this.onChange = this.onChange.bind(this)
}
onChange(event) {
this.setState({
name: event.target.value,
})
}
render() {
const newProps = {
name: {
value: this.state.name,
onChange: this.onChange,
},
};
return <WrappedComponent {...this.props} {...newProps} />;
}
}
}
- 条件渲染
function HOC(WrappedComponent) {
return (props) => {
<div>
{
props.isShow ? (<WrappedComponent {...props} />) : <div>暂无数据</div>
}
</div>
}
}
3.2 反向继承
⽤⼀个函数接受⼀个组件作为参数传⼊,并返回⼀个继承了该传⼊组件的类组件,且在返回组件的 render() ⽅法中返回 super.render() ⽅法
const HOC = (WrappedComponent) => {
return class extends WrappedComponent {
render() {
return super.render();
}
}
}
-
允许HOC通过this访问到原组件,可以直接读取和操作原组件的state/ref等
-
可以通过super.render()获取传⼊组件的render,可以有选择的渲染劫持
-
劫持原组件⽣命周期⽅法
function HOC(WrappedComponent) {
const didMount = WrappedComponent.prototype.componentDidMount
return class HOC extends WrappedComponent {
async componentDidMount() {
if(didMount) {
await didMount.apply(this)
}
this.setState({ number: 2 })
}
render() {
return super.render()
}
}
}
- 条件渲染
const HOC = (WrappedComponent: any) => {
return class extends WrappedComponent {
render() {
if(this.props.isRender) {
return super.render()
}else {
return <div>暂无数据</div>
}
}
}
}
3.3 实际用例
- 电影列表
功能:通过自定义接口给目标组件传递数据
components/hoc.jsx
export const withFetchingHOC = (WrappedComponent, fetchingMethod, defaultProps) => {
return class extends Component {
state = {
data: []
}
async componentDidMount() {
const data = await fetchingMethod()
this.setState({
data
})
}
render() {
return <WrappedComponent
data={this.state.data}
{...defaultProps}
{...this.props}
></WrappedComponent>
}
}
}
components/test.tsx
interface test1Props {
data: Array<String>
}
export default function test1(props: test1Props) {
let data = props.data
return (<>
{
data.map((item, index) => {
return (
<div key={index}>
{ item }
</div>)
})
}
</>)
}
utils/index.ts
export const fetchMovieListByType = (type: string) => {
return () => new Promise((resolve, reject) => {
let actionArr = ['蜘蛛侠', '钢铁侠', '蝙蝠侠']
let likeArr = ['泰坦尼克号', '怦然心动']
let comicArr = ['海贼王', '电锯人', '火影忍者']
if(type === 'action') {
resolve(actionArr)
}else if(type === 'like') {
resolve(likeArr)
}else {
resolve(comicArr)
}
})
}
app.tsx
import { withFetchingHOC } from './components/hoc'
import { fetchMovieListByType } from './utils/index'
import Test from './components/test'
import './App.css'
let HocTest = withFetchingHOC(Test, fetchMovieListByType('like'), {})
let HocTest2 = withFetchingHOC(Test, fetchMovieListByType('action'), {})
function App() {
return (
<div>
<HocTest></HocTest>
<div className='split'></div>
<HocTest2></HocTest2>
</div>
)
}
export default App
效果: