HOC高阶组件是一个函数,接受一个组件作为参数,返回一个新的组件
1、高阶组件HOC的特性
组件是将state转UI,即UI=f(state);高阶组件将组件转换成另一个组件,即EnhancedComponent=f(Comonent)。
- 1、不修改传入的组件,也不会使用继承来复制其行为,是纯函数,没有副作用
- 2、应该透传与自身无关的 props
- 3、不要在render方法中使用HOC
- 4、务必复制静态方法
- 5、ref不会被传递
2、属性代理(Props Proxy)
- 操作props
- 抽象状态state,react-redux中的connect
- 获取ref
- 包装组件(用其他组件包裹/功能集成/样式包裹)
const ppHoc = WrappedComponent => class extends React.Component {
render() {
// 此处可以修改props,或者注入state,
// 总之对WrappedComponent而言就是修改了props
return <WrappedComponent {...this.props} />
}
}
3、反向继承(Inheritance Inversion)
- 渲染劫持 render hijacking
- 操纵state
const iiHoc = WrappedComponent => class extends WrappedComponent{
render() {
const elementTree = super.render();
const { props } = elementTree;
// 可以修改props
const newProps = {aa: 1};
return React.cloneElement(elementTree, {...props, ...newProps}, elementsTree.props.children)
}
}
继承该组件,可基于其elementTree进行修改。能力更强,但风险也更高,不建议使用
4、以函数为子组件
高阶组件也有缺点。对原组件的props有了固化的要求。“以函数为子组件”的模式克服高阶组件这种局限而生的。
class CountDown extends React.Component{
constructor(props){
super(props);
this.state = {
count: this.props.startCount,
}
}
componentDidMount(){
this.intervalTimer = setInterval(()=>{
if(this.state.count >= 0){
this.setState({count: this.state.count+1});
} else {
clearInterval(this.intervalTimer);
}
}, 1000)
}
componentWillUnmount(){
clearInterval(this.intervalTimer);
}
render(){
return this.props.children(this.state.count);
}
}
<CountDown startCount={10}>
{(count) => <div>{count}</div>}
</CountDown>
<CountDown startCount={10}>
{(count) => <Bomb countDown={count}/>}
</CountDown>
5、实际应用
高阶组件的应用:react-redux中的connect
// React Redux 的 `connect` 函数
const NewMyComponent = connect(mapStateToProps, mapDispatchToProps)(MyComponent);
//等价于
// connect 是一个函数,它的返回值为另外一个函数。
const enhance = connect(mapStateToProps, mapDispatchToProps);
// 返回值为 HOC,它会返回已经连接 Redux store 的组件
const NewMyComponent = enhance(MyComponent);
const doNothing = () => ({});
function connect(mapStateToProps=doNothing, mapDispatchToProps=doNothing){
return function(WrappedComponent){
return HOCComponent extends React.Component{
constructor(props){
super(props);
this.onChange = this.onChange.bind(this);
}
componentDidMount(){
this.context.store.subscribe(this.onChange)
}
componentWillUnmount(){
this.context.store.unsubscribe(this.onChange);
}
onChange(){
this.setState({});
}
render(){
const store = this.context.store;
const newProps = {
...this.props,
mapStateToProps(store.getState()),
mapDispatchToProps(store.dispatch),
};
return <WrappedComponent {...newProps}/>
}
}
}
}
高阶组件的应用:发布订阅中
// 此函数接收一个组件
function withSubscription(WrappedComponent, selectData) {
// 并返回另一个组件
class Enhance extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {
data: selectData(DataSource, props)
};
}
componentDidMount() {
// 负责订阅相关的操作
DataSource.addChangeListener(this.handleChange);
}
componentWillUnmount() {
DataSource.removeChangeListener(this.handleChange);
}
handleChange() {
this.setState({
data: selectData(DataSource, this.props)
});
}
render() {
// 并使用新数据渲染被包装的组件!
// 请注意,我们可能还会传递其他属性
// ref接收
const {forwardedRef, ...rest} = this.props;
return <WrappedComponent data={this.state.data} {...rest} ref={forwardedRef}/>;
}
};
//必须准确知道应该拷贝哪些方法
Enhance.staticMethod = WrappedComponent.staticMethod;
//ref转发
return React.forwardRef((props, ref) => {
return <Enhance {...props} forwardedRef={ref}/>
});
}
//使用
const CommentListWithSubscription = withSubscription(
CommentList, (DataSource) => DataSource.getComments()
);
const ref = React.createRef();
<CommentListWithSubscription ref={ref}/>