什么是 React Hooks

HarrimanDoreen 发布于1年前
0 条问题

React Hooks是React在2018年带给我们的一个惊喜。

Dan Abramov 在2018年10月举行的 React Conf开发者大会上 宣布将React Hooks作为React 16.7.0-alpha的提议引入。react hooks作为在React函数组件中使用状态和副作用的一种方法,它将带给函数组件翻天覆地的变化。

你需要把 react 和 react-dom 更新到 16.7.0-alpha 及以上体验react hooks,如果你有疑问可以 反馈 。

本文将带你了解react hoos的背后动机,以及他会带来哪些改变,本文不详细介绍官方api。如果你不了解也没有关系,react hooks是100%向后兼容的。

为什么是React Hooks?

长期以来我们使用react时需要明白 Stateless Component (类组件)和 Functional Component (无状态组件)有什么区别,不能把他们混为一谈,甚者会全用 Stateless Component 以免出错 。

首先我们要知道什么是hooks,他用于在函数组件内引入状态管理和副作用,而不需要将 Functional Component 重构为 Stateless Component 。

我们在之前的React开发中遇到了哪些问题:

  • 不必要的组件重构 :以前,只有类组件用于本地状态管理和生命周期方法。当我们想在无状态组件内使用state或者生命周期方法,我们必须将无状态组件重构为类组件(反之亦然)。

  • 副作用 :在Stateless Component中,副作用主要在生命周期方法中引入(例如componentDidMount,componentDidUpdate,componentWillUnmount)。副作用可能是在需要状态改变时,通常伴随着设置和清理阶段。例如,如果没有移除事件侦听器,则可能会遇到react性能问题。

  • 多组件间的逻辑复用问题 :在React中使用 higher-order component (HOC 高阶组件) 和 render-props 引入抽象和可重用性。还有 React的Context的Provider和Consumer Components 引入了另一个抽象层次。这些方式都是使用了包装组件,他带来了“嵌套地狱“(wrapper hell)。

  • javascript class混淆 :React使用JavaScript中的类作为定义React组件的一种方法。class只是声明,而组件的实际用法是它的实例化,而类实例的this对象与setState等交互,对于初学者而言,常常让其困惑。

React官方一直在致力于解决如何实现业务分离,以及组件内逻辑的复用。这里说的业务逻辑复用主要是指生命周期的业务逻辑,不是简单的UI到视图组件(组件拆分也能实现复用,但是会导致组件堆积、业务复杂化)。如果你对React的发展历程比较清楚,可以略过下面的内容直接看下一节,在React发展过程中产生了很多解决方案:

Mixins

React早起版本引入,现在已经废弃,如果想了解可以查看 Mixins Considered Harmful 。

High-Order Components

高阶组件 是一个接受一个组件作为参数并返回新组件的函数。这里不得不提鼎鼎大名的 recompose 高阶组件库了,现在作者已经加入React团队。如:

import { compose } from 'recompose';
import { withRouter } from 'react-router-dom';

function App({ history, state, dispatch }) {
  return (
    <ThemeContext.Consumer>
      {theme =>
        <Content theme={theme}>
          ...
        </Content>
      }
    </ThemeContext.Consumer>
  );
}

export default compose(
  withRouter,
  withReducer(reducer, initialState)
)(App);

这种方式我们经常使用,例如我们比较熟悉的react-router,其中的connect方法就是我们比较常见的高阶组件。

Render Props

在放组件的地方换成回调,这样当前组件就可以拿到子组件的状态, React Context :

import React from 'react';

class Example extends React.Component {
  render() {
    return (
      <ThemeProvider>
          <MyContext.Consumer>
              {value => /* render something based on the context value */}
          </MyContext.Consumer>
      </ThemeProvider>
    );
  }
}

export default Example;

React Hooks带来什么变化

当我们的项目越来越复杂,我们组件的各个生命周期函数内充斥着大量的业务逻辑。

什么是 React Hooks

而使用React hooks写代码是这个样子的:

什么是 React Hooks

如上例,React hooks利用useEffect代替了一些生命周期函数,让我们不再需要关注生命周期,从而使得我们更加专注于业务。

React Hooks带来的好处不仅是代码更清晰,还有以下特性:

  • 将状态和变更状态的逻辑进行组合,后者影响前者,前者仅被后者影响,且不受外界影响

  • 颗粒化的对状态进行拆分管理,可以多次使用useState,hooks可以引用其他hooks

  • 状态易于与UI分离

因为 React Hooks 的特性,如果一个hook没有UI,那么他就可以被其他Hook封装,根据hook的写法,更加容易实现状态与UI分离。

看一个例子,如:

function MyComponent() {
  // 无状态的
  const [count, setCount] = useCount();
  return <div>{count}</div>;
}

