【ReactNative】高阶组件

理解 React 高阶组件

高阶组件相似于高阶函数,如 map()reduce()sort(),即这样一种函数:接收函数做为输入,或是输出一个函数。一样做为高阶组件:它接受 React 组件做为输入,输出一个新的 React 组件。前端

比较典型的高阶组件是 react-redux 中的 connnect() 函数,通常的使用状况: export default connect(mapStateToProps, mapDispatchToProps)(WrappedComponent)。首先,connect(mapStateToProps, mapDispatchToProps) 执行后返回高阶组件函数,而后,输入(WrappedComponent) 组件返回处理事后的组件并导出。react

先看一个很是简单的高阶组件的例子:redux

const HocComponent = (WarppedComponent) => {
    return class WrappingComponent extends Component {
        render() {
            return <WarppedComponent/>;
        }
    };
};

export default HocComponent;
复制代码

经过示例可知 高阶组件 实际上是一个函数:输入一个组件,输出一个新的组件,这个新的组件是对输入组件的一个加强。其实 高阶组件 并非真正的组件,比较严谨的说法是: 高阶组件工厂函数。但在业界,更遵照广泛的定义:即 具备加强组件功能的函数 称为 高阶组件数组

那么定义高阶组件的意义在哪?promise

首先,重用代码。如比较常见的状况:不少组件都须要一个公共的逻辑,那么这个逻辑,没有必要在每一个组件当中都实现一遍。最好的方式是将这部分逻辑抽取出来,利用 高阶组件 的方式抛出,这样就会减小不少组件当中的重复代码。网络

其次,修改组件行为。好比有的时候,想对一个组件作一些操做。可是又不想触碰其中的内部逻辑,这时能够经过一个函数生成另外一个组件并包裹原组件,组件之间相对独立,又不会侵入原组件。app

属性代理高阶组件

这是实现高阶组件比较经常使用的一种模式, 文章开头的代码示例就是一个代理方式的高阶组件,只不过没有实现任何功能。新生成的组件 继承自 React.Component,同时 新生成的组件传入组件代理, 并将 传入的组件 显示出来。新生成的组件 本身会作一些事情,其他工做所有由 传入的组件 实现。固然,所谓的 新生成组件 就是 高阶组件 了。代理方式的高阶组件 主要有下面几种应用场景:函数

  • 操控Props

    能够 编辑 传入的组件props性能

    const addHOCComponent = (WrappedComponent) => {
        return class WrappingComponent extends Component {
             render() {
                 const newProps = {add: 'newProps'};
                 return <WrappedComponent {...this.props} {...newProps}/>; } } } export default AddHOCComponent; 复制代码

    当使用 高阶组件函数 时, 函数返回的 新的组件 会多了一个 addprop。 下面是移除一个 prop 的例子:优化

    const removeHOCComponent = (WrappedComponent) => {
          return class WrappingComponent extends Component {
             render() {
                   const {remove, ...otherProps} = this.props;
                   return <WrappedComponent {...otherProps}/>; } } } export default RemoveHOCComponent; 复制代码

    RemoveHOCComponent 组件和 WrappedComponent 具备彻底一致的行为,除了它们的 props 不同。

    如上代码可知,高阶组件的复用性是很强的,经过高阶组件能够垂手可得的操控 props ,使用高阶组件代码以下:

    const AddPropHoc = addHOCComponent(SomeComponent);
    const RemovePropHoc = removeHOCComponent(OtherComponent);
    复制代码
  • 抽取 State

    高阶组件 能够将 原组件内部状态 分离,使其仅仅具备 展现组件 的功能。

    const ExtractStateHOC = (WrappedComponent) => {
        return class WrappingComponent extends Component {
            constructor(props) {
                 super(props);
                 this.state = {
                  name: 'ExtractState'
                 };
                this.onChange = this
                    .onChange
                    .bind(this);
             }
            render() {
                 const newProps = {
                    name: this.state.name,
                    onChange: this.onChange
                 };
                 return (<WrappedComponent {...newProps}/>) } onChange(event) { this.setState({name: event.nativeEvent.text}) } } }; export default ExtractStateHOC; 复制代码

    ExtractStateHOC 函数将 WrappedComponentprops 增长了 nameonChange 。而后经过 props 传递给原组件,这样原组件的 内部状态 就被抽离到了 高阶组件 中去处理。下面是个具体例子:

    class TextInputComponent extends Component {
        constructor(props) {
             super(props);
             this.state = {};
        }
    
         render() {
             const {onChange, name} = this.props;
             return (
                   <View style={styles.container}> <Text>{name}</Text> <TextInput onChange={onChange}></TextInput> </View>
             );
        }    
    }
    
    const ExtractStateComponent = ExtractStateHOC(TextInputComponent);
    复制代码
  • 包装组件

    上文中写到的经过 高阶组件 产生的 新组件 都是经过render() 函数直接返回 原组件, 其实也能够在 原组件 的基础之上添加一些其余的组件。

    const PackagingHOC = (WrappedComponent, style) => {
    return class WrappingComponent extends Component {
        render() {
            return (
                <View style={style}> <WrappedComponent/> </View>
                 )
             }
         }
    };
    
    export default PackagingHOC;
    复制代码

