当前位置: 首页 > news >正文

[React] React高阶组件(HOC)

文章目录

      • 1.Hoc介绍
      • 2.几种包装强化组件的方式
        • 2.1 mixin模式
        • 2.2 extends继承模式
        • 2.3 HOC模式
        • 2.4 自定义hooks模式
      • 3.高阶组件产生初衷
      • 4.高阶组件使用和编写结构
        • 4.1 装饰器模式和函数包裹模式
        • 4.2 嵌套HOC
      • 5.两种不同的高阶组件
        • 5.1 正向的属性代理
        • 5.2 反向的继承
      • 6.如何编写高阶组件
        • 6.1 强化props
          • 6.1.1. 混入props
          • 6.1.2 抽离state控制更新
        • 6.2 控制渲染
          • 6.2.1 条件渲染
            • 6.2.1.1 动态渲染
            • 6.2.1.2 分片渲染
            • 6.2.1.3 异步组件
            • 6.2.1.4 渲染劫持
            • 6.2.1.5 修改渲染树
          • 6.2.2 节流渲染
            • 6.2.2.1 节流原理
            • 6.2.2.2 定制化渲染流
        • 6.3 赋能组件
          • 6.3.1 劫持原型链
            • 6.3.1.1 属性代理实现
            • 6.3.1.2 反向继承实现
          • 6.3.2 事件监控
            • 6.3.2.1 组件内的事件监听
          • 6.3.3 ref助力操控组件实例
            • 6.3.3.1 属性代理-添加额外生命周期
        • 6.4 总结
      • 7.高阶组件源码级实践
        • 7.1 强化props
        • 7.2 控制渲染
        • 7.3 赋能组件
      • 8.高阶组件的注意事项
        • 8.1 谨慎修改原型链
        • 8.2 继承静态属性
          • 8.2.1 手动继承
          • 8.2.2 引入第三方库
        • 8.3 跨层级捕获ref
        • 8.4 render中不要声明HOC

1.Hoc介绍

高阶作用用于强化组件,复用逻辑,提升渲染性能等作用。

高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。

在这里插入图片描述

2.几种包装强化组件的方式

2.1 mixin模式

在这里插入图片描述

在react初期提供一种组合方法。通过React.createClass,加入mixins属性,具体用法和vue 中mixins相似。

const customMixin = {componentDidMount(){console.log( '------componentDidMount------' )},say(){console.log(this.state.name)}
}const APP = React.createClass({mixins: [ customMixin ],getInitialState(){return {name:'alien'}},render(){const { name  } = this.statereturn <div> hello ,world , my name is { name } </div>}
})

这种mixins只能存在createClass中,后来React.createClass连同mixins这种模式被废弃了。mixins会带来一些负面的影响。

  1. mixin引入了隐式依赖关系。
  2. 不同mixins之间可能会有先后顺序甚至代码冲突覆盖的问题
  3. mixin代码会导致滚雪球式的复杂性

我们可以通过原型链继承来实现mixins。

const customMixin = {  /* 自定义 mixins */componentDidMount(){console.log( '------componentDidMount------' )},say(){console.log(this.state.name)}
}function componentClassMixins(Component,mixin){ /* 继承 */for(let key in mixin){Component.prototype[key] = mixin[key]}
}class Index extends React.Component{constructor(){super()this.state={  name:'alien' }}render(){return <div> hello,world<button onClick={ this.say.bind(this) } > to say </button></div>}
}
componentClassMixins(Index,customMixin)const customMixin = {  /* 自定义 mixins */componentDidMount(){console.log( '------componentDidMount------' )},say(){console.log(this.state.name)}
}function componentClassMixins(Component,mixin){ /* 继承 */for(let key in mixin){Component.prototype[key] = mixin[key]}
}class Index extends React.Component{constructor(){super()this.state={  name:'alien' }}render(){return <div> hello,world<button onClick={ this.say.bind(this) } > to say </button></div>}
}
componentClassMixins(Index,customMixin)
2.2 extends继承模式

在这里插入图片描述

在class组件盛行之后,我们可以通过继承的方式进一步的强化我们的组件。这种模式的好处在于,可以封装基础功能组件,然后根据需要去extends我们的基础组件,按需强化组件,但是值得注意的是,必须要对基础组件有足够的掌握,否则会造成一些列意想不到的情况发生。

class Base extends React.Component{constructor(){super()this.state={name:'alien'}}say(){console.log('base components')}render(){return <div> hello,world <button onClick={ this.say.bind(this) } >点击</button>  </div>}
}
class Index extends Base{componentDidMount(){console.log( this.state.name )}say(){ /* 会覆盖基类中的 say  */console.log('extends components')}
}
export default Index
2.3 HOC模式

在这里插入图片描述

function HOC(Component) {return class wrapComponent extends React.Component{constructor(){super()this.state={name:'alien'}}render=()=><Component { ...this.props } { ...this.state } />}
}@HOC
class Index extends React.Component{say(){const { name } = this.propsconsole.log(name)}render(){return <div> hello,world <button onClick={ this.say.bind(this) } >点击</button>  </div>}
}
2.4 自定义hooks模式

在这里插入图片描述

hooks解决无状态组件没有state和逻辑难以复用问题。hooks可以将一段逻辑封装起来,做到开箱即用。

3.高阶组件产生初衷

