内容简介:高阶组件类似于高阶函数,如比较典型的高阶组件是先看一个非常简单的高阶组件的例子:
高阶组件类似于高阶函数,如 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; 复制代码
当使用 高阶组件函数 时, 函数返回的 新的组件 会多了一个
add
的 prop 。 下面是移除一个 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
函数将WrappedComponent
的 props 增加了 name 和 onChange 。然后通过 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从入门到实战
黄靖钧 / 机械工业出版社 / 2017-6 / 69.00元
本书从Docker的相关概念与基础知识讲起,结合实际应用,通过不同开发环境的实战例子,详细介绍了Docker的基础知识与进阶实战的相关内容,以引领读者快速入门并提高。 本书共19章,分3篇。第1篇容器技术与Docker概念,涵盖的内容有容器技术、Docker简介、安装Docker等。第2篇Docker基础知识,涵盖的内容有Docker基础、Docker镜像、Dockerfile文件、Dock......一起来看看 《Docker从入门到实战》 这本书的介绍吧!
随机密码生成器
多种字符组合密码
UNIX 时间戳转换
UNIX 时间戳转换