一 前言

React高阶组件(HOC),对于很多react开发者来说并不陌生,它是灵活使用react组件的一种技巧,高阶组件本身不是组件,它是一个参数为组件,返回值也是一个组件的函数。高阶作用用于强化组件,复用逻辑,提升渲染性能等作用。高阶组件也并不是很难理解,其实接触过后还是蛮简单的,接下来我将按照,高阶组件理解?,高阶组件具体怎么使用?应用场景, 高阶组件实践(源码级别) 为突破口,带大家详细了解一下高阶组件。本文篇幅比较长,建议收藏观看

我们带着问题去开始今天的讨论:

  • 1 什么是高阶组件,它解决了什么问题?

  • 2 有几种高阶组件,它们优缺点是什么?

  • 3 如何写一个优秀高阶组件?

  • hoc怎么处理静态属性,跨层级ref等问题?

  • 5 高阶组件怎么控制渲染,隔离渲染?

  • 6 高阶组件怎么监控原始组件的状态?

  • ...

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

二 全方位看高阶组件

1 几种包装强化组件的方式

① mixin模式

原型图

老版本的react-mixins

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代码会导致滚雪球式的复杂性

衍生方式

createClass的废弃,不代表mixin模式退出react舞台,在有状态组件class,我们可以通过原型链继承来实现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)

②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

③HOC模式

原型图

HOC是我们本章主要的讲的内容,具体用法,我们接下来会慢慢道来,我们先简单尝试一个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>}
}

④自定义hooks模式

原型图

hooks的诞生,一大部分原因是解决无状态组件没有state和逻辑难以复用问题。hooks可以将一段逻辑封装起来,做到开箱即用,我这里就不多讲了,接下来会出react-hooks原理的文章,完成react-hooks三部曲。感兴趣的同学可以看笔者的另外二篇文章,里面详细介绍了react-hooks复用代码逻辑的原则和方案。

传送门:

玩转react-hooks,自定义hooks设计模式及其实战

react-hooks如何使用?

2 高阶组件产生初衷

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

① 复用逻辑:高阶组件更像是一个加工react组件的工厂,批量对原有组件进行加工,包装处理。我们可以根据业务需求定制化专属的HOC,这样可以解决复用逻辑。

② 强化props:这个是HOC最常用的用法之一,高阶组件返回的组件,可以劫持上一层传过来的props,然后混入新的props,来增强组件的功能。代表作react-router中的withRouter

③ 赋能组件:HOC有一项独特的特性,就是可以给被HOC包裹的业务组件,提供一些拓展功能,比如说额外的生命周期,额外的事件,但是这种HOC,可能需要和业务组件紧密结合。典型案例react-keepalive-router中的 keepaliveLifeCycle就是通过HOC方式,给业务组件增加了额外的生命周期。

④ 控制渲染:劫持渲染是hoc一个特性,在wrapComponent包装组件中,可以对原来的组件,进行条件渲染节流渲染懒加载等功能,后面会详细讲解,典型代表做react-reduxconnect和 dva中 dynamic 组件懒加载。

我会针对高阶组件的初衷展开,详细介绍其原理已经用法。跟上我的思路,我们先来看一下,高阶组件如何在我们的业务组件中使用的。

3 高阶组件使用和编写结构

HOC使用指南是非常简单的,只需要将我们的组件进行包裹就可以了。

使用:装饰器模式和函数包裹模式

对于class声明的有状态组件,我们可以用装饰器模式,对类组件进行包装:

@withStyles(styles)
@withRouter
@keepaliveLifeCycle
class Index extends React.Componen{/* ... */
}

我们要注意一下包装顺序,越靠近Index组件的,就是越内层的HOC,离组件Index也就越近。

对于无状态组件(函数声明)我们可以这么写:

function Index(){/* .... */
}
export default withStyles(styles)(withRouter( keepaliveLifeCycle(Index) )) 

模型:嵌套HOC

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

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

对于需要参数的HOC,我们需要一层代理,如下:

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

我们看出两种hoc模型很简单,对于代理函数,可能有一层,可能有很多层,不过不要怕,无论多少层本质上都是一样的,我们只需要一层一层剥离开,分析结构,整个hoc结构和脉络就会清晰可见。吃透hoc也就易如反掌。

