了解 React Hooks

254 阅读6分钟

新到一个公司,其中有一个系统用到了hooks,之前没有使用过,所以全面了解了一下,在此总结。

Hooks介绍

之前没有用hooks写react的时候,用class类组件,state在构造函数定义,然后是钩子函数。


在这里,发现用的是函数组件,useState来定义state,useEffect来初始获取接口数据,比如查询列表等等。使用hooks的目的是让函数组件具备class组件的能力。

  • React 一直都提倡使用函数组件,但是有时候需要使用 state 或者其他一些功能时,只能使用类组件,因为函数组件没有实例,没有生命周期函数,只有类组件才有。
  • Hooks 是 React 16.8 新增的特性,React Hooks 就是让你不必写class组件就可以用state和其他的React特性;
  • 如果你在编写函数组件并意识到需要向其添加一些 state,以前的做法是必须将其它转化为 class。现在你可以直接在现有的函数组件中使用 Hooks
  • 凡是 use 开头的 React API 都是 Hooks

这里useState比原来的setstate感觉方便一点。在这里,没有super(props),没有this,没有生命周期。hooks其实就是对原有React 的 API 进行了封装,暴露比较方便使用的钩子。


常用hook

useState 初始化和设置状态 。会返回一个数组:一个 state,一个更新 state 的函数。

 const [isEdit, setEdit] = useState(false);

  • 在初始化渲染期间,返回的状态 (state) 与传入的第一个参数 (initialState) 值相同
  • 你可以在事件处理函数中或其他一些地方调用这个函数。它类似 class 组件的 this.setState,但是它不会把新的 state 和旧的 state 进行合并,而是直接替换

useEffect  componentDidMount,componentDidUpdate和componentWillUnmount和结合体,所以可以监听useState定义值的变化 监听变化 。会在第一次渲染之后和每次更新之后都会执行。

useEffect(() => {        getList();    }, [fresh]);

fresh 改变,就会调用getList()。

import React, { useState, useEffect } from 'react';
import axios from 'axios';

function App() {
  const [data, setData] = useState({ hits: [] });

  useEffect(async () => {
    const result = await axios(
      'http://localhost/api/v1/search?query=redux',
    );

    setData(result.data);
  });

  return (
    <ul>
      {data.hits.map(item => (
        <li key={item.objectID}>
          <a href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  );
}

export default App;

在useEffect中,不仅会请求后端的数据,还会通过调用setData来更新本地的状态,这样会触发view的更新。

useEffect在组件mount时执行,但也会在组件更新时执行。因为我们在每次请求数据之后都会设置本地的状态,所以组件会更新,因此useEffect会再次执行,因此出现了无限循环的情况。我们只想在组件mount时请求数据。我们可以传递一个空数组作为useEffect的第二个参数,这样就能避免在组件更新执行useEffect,只会在组件mount时执行。就是图一里面的使用方法。

useCallback  记忆作用,共有两个参数,第一个参数为一个匿名函数,就是我们想要创建的函数体。第二参数为一个数组,里面的每一项是用来判断是否需要重新创建函数体的变量,如果传入的变量值保持不变,返回记忆结果。如果任何一项改变,则返回新的结果。 

    const submit = useCallback(() => {        validateFields((err, values) => {            if (err) {                console.log(err);                return message.info('请完整填写表单');            }else{                onOk({                    vmData,                    userId: values.userId                });            }        })    },[]);

useMemo 和useCallback就是解决性能问题的杀手锏。

useCallback和useMemo的参数跟useEffect一致,他们之间最大的区别有是useEffect会用于处理副作用,而前两个hooks不能。

useMemo和useCallback都会在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行;并且这两个hooks都返回缓存的值,useMemo返回缓存的变量,useCallback返回缓存的函数。

他们的作用,就是类似生命周期中的shouldComponentUpdate,当变量改变,去判断是否重新渲染。

知道useCallback有什么样的特点,那有什么作用呢?

使用场景是:有一个父组件,其中包含子组件,子组件接收一个函数作为props;通常而言,如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新。

不仅是上面的例子,所有依赖本地状态或props来创建函数,需要使用到缓存函数的地方,都是useCallback的应用场景。

useContext  定义一个全局的对象,类似 context  

useReducer 可以增强函数提供类似 Redux 的功能 

useMemo 作用和传入参数与 useCallback 一致,useCallback返回函数,useDemo 返回值   

useRef  获取 ref 属性对应的 dom   

前面三个用的比较多。另外,useEffect、useMemo、useCallback都是自带闭包的。也就是说,每一次组件的渲染,其都会捕获当前组件函数上下文中的状态(state, props),所以每一次这三种hooks的执行,反映的也都是当前的状态,你无法使用它们来捕获上一次的状态。对于这种情况,我们应该使用ref来访问。


注意

  • 只能在顶层调用 Hook,不要在循环、条件或嵌套函数中调用 Hook。
  • 仅从 React 函数式组件中调用 Hook。不要从常规 JavaScript 函数调用 Hook。(还有另一个有效的地方来调用 Hook,即你的自定义 Hook。)


举个栗子

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

function App() {
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('redux');
  const [url, setUrl] = useState(
    'http://localhost/api/v1/search?query=redux',
  );
  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState(false);

  useEffect(() => {
    const fetchData = async () => {
      setIsError(false);
      setIsLoading(true);

      try {
        const result = await axios(url);

        setData(result.data);
      } catch (error) {
        setIsError(true);
      }

      setIsLoading(false);
    };

    fetchData();
  }, [url]);
  return (
    <Fragment>
      <input
        type="text"
        value={query}
        onChange={event => setQuery(event.target.value)}
      />
      <button
        type="button"
        onClick={() =>
          setUrl(`http://localhost/api/v1/search?query=${query}`)
        }
      >
        Search
      </button>

      {isError && <div>Something went wrong ...</div>}

      {isLoading ? (
        <div>Loading ...</div>
      ) : (
        <ul>
          {data.hits.map(item => (
            <li key={item.objectID}>
              <a href={item.url}>{item.title}</a>
            </li>
          ))}
        </ul>
      )}
    </Fragment>
  );
}

export default App;

在useEffect中,请求数据前将loading置为true,在请求完成后,将loading置为false。我们可以看到useEffect的依赖数据中并没有添加loading,这是因为,我们不需要再loading变更时重新调用useEffect。请记住:只有某个变量更新后,需要重新执行useEffect的情况,才需要将该变量添加到useEffect的依赖数组中。

loading处理完成后,还需要处理错误,这里的逻辑是一样的,使用useState来创建一个新的state,然后在useEffect中特定的位置来更新这个state。由于我们使用了async/await,可以使用一个大大的try-catch。

每次useEffect执行时,将会重置error;在出现错误的时候,将error置为true;在正常请求完成后,将error置为false。


todo:后面还需要研究自定义hook,以及如何整合项目冲重复使用的逻辑,进行优化。