React进阶——使用高阶组件(Higher-order Components)优化你的
什么是高阶组件
通过函数向现有组件类添加逻辑,就是高阶组件。 让我们先来看一个可能是史上最无聊的高阶组件: function noId() { return function(Comp) { return class NoID extends Component { render() { const {id,...others} = this.props; return ( <Comp {...others}/> ) } } } } const WithoutID = noId()(Comp); 这个例子向我们展示了高阶组件的工作方式:通过函数和闭包,改变已有组件的行为——这里是忽略 之所以称之为
从图上也可以看出,组件树虽然嵌套了多层,但是实际渲染的DOM结构并没有改变。 借助函数的逻辑表现力,高阶组件的用途几乎是无穷无尽的: 适配器有的时候你需要替换一些已有组件,而新组件接收的参数和原组件并不完全一致。 你可以修改所有使用旧组件的代码来保证传入正确的参数——考虑改行吧如果你真这么想 也可以把新组件做一层封装: class ListAdapter extends Component { mapProps(props) { return {/* new props */} } render() { return <NewList {...mapProps(this.props)} /> } } 如果有十个组件需要适配呢?如果你不想照着上面写十遍,或许高阶组件可以给你答案 function mapProps(mapFn) { return function(Comp) { return class extends Component { render() { return <Comp {...mapFn(this.props)}/> } } } } const ListAdapter = mapProps(mapPropsForNewList)(NewList); 借助高阶组件,关注点被分离得更加干净:只需要关注真正重要的部分——属性的mapping。 这个例子有些价值,却仍然不够打动人,如果你也这么想,请往下看: 处理副作用纯组件易写易测,越多越好,这是常识。然而在实际项目中,往往有许多的状态和副作用需要处理,最常见的情况就是异步了。 假设我们需要异步加载一个用户列表,通常的代码可能是这样的: class UserList extends Component { constructor(props) { super(); this.state = { list: [] } } componentDidMount() { loadUsers() .then(data=> this.setState({list: data.userList}) ) } render() { return ( <List list={this.state.list} /> ) } /* other bussiness logics */ } 实际情况中,以上代码往往还会和其它一些业务函数混杂在一起——我们创建了一个业务与副作用混杂的、有状态的组件。 如果再来一个书单列表呢?再写一个BookList然后把loadUsers改成loadBooks ? 也许你会考虑使用Flux。它确实能让你的代码更清晰,但是在有些场景下使用Flux就像大炮打蚊子。比如一个异步的下拉选择框,如果要考虑复用的话,传统的Flux/Reflux几乎无法优雅的处理,Redux稍好一些,但仍然很难做优雅。关于flux/redux的缺点不深入,有兴趣的可以参考Cycle.js作者的文章 回到问题的本源:其实我们只想要一个能复用的异步下拉列表而已啊! 高阶函数试试? import React,{ Component } from 'react'; const DEFAULT_OPTIONS = { mapStateToProps: undefined,mapLoadingToProps: loading => ({ loading }),mapDataToProps: data => ({ data }),mapErrorToProps: error => ({ error }),}; export function connectPromise(options) { return (Comp) => { const finalOptions = { ...DEFAULT_OPTIONS,...options,}; const { promiseLoader,mapLoadingToProps,mapStateToProps,mapDataToProps,mapErrorToProps,} = finalOptions; class AsyncComponent extends Component { constructor(props) { super(props); this.state = { loading: true,data: undefined,error: undefined,}; } componentDidMount() { promiseLoader(this.props) .then( data => this.setState({ data,loading: false }),error => this.setState({ error,); } render() { const { data,error,loading } = this.state; const dataProps = data ? mapDataToProps(data) : undefined; const errorProps = error ? mapErrorToProps(error) : undefined; return ( <Comp {...mapLoadingToProps(loading)} {...dataProps} {...errorProps} {...this.props} /> ); } } return AsyncComponent; }; } const UserList = connectPromise({ promiseLoader: loadUsers,mapDataToProps: result=> ({list: result.userList}) })(List); //List can be a pure component const BookList = connectPromise({ promiseLoader: loadBooks,mapDataToProps: result=> ({list: result.bookList}) })(List); 不仅大大减少了重复代码,还把散落各处的异步逻辑装进了可以单独管理和测试的笼子,在业务场景中,只需要 使用curry & compose高阶组件的另一个亮点,就是对函数式编程的友好。你可能已经注意到,目前我写的所有高阶函数,都是形如: config => { return Component=> { return HighOrderCompoent } } 表示为 写成嵌套的函数是为了手动curry化,而参数的顺序(为什么不是 举个栗子,前面讲了适配器和异步,我们可以很快就组合出两者的结合体:使用NewList的异步用户列表 UserList = compose( connectPromise({ promiseLoader: loadUsers,mapResultToProps: result=> ({list: result.userList}) }),mapProps(mapPropsForNewList) )(NewList); 总结在团队内部分享里,我的总结是三个词 Easy,Light-weight & Composable. 其实高阶组件并不是什么新东西,本质上就是 使用高阶组件还有两个好处:
值得庆幸的是,社区也明显注意到了高阶组件的价值,无论是大家非常熟悉的react-redux 的 下次当你想写 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |