React高阶组件总结(转)

在多个不同的组件中需要用到相同的功能,这个解决方法,通常有Mixin和高阶组件。
Mixin方法例如:

//给所有组件添加一个name属性
var defaultMixin = {
    getDefaultProps: function() {
        return {
            name: "Allen"
        }
    }
}

var Component = React.createClass({
    mixins: [defaultMixin],
    render: function() {
        return <h1>Hello, {this.props.name}</h1>
    }
})

但是由于Mixin过多会使得组件难以维护,在React ES6中Mixin不再被支持。
高阶组件其实是一个函数,接收一个组件作为参数,返回一个包装组件作为返回值,类似于高阶函数。高阶组件和装饰器就是一个模式,因此,高阶组件可以作为装饰器来使用。
高阶组件有如下好处:

  1. 适用范围广,它不需要es6或者其它需要编译的特性,有函数的地方,就有HOC。
  2. Debug友好,它能够被React组件树显示,所以可以很清楚地知道有多少层,每层做了什么。
//高阶组件基本形式:
const EnhancedComponent = higherOrderComponent(WrappedComponent);

详细如下:

function hoc(ComponentClass) {
    return class HOC extends React.Component {
        componentDidMount() {
            console.log("hoc");
        }

        render() {
            return <ComponentClass />
        }
    }
}
//使用高阶组件
class ComponentClass extends React.Component {
    render() {
        return <div></div>
    }
}

export default hoc(MyComponent);

//作为装饰器使用
@hoc
export default class ComponentClass extends React.Component {
    //...
}

高阶组件有两种常见的用法:

  1. 属性代理(Props Proxy): 高阶组件通过ComponentClass的props来进行相关操作
  2. 继承反转(Inheritance Inversion)): 高阶组件继承自ComponentClass

1. 属性代理(Props Proxy)

属性代理有如下4点常见作用:

  1. 操作props
  2. 通过refs访问组件实例
  3. 提取state
  4. 用其他元素包裹WrappedComponent,实现布局等目的
(1). 操作props

可以对原组件的props进行增删改查,通常是查找和增加,删除和修改的话,需要考虑到不能破坏原组件。
下面是添加新的props:

function ppHOC(WrappedComponent) {
  return class PP extends React.Component {
    render() {
      const newProps = {
        user: currentLoggedInUser
      }
      return <WrappedComponent {...this.props} {...newProps}/>
    }
  }
}
(2). 通过refs访问组件实例

可以通过ref回调函数的形式来访问传入组件的实例,进而调用组件相关方法或其他操作。
例如:

//WrappedComponent初始渲染时候会调用ref回调,传入组件实例,在proc方法中,就可以调用组件方法
function refsHOC(WrappedComponent) {
  return class RefsHOC extends React.Component {
    proc(wrappedComponentInstance) {
      wrappedComponentInstance.method()
    }

    render() {
      const props = Object.assign({}, this.props, {ref: this.proc.bind(this)})
      return <WrappedComponent {...props}/>
    }
  }
}
(3). 提取state

你可以通过传入 props 和回调函数把 state 提取出来,类似于 smart component 与 dumb component。
提取 state 的例子:提取了 input 的 value 和 onChange 方法。这个简单的例子不是很常规,但足够说明问题。

function ppHOC(WrappedComponent) {
  return class PP extends React.Component {
    constructor(props) {
      super(props)
      this.state = {
        name: ''
      }

      this.onNameChange = this.onNameChange.bind(this)
    }
    onNameChange(event) {
      this.setState({
        name: event.target.value
      })
    }
    render() {
      const newProps = {
        name: {
          value: this.state.name,
          onChange: this.onNameChange
        }
      }
       return <WrappedComponent {...this.props} {...newProps}/>
    }
  }
}

//使用方式如下
@ppHOC
class Example extends React.Component {
  render() {
    //使用ppHOC装饰器之后,组件的props被添加了name属性,可以通过下面的方法,将value和onChange添加到input上面
    //input会成为受控组件
    return <input name="name" {...this.props.name}/>
  }
}
(4). 包裹WrappedComponent