组件是把prop渲染成UI,而高阶组件是将组件转换成另外一个组件,我们更应该注意的是,经过包装后的组件,获得了那些强化,节省多少逻辑,或是解决了原有组件的那些缺陷,这就是高阶组件的意义。

  1. 复用逻辑:高阶组件更像是一个加工react组件的工厂,批量对原有组件进行加工,包装处理。我们可以根据业务需求定制化专属的HOC,这样可以解决复用逻辑。
  2. 强化props:这个是HOC最常用的用法之一,高阶组件返回的组件,可以劫持上一层传过来的props,然后混入新的props,来增强组件的功能。代表作react-router中的withRouter。
  3. 赋能组件:HOC有一项独特的特性,就是可以给被HOC包裹的业务组件,提供一些拓展功能,比如说额外的生命周期,额外的事件,但是这种HOC,可能需要和业务组件紧密结合。典型案例react-keepalive-router中的 keepaliveLifeCycle就是通过HOC方式,给业务组件增加了额外的生命周期。
  4. 控制渲染:劫持渲染是hoc一个特性,在wrapComponent包装组件中,可以对原来的组件,进行条件渲染,节流渲染,懒加载等功能,后面会详细讲解,典型代表做react-redux中connect和 dva中 dynamic 组件懒加载。

4.高阶组件使用和编写结构

4.1 装饰器模式和函数包裹模式
@withStyles(styles)
@withRouter
@keepaliveLifeCycle
class Index extends React.Componen{/* ... */
}

越靠近Index组件的,就是越内层的HOC,离组件Index也就越近。

function Index(){/* .... */
}
export default withStyles(styles)(withRouter( keepaliveLifeCycle(Index) )) 
4.2 嵌套HOC

对于不需要传递参数的HOC,我们编写模型我们只需要嵌套一层就可以,比如withRouter。

function withRouter(){return class wrapComponent extends React.Component{/* 编写逻辑 */}
}

对于需要参数的HOC,我们需要一层代理。

function connect (mapStateToProps){/* 接受第一个参数 */return function connectAdvance(WrapComponent){/* 接受组件 */return class WrapComponent extends React.Component{  }}
}

对于代理函数,可能有一层,可能有很多层。

5.两种不同的高阶组件

正向的属性代理和反向的组件继承

5.1 正向的属性代理

用组件包裹一层代理组件, 在代理组件上,我们可以做一些,对源组件的代理操作。在fiber tree 上,先mounted代理组件,然后才是我们的业务组件。我们可以理解为父子组件关系,父组件对子组件进行一系列强化操作。

function HOC(WrapComponent){return class Advance extends React.Component{state={name:'alien'}render(){return <WrapComponent  { ...this.props } { ...this.state }  />}}
}

优点:

  • ① 正常属性代理可以和业务组件低耦合,零耦合,对于条件渲染和props属性增强,只负责控制子组件渲染和传递额外的props就可以,所以无须知道,业务组件做了些什么。所以正向属性代理,更适合做一些开源项目的hoc,目前开源的HOC基本都是通过这个模式实现的。
  • ② 同样适用于class声明组件,和function声明的组件。
  • ③ 可以完全隔离业务组件的渲染,相比反向继承,属性代理这种模式。可以完全控制业务组件渲染与否,可以避免反向继承带来一些副作用,比如生命周期的执行。
  • ④ 可以嵌套使用,多个hoc是可以嵌套使用的,而且一般不会限制包装HOC的先后顺序。

缺点:

  • ① 一般无法直接获取业务组件的状态,如果想要获取,需要ref获取组件实例。

  • ② 无法直接继承静态属性。如果需要继承需要手动处理,或者引入第三方库。

class Index extends React.Component{render(){return <div> hello,world  </div>}
}
Index.say = function(){console.log('my name is alien')
}
function HOC(Component) {return class wrapComponent extends React.Component{render(){return <Component { ...this.props } { ...this.state } />}}
}
const newIndex =  HOC(Index) 
console.log(newIndex.say)
5.2 反向的继承

反向继承和属性代理有一定的区别,在于包装后的组件继承了业务组件本身,所以我们我无须在去实例化我们的业务组件。当前高阶组件就是继承后,加强型的业务组件。

class Index extends React.Component{
render(){return <div> hello,world  </div>
}
}
function HOC(Component){return class wrapComponent extends Component{ /* 直接继承需要包装的组件 */}
}
export default HOC(Index) 

优点:

  • ① 方便获取组件内部状态,比如state,props ,生命周期,绑定的事件函数等
  • ② es6继承可以良好继承静态属性。我们无须对静态属性和方法进行额外的处理。
class Index extends React.Component{render(){return <div> hello,world  </div>}
}
Index.say = function(){console.log('my name is alien')
}
function HOC(Component) {return class wrapComponent extends Component{}
}
const newIndex =  HOC(Index) 
console.log(newIndex.say)

缺点:

  • ① 无状态组件无法使用。
  • ② 和被包装的组件强耦合,需要知道被包装的组件的内部状态,具体是做什么?
  • ③ 如果多个反向继承hoc嵌套在一起,当前状态会覆盖上一个状态。这样带来的隐患是非常大的,比如说有多个componentDidMount,当前componentDidMount会覆盖上一个componentDidMount。这样副作用串联起来,影响很大。

6.如何编写高阶组件

  1. 强化props: 混入props和抽离state控制更新。
  2. 控制渲染: 条件渲染和节流渲染。
  3. 赋值组件: 劫持原型链, 劫持生命周期,事件函数, 事件监控, ref助力操控组件。
6.1 强化props
6.1.1. 混入props

