高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。
高阶组件是参数为组件,返回值为新组件的函数
在此期间,我们可以对该组件进行props处理,事件处理等工作
复用逻辑
对组件进行加工处理,根据需求来定制专属化的HOC
强化props
劫持上一层传过来的props,混入新的props
赋能组件
HOC有一项独特的特性,就是可以给被HOC包裹的业务组件,提供一些拓展功能,比如说额外的生命周期,额外的事件,但是这种HOC,可能需要和业务组件紧密结合
控制渲染
劫持渲染是hoc一个特性,在wrapComponent包装组件中,可以对原来的组件,进行条件渲染,节流渲染,懒加载等功能
使用方式
装饰器模式
函数包裹
两种不同的高阶组件
正向的属性代理
function HOC(WrapComponent){
return class Advance extends React.Component{
state={
name:'loong'
}
render(){
return <WrapComponent { ...this.props } { ...this.state } />
}
}
}
我们来看一下上面这个例子,return 返回结果是父组件(代理组件)对子组件(业务组件)的一系列操作
在 fiber tree上,先mounted组件,然后才是我们的业务组件
反向的组件继承
class Index extends React.Component{
render(){
return <div> hello,world </div>
}
}
function HOC(Component){
return class wrapComponent extends Component{
}
}
export default HOC(Index)
HOC采用继承的方式, 代理组件继承了业务组件的本身,我们在使用的时候直接实例化代理组件HOC即可
很多文章对其有很多分类,我这里就按照我的理解去分类
增强props
混入props 代理组件state状态会混入(上面有混入的例子), 初次之外我们还可以进行控制state的更新
function classHOC(WrapComponent){
return class Idex extends React.Component{
constructor(){
super()
this.state={
name:'alien'
}
}
changeName(name){
this.setState({ name })
}
render(){
return <WrapComponent { ...this.props } { ...this.state } changeName={this.changeName.bind(this) } />
}
}
}
function Index(props){
const [ value ,setValue ] = useState(null)
const { name ,changeName } = props
return <div>
<div> hello,world , my name is { name }</div>
改变name <input onChange={ (e)=> setValue(e.target.value) } />
<button onClick={ ()=> changeName(value) } >确定</button>
</div>
}
export default classHOC(Index)
我们看这个例子,代理组件中changeName={this.changeName.bind(this)}
,绑定changeName方法。用来更新name属性(代理组件中,说实话让我想起来了闭包)。
题外话:上面的例子中changeName绑定的是一个函数,然后利用bind改变其this指向(返回值是一个函数),在Index组件使用的时候 , 才可以正确更新到代理组建的state。如果不绑定就会找不到该方法。
控制渲染
代理组件通过处理可以控制子组件是否显示
可以通过变量控制,也可以通过其它方式。达到控制渲染的目的就可以了
我们来看一个优化的例子💨
function HOC (Component){
return function renderWrapComponent(props){
const { num } = props
const RenderElement = useMemo(() => <Component {...props} /> ,[ num ])
return RenderElement
}
}
class Index extends React.Component{
render(){
console.log(`当前组件是否渲染`,this.props)
return <div>hello,world, my name is alien </div>
}
}
const IndexHoc = HOC(Index)
export default ()=> {
const [ num ,setNumber ] = useState(0)
const [ num1 ,setNumber1 ] = useState(0)
const [ num2 ,setNumber2 ] = useState(0)
return <div>
<IndexHoc num={ num } num1={num1} num2={ num2 } />
<button onClick={() => setNumber(num + 1) } >num++</button>
<button onClick={() => setNumber1(num1 + 1) } >num1++</button>
<button onClick={() => setNumber2(num2 + 1) } >num2++</button>
</div>
}
上面的例子应该很好理解,代理组件通过useMemo钩子来依据props传入的num,来决定是否更新子组件
劫持生命周期
function HOC (Component){
const proDidMount = Component.prototype.componentDidMount
Component.prototype.componentDidMount = function(){
console.log('劫持生命周期:componentDidMount')
proDidMount.call(this)
}
return class wrapComponent extends React.Component{
render(){
return <Component {...this.props} />
}
}
}
也是很好理解,代理组件保存子组建的生命周期函数,然后重新设置对应的生命周期函数
事件处理
我们来看分析下面这个例子💨
function ClickHoc (Component){
return function Wrap(props){
const dom = useRef(null)
useEffect(()=>{
const handerClick = () => console.log('发生点击事件')
dom.current.addEventListener('click',handerClick)
return () => dom.current.removeEventListener('click',handerClick)
},[])
return <div ref={dom} ><Component {...props} /></div>
}
}
其实就是代理组件上面加了一个click监听事件
获取自定义组件ref
我们知道在自定义组件上面ref是无用的,我们可以使用代理组件包裹业务组件即可,ref={dom}
在代理组件上,即可获取
return <div ref={dom} ><Component {...props} /></div>
可以参卡`推荐文章`中的这一章节,写的很详细