React高阶组件探究

作者: 绯色流火 | 来源:发表于2017-01-31 22:58 被阅读630次

    React高阶组件探究

    在使用React构建项目的过程中,经常会碰到在不同的组件中需要用到相同功能的情况。不过我们知道,去这些组件中到处编写同样的代码并不优雅。

    在以往,为了解决以上的情况,我们会用到Mixin这种方式来解决问题。

    以下是一个最为简单的Mixin

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

    这个例子很好理解,在这里就不详述了。要使用mixin属性,我们只需要简单地在组件中加入mixins属性即可。

    不过在React ES6中,因为种种原因,mixin将不再被支持。

    详情可以参考以下文章:

    1. Mixin已死,Composition 万岁

    2. React官方在ES6中不建议使用mixin

    文章中提到用高阶组件来替代mixin
    所谓高阶组件其实只是一个方法。这个方法中返回一个包裹着原组件的新组件。接下来看看它的基本用法。

    高阶组件基本介绍

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

    以上代码可以看作是一个最简的高阶组件

    使用起来也很简单,比如有一个React组件。

    class ComponentClass extends React.Component {
        render() {
            return <div></div>
        }
    }
    
    export default hoc(MyComponent);
    

    只需要将你的组件类作为参数传入高阶组件这个方法中即可。

    如果采用ES7的decorator语法。则可以更简洁

    @hoc
    export default class ComponentClass extends React.Component {
        //...
    }
    

    简单来说,高阶组件就是一个函数,它让你传入一个组件类,然后返回给你一个更强大的组件类。

    用更生动一点的说法,高阶组件就像是一个buff……

    鸡血(大锤)  ===>  打了鸡血的大锤
    
    @超级buff
    class 赛亚人 {}   //超级赛亚人get~~~
    

    类似以上这种感觉……

    高阶组件中的props、ref、state

    操作props###

    你可以在高阶组件中对ComponentClass中的props进行读取、添加、编辑操作。

    例子:

    function hoc(ComponentClass) {
        return class HOC extends React.Component {
            render() {
                const newProps = {
                    name: "cqm",
                    value: "testData",
                }
                return <ComponentClass {...this.props} {...newProps} />
            }
        }
    }
    

    高阶组件中能够通过this.props直接获取到ComponentClass中的props,假设this.props中有name,那么newProps中的name会覆盖掉this.props中的name。

    通过ref访问组件实例###

    你可以通过ref访问到组件中的实例。可以看以下例子:

    function hoc(ComponentClass) {
        return class HOC extends React.Component {
            
            componentDidMount() {
                console.log(this.refs.componentClass);
            }
        
            render() {
                return <ComponentClass ref="componentClass" />
            }
        }
    }
    

    提示:React中ref还有这种回调函数的写法: ref={(dom) => this.dom = dom} 依稀记得好像在哪里看到过,说这种写法更新更好

    你也可以使用另一种方式:

    function hoc(ComponentClass) {
        return class HOC extends React.Component {
            
            getDom(dom) {
                console.log(dom);
            }
            
            render() {
                return <ComponentClass ref={(dom) => this.getDom(dom)} />
            }
        }
    }
    

    ref回调函数在ComponentClass渲染时执行,从而可以轻松拿到它的引用。

    获取state###

    在高阶组件中获取state有若干种方式,下面将一一阐述。

    和React父子组件交互的方式类似,你可以往ComponentClass中传入一个函数,之后ComponentClass中通过props拿到这个函数,往里面传入你想要的state参数。

    示例如下:

    function hoc(ComponentClass) {
        return class HOC extends React.Component {
            
            getState(state) {
                console.log(state);
            }
            
            render() {
                const newProps = {
                    getState: (state) => this.getState(state)
                };
                return <ComponentClass {...newProps} />
            }
        }
    }
    
    @hoc
    export default class ComponentClass extends React.Component {
        state = {
            value = ""
        };
        
        render() {
            return (
                <div>
                    <input onChange={(z) => this.props.getState(z.target.value)} />
                </div>
            );
        }
    }
    

    理论上你还可以通过this.refs.componentClass.state这种方式来获取全部的state(不过我一般不这么干)。


    接下来用到的获取state的方式应该算是高阶组件的另一种运用方式

    我在文章开篇提到过这句话

    所谓高阶组件其实只是一个方法。这个方法中返回一个包裹着原组件的新组件。接下来看看它的基本用法。
    

    高阶组件中所谓的包裹方式主要有以下两种:

    1. Props Proxy: 高阶组件通过ComponentClass中的props来进行相关的操作。

    2. Inheritance Inversion: 高阶组件继承自ComponentClass。

    之前我们并未用到第二种包裹方式,大部分采用的Props Proxy的方式来进行操作。接下来我们来详细讲讲第二种方式。

    Inheritance Inversion

    Inheritance Inversion的最简实现大概如下:

    function hoc(ComponentClass) {
        return class HOC extends ComponentClass {
            render() {
                return super.render();
            }
        }
    }
    

    高阶组件hoc中,返回了一个继承了原先组件类的组件。颇有一种反转的味道。

    显而易见,你可以在这个组件类中拿到一切ComponentClass的props、state。

    代码如下:

    function hoc(ComponentClass) {
        return class HOC extends ComponentClass {
            
            componentDidMount() {
                console.log(this.state);
            }
        
            render() {
                return super.render();
            }
        }
    }
    
    @hoc
    export default class ComponentClass extends React.Component {
        
        state = {
            id: 0,
            value: "hahaha"
        };
        
        render() {
            return <div>ComponentClass</div>
        }
    }
    

    你也可以通过super.[lifecycle]来调取ComponentClass的声明周期或是render函数

    接下来我们可以来实现一个简单的需求。比如说一个组件中存在网络请求,你希望在请求完成之前组件显示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>
        }
    }
    

    这个例子应该很好理解。这边就不再详细解释了。

    Inheritance Inversion渲染劫持

    渲染劫持这句话的意思很好理解。因为在高阶组件中你可以控制ComponentClass的渲染输出,因此你可以在渲染的时候做出各种各样的事情。比如修改props,修改render的组件树等等。

    以下示例用以演示修改render方法输出的组件树

    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 = {
                width: '100px',
                height: '100px',
                backgroundColor: 'red'
            };
            
            return (
                <div>
                    <p style={{color: 'brown'}}>啦啦啦</p>
                    <ul>
                        <li>1</li>
                        <li>2</li>
                    </ul>
                    <h1>哈哈哈</h1>
                </div>
            )
        }
    }
    

    以上就是一个渲染劫持的示例,我将ComponentClass中的ul标签给移除了。你可以使用渲染劫持完成类似的需求。如果你对于React中的组件元素操作不是很了解的话,推荐您去看看官方文档中的这一节:

    React官方关于component-elements的文档

    实际案例

    mobx-react

    /**MyStore文件**/
    import { observable } from 'mobx';
    
    export class MyStore() {
        @observable value = 1;
        
        @action add = () => {
            this.value++;
        }
    }
    
    /**另一个文件**/
    import MyStore from './MyStore';
    
    const store = new MyStore();
    
    @observer
    export default class MyComponent extends React.Component {
        render() {
            return (
                <div>
                    <p>这只青蛙的寿命{store.value}</p>
                    <button onClick={store.add}>+1s</button>
                </div>
            );
        }
    }
    

    可以把observer当成高阶组件。传入MyComponent这个类,之后mobx-react会对其生命周期进行各种处理,并通过调用实例的forceUpdate来进行刷新实现最小粒度渲染。

    算是小小安利一下mobx这个框架吧。

    顺便一提, mobx中提倡一份数据引用,而在redux中则更提倡immutable思想,返回新对象。

    pure-render-decorator

    @pureRender
    export default class MyComponent extends React.Component {
        
        static propTypes = {
            name: React.PropTypes.string,
        };
        
        render() {
            return (
                <div>{this.props.name}</div>
            );
        }
    }
    

    pureRender中重新定义了shouldComponentUpdate这一生命周期,当传来的props未发生改变时,不重新render,从而达到性能优化的目的。


    以上。。。

    References

    1. Mixin的使用
    2. 深入理解React高阶组件
    3. React进阶——使用高阶组件(Higher-order Components)优化你的代码
    4. Mixin 已死,Composition 万岁

    相关文章

      网友评论

      • 耀伍杨威:为啥我用@hoc decorator 方式来转换就报语法错误,用 export default MyContainner(Demo1)这种方式又不报错?
        耀伍杨威: @绯色流火 是的,我已经解决了,谢谢你
        绯色流火:估计是babel没有配decorator相关的东西。

      本文标题:React高阶组件探究

      本文链接:https://www.haomeiwen.com/subject/zdvkittx.html