这个是高阶组件最常用的功能,承接上层的props,在混入自己的props,来强化组件。

有状态组件:

function classHOC(WrapComponent){return class Index extends React.Component{state={name:'alien'}componentDidMount(){console.log('HOC')}render(){return <WrapComponent { ...this.props }  { ...this.state }   />}}
}
function Index(props){const { name } = propsuseEffect(()=>{console.log( 'index' )},[])return <div>hello,world , my name is { name }</div>
}export default classHOC(Index)

无状态组件:

function functionHoc(WrapComponent){return function Index(props){const [ state , setState ] = useState({ name :'alien'  })       return  <WrapComponent { ...props }  { ...state }   />}
}
6.1.2 抽离state控制更新

高阶组件可以将HOC的state的配合起来,控制业务组件的更新。这种用法在react-redux中connect高阶组件中用到过,用于处理来自redux中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 } = propsreturn <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)
6.2 控制渲染
  • 条件渲染
  • 节流渲染
6.2.1 条件渲染
  1. 动态渲染。
  2. 分片渲染。
  3. 异步组件(懒加载)。
  4. 反向继承: 渲染劫持。
  5. 反向继承: 修改渲染树。
6.2.1.1 动态渲染

对于属性代理的高阶组件,虽然不能在内部操控渲染状态,但是可以在外层控制当前组件是否渲染,这种情况应用于,权限隔离,懒加载 ,延时加载等场景。

实现一个动态挂载组件的HOC

function renderHOC(WrapComponent){return class Index  extends React.Component{constructor(props){super(props)this.state={ visible:true }  }setVisible(){this.setState({ visible:!this.state.visible })}render(){const {  visible } = this.state return <div className="box"  ><button onClick={ this.setVisible.bind(this) } > 挂载组件 </button>{ visible ? <WrapComponent { ...this.props } setVisible={ this.setVisible.bind(this) }   />  : <div className="icon" ><SyncOutlined spin  className="theicon"  /></div> }</div>}}
}class Index extends React.Component{render(){const { setVisible } = this.propsreturn <div className="box" ><p>hello,my name is alien</p><img  src='https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=294206908,2427609994&fm=26&gp=0.jpg'   /> <button onClick={() => setVisible()}  > 卸载当前组件 </button></div>}
}
export default renderHOC(Index)
6.2.1.2 分片渲染

实现一个懒加载功能的HOC,可以实现组件的分片渲染,用于分片渲染页面,不至于一次渲染大量组件造成白屏效果。

const renderQueue = []
let isFirstrender = falseconst tryRender = ()=>{const render = renderQueue.shift()if(!render) returnsetTimeout(()=>{render() /* 执行下一段渲染 */},300)
} 
/* HOC */
function renderHOC(WrapComponent){return function Index(props){const [ isRender , setRender ] = useState(false)useEffect(()=>{renderQueue.push(()=>{  /* 放入待渲染队列中 */setRender(true)})if(!isFirstrender) {tryRender() /**/isFirstrender = true}},[])return isRender ? <WrapComponent tryRender={tryRender}  { ...props }  /> : <div className='box' ><div className="icon" ><SyncOutlined   spin /></div></div>}
}
/* 业务组件 */
class Index extends React.Component{componentDidMount(){const { name , tryRender} = this.props/* 上一部分渲染完毕,进行下一部分渲染 */tryRender()console.log( name+'渲染')}render(){return <div><img src="https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=294206908,2427609994&amp;fm=26&amp;gp=0.jpg" /></div>}
}
/* 高阶组件包裹 */
const Item = renderHOC(Index)export default () => {return <React.Fragment><Item name="组件一" /><Item name="组件二" /><Item name="组件三" /></React.Fragment>
}

初始化的时候,HOC中将渲染真正组件的渲染函数,放入renderQueue队列中,然后初始化渲染一次,接下来,每一个项目组件,完成 didMounted 状态后,会从队列中取出下一个渲染函数,渲染下一个组件, 一直到所有的渲染任务全部执行完毕,渲染队列清空,有效的进行分片的渲染。

用HOC实现了条件渲染-分片渲染的功能,实际条件渲染理解起来很容易,就是通过变量,控制是否挂载组件,从而满足项目本身需求,条件渲染可以演变成很多模式。

6.2.1.3 异步组件

dva里面的dynamic就是应用HOC模式实现的组件异步加载。

/* 路由懒加载HOC */
export default function AsyncRouter(loadRouter) {return class Content extends React.Component {state = {Component: null}componentDidMount() {if (this.state.Component) returnloadRouter().then(module => module.default).then(Component => this.setState({Component},))}render() {const {Component} = this.statereturn Component ? <Component {...this.props}/> : null}}
}const Index = AsyncRouter(()=>import('../pages/index'))

hoc还可以配合其他API,做一下衍生的功能。如上配合import实现异步加载功能。

6.2.1.4 渲染劫持

HOC反向继承模式,可以实现颗粒化的渲染劫持,也就是可以控制基类组件的render函数,还可以篡改props,或者是children。

const HOC = (WrapComponent) =>class Index  extends WrapComponent {render() {if (this.props.visible) {return super.render()} else {return <div>暂无数据</div>}}}
6.2.1.5 修改渲染树

修改渲染状态(劫持render替换子节点)

