React Hooks 深刻不浅出

这个标题可能不太好,但此文章确实不是一篇使用教程,并且也不会覆盖太多点,建议时间充裕的仍是应该完整地看下 官网文档javascript

React Hooks 对于部分人来讲可能仍是陌生的,但仍是阻止不了它成为了当前 React 社区里「最」热门的一个词汇。html

一开始了解到这个仍是 Dan Abramov 在十月底的时候发了一个推,是一篇文章 Making Sense of React Hooks,建议没看过的先看下。看完第一感觉就是:React 本就应该是这样的啊!java

看完这篇文章,但愿你能够从总体上对 Hooks 有个认识,并对其设计哲学有一些理解,但愿看的过程不要急,跟着个人思路走。react

若是你想本身跟着文章一块儿练手,须要把 reactreact-dom 更新到 16.7.0-alpha 及以上,若是配置了 ESLint,记得添加对应的 Plugin

插曲

长期以来不少人会把 Stateless ComponentFunctional Component 混为一谈,我会试着跟他们解释这不是一回事(不是一个维度),但当时 Functional Component 里确实没法使用 state,我不管怎么解释都会显得很无力。难道这冥冥之中都预示着会有相似 React Hooks 的东西出现?npm

React Hooks 的本质

稍微复杂点的项目确定是充斥着大量的 React 生命周期函数(注意,即便你使用了状态管理库也避免不了这个),每一个生命周期里几乎都承担着某个业务逻辑的一部分,或者说某个业务逻辑是分散在各个生命周期里的。编程

而 Hooks 的出现本质是把这种面向生命周期编程变成了面向业务逻辑编程,你不用再去关心本不应关心的生命周期。json

一个 Hooks 演变

咱们先假想一个常见的需求,一个 Modal 里须要展现一些信息,这些信息须要经过 API 获取且跟 Modal 强业务相关,要求咱们:redux

  • 由于业务简单,没有引入额外状态管理库
  • 由于业务强相关,并不想把数据跟组件分开放
  • API 数据会随机变更,所以须要每次打开 Modal 才获取最新数据
  • 为了后期优化,不能够有额外的组件建立和销毁

咱们可能的实现以下:api

代码完整演示地址app

class RandomUserModal extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      user: {},
      loading: false,
    };
    this.fetchData = this.fetchData.bind(this);
  }

  componentDidMount() {
    if (this.props.visible) {
      this.fetchData();
    }
  }

  componentDidUpdate(prevProps) {
    if (!prevProps.visible && this.props.visible) {
      this.fetchData();
    }
  }

  fetchData() {
    this.setState({ loading: true });
    fetch('https://randomuser.me/api/')
      .then(res => res.json())
      .then(json => this.setState({
        user: json.results[0],
        loading: false,
      }));
  }

  render() {
    const user = this.state.user;
    return (
      <ReactModal
        isOpen={this.props.visible}
      >
        <button onClick={this.props.handleCloseModal}>Close Modal</button>
        {this.state.loading ?
          <div>loading...</div>
          :
          <ul>
            <li>Name: {`${(user.name || {}).first} ${(user.name || {}).last}`}</li>
            <li>Gender: {user.gender}</li>
            <li>Phone: {user.phone}</li>
          </ul>
        }
      </ReactModal>
    )
  }
}

咱们抽象了一个包含业务逻辑的 RandomUserModal,该 Modal 的展现与否由父组件控制,所以会传入参数 visiblehandleCloseModal(用于 Modal 关闭本身)。

为了实如今 Modal 打开的时候才进行数据获取,咱们须要同时在 componentDidMountcomponentDidUpdate 两个生命周期里实现数据获取的逻辑,并且 constructor 里的一些初始化操做也少不了。

其实咱们的要求很简单:在合适的时候经过 API 获取新的信息,这就是咱们抽象出来的一个业务逻辑,为了这个业务逻辑能在 React 里正确工做,咱们须要将其按照 React 组件生命周期进行拆解。这种拆解除了代码冗余,还很难复用

下面咱们看看采用 Hooks 改造后会是什么样:

完整演示地址

function RandomUserModal(props) {
  const [user, setUser] = React.useState({});
  const [loading, setLoading] = React.useState(false);

  React.useEffect(() => {
    if (!props.visible) return;
    setLoading(true);
    fetch('https://randomuser.me/api/').then(res => res.json()).then(json => {
      setUser(json.results[0]);
      setLoading(false);
    });
  }, [props.visible]);
  
  return (
    // View 部分几乎与上面相同
  );
}

很明显地能够看到咱们把 Class 形式变成了 Function 形式,使用了两个 State Hook 进行数据管理(类比 constructor),以前 cDMcDU 两个生命周期里干的事咱们直接在一个 Effect Hook 里作了(若是有读取或修改 DOM 的需求能够看 这里)。作了这些,最大的优点是代码精简,业务逻辑变的紧凑,代码行数也从 50+ 行减小到 30+ 行。

Hooks 的强大之处还不只仅是这个,最重要的是这些业务逻辑能够随意地的的抽离出去,跟普通的函数没什么区别(仅仅是看起来没区别),因而就变成了能够复用的自定义 Hook。具体能够看下面的进一步改造:

完整演示地址

