zoukankan      html  css  js  c++  java
  • 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不再被支持。

    高阶组件是一个接替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。更多关于 dumb and smart 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。

    例如下面,过滤掉原组件中的ul元素:

    function hoc(ComponentClass) {
        return class HOC extends ComponentClass {
            render() {
                const elementTree = super.render();
                elementTree.props.children = elementTree.props.children.filter((z) => {
                    return z.type !== "ul" && z;
                }
                const newTree = React.cloneElement(elementTree);
                return newTree;
            }
        }
    }
    
    @hoc
    export default class ComponentClass extends React.Component {
        render() {
            const divStyle = {
                 '100px',
                height: '100px',
                backgroundColor: 'red'
            };
    
            return (
                <div>
                    <p style={{color: 'brown'}}>啦啦啦</p>
                    <ul>
                        <li>1</li>
                        <li>2</li>
                    </ul>
                    <h1>哈哈哈</h1>
                </div>
            )
        }
    }

    (2) 操作state

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

    export function 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://zhuanlan.zhihu.com/p/24776678?group_id=802649040843051008
       https://blog.csdn.net/cqm1994617/article/details/54800360
         https://blog.csdn.net/xiangzhihong8/article/details/73459720
       https://segmentfault.com/a/1190000004598113
       https://blog.csdn.net/sinat_17775997/article/details/79087977
       https://blog.csdn.net/neoveee/article/details/69212146

  • 相关阅读:
    Daemon Tools手工完全卸载方案
    不要轻易删除/windows/install下文件
    Dumpbin命令的使用
    v4l2 视频捕获
    2瓶4两酒,1个1.5两的酒杯
    n个平面分空间最多可分成多少份
    &#65279导致页面顶部空白一行解决方法
    Base64编码原理分析
    浏览器中“JavaScript解析器”工作原理
    IList转化为DataSet,解决了System.nullable()的问题
  • 原文地址:https://www.cnblogs.com/mengff/p/9657232.html
Copyright © 2011-2022 走看看