class Index extends React.Component{render(){return <div><ul><li>react</li><li>vue</li><li>Angular</li></ul></div>}
}function HOC (Component){return class Advance extends Component {render() {const element = super.render()const otherProps = {name:'alien'}/* 替换 Angular 元素节点 */const appendElement = React.createElement('li' ,{} , `hello ,world , my name  is ${ otherProps.name }` )const newchild =  React.Children.map(element.props.children.props.children,(child,index)=>{if(index === 2) return appendElementreturn  child}) return  React.cloneElement(element, element.props, newchild)}}
}
export  default HOC(Index)

用劫持渲染的方式,来操纵super.render()后的React.element元素,然后配合 createElement , cloneElement , React.Children 等 api,可以灵活操纵,真正的渲染react.element。

6.2.2 节流渲染

hoc除了可以进行条件渲染,渲染劫持功能外,还可以进行节流渲染,也就是可以优化性能。

  1. 节流原理。
  2. 定制化渲染流。
6.2.2.1 节流原理

hoc可以配合hooks的useMemo等API配合使用,可以实现对业务组件的渲染控制,减少渲染次数,从而达到优化性能的效果。如下案例,我们期望当且仅当num改变的时候,渲染组件,但是不影响接收的props。

function HOC (Component){return function renderWrapComponent(props){const { num } = propsconst 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>
}

我们只有点击 num++时候,才重新渲染子组件,点击其他按钮,只是负责传递了props,达到了期望的效果。

6.2.2.2 定制化渲染流

我们需要对上述hoc进行改造升级,是组件可以根据定制化方向,去渲染组件。也就是Hoc生成的时候,已经按照某种契约去执行渲染。

function HOC (rule){return function (Component){return function renderWrapComponent(props){const dep = rule(props)const RenderElement = useMemo(() =>  <Component {...props}  /> ,[ dep ])return RenderElement}}
}
/* 只有 props 中 num 变化 ,渲染组件  */
@HOC( (props)=> props['num'])
class IndexHoc extends React.Component{render(){console.log(`组件一渲染`,this.props)return <div> 组件一 : hello,world </div>}
}/* 只有 props 中 num1 变化 ,渲染组件  */
@HOC((props)=> props['num1'])
class IndexHoc1 extends React.Component{render(){console.log(`组件二渲染`,this.props)return <div> 组件二 : my name is alien </div>}
}
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 }  /><IndexHoc1  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>
}

可以灵活控制React组件层面上的,props数据流和更新流。

6.3 赋能组件

外生命周期,劫持事件,监控日志等等。

  1. 劫持原型链, 劫持生命周期,事件函数。
  2. 事件监控。
  3. ref助力操控组件实例。
6.3.1 劫持原型链

劫持原型链, 劫持生命周期,事件函数。

6.3.1.1 属性代理实现
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}  />}}
}
@HOC
class Index extends React.Component{componentDidMount(){console.log('———didMounted———')}render(){return <div>hello,world</div>}
}
6.3.1.2 反向继承实现

因为在继承原有组件的基础上,可以对原有组件的生命周期或事件进行劫持,甚至是替换。

function HOC (Component){const didMount = Component.prototype.componentDidMountreturn class wrapComponent extends Component{componentDidMount(){console.log('------劫持生命周期------')if (didMount) {didMount.apply(this) /* 注意 `this` 指向问题。 */}}render(){return super.render()}}
}@HOC
class Index extends React.Component{componentDidMount(){console.log('———didMounted———')}render(){return <div>hello,world</div>}
}
6.3.2 事件监控

HOC还可以对原有组件进行监控。比如对一些事件监控,错误监控,事件监听等一系列操作。

6.3.2.1 组件内的事件监听
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>}
}@ClickHoc
class Index extends React.Component{render(){return <div  className='index'  ><p>hello,world</p><button>组件内部点击</button></div>}
}
export default ()=>{return <div className='box'  ><Index /><button>组件外部点击</button></div>
}
6.3.3 ref助力操控组件实例

对于属性代理我们虽然不能直接获取组件内的状态,但是我们可以通过ref获取组件实例,获取到组件实例,就可以获取组件的一些状态,或是手动触发一些事件,进一步强化组件,但是注意的是:class声明的有状态组件才有实例,function声明的无状态组件不存在实例。

6.3.3.1 属性代理-添加额外生命周期

针对某一种情况, 给组件增加额外的生命周期,我做了一个简单的demo,监听number改变,如果number改变,就自动触发组件的监听函数handerNumberChange。

function Hoc(Component){return class WrapComponent extends React.Component{constructor(){super()this.node = null}UNSAFE_componentWillReceiveProps(nextprops){if(nextprops.number !== this.props.number ){this.node.handerNumberChange  &&  this.node.handerNumberChange.call(this.node)}}render(){return <Component {...this.props} ref={(node) => this.node = node }  />}}
}
@Hoc
class Index extends React.Component{handerNumberChange(){/* 监听 number 改变 */}render(){return <div>hello,world</div>}
}
6.4 总结

对于属性代理HOC:

  1. 强化props & 抽离state。
  2. 条件渲染,控制渲染,分片渲染,懒加载。
  3. 劫持事件和生命周期
  4. ref控制组件实例
  5. 添加事件监听器,日志

对于反向代理的HOC:

  1. 劫持渲染,操纵渲染树
  2. 控制/替换生命周期,直接获取组件状态,绑定事件。

7.高阶组件源码级实践

  1. 强化prop- withRoute
  2. 控制渲染案例 connect
  3. 赋能组件-缓存生命周期 keepaliveLifeCycle
7.1 强化props