useCount算是无状态的,React Hooks的本质就是Higher-Order Components或者render-props的另一种写法,我们可以使用render-props的写法,更好理解:

<Count>{(count, setCount) => <MyComponent count={count} setCount={setCount} />}</Count>;

function MyComponent(props) {
  // 渲染UI
  return <div>{props.count}</div>;
}

那什么是有状态的呢?

// 自定义Hook
function useCount() {
  // 有状态的,状态可以改变,useState使用官方hook
  const [count, setCount] = useState(0);
  return [count, setCount];
}

useCount是自定义Hook,useState是官方的Hook,他们的定义是一样的。

因此我们可以说hook更好的践行了“有状态的组件没有渲染,有渲染的组件没有状态”这句话。

React Hooks 怎么工作

React Hooks规则

Hooks 是JavaScript函数,但使用时需要遵循两个规则:

  • 只能在顶层调用hook,不能在循环、条件或嵌套函数中调用

  • 只能在react Function中调用,不能在常规JavaScript函数中调用。可以在自定义hook中调用hook

怎么使用

下面用一个官方给出的例子看一下他的实现:

这是我们熟悉的类组件写法:

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  componentDidMount() {
    document.title = `You clicked ${this.state.count} times`;
  }

  componentDidUpdate() {
    document.title = `You clicked ${this.state.count} times`;
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

如果用react hooks怎么写呢:

import { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);
  // Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {
    // Update the document title using the browser API
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

看上去是不是简单清晰,官方提供了很多方法,完全可以替换类组件的功能,下面我们以一个搜索功能为例演示如何实现一个自定义hook:

import React, { Fragment, useState, useEffect } from 'react';
// ajax库
import axios from 'axios';

// 自定义组件initialUrl请求的url,initialData参数
const useDataApi = (initialUrl, initialData) => {
  // 获取接口数据
  const [data, setData] = useState(initialData);
  // 变更请求的url
  const [url, setUrl] = useState(initialUrl);
  // 加载状态
  const [isLoading, setIsLoading] = useState(false);
  // 错误状态
  const [isError, setIsError] = useState(false);

  // 获取数据
  const fethData = async () => {
    // 请求前状态变更
    setIsError(false);
    setIsLoading(true);
    try {
      const result = await axios(url);
      setData(result.data);
    } catch (error) {
      // 异常处理
      setIsError(true);
    }
    // 请求后状态变更
    setIsLoading(false);
  }

  useEffect(()=>{
    // async使用隐式Promise返回其结果,useEffect应该不返回任何内容或清理功能
    // 这就是为什么useEffect不允许在函数中直接使用async的原因
    // 我们可以通过如下方式
    fethData()
  },[url])

  // 从doGet函数外部传递URL状态
  const doGet = (event, url) => {
    setUrl(url);
    event.preventDefault();
  };

  return { data, isLoading, isError, doGet };
};

function App() {
  //更改url参数
  const [query, setQuery] = useState('react-hooks');

  const { data, isLoading, isError, doGet } = useDataApi(
    'https://www.58.com/api/search?query=react-hooks',
    { list: [] },
  );

  return (
    <Fragment>
      <form
        onSubmit={event =>
          doGet(event,`https://www.58.com/api/search?query=${query}`,)
        }
      >
        <input
          type="text"
          value={query}
          onChange={event => setQuery(event.target.value)}
        />
        <button type="submit">搜索</button>
      </form>

      {isError && <div>加载失败...</div>}

      {isLoading ? (
        <div>加载中...</div>
      ) : (
        <ul>
          {data.list.map(item => (
            <li key={item.id}>
              <a href={item.url}>{item.title}</a>
            </li>
          ))}
        </ul>
      )}
    </Fragment>
  );
}

export default App;

结语

React Hooks并不是类组件的替代,它更好的践行了“有状态的组件没有渲染,有渲染的组件没有状态”, 想了解更多关于React Hooks的信息

参考资料

  • React Hooks: https://reactjs.org/docs/hooks-intro.html

  • React Conf开发者大会: https://www.youtube.com/watch?v=dpw9EHDh2bM

  • recompose: https://github.com/acdlite/recompose

  • React Hooks开发讨论: https://github.com/reactjs/rfcs/pull/68

  • React Context: https://reactjs.org/docs/context.html#contextconsumer

  • 高阶组件: https://reactjs.org/docs/higher-order-components.html#use-hocs-for-cross-cutting-concerns

  • [译]React 的今天和明天 —— 第二部分 https://juejin.im/post/5bfccbf8f265da61407e97b5

什么是 React Hooks

查看原文: 什么是 React Hooks

  • organicladybug
  • ticklishfrog
  • purplepanda
  • whitebutterfly
  • silvertiger
  • purplebear
  • organicwolf
  • yellowelephant
需要 登录 后回复方可回复, 如果你还没有账号你可以 注册 一个帐号。