资讯专栏INFORMATION COLUMN

React进阶——使用高阶组件(Higher-order Components)优化你的代码

happyfish / 1740人阅读

摘要:之所以称之为高阶,是因为在中,这种嵌套关系会反映到组件树上,层层嵌套就好像高阶函数的一样,如图从图上也可以看出,组件树虽然嵌套了多层,但是实际渲染的结构并没有改变。你可能已经注意到,目前我写的所有高阶函数,都是形如表示为。

什么是高阶组件

Higher-Order Components (HOCs) are JavaScript functions which add functionality to existing component classes.

通过函数向现有组件类添加逻辑,就是高阶组件。

让我们先来看一个可能是史上最无聊的高阶组件:

function noId() {
  return function(Comp) {
    return class NoID extends Component {
      render() {
        const {id, ...others} = this.props;
        return (
          
        )
      }
    }
  }
}

const WithoutID = noId()(Comp);

这个例子向我们展示了高阶组件的工作方式:通过函数和闭包,改变已有组件的行为——这里是忽略id属性——而完全不需要修改任何代码。

之所以称之为高阶,是因为在React中,这种嵌套关系会反映到组件树上,层层嵌套就好像高阶函数的function in function一样,如图:

从图上也可以看出,组件树虽然嵌套了多层,但是实际渲染的DOM结构并没有改变。
如果你对这点有疑问,不妨自己写写例子试下,加深对React的理解。现在可以先记下结论:我们可以放心的使用多层高阶组件,甚至重复地调用,而不必担心影响输出的DOM结构。

借助函数的逻辑表现力,高阶组件的用途几乎是无穷无尽的:

适配器

有的时候你需要替换一些已有组件,而新组件接收的参数和原组件并不完全一致。

你可以修改所有使用旧组件的代码来保证传入正确的参数——考虑改行吧如果你真这么想

也可以把新组件做一层封装:

class ListAdapter extends Component {
    mapProps(props) {
        return {/* new props */}
    }
    render() {
        return 
    }
}

如果有十个组件需要适配呢?如果你不想照着上面写十遍,或许高阶组件可以给你答案

function mapProps(mapFn) {
    return function(Comp) {
        return class extends Component {
            render() {
                return 
            }
        }
    } 
}

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 (
      
    )
  }
  /* 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, loading: false }),
          );
      }
      render() {
        const { data, error, loading } = this.state;

        const dataProps = data ? mapDataToProps(data) : undefined;
        const errorProps = error ? mapErrorToProps(error) : undefined;

        return (
          
        );
      }
    }

    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
    }
}

表示为config=> Component=> Component

写成嵌套的函数是为了手动curry化,而参数的顺序(为什么不是Component=> config=> Component),则是为了组合方便。关于curry与compose的使用,可以移步我的另一篇blog

举个栗子,前面讲了适配器和异步,我们可以很快就组合出两者的结合体:使用NewList的异步用户列表

UserList = compose(
  connectPromise({
    promiseLoader: loadUsers,
    mapResultToProps: result=> ({list: result.userList})
  }),
  mapProps(mapPropsForNewList)
)(NewList);
总结

在团队内部分享里,我的总结是三个词 Easy, Light-weight & Composable.

其实高阶组件并不是什么新东西,本质上就是Decorator模式在React的一种实现,但在相当一段时间内,这个优秀的模式都被人忽略。在我看来,大部分使用mixinclass extends的地方,高阶组件都是更好的方案——毕竟组合优于继承,而mixin——个人觉得没资格参与讨论。

使用高阶组件还有两个好处:

适用范围广,它不需要es6或者其它需要编译的特性,有函数的地方,就有HOC。

Debug友好,它能够被React组件树显示,所以可以很清楚地知道有多少层,每层做了什么。相比之下无论是mixin还是继承,都显得非常隐晦。

值得庆幸的是,社区也明显注意到了高阶组件的价值,无论是大家非常熟悉的react-redux 的connect函数,还是redux-form,高阶组件的应用开始随处可见。

下次当你想写mixinclass extends的时候,不妨也考虑下高阶组件。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/78902.html

相关文章

  • React 高阶组件(HOC)实践

    摘要:简单来说高阶组件就是一个函数,它接受一个组件作为参数然后返回一个新组件。主要用于组件之间逻辑复用。使用由于数据请求是异步的,为了不让用户看到一片空白,当数据请求还没有返回时,展示组件。组合函数,提升代码可阅读性。 简单来说高阶组件(HOC)就是一个函数,它接受一个组件作为参数然后返回一个新组件。HOC 主要用于组件之间逻辑复用。比如你写了几个组件,他们之间的逻辑几乎相同,就可以用 HOC 对...

    caiyongji 评论0 收藏0
  • React进阶系列】手写实现react-redux api

    简介:简单实现react-redux基础api react-redux api回顾 把store放在context里,所有子组件可以直接拿到store数据 使组件层级中的 connect() 方法都能够获得 Redux store 根组件应该嵌套在 中 ReactDOM.render( , rootEl ) ReactDOM.render( ...

    刘玉平 评论0 收藏0
  • React 组件设计和分解思考

    摘要:我们可以在组件的设计上,玩转出很多花样。但是,如何对一个功能复杂且臃肿的组件进行分解,也许并不是一件简单的事情。同时,借助于新的算法引擎,两个单元组件在渲染的效率上,乐观地预计会有较大幅度的提升。 之前分享过几篇关于React技术栈的文章: 做出Uber移动网页版还不够 极致性能打造才见真章 解析Twitter前端架构 学习复杂场景数据设计 React Conf 2017 干货总结1...

    liukai90 评论0 收藏0
  • 写一本关于 React.js 的小书

    摘要:因为工作中一直在使用,也一直以来想总结一下自己关于的一些知识经验。于是把一些想法慢慢整理书写下来,做成一本开源免费专业简单的入门级别的小书,提供给社区。本书的后续可能会做成视频版本,敬请期待。本作品采用署名禁止演绎国际许可协议进行许可 React.js 小书 本文作者:胡子大哈本文原文:React.js 小书 转载请注明出处,保留原文链接以及作者信息 在线阅读:http://huzi...

    Scorpion 评论0 收藏0
  • React高阶组件(HOC)模型理论与实践

    摘要:栗子的方法就是一个,他获取,在中给添加需要的。本来准备把详细代码当个栗子贴出来的,结果突然想到公司保密协议,所以。。。栗子这样子你就可以在父组件中这样获取的值了。 什么是HOC? HOC(全称Higher-order component)是一种React的进阶使用方法,主要还是为了便于组件的复用。HOC就是一个方法,获取一个组件,返回一个更高级的组件。 什么时候使用HOC? 在Reac...

    Leo_chen 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<