withRoute: 对于没有被Route包裹的组件,给添加history对象等和路由相关的状态,方便我们在任意组件中,都能够获取路由状态,进行路由跳转。强化props,把Router相关的状态都混入到props中。

function withRouter(Component) {const displayName = `withRouter(${Component.displayName || Component.name})`;const C = props => {/*  获取 */const { wrappedComponentRef, ...remainingProps } = props;return (<RouterContext.Consumer>{context => {return (<Component{...remainingProps}{...context}ref={wrappedComponentRef}/>);}}</RouterContext.Consumer>);};C.displayName = displayName;C.WrappedComponent = Component;/* 继承静态属性 */return hoistStatics(C, Component);
}export default withRouter

先从props分离出ref和props, 然后从存放整个route对象上下文RouterContext取出route对象,然后混入到原始组件的props中,最后用hoistStatics继承静态属性。

7.2 控制渲染

connect: connect的作用也有合并props, 但是更重要的是接受state, 来控制更新组件。

import store from './redux/store'
import { ReactReduxContext } from './Context'
import { useContext } from 'react'
function connect(mapStateToProps){/* 第一层: 接收订阅state函数 */return function wrapWithConnect (WrappedComponent){/* 第二层:接收原始组件 */function ConnectFunction(props){const [ , forceUpdate ] = useState(0)const { reactReduxForwardedRef ,...wrapperProps } = props/* 取出Context */const { store } = useContext(ReactReduxContext)/* 强化props:合并 store state 和 props  */const trueComponentProps = useMemo(()=>{/* 只有props或者订阅的state变化,才返回合并后的props */return selectorFactory(mapStateToProps(store.getState()),wrapperProps) },[ store , wrapperProps ])/* 只有 trueComponentProps 改变时候,更新组件。  */const renderedWrappedComponent = useMemo(() => (<WrappedComponent{...trueComponentProps}ref={reactReduxForwardedRef}/>),[reactReduxForwardedRef, WrappedComponent, trueComponentProps])useEffect(()=>{/* 订阅更新 */const checkUpdate = () => forceUpdate(new Date().getTime())store.subscribe( checkUpdate )},[ store ])return renderedWrappedComponent}/* React.memo 包裹  */const Connect = React.memo(ConnectFunction)/* 处理hoc,获取ref问题 */  if(forwardRef){const forwarded = React.forwardRef(function forwardConnectRef( props,ref) {return <Connect {...props} reactReduxForwardedRef={ref} reactReduxForwardedRef={ref} />})return hoistStatics(forwarded, WrappedComponent)} /* 继承静态属性 */return hoistStatics(Connect,WrappedComponent)} 
}
export default Index

第一层接受订阅函数,第二层接收原始组件,然后用forwardRef处理ref,用hoistStatics 处理静态属性的继承,在包装组件内部,合并props,useMemo缓存原始组件,只有合并后的props发生变化,才更新组件,然后在useEffect内部通过store.subscribe()订阅更新。

7.3 赋能组件

缓存生命周期 keepaliveLifeCycle: actived 作为缓存路由组件激活时候用,初始化的时候会默认执行一次 , unActived 作为路由组件缓存完成后调用。但是生命周期需要用一个 HOC 组件keepaliveLifeCycle 包裹。

import React from 'react'
import { keepaliveLifeCycle } from 'react-keepalive-router'@keepaliveLifeCycle
class index extends React.Component<any,any>{state={activedNumber:0,unActivedNumber:0}actived(){this.setState({activedNumber:this.state.activedNumber + 1})}unActived(){this.setState({unActivedNumber:this.state.unActivedNumber + 1})}render(){const { activedNumber , unActivedNumber } = this.statereturn <div  style={{ marginTop :'50px' }}  ><div> 页面 actived 次数: {activedNumber} </div><div> 页面 unActived 次数:{unActivedNumber} </div></div>}
}
export default index
import {lifeCycles} from '../core/keeper'
import hoistNonReactStatic from 'hoist-non-react-statics'
function keepaliveLifeCycle(Component) {class Hoc extends React.Component {cur = nullhanderLifeCycle = type => {if (!this.cur) returnconst lifeCycleFunc = this.cur[type]isFuntion(lifeCycleFunc) && lifeCycleFunc.call(this.cur)}componentDidMount() { const {cacheId} = this.propscacheId && (lifeCycles[cacheId] = this.handerLifeCycle)}componentWillUnmount() {const {cacheId} = this.propsdelete lifeCycles[cacheId]}render=() => <Component {...this.props} ref={cur => (this.cur = cur)}/>}return hoistNonReactStatic(Hoc,Component)
}

keepaliveLifeCycle: 通过ref或获取 class 组件的实例,在 hoc 初始化时候进行生命周期的绑定, 在 hoc 销毁阶段,对生命周期进行解绑, 然后交给keeper统一调度,keeper通过调用实例下面的生命周期函数,来实现缓存生命周期功能的。

8.高阶组件的注意事项

  1. 谨慎修改原型链
  2. 继承静态属性
  3. 跨层级捕获ref
  4. render中不要声明HOC
8.1 谨慎修改原型链
function HOC (Component){const proDidMount = Component.prototype.componentDidMount Component.prototype.componentDidMount = function(){console.log('劫持生命周期:componentDidMount')proDidMount.call(this)}return  Component
}

这样做会产生一些不良后果。比如如果你再用另一个同样会修改 componentDidMount 的 HOC 增强它,那么前面的 HOC 就会失效!同时,这个 HOC 也无法应用于没有生命周期的函数组件。