为了封装样式、布局等目的,可以将WrappedComponent用组件或元素包裹起来。
例如:

function ppHOC(WrappedComponent) {
  return class PP extends React.Component {
    render() {
      return (
        <div style={{display: 'block'}}>
          <WrappedComponent {...this.props}/>
        </div>
      )
    }
  }
}

2. 继承反转(Inheritance Inversion)

HOC继承了WrappedComponent,意味着可以访问到WrappedComponent的state,props,生命周期和render方法。如果在HOC中定义了与WrappedComponent同名方法,将会发生覆盖,就必须手动通过super进行调用。通过完全操作WrappedComponent的render方法返回的元素树,可以真正实现渲染劫持。这种思想具有较强的入侵性。
大致形式如下:

function ppHOC(WrappedComponent) {
  return class ExampleEnhance extends WrappedComponent {
    ...
    componentDidMount() {
      super.componentDidMount();
    }
    componentWillUnmount() {
      super.componentWillUnmount();
    }
    render() {
      ...
      return super.render();
    }
  }
}

例如,实现一个显示loading的请求。组件中存在网络请求,完成请求前显示loading,完成后再显示具体内容。
可以用高阶组件实现如下:

function hoc(ComponentClass) {
    return class HOC extends ComponentClass {
        render() {
            if (this.state.success) {
                return super.render()
            }
            return <div>Loading...</div>
        }
    }
}

@hoc
export default class ComponentClass extends React.Component {
    state = {
        success: false,
        data: null
    };
    async componentDidMount() {
        const result = await fetch(...请求);          
     this.setState({
            success: true,
            data: result.data
        });
    }
    render() {
        return <div>主要内容</div>
    }
}
(1) 渲染劫持

继承反转这种模式,可以劫持被继承class的render内容,进行修改,过滤后,返回新的显示内容。
之所以被称为渲染劫持是因为 HOC 控制着 WrappedComponent 的渲染输出,可以用它做各种各样的事。

通过渲染劫持,你可以完成:

在由 render输出的任何 React 元素中读取、添加、编辑、删除 props
读取和修改由 render 输出的 React 元素树
有条件地渲染元素树
把样式包裹进元素树,就行Props Proxy那样包裹其他的元素

注:在 Props Proxy 中不能做到渲染劫持。
虽然通过 WrappedComponent.prototype.render 你可以访问到 render 方法,不过还需要模拟 WrappedComponent 的实例和它的 props,还可能亲自处理组件的生命周期,而不是交给 React。记住,React 在内部处理了组件实例,你处理实例的唯一方法是通过 this 或者 refs。

export const hocInversion2 = config => (ComponentClass) => {
    return class hoc extends ComponentClass {
       render() {
        const { style = {} } = config;
        const elementsTree = super.render();
        if (config.type === 'add-style') {
            return <div style={{...style}}>
              {elementsTree}
            </div>;
        }
        return elementsTree;
       }
    }
}

@hocInversion2({type: 'add-style', style: { color: 'red'}})
(2) 操作state

HOC可以读取,编辑和删除WrappedComponent实例的state,可以添加state。不过这个可能会破坏WrappedComponent的state,所以,要限制HOC读取或添加state,添加的state应该放在单独的命名空间里,而不是和WrappedComponent的state混在一起。
例如:通过访问WrappedComponent的props和state来做调试

export const IIHOCDEBUGGER = (WrappedComponent) => {
    return class II extends WrappedComponent {
      render() {
        return (
          <div>
            <h2>HOC Debugger Component</h2>
            <p>Props</p> <pre>{JSON.stringify(this.props, null, 2)}</pre>
            <p>State</p><pre>{JSON.stringify(this.state, null, 2)}</pre>
            {super.render()}
          </div>
        )
      }
    }
  }
(3) 条件渲染

