【ReactNative】高阶组件

栏目: 服务器 · 发布时间: 4年前

内容简介:高阶组件类似于高阶函数,如比较典型的高阶组件是先看一个非常简单的高阶组件的例子:

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

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

先看一个非常简单的高阶组件的例子:

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

export default HocComponent;
复制代码

通过示例可知 高阶组件 其实是一个函数:输入一个组件,输出一个新的组件,这个新的组件是对输入组件的一个增强。其实 高阶组件 并不是真正的组件,比较严谨的说法是: 高阶组件工厂函数 。但在业界,更遵守普遍的定义:即 具有增强组件功能的函数 称为 高阶组件

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

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

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

属性代理高阶组件

这是实现高阶组件比较常用的一种模式, 文章开头的代码示例就是一个代理方式的高阶组件,只不过没有实现任何功能。 新生成的组件 继承自 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() 避免不必要的渲染。凡事有利即有弊。具体要使用哪种方式要根据使用场景而定。


以上所述就是小编给大家介绍的《【ReactNative】高阶组件》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Learning PHP, MySQL, and JavaScript

Learning PHP, MySQL, and JavaScript

Robin Nixon / O'Reilly Media / 2009-7-21 / USD 39.99

Learn how to create responsive, data-driven websites with PHP, MySQL, and JavaScript - whether or not you know how to program. This simple, streamlined guide explains how the powerful combination of P......一起来看看 《Learning PHP, MySQL, and JavaScript》 这本书的介绍吧!

html转js在线工具
html转js在线工具

html转js在线工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具