8.2 继承静态属性
8.2.1 手动继承

我们可以手动将原始组件的静态方法copy到 hoc组件上来,但前提是必须准确知道应该拷贝哪些方法。

function HOC(Component) {class WrappedComponent extends React.Component {/*...*/}// 必须准确知道应该拷贝哪些方法 WrappedComponent.staticMethod = Component.staticMethodreturn WrappedComponent
}
8.2.2 引入第三方库

原生组件的静态方法是未知的,我们可以使用 hoist-non-react-statics 自动拷贝所有的静态方法。

import hoistNonReactStatic from 'hoist-non-react-statics'
function HOC(Component) {class WrappedComponent extends React.Component {/*...*/}hoistNonReactStatic(WrappedComponent,Component)return WrappedComponent
}
8.3 跨层级捕获ref

高阶组件的约定是将所有 props 传递给被包装组件,但这对于 refs 并不适用。那是因为 ref 实际上并不是一个 prop - 就像 key 一样,它是由 React 专门处理的。如果将 ref 添加到 HOC 的返回组件中,则 ref 引用指向容器组件,而不是被包装组件。我们可以通过forwardRef来解决这个问题。

function HOC(Component,isRef){class Wrap extends React.Component{render(){const { forwardedRef ,...otherprops  } = this.propsreturn <Component ref={forwardedRef}  {...otherprops}  />}}if(isRef){return  React.forwardRef((props,ref)=> <Wrap forwardedRef={ref} {...props} /> )}return Wrap
}class Index extends React.Component{componentDidMount(){console.log(666)}render(){return <div>hello,world</div>}
}const HocIndex =  HOC(Index,true)export default ()=>{const node = useRef(null)useEffect(()=>{/* 就可以跨层级,捕获到 Index 组件的实例了 */ console.log(node.current.componentDidMount)},[])return <div><HocIndex ref={node}  /></div>
}
8.4 render中不要声明HOC
class Index extends React.Component{render(){const WrapHome = HOC(Home)return <WrapHome />}
}

因为每一次HOC都会返回一个新的WrapHome,react diff会判定两次不是同一个组件,那么每次Index 组件 render触发,WrapHome,会重新挂载,状态会全都丢失。

const WrapHome = HOC(Home)
class index extends React.Component{render(){return <WrapHome />}
}

相关文章:

[React] React高阶组件(HOC)

文章目录 1.Hoc介绍2.几种包装强化组件的方式2.1 mixin模式2.2 extends继承模式2.3 HOC模式2.4 自定义hooks模式 3.高阶组件产生初衷4.高阶组件使用和编写结构4.1 装饰器模式和函数包裹模式4.2 嵌套HOC 5.两种不同的高阶组件5.1 正向的属性代理5.2 反向的继承 6.如何编写高阶组…...

【逐步剖C++】-第二章-C++类和对象(中)

前言&#xff1a;本章继【逐步剖C】-第二章-C类和对象&#xff08;上&#xff09;介绍有关类和对象更深层次的知识点&#xff0c;这里是文章导图&#xff1a; 本文较长&#xff0c;内容较多&#xff0c;大家可以根据需求跳转到自己感兴趣的部分&#xff0c;希望能对读者有一些帮…...

PL/SQL动态SQL