反向继承高阶组件

继承方式的高阶组件采用 继承 的方式将 原组件新生成的组件 关联起来。即 新组件 继承自 原组件 的方式。

const ExtendsHOC = (WrappedComponent) => {
    return class WrappingComponent extends WrappedComponent {
        render() {
           return super.render();
        }
    }
}
复制代码

由于是 继承 的关系,因此在 render() 函数中直接经过 super.render() 就能够渲染出父类的元素。代理方式的高阶组件是生成新的组件,原组件和新组件是两个不一样的组件,每次渲染,两个组件都要经历完整的生命周期。而到了 继承方式的高阶组件 ,两个组件的生命周期合并在了一块儿,有相同的生命周期。

  • 操控Props

    该方式一样能够操控 Props,使用 React.cloneElement 从新绘制从父类获得的元素。

    const ExtendsHOC = (WrappedComponent) => {
         return class WrappingComponent extends WrappedComponent {
             render() {
                 const elements = super.render();
                 const newProps = {
                      name: elements.type.displayName === 'Text' ? 'extends': 'other',
                     ...this.props
                 };
                 return React.cloneElement(elements, newProps, elements.props.children);
             }
        }
    }
    复制代码

    若是 原组件 的第一层元素是 Text,那么添加 name。 虽然能够达到操控 Props 的目的,但这种方式太复杂,没什么实际意义,因此不多采用这种方式。除非要根据父类的显示状况操控不一样的 Props

  • 操控生命周期函数

    由于是组件之间是 继承关系 ,因此能够操控生命周期,代理方式的高阶组件没法作到这一点,由于它生成的新的组件和原组件没有实质的联系。

    假设有这样一种需求:某些特定的页面在要刷新的时,判断是否有权限,如无则留在原地,不作刷新。

    const ExtendsHOC = (WrappedComponent) => {
         return class WrappingComponent extends WrappedComponent {
             shouldComponentUpdate(nextProps, nextState, nextContext) {
                 return condition;
             }
             render() {
                 return super.render();
              }
         }
    }
    复制代码

    shouldComponentUpdate() 函数决定是否从新渲染 原组件 的元素。如返回 false 则不会执行 render()

组合式组件

