高阶组件是一个函数,能够接受一个组件并返回一个新的组件。
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 'reac t';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('sessionI d')){ 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 'reac t';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={'sideba r'}> <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 import React ,{Component } from 'reac t';import Nav from './Nav /Nav ';const InjectionNavHoc =WrappedComponent => class extends Component { constructor(props){ super (props); let sessionId = localStorage.getItem('sessionI d'); if (!sessionId){ this .props.push("/" ); } } render(){ if (!localStorage.getItem('sessionI d')){ 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 ; }
<
ArcGIS For JS之二——与React集成
获取代码样式
>