【ReactNative】高阶组件

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

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

高阶组件类似于高阶函数,如 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】高阶组件》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

Docker从入门到实战

Docker从入门到实战

黄靖钧 / 机械工业出版社 / 2017-6 / 69.00元

本书从Docker的相关概念与基础知识讲起,结合实际应用,通过不同开发环境的实战例子,详细介绍了Docker的基础知识与进阶实战的相关内容,以引领读者快速入门并提高。 本书共19章,分3篇。第1篇容器技术与Docker概念,涵盖的内容有容器技术、Docker简介、安装Docker等。第2篇Docker基础知识,涵盖的内容有Docker基础、Docker镜像、Dockerfile文件、Dock......一起来看看 《Docker从入门到实战》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换