高阶组件相似于高阶函数,如 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。性能
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);
复制代码
高阶组件 能够将 原组件 的 内部状态 分离,使其仅仅具备 展现组件 的功能。
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,使用 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()
的参数,而后将这些做为参数的函数按照从右到左的顺序执行:第一个被执行的函数的返回值做为下一个函数的参数,以此类推。
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()
避免没必要要的渲染。凡事有利即有弊。具体要使用哪一种方式要根据使用场景而定。