目录 1. 动态 sql 2. 带参数的动态 sql -- 不使用 USING 传参 1. 动态 sql -- 在 PL/SQL 程序开发中,可以使用 DML 语句,但是很多语句(如 DDL),不能直接在 PL/SQL中执行,这些语句可以使用动态 sql 来实现. 语法格式: EXECUTE IMMEDIATE --动态语句的字符串 [into 变量…...

Python绘图系统24:添加辅助坐标轴

文章目录 辅助坐标增减坐标轴时间轴**代码优化源代码 Python绘图系统&#xff1a; 前置源码&#xff1a; Python打造动态绘图系统&#x1f4c8;一 三维绘图系统 &#x1f4c8;二 多图绘制系统&#x1f4c8;三 坐 标 轴 定 制&#x1f4c8;四 定制绘图风格 &#x1f4c8;五 数据…...

Java自学网站--十几个网站的分析与评测

原文网址&#xff1a;Java自学网站--十几个网站的分析与评测_IT利刃出鞘的博客-CSDN博客 简介 很多想学Java的人不知道怎样选教程&#xff0c;本文对Java自学网站进行评测。 本文不带主观倾向&#xff0c;只客观分析各个网站的区别。 第1类&#xff1a;大型培训机构(黑马等…...

java接口怎么写

Java接口是一种定义规范的抽象类型&#xff0c;可以包含常量和方法的声明。接口在Java编程中具有重要的作用&#xff0c;可以实现代码的重用和灵活性。本文将详细介绍Java接口的编写方式和使用方法。 一、什么是Java接口 在Java中&#xff0c;接口&#xff08;Interface&…...

第8章 Spring(二)

8.11 Spring 中哪些情况下,不能解决循环依赖问题 难度:★★ 重点:★★ 白话解析 有一下几种情况,循环依赖是不能解决的: 1、原型模式下的循环依赖没办法解决; 假设Girl中依赖了Boy,Boy中依赖了Girl;在实例化Girl的时候要注入Boy,此时没有Boy,因为是原型模式,每次都…...

从0开始python学习-24.selenium 浏览器常见的操作

1. 浏览器的最大化/最小化&#xff1a;maximize_window () / minimize_window() 2. 设置浏览器的宽高&#xff1a;set_window_size() 3. 设置浏览器的位置&#xff1a;set_window_position(0,0) —》左上角为原点 4. 刷新&#xff1a;refresh() 5. 前进&#xff1a;forward() 6…...

Canal实现数据同步

1、Canal实现数据同步 canal可以用来监控数据库数据的变化&#xff0c;从而获得新增数据&#xff0c;或者修改的数据。 1.1 Canal工作原理 原理相对比较简单&#xff1a; 1、canal模拟mysql slave的交互协议&#xff0c;伪装自己为mysql slave&#xff0c;向mysql master发送…...

数据库学习笔记——DDL

数据库学习笔记——DDL 建立EMPLOYEE数据库&#xff1a; CREATE TABLE employee(employee_ID int not null,employee_name varchar(20) not null,street varchar(20) not null,city varchar(20) not null,PRIMARY KEY(employee_ID) );CREATE TABLE company(company_name varc…...

MATLAB算法实战应用案例精讲-【人工智能】边缘计算(附python代码实现)

目录 前言 几个高频面试题目 边缘计算和云计算的关系 云计算(cloud computing) 边缘计算...

精彩回顾 | 迪捷软件亮相2023世界智能网联汽车大会

2023年9月24日&#xff0c;2023世界智能网联汽车大会&#xff08;以下简称大会&#xff09;在北京市圆满落幕。迪捷软件北京参展之行圆满收官。 本次大会由工业和信息化部、公安部、交通运输部、中国科学技术协会、北京市人民政府联合主办&#xff0c;是我国首个经国务院批准的…...

【ShaderLab PBR 嗜血边缘角色_美式朋克风格_“Niohoggr“_角色渲染(第一篇)】

嗜血边缘角色Cyberpunk style Unity 渲染 《嗜血边缘》截取其中的片段如下:资源分析其中Guitar贴图4张模型:人物细节图:人物模型 Inspector面板这里做一个区域区分:Body贴图1_BC贴图1_BC属性:Body贴图2_NBody贴图3_CMBody贴图4_SRMBody贴图4_RGBReflection Probe第一版Sha…...

python经典百题之围圈报数

题目:有n个人围成一圈&#xff0c;顺序排号。从第一个人开始报数&#xff08;从1到3报数&#xff09;&#xff0c;凡报到3的人退出圈子&#xff0c;问最后留下的是原来第几号的那位。 程序分析 思路1&#xff1a;模拟游戏过程 使用一个循环队列模拟游戏过程&#xff0c;每次循…...

Google Earth Engine(GEE)案例——如何去除和过滤Landsat和sentinel等系列影像集合中的空影像(三种方法)

简介 本文的主要解决的问题是如何去除和过滤Landsat和sentinel等系列影像集合中的空影像?这个主要源于一下的问题: “从图像集中删除空图像”是什么意思?您的脚本将图像集合过滤到没有图像的日期,这会产生包含 0 个图像的图像集合:https: https://code.earthengine.goog…...

Leetcode 69.x的平方根

给你一个非负整数 x &#xff0c;计算并返回 x 的 算术平方根 。 由于返回类型是整数&#xff0c;结果只保留 整数部分 &#xff0c;小数部分将被 舍去 。 注意&#xff1a;不允许使用任何内置指数函数和算符&#xff0c;例如 pow(x, 0.5) 或者 x ** 0.5 。 示例 1&#xff1…...

Node18.x基础使用总结(二)

Node18.x基础使用总结 1、Node.js模块化1.1、模块暴露数据1.2、引入模块 2、包管理工具2.1、npm2.2、npm的安装2.3、npm基本使用2.4、搜索包2.5、下载安装包2.6、生产环境与开发环境2.7、生产依赖与开发依赖2.8、全局安装2.9、修改windows执行策略2.10、安装包依赖2.11、安装指…...

LCD 的RGB接口(SYNC Mode/ SYNC-DE Mode/ DE Mode)

1、 SYNC Mode Timing Diagram 2、 SYNC-DE Mode Timing Diagram 3、 DE Mode Timing Diagram RGB接口&#xff08;SYNC Mode/ SYNC-DE Mode/ DE Mode&#xff09;-CSDN博客...

flink生成水位线记录方式--周期性水位线生成器

背景 在flink基于事件的时间处理中&#xff0c;水位线记录的生成是一个很重要的环节&#xff0c;本文就来记录下几种水位线记录的生成方式的其中一种&#xff1a;周期性水位线生成器 周期性水位线生成器 1.1 BoundedOutOfOrdernessTimeStampExtractor 他会接收一个表示最大延…...

百度资源搜索平台出现:You do not have the proper credential to access this page.怎么办?

Forbidden site not allowed You do not have the proper credential to access this page. If you think this is a server error, please contact the webmaster. 如果你的百度资源平台&#xff0c;点进去出现这个提示&#xff0c;说明您的网站已经被百度清退了。如果你的网站…...

树莓派CM4开启I2C与UART串口登录同时serial0映射到ttyS0 开启多串口

文章目录 前言1. 树莓派开启I2C与UART串口登录2. 开启多串口总结&#xff1a; 前言 最近用CM4的时候使用到了I2C以及多个UART的情况。 同时配置端口映射也存在部分问题。 这里集中记录一下。 1. 树莓派开启I2C与UART串口登录 输入指令sudo raspi-config 跳转到如下界面&#…...

【租车骑绿道】python实现-附ChatGPT解析

1.题目 租车骑绿道 时间限制:1s 空间限制:256MB 限定语言:不限 题目描述: 部门组织绿道骑行团建活动。租用公共双人自行车骑行,每辆自行车最多坐两人、做大载重M。 给出部门每个人的体重,请问最多需要租用多少双人自行车 输入描述 第一行两个数字m、n,自行车限重m,代表部门总…...

【ONE·Linux || 多线程(一)】

总言 多线程&#xff1a;进程线程基本概念、线程控制、互斥与同步。 文章目录 总言1、基本概念1.1、补充知识1.1.1、堆区细粒度划分1.1.2、虚拟地址到物理空间的转化 1.2、如何理解线程、进程1.2.1、如何理解线程&#xff1f;1.2.2、如何理解进程&#xff1f; 1.3、实践操作1.…...

华为智能企业上网行为管理安全解决方案(1)

华为智能企业上网行为管理安全解决方案&#xff08;1&#xff09; 课程地址方案背景需求分析企业上网行为概述企业上网行为安全风险分析企业上网行为管理需求分析 方案设计组网架构设备选型设备简介行为管理要点分析方案功能概述 课程地址 本方案相关课程资源已在华为O3社区发…...

Acwing 240. 食物链

Acwing 240. 食物链 题目描述思路讲解代码展示 题目描述 思路讲解 代码展示 #include <iostream>using namespace std;const int N 50010;int n, m; int p[N], d[N]; //p[]是并查集的father,d[]是距离int find(int x) {if (p[x] ! x) { //如果说x不是树根的话int t f…...

c++ 容器适配器

Container //创建一个命名空间&#xff0c;避免和库里的冲突 namespace chen {//写一个模版template<class T, class Container deque<T>>//开始写我们的类class stack{public:void push(const T& x){_con.push_back(x);}void pop(){_con.pop_back();}const …...

正则表达式的应用领域及基本语法解析

目录 一、正则表达式的应用领域 1. 文本搜索和替换 2. 表单验证 3. 数据提取和分析 4. 数据清洗和处理 5. URL路由和路由匹配 二、正则表达式的基本语法 1. 字符匹配 2. 元字符和字符类 3. 量词和边界 4. 分组和捕获 5. 转义字符 三、常见正则表达式示例 1. 邮箱…...

CIP或者EtherNET/IP中的PATH是什么含义?

目录 SegmentPATH举例 最近在学习EtherNET/IP&#xff0c;PATH不太明白&#xff0c;翻了翻规范&#xff0c;在这里记个笔记。下面的叙述可能是中英混合&#xff0c;有一些是规范中的原文我直接搬过来的。我翻译的不准确。 Segment PATH是CIP Segment中的一个分类。要了解PATH…...

使用lombok进行bulider之后调取HashMap的自定义方法进行对象操作报空指针异常(pojo也适用)

概论 这主要的问题就是bulider的特性的问题&#xff0c;就是他只能给你搭建了一个脚手架&#xff0c;里面的东西其实他没动你的&#xff0c;你得自己去给他实体化&#xff0c;如果你使用了类似HashMap等集合的话&#xff0c;你得自己去bulid一个在那个里面作为初始化对象你才可…...

矩阵-day14

...

wordpress 不显示分页/中国新冠疫苗接种率

1.背景在项目的执行过程中&#xff0c;发现串口通讯虽然不如Profinet方便&#xff0c;但是远比4-20mA或者0-10V的硬接线方便很多&#xff0c;而且在仪表类使用广泛。学会串口通讯&#xff0c;可以在设备不支持以太网通讯的情况下实现数据读取&#xff08;比如西门子和第三方驱动…...

建设电影会员网站首页/seo是什么部位

孔乙己显出极高兴的样子&#xff0c;将两个指头的长指甲敲着柜台&#xff0c;点头说&#xff0c;“对呀对呀&#xff01;……茴字有四样写法&#xff0c;你知道么&#xff1f;”我愈不耐烦了&#xff0c;努着嘴走远。孔乙己刚用指甲蘸了酒&#xff0c;想在柜上写字&#xff0c;…...

企业网站设计沈阳/海外品牌推广

在办公室等室内场所, 和公共场所 如地铁上 高铁动车上, 说法声音都要压低, 尽量保持只有一个人能正常听到. 在保证一个人能听到的前提下,尽量保持说话的声音更小....

做网站还能挣钱吗/2023年6月份疫情严重吗

打开internet选项&#xff0c;按照图中指示进行设置 QT Tabbar不能使用的原因是被禁用了&#xff0c;右击启用它即可恢复到其原始模样。 转自&#xff1a;https://jingyan.baidu.com/article/5225f26b5c4326e6fa0908cd.html PS: QTTabBar是一款可以让你在Windows资源管理器中…...

wordpress 自动 采集/电工培训内容

在父工程下创建了一个模块service_test&#xff0c;发现配置文件无效&#xff0c;没有变成小绿叶。 打开pom.xml文件后发现组织名错了&#xff0c;新建的模块放在了service下&#xff0c;但pom.xml文件中的组织名为parent 只需要将改成service即可。 如下所示&#xff0c;改后…...

网站制作教学/电话营销系统

1二分法可以说是鼎鼎大名&#xff0c;哪怕是没有学过编程的同学&#xff0c;也许说不上来二分法这个名字&#xff0c;但是对于其中的精髓应该都是有所了解的。不了解的同学也没关系&#xff0c;我一句话就能交代清楚&#xff1a;我们每次将一个集合一分为二&#xff0c;每次舍弃…...