4 两种不同的高阶组件

常用的高阶组件有两种方式正向的属性代理和反向的组件继承,两者之前有一些共性和区别。接下具体介绍两者区别,在第三部分会详细介绍具体实现。

正向属性代理

所谓正向属性代理,就是用组件包裹一层代理组件,在代理组件上,我们可以做一些,对源组件的代理操作。在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)

打印结果

反向继承

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

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

优点

  • ① 方便获取组件内部状态,比如stateprops ,生命周期,绑定的事件函数等

  • ② 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。这样副作用串联起来,影响很大。

三 如何编写高阶组件

接下来我们来看看,如何编写一个高阶组件,你可以参考如下的情景,去编写属于自己的HOC

1 强化props

① 混入props

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

有状态组件(属性代理)

function classHOC(WrapComponent){return class  Idex 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 }   />}
}

效果

② 抽离state控制更新

高阶组件可以将HOCstate的配合起来,控制业务组件的更新。这种用法在react-reduxconnect高阶组件中用到过,用于处理来自reduxstate更改,带来的订阅更新作用。

我们将上述代码进行改造。

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)

效果

2 控制渲染

控制渲染是高阶组件的一个很重要的特性,上边说到的两种高阶组件,都能完成对组件渲染的控制。具体实现还是有区别的,我们一起来探索一下。

2.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)

效果:

② 进阶 :分片渲染

是不是感觉不是很过瘾,为了让大家加强对HOC条件渲染的理解,我再做一个分片渲染+懒加载功能。为了让大家明白,我也是绞尽脑汁啊????????????。

进阶:实现一个懒加载功能的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实现了条件渲染-分片渲染的功能,实际条件渲染理解起来很容易,就是通过变量,控制是否挂载组件,从而满足项目本身需求,条件渲染可以演变成很多模式,我这里介绍了条件渲染的二种方式,希望大家能够理解精髓所在。

③ 进阶:异步组件(懒加载)

不知道大家有没有用过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实现异步加载功能。HOC用起来非常灵活,

④ 反向继承 :渲染劫持

HOC反向继承模式,可以实现颗粒化的渲染劫持,也就是可以控制基类组件的render函数,还可以篡改props,或者是children,我们接下来看看,这种状态下,怎么使用高阶组件。

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

⑤ 反向继承:修改渲染树

修改渲染状态(劫持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,可以说是偷天换日,不亦乐乎。

2.2节流渲染

hoc除了可以进行条件渲染,渲染劫持功能外,还可以进行节流渲染,也就是可以优化性能,具体怎么做,请跟上我的节奏往下看。

① 基础: 节流原理

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

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,达到了期望的效果。

② 进阶:定制化渲染流

思考:????上述的案例只是介绍了原理,在实际项目中,是量化生产不了的,原因是,我们需要针对不同props变化,写不同的HOC组件,这样根本起不了Hoc真正的用途,也就是HOC产生的初衷。所以我们需要对上述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数据流和更新流,优秀的高阶组件有 mobx 中observer ,inject , react-redux中的connect,感兴趣的同学,可以抽时间研究一下。

3 赋能组件

高阶组件除了上述两种功能之外,还可以赋能组件,比如加一些额外生命周期,劫持事件,监控日志等等。

3.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>}
}

效果

② 反向继承实现

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

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>}
}

3.2 事件监控

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

① 组件内的事件监听

接下来,我们做一个HOC,只对组件内的点击事件做一个监听效果。

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>
}

效果

3 ref助力操控组件实例

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

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

我们可以针对某一种情况, 给组件增加额外的生命周期,我做了一个简单的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>}
}

这种写法有点不尽人意,大家不要着急,在第四部分,源码实战中,我会介绍一种更好的场景。方便大家理解Hoc对原有组件的赋能。

4 总结