当 this.props.loggedIn 为 true 时,这个 HOC 会完全渲染 WrappedComponent 的渲染结果。(假设 HOC 接收到了 loggedIn 这个 prop)

function iiHOC(WrappedComponent) {
  return class Enhancer extends WrappedComponent {
    render() {
      if (this.props.loggedIn) {
        return super.render()
      } else {
        return null
      }
    }
  }
}
(4) 解决WrappedComponent名字丢失问题

用HOC包裹的组件会丢失原先的名字,影响开发和调试。可以通过在WrappedComponent的名字上加一些前缀来作为HOC的名字,以方便调试。
例如:

//或
class HOC extends ... {
  static displayName = `HOC(${getDisplayName(WrappedComponent)})`
  ...
}

//getDisplayName
function getDisplayName(WrappedComponent) {
  return WrappedComponent.displayName ||
         WrappedComponent.name ||
         ‘Component’
}
(5) 实际应用
1. mobx-react就是高阶组件是一个实际应用

@observer装饰器将组件包装为高阶组件,传入组件MyComponent后,mobx-react会对其生命周期进行各种处理,并通过调用forceUpdate来进行刷新实现最小粒度的渲染。mobx提倡一份数据引用,而redux中则提倡immutable思想,每次返回新对象。

2. 实现一个从localStorage返回记录的功能
//通过多重高阶组件确定key并设定组件
const withStorage = (key) => (WrappedComponent) => {
  return class extends Component {
    componentWillMount() {
        let data = localStorage.getItem(key);
        this.setState({data});
    }
    render() {
      return <WrappedComponent data={this.state.data} {...this.props} />
    }
  }
}

@withStorage('data')
class MyComponent2 extends Component {  
    render() {
        return <div>{this.props.data}</div>
    }
}

@withStorage('name')
class MyComponent3 extends Component {  
    render() {
        return <div>{this.props.data}</div>
    }
}
3. 实现打点计时功能
(1). Props Proxy方式
//性能追踪:渲染时间打点
export default (Target) => (props)=>{
    let func1 = Target.prototype['componentWillMount']    
    let func2 = Target.prototype['componentDidMount']//Demo并没有在prototype上定义该方法,func2为undefined,但是并不会有影响,这样做只是为了事先提取出可能定义的逻辑,保持原函数的纯净
    let begin, end;
    Target.prototype['componentWillMount'] = function (...argus){//do not use arrow funciton to bind 'this' object
        func1.apply(this,argus);//执行原有的逻辑
        begin = Date.now();
    }
    Target.prototype['componentDidMount'] = function (...argus){
        func2.apply(this,argus);//执行原有的逻辑
        end = Date.now();
        console.log(Target.name+'组件渲染时间:'+(end-begin)+'毫秒')
    }
    return <Target {...props}/>//do not forget to pass props to the element of Target
}
(2) Inheritance Inversion方式
// 另一种HOC的实现方式 Inheritance Inversion
export default Target => class Enhancer extends Target {
    constructor(p){
        super(p);//es6 继承父类的this对象,并对其修改,所以this上的属性也被继承过来,可以访问,如state
        this.end =0;
        this.begin=0;
    }
    componentWillMount(){
        super.componentWilMount && super.componentWilMount();// 如果父类没有定义该方法,直接调用会出错
        this.begin = Date.now();
    }
    componentDidMount(){
        super.componentDidMount && super.componentDidMount();
        this.end=Date.now();
        console.log(Target.name+'组件渲染时间'+(this.end-this.begin)+'ms')
    }
    render(){
        let ele = super.render();//调用父类的render方法
        return ele;//可以在这之前完成渲染劫持
    }
}

原文地址:
https://www.cnblogs.com/mengff/p/9657232.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,710评论 4 376
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,839评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,295评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,776评论 0 223
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,198评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,074评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,200评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,986评论 0 214
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,733评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,877评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,348评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,675评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,393评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,209评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,212评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,003评论 2 280

推荐阅读更多精彩内容