// 自定义 Hook
function useFetchUser(visible) {
  const [user, setUser] = React.useState({});
  const [loading, setLoading] = React.useState(false);
  
  React.useEffect(() => {
    if (!visible) return;
    setLoading(true);
    fetch('https://randomuser.me/api/').then(res => res.json()).then(json => {
      setUser(json.results[0]);
      setLoading(false);
    });
  }, [visible]);
  return { user, loading };
}

function RandomUserModal(props) {
  const { user, loading } = useFetchUser(props.visible);
  
  return (
    // 与上面相同
  );
}

这里的 useFetchUser 为自定义 Hook,它的地位跟自带的 useState 等比也没什么区别,你能够在其它组件里使用,甚至在这个组件里使用两次,它们会自然地隔离开。

业务逻辑复用

这里说的业务逻辑复用主要是须要跨生命周期的业务逻辑。单单按照组件堆积的形式组织代码虽然也能够达到各类复用的目的,可是会致使组件很是复杂,数据流也会很乱。组件堆积适合 UI 布局,可是不适合逻辑组织。为了解决这些问题,在 React 发展过程当中,产生了不少解决方案,我认知里常见的有如下几种:

Mixins

坏处远远大于带来的好处,由于如今已经再也不支持,很少说,能够看看这篇文章:Mixins Considered Harmful

Class Inheritance

官方 很不推荐此作法,实际上我也没真的看到有人这么作。

High-Order Components (HOC)

React 高阶组件 在封装业务组件上简直是屡试不爽,它的实现是把本身做为一个函数,接受一个组件,再返回一个组件,这样它能够统一处理掉一些业务逻辑并达到复用目的。

比较常见的一个就是 react-redux 里的 connect 函数:

(图片来自 这里

可是它也被不少人吐槽嵌套问题:

(图片来自 这里

Render Props

Render Props 其实很常见,好比 React Context API

class App extends React.Component {
  render() {
    return (
      <ThemeProvider>
        <ThemeContext.Consumer>
          {val => <div>{val}</div>}
        </ThemeContext.Consumer>
      </ThemeProvider>
    )
  }
}

它的实现思路很简单,把原来该放「组件」的地方,换成了回调,这样当前组件里就能够拿到子组件的状态并使用。

可是,一样这会产生 Wrapper Hell 问题:

(图片来自 这里

Hooks

Hooks 本质上面说了,是把面向生命周期编程变成了面向业务逻辑编程,写法上带来的优化只是顺带的。

这里,作一个类比,await/async 本质是把 JS 里异步编程思惟变成了同步思惟,写法上表现出来的特色就是原来的 Callback Hell 被打平了。

总结对比:

  • await/async 把 Callback Hell 干掉了,异步编程思惟变成了同步编程思惟
  • Hooks 把 Wrapper Hell 干掉了,面向生命周期编程变成了面向业务逻辑编程

这里不得不客观地说,HOC 和 Render Props 仍是有存在的必要,一方面是支持 React Class,另外一方面,它们不光适用于纯逻辑封装,不少时候也适合逻辑 + 组件的封装场景,虽然此时使用 Hooks 也能够,可是会显得啰嗦点。另外,上面诟病的最大的问题 Wrapper Hell,我我的以为使用 Fragment 也能够基本解决。

状态盒子

首先,React Hooks 的设计是反直觉的,为何这样说呢?能够先试着问本身:为何 Hooks 只能在其它 Hooks 的函数或者 React Function 组件里?

在咱们的认知里,React 社区一直推崇函数式、纯函数等思想,引入 Hooks 概念后的 Functional Component 变的再也不纯了,useXxx 与其说是一条执行语句,不如说是一个声明。声明这里放了一个「状态盒子」,盒子有输入和输出,剩下的内部实现就一无所知,重要的是,盒子是有记忆的,下次执行到此位置时,它有以前上下文信息。

类比「代码」和「程序」的区别,前者是死的,后者是活的。表达式 c = a + b 表示把 ab 累加后的值赋值给 c,可是若是写成 c := a + b 就表示 c 的值由 ab 相加获得。看起来表述差很少,但实际上,后者隐藏着一个时间的维度,它表示的是一种联系,而不仅仅是个运算。这在 RxJS 等库中被大量使用。

这种声明目前是经过很弱的 use 前缀标识的(可是设计上会简洁不少),为了避免弄错每一个盒子和状态的对应关系,书写的时候 Hooks 须要 use 开头且放在顶层做用域,即不能够包裹 if/switch/when/try 等。若是你按文章开头引入了那个 ESLint Plugin 就不用担忧会弄错了。

总结

这篇文章可能并无一个很条理的目录结构,大可能是一些我的理解和相关思考。所以,这不能替代你去看真正的文档了解更多。若是你看完后仍是以为废话太多,不知所云,那我但愿你至少能够在下面几点上跟做者达成共鸣:

  • Hooks 本质是把面向生命周期编程变成了面向业务逻辑编程
  • Hooks 使用上是一个逻辑状态盒子,输入输出表示的是一种联系;
  • Hooks 是 React 的将来,但仍是没法彻底替代原始的 Class。

参考

文章可随意转载,但请保留此 原文连接
很是欢迎有激情的你加入 ES2049 Studio,简历请发送至 caijun.hcj(at)alibaba-inc.com 。
相关文章
相关标签/搜索