在前端组件式业务开发中,复用是无疑是提升开发效率的一大利器。React 组件经过配置 Props 能够实现不一样的业务逻辑,但随着需求的不断增多,单纯的经过增长 Props 必然会形成 Props 泛滥,给持续维护形成很大障碍。这时候能够组合式的组件开发方式,将组件可复用的部分抽象为粒度更小的组件,而后组合在一块儿获得完整功能的组件。下面是一个简单的例子,一个组合式的列表:

  • 点击事件抽取

    const HandleDecorator = (WrappedComponent) => {
    
      return class WrappingComponent extends Component {   
        handlePress(msg) {
            console.log(msg);
            console.log(this.state);
        }
        
        render() {
            return <WrappedComponent {...this.props} handlePress={this .handlePress .bind(this)}/> } } }; 复制代码
  • 网络数据抽取

    const DataDecorator = (WrappedComponent) => {
     return class WrappingComponent extends Component {
        constructor(props) {
            super(props);
    
            this.state = {
                dataSource: []
            };
        }
        componentDidMount() {
    
            const promise = new Promise((resolve) => {
                setTimeout(() => {
                    const simulateData = ['Swift', 'React', 'SwiftUI', 'JavaScript'];
                    resolve(simulateData);
                }, 3000);
            });
    
            promise.then(data => {
                this.setState({
                    dataSource: data
                });
            })            
        }
    
        render() {
            return <WrappedComponent {...this.props} dataSource={this.state.dataSource}/> } } } 复制代码
  • compose 组合

    compose() 函数提供了将不一样的函数组合在一块儿的功能。可将不定数量的函数做为 compose() 的参数,而后将这些做为参数的函数按照从右到左的顺序执行:第一个被执行的函数的返回值做为下一个函数的参数,以此类推。

    const ComposeComponent = compose(HandleDecorator, DataDecorator)(ListComponent);
    复制代码
  • 基础组件

    到目前为止,ListComponent 组件只须要读取被组合增强过的 props 就能够了。它的数据来源和事件的处理都交给了其余高阶组件处理。这样,经过简单的配置工做,就能完成对组件的自由支配,经过更细粒度的高阶组件使组件更灵活、更容易扩展。

    class ListComponent extends Component {
    
      render() {
           const {dataSource} = this.props;
             return (<FlatList
                style={styles.container}
                data={dataSource}
                renderItem={this._createRow}
                keyExtractor={this._keyExtractor}
                />);
       };
    
      _createRow = (wrappedItem) => <Button title={wrappedItem.item} onPress={() => this.props.handlePress(wrappedItem.item)}/>
    
     _keyExtractor = (item, index) => item;
    }
    复制代码

以函数做为子组件

高阶组件对原组件的扩展主要依赖于对 props 的操做,一旦原组件没有声明自身能支持的 props ,高阶组件对原组件再多的扩展也是无用的。以代理方式的高阶组件为例,高阶组件和原组件是父子关系,它们之间天然要经过 props 交互,被加强事后的 props 是否有效,取决于原组件的支持。这样就产生了必定的局限性,将子组件当作函数处理能够解决这个问题。

以这种方式实现代码重用不是经过函数了,而是经过一个真正的 React 组件,这个组件要求必须有子组件,并且子组件必须是函数。this.props.children 引用的是子组件,获得的结果在 render() 方法中返回,这样就将子组件渲染出来了。

以一个倒计时程序为例:倒计时逻辑是可重用的,倒计时结束后所要显示的画面应该是灵活的,因此倒计时器抽取到一个父组件中,并将一些信息经过 props 传递给子组件。

class CountDownView extends Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 10
        };
    }

    componentDidMount() {
        this.timer = setInterval(() => {
            const newCount = this.state.count - 1;
            this.setState({count: newCount});
            console.log(newCount);
        }, 1000);

    }

    render() {
        return this
            .props
            .children(this.state.count);
    }

    componentWillUnmount() {
        clearInterval(this.timer);
    }
}
复制代码

上面的代码设置了一个从 10 开始的倒计时器,每次定时器触发都会更新 state ,也就每次都会执行 render() ,在其中经过 this.props.childre(this.state.count) 将当前的倒计时数传递给子组件。固然,起始 count 也能够经过 props 传递给倒计时器组件,在 count<0 时将定时器移除。

CountDown 封装好后,就能够将其应用在所须要倒计时的应用场景,所须要作的仅仅是编写一个合适的子组件,固然,要是一个函数:

<DetailView >
    {
        (count) => <View style={styles.container}>
            <Text
                style={{
                color: 'black',
                fontSize: 50
            }}>{count > 0
                    ? count
                    : '倒计时结束'}</Text>
             </View>
    }
</DetailView>
复制代码

由上可知,函数做为子组件 的模式相对来讲灵活性显然更高。但以 函数做为子组件 虽然更灵活,但很难作性能上的优化。每次父组件的更新过程都要得到一个函数来渲染子组件,这样没法经过 shouldComponentUpdate() 避免没必要要的渲染。凡事有利即有弊。具体要使用哪一种方式要根据使用场景而定。

就这些了

相关文章
相关标签/搜索