高阶组件是什么?
高阶组件(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组件使用的时候
<button onClick={ ()=> changeName(value) } >确定</button>
, 才可以正确更新到代理组建的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>
高阶组件实践
可以参卡`推荐文章`中的这一章节,写的很详细