React-高阶组件

高阶组件是一个函数,能够接受一个组件并返回一个新的组件。

1
const EnhancedComponent = higherOrderComponent(WrappedComponent);

在React中,高阶组件是重用组件逻辑的一项高级技术。它是一个纯函数,不应该修改输入组件,也不应该通过继承来复制行为,而是通过包裹的形式将原先的组件组合在container组件中。

在横切关注点中使用高阶组件

一个非常常见的例子:在做前端页面的时候,除了登陆页,每个页面都有导航栏<Nav>。因此我们把<Nav>分离出来,成为一个独立的组件,将页面的中的其他组件页分别分离出来,这样在每个页面都引用这个<Nav>即可。但是有一个问题,如果在开发过程中,<Nav>有什么变化的时候,我们就需要逐个页面的去修改。这显然是不合理的。这时我们想到了将这个<Nav>切出去,使用高阶组件来维护,这样不是更方便了么。
那么我们首先对页面进行分离,首先将导航栏和页面剩余部分的分为两大组件(如果有页脚,页脚也应该参与分离,那就分为3大组件了),然后逐个细分导航栏和页面剩余部分。

导航栏组件(show code)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import React,{Component} from 'react';
import Menu from './Menu';
import './nav.css';
import PropTypes from 'prop-types';


export default class Nav extends Component{

static propTypes={
menus: PropTypes.arrayOf(PropTypes.object),
user: PropTypes.object,
push: PropTypes.func,
loadMenus: PropTypes.func,
};
handleExit = ()=>{
localStorage.clear();
this.props.push('/');
};
componentWillMount(){}
render(){
if(!localStorage.getItem('sessionId')){
return <div/>
}
const {menus, user, push, loadMenus} = this.props;
return(
<nav className="header">
<div className="header-title"/>
<Menu menus={menus} push={push} loadMunus={loadMenus}/>
<div className="header-right">
<a href="javascript:;" className="dropdown user-link">
<i className="header-icon icon-user"/>{user.userName}</a>
<a href="javascript:;"><i className="header-icon icon-exit" onClick={this.handleExit}/></a>
</div>
</nav>
);
}
}

这是我们分离出来的<Nav>组件,和普通的组件没什么区别。

页面剩余部分组件(show code)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import React, {Component} from 'react';
import CategoryTree from './CategoryTree';
import ResourceTable from './ResourceTable';
import PropTypes from 'prop-types';
import './css.css'

export default class ResourceManager extends Component{
static propTypes={
funCategories: PropTypes.arrayOf(PropTypes.object),
funList: PropTypes.arrayOf(PropTypes.object),
loadCategories: PropTypes.func,
loadFunList: PropTypes.func,
dispatch: PropTypes.func
};
render(){
return(
<div>
<div className={'sidebar'}>
<CategoryTree categories={this.props.funCategories} loadCategories={this.props.loadCategories}
updateFunType = {this.props.updateFunType}
loadFunList={this.props.loadFunList}
/>
</div>
<div className="right-content">
<ResourceTable dataSource ={this.props.funList} loadFunList={this.props.loadFunList} dispatch={this.props.dispatch}/>
</div>
</div>
);
}
}

由此可见,我们把一个页面拆分为两大组件,在着两大的组件的基础上又进行了细分。现在我们用这两大组件来组成一个页面。

HOC(show code)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//Contanier.js文件

import React,{Component} from 'react';
import Nav from './Nav/Nav';

const InjectionNavHoc =WrappedComponent => class extends Component {
constructor(props){
super(props);
let sessionId = localStorage.getItem('sessionId');
if(!sessionId){
this.props.push("/");
}
}
render(){
if(!localStorage.getItem('sessionId')){
return(<div/>);
}
return (
<div style={{height:'100%'}}>
<Nav
menus={this.props.menus}
user={this.props.user}
push={this.props.push}
loadMenus={this.props.loadMenus}/>
<WrappedComponent {...this.props}/>
</div>
);
}
};
export default InjectionNavHoc;

从HOC中可以看出,它接受一个组件,没有对输入的组件(WrappedComponent)进行任何的修改,而是对它进行了一层的包装,以加入<Nav>组件,然后WrappedComponent接受所有的props,如果有新的props也需要一并传递进去。

组合页面(show code)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import React,{Component} from 'react';
import configureStore from '../redux/configureStore';
import {connect} from 'react-redux';
import {push} from 'react-router-redux';
import {bindActionCreators} from 'redux';
import ResourceManager from '../component/FUNResource/ResourceManager';
import InjectionNavHoc from '../component/Contanier';
import MenusReducer from '../component/Nav/MenuRedux';
import RMReducer from '../component/FUNResource/ResourceManagerRedux';

@connect(
state=>{
return {
menus: state.menuState.menus,
user: state.login.user,
funCategories: state.funCategories,
funList: state.funList
};
},
dispatch=>{
return {
loadMenus: bindActionCreators(MenusReducer.action,dispatch),
loadCategories: bindActionCreators(RMReducer.funCategory.action, dispatch),
loadFunList: bindActionCreators(RMReducer.funList.action, dispatch),
push: bindActionCreators(push, dispatch),
dispatch,
updateFunType:RMReducer.updateFunType
};
}
)
@InjectionNavHoc
class ResourceManagerV extends Component{
render(){
return (
<ResourceManager funCategories={this.props.funCategories.categories}
funList={this.props.funList.list}
loadCategories={this.props.loadCategories}
loadFunList={this.props.loadFunList}
dispatch = {this.props.dispatch}
updateFunType={this.props.updateFunType}
/>
);
}
}
export default ResourceManagerV;

现在每个页面对导航栏的整合就是@InjectionNavHoc它了,是不是非常简洁了。

上面我们将公共的组件提取出来,如果一个组件有些属性也是公用,也可以使用高阶组件。

一些注意事项

静态方法必须复制

组件的静态方法往往都是很重要的,在通过HOC包装了之后,也应该将它的静态方法复制进来。

1
2
3
4
5
6
function enhance(WrappedComponent) {
class Enhance extends React.Component {/*...*/}
// 必须得知道要拷贝的方法 :(
Enhance.staticMethod = WrappedComponent.staticMethod;
return Enhance;
}

然而,这需要你明确地知道哪些方法需要别复制。你可以使用hoist-non-react-statics来自动复制非React的静态方法。

1
2
3
4
5
6
import hoistNonReactStatic from 'hoist-non-react-statics';
function enhance(WrappedComponent) {
class Enhance extends React.Component {/*...*/}
hoistNonReactStatic(Enhance, WrappedComponent);
return Enhance;
}

×

纯属好玩

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

文章目录
  1. 1. 在横切关注点中使用高阶组件
    1. 1.1. 导航栏组件(show code)
    2. 1.2. 页面剩余部分组件(show code)
    3. 1.3. HOC(show code)
    4. 1.4. 组合页面(show code)
  2. 2. 一些注意事项
    1. 2.1. 静态方法必须复制
,