上面我分别按照hoc主要功能,强化props , 控制渲染 ,赋能组件 三个方向对HOC编写做了一个详细介绍,和应用场景的介绍,目的让大家在理解高阶组件的时候,更明白什么时候会用到?,怎么样去写?` 里面涵盖的知识点我总一个总结。

对于属性代理HOC,我们可以:

  • 强化props & 抽离state。

  • 条件渲染,控制渲染,分片渲染,懒加载。

  • 劫持事件和生命周期

  • ref控制组件实例

  • 添加事件监听器,日志

对于反向代理的HOC,我们可以:

  • 劫持渲染,操纵渲染树

  • 控制/替换生命周期,直接获取组件状态,绑定事件。

每个应用场景,我都举了例子????????,大家可以结合例子深入了解一下其原理和用途。

四 高阶组件源码级实践

hoc的应用场景有很多,也有很多好的开源项目,供我们学习和参考,接下来我真对三个方向上的功能用途,分别从源码角度解析HOC的用途。

1 强化prop- withRoute

用过withRoute的同学,都明白其用途,withRoute用途就是,对于没有被Route包裹的组件,给添加history对象等和路由相关的状态,方便我们在任意组件中,都能够获取路由状态,进行路由跳转,这个HOC目的很清楚,就是强化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

withRoute的流程实际很简单,就是先从props分离出refprops,然后从存放整个route对象上下文RouterContext取出route对象,然后混入到原始组件的props中,最后用hoistStatics继承静态属性。至于hoistStatics我们稍后会讲到。

2 控制渲染案例 connect

由于connect源码比较长和难以理解,所以我们提取精髓,精简精简再精简, 总结的核心功能如下,connect的作用也有合并props,但是更重要的是接受state,来控制更新组件。下面这个代码中,为了方便大家理解,我都给简化了。希望大家能够理解hoc如何派发和控制更新流的。

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

connect 涉及到的功能点还真不少呢,首先第一层接受订阅函数,第二层接收原始组件,然后用forwardRef处理ref,用hoistStatics 处理静态属性的继承,在包装组件内部,合并props,useMemo缓存原始组件,只有合并后的props发生变化,才更新组件,然后在useEffect内部通过store.subscribe()订阅更新。这里省略了Subscription概念,真正的connect中有一个Subscription专门负责订阅消息。

3 赋能组件-缓存生命周期 keepaliveLifeCycle

之前笔者写了一个react缓存页面的开源库react-keepalive-router,可以实现vue中 keepalive + router功能,最初的版本没有缓存周期的,但是后来热心读者,期望在被缓存的路由组件中加入缓存周期,类似activated这种的,后来经过我的分析打算用HOC来实现此功能。

于是乎 react-keepalive-router加入了全新的页面组件生命周期 actived 和 unActivedactived 作为缓存路由组件激活时候用,初始化的时候会默认执行一次 , 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通过调用实例下面的生命周期函数,来实现缓存生命周期功能的。

五 高阶组件的注意事项

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 也无法应用于没有生命周期的函数组件。

2 继承静态属性

在用属性代理的方式编写HOC的时候,要注意的是就是,静态属性丢失的问题,前面提到了,如果不做处理,静态方法就会全部丢失。

手动继承

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

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

引入第三方库

这样每个静态方法都绑定会很累,尤其对于开源的hoc,对原生组件的静态方法是未知的,我们可以使用 hoist-non-react-statics 自动拷贝所有的静态方法:

import hoistNonReactStatic from 'hoist-non-react-statics'
function HOC(Component) {class WrappedComponent extends React.Component {/*...*/}hoistNonReactStatic(WrappedComponent,Component)return WrappedComponent
}

3 跨层级捕获ref

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

/*** * @param {*} Component 原始组件* @param {*} isRef  是否开启ref模式*/
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>
}

打印结果:

如上就解决了,HOC跨层级捕获ref的问题。

4 render中不要声明HOC

????错误写法:

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

如果这么写,会造成一个极大的问题,因为每一次HOC都会返回一个新的WrapHome,react diff会判定两次不是同一个组件,那么每次Index 组件 render触发,WrapHome,会重新挂载,状态会全都丢失。如果想要动态绑定HOC,请参考如下方式。

????正确写法:

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

六 总结

本文从高阶组件功能为切入点,介绍二种不同的高阶组件如何编写,应用场景,以及实践。涵盖了大部分耳熟能详的开源高阶组件的应用场景,如果你觉得这篇文章对你有启发,最好还是按照文章中的demo,跟着敲一遍,加深印象,知道什么场景用高阶组件,怎么用高阶组件。

实践是检验真理的唯一标准,希望大家能把高阶组件起来,用起来。

关于奇舞精选

《奇舞精选》是360公司专业前端团队「奇舞团」运营的前端技术社区。关注公众号后,直接发送链接到后台即可给我们投稿。

奇舞团是360集团最大的大前端团队,代表集团参与W3C和Ecma会员(TC39)工作。奇舞团非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队Leader等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. Angular 2 + 折腾记 :(3)初步了解服务及使用

    前言 不探究高深理论&#xff0c;只探究实际使用&#xff0c;有更好的写法或者经验请指出; 有些暂时没涉及到的知识我可能会顺着例子解释&#xff1b; 什么是服务 简言之&#xff1a;就是资源获取&#xff0c;以及通讯逻辑处理的地方&#xff1b; Angular2的服务引入了依赖注入…...

    2024/5/8 17:35:34
  2. Angular 2 + 折腾记 :(1)初识Angular-cli[官方脚手架]及脱坑要点

    前言 这个系列的进度有些跳跃性&#xff0c;我尽量直白点解释&#xff0c;但是我不是官方文档&#xff0c;直入主题&#xff01;&#xff01;&#xff01;&#xff01; 什么是Angular-cli 简言之&#xff1a;就是NG团队自行维护的一个&#xff40;脚手架&#xff40;[内置单元测…...

    2024/5/9 1:36:32
  3. Nginx 配置备忘、参数优化、http强制跳转以及负载平衡

    Nginx 参数优化、http强制跳转以及负载平衡 这里是我博客中这篇文章的地址&#xff1a; 个人博客 欢迎访问&#xff01;&#xff01;&#xff01; Nginx 作为一个非常好用的 web 服务器&#xff0c;被广泛运用在服务器部署的最前端或者是高可用集群下的 Keeplived 等之后&…...

    2024/4/28 19:24:10
  4. 前端小白必读之跨域问题:Access to XMLHttpRequest*from origin*has been blocked by CORS..Access-Control-Allow-O...

    问题描述 某小白是一个只略懂基础三件套和 Angular 框架的前端初学者&#xff0c;这天&#xff0c;小白开始尝试和后端服务器对接&#xff0c;控制台却打印出了如下错误信息&#xff1a; 小白看得一头雾水&#xff0c;ta 赶忙搜索与has been blocked by CORS policy: No Acce…...

    2024/5/9 6:13:43
  5. nginx,代理

    https://nginx.org/en/nginx学习网址 一、nginx是一个http 服务功能和一个反向代理的服务器&#xff0c;一个邮件代理服务和一个TCP/UDP代理服务器 二、代理&#xff1a; 正向代理&#xff1a;客户端的请求送给服务器 反向代理&#xff1a;服务器的响应分给客户端 三、PV: P…...

    2024/5/9 3:00:40
  6. 缝线法双眼皮多久恢复自然

    ...

    2024/5/9 0:16:30
  7. 崔东做广州割双眼皮修复论坛

    ...

    2024/5/8 18:12:46
  8. 割欧式6.5毫米双眼皮妹妹图片

    ...

    2024/5/8 4:47:40
  9. 没有服务器,关于angular路由访问静态页面chrome报错的问题

    这个找不到html,报错因为没有xhr&#xff0c;但是在火狐下没有问题的。 比如说ajax&#xff0c;直接写路径的话&#xff0c;我们的chrome也是不支持的&#xff0c;火狐可以的。 转载于:https://www.cnblogs.com/coding4/p/5607129.html...

    2024/5/9 8:32:13
  10. 割双眼皮好丑想死

    ...

    2024/5/9 6:24:52
  11. 你想成为什么样的人,你就要以什么样的标准来要求自己

    梦想还是要有的&#xff0c;没有梦想跟咸鱼有什么两样 慎重的考虑了&#xff0c;最后给自己一个阶段的小目标——“前端第一人” 主要职责&#xff1a; 1.准确理解产品需求、交互文档或原型, 进行 web 产品前端开发&#xff1b; 2.优化用户体验&#xff0c;修正项目中出现的问…...

    2024/5/8 23:12:06
  12. FEDAY第二届前端开发者大会回顾

    使用React、Redux和Node.js构建通用应用 演讲者是来自Facebook的Stepan&#xff0c;他为我们介绍了怎么样用Node和React、React-Router、Redux等技术&#xff0c;建立一个通用应用(Universal APP)或者同构应用&#xff08;Isomorphism APP&#xff09;。 历史 他先从2008年讲起…...

    2024/5/3 7:39:36
  13. web前端开发学习线路图2019版(适用本科生)

    一般来说&#xff0c;现在有一定编程基础准备在大学毕业后入坑前端开发的&#xff0c;大多都是计算机科学与技术、软件工程、信息与计算科学和移动互联网开发专业出身的本科生。 其实现阶段中国高校还是非常推崇Java、C/C等语言的&#xff0c;但是前端的处境就不一样了。web前…...

    2024/5/9 1:02:15
  14. Web基础总结、思维导图Web、JS、JQuery

    前端开发入门学习有&#xff1a;HTML、CSS、JavaScript&#xff08;简称JS&#xff09;这三个部分。所以在学习之前我们需要先明确三个概念&#xff1a; HTML——内容层&#xff0c;它的作用是表示一个HTML标签在页面里是个什么角色。CSS——样式层&#xff0c;它的作用是表示…...

    2024/4/21 15:47:37
  15. 聊一聊迅雷前端敏捷开发那些事儿

    福利来了&#xff1a; 我们要跟 前端早读课 合作搞一次线上直播&#xff0c;时间是12月12日晚8点半&#xff0c;分享迅雷前端的敏捷开发实践。如果你错过了上次的分享&#xff0c;或者希望和我们一起近距离沟通前端敏捷开发相关的话题&#xff0c;请一定不要错过这次的 Live&am…...

    2024/4/21 15:47:37
  16. angularjs+微信,解决chooseImage不能预览的问题

    angularjs微信&#xff0c;解决chooseImage不能预览的问题参考文章&#xff1a; &#xff08;1&#xff09;angularjs微信&#xff0c;解决chooseImage不能预览的问题 &#xff08;2&#xff09;https://www.cnblogs.com/johnnydan/p/5805184.html &#xff08;3&#xff09…...

    2024/5/5 10:21:09
  17. 割了双眼皮眉毛跟眼睛变近

    ...

    2024/4/21 15:47:36
  18. 株洲市中心医院割石家庄高低眼割韩式两点双眼皮宽度7mm图

    ...

    2024/4/21 15:47:33
  19. 最先进的双眼皮技术

    ...

    2024/4/28 17:26:12
  20. 上海割杭州埋线杭州双眼皮埋线哪家好

    ...

    2024/5/9 1:48:27

最新文章

  1. 151.翻转字符串里的单词

    给定一个字符串&#xff0c;逐个翻转字符串中的每个单词。 示例 1&#xff1a; 输入: "the sky is blue" 输出: "blue is sky the" 示例 2&#xff1a; 输入: " hello world! " 输出: "world! hello" 解释: 输入字符串可以在前面或…...

    2024/5/9 9:25:58
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/5/7 10:36:02
  3. Redis分区

    Redis分区是一种数据分片技术&#xff0c;用于将数据分布到多个Redis实例&#xff08;节点&#xff09;上以提高性能和扩展性。分区使得Redis能够处理比单个实例更大的数据集&#xff0c;并允许并行处理客户端请求。 原理&#xff1a; Redis分区通过一致性哈希算法&#xff08;…...

    2024/5/5 1:23:35
  4. Stable Diffusion 本地部署教程

    Stable Diffusion 是一个开源的本地部署的软件&#xff0c;用于在本地网络中进行消息传递和同步。下面是 Stable Diffusion 的本地部署教程&#xff1a; 安装稳定扩散软件&#xff1a;首先&#xff0c;您需要从 Stable Diffusion 的官方网站或 GitHub 页面上下载并安装 Stable …...

    2024/5/9 4:30:20
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/5/8 6:01:22
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/5/7 9:45:25
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/5/4 23:54:56
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/5/9 4:20:59
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/5/4 23:54:56
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/5/4 23:55:05
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/5/4 23:54:56
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/5/7 11:36:39
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/5/4 23:54:56
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/5/6 1:40:42
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/5/4 23:54:56
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/5/8 20:48:49
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/5/7 9:26:26
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/5/4 23:54:56
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/8 19:33:07
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/5/5 8:13:33
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/5/8 20:38:49
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/5/4 23:54:58
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/5/9 7:32:17
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/5/4 23:54:56
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57