1、hooks 是什么

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。Hook 不能在 class 组件中使用 —— 这使得你不使用 class 也能使用 React。

2、为什么要使用 hooks

  • 难以理解的 class 、组件中必须去理解 javascript 与 this 的工作方式、需要绑定事件处理器、纯函数组件与 class 组件的差异存在分歧、甚至需要区分两种组件的使用场景;Hook 可以使你在非 class 的情况下可以使用更多的 React 特性。

  • 在组件之间复用状态逻辑很难、大型组件很难拆分和重构,也很难测试。Hook 使你在无需修改组件结构的情况下复用状态逻辑。

  • 复杂组件变得难以理解、业务逻辑分散在组件的各个方法之中,导致重复逻辑或关联逻辑。Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据)

3、react 中我们常用的 hook

什么是 Hook?

Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。Hook 不能在 class 组件中使用 —— 这使得你不使用 class 也能使用 React。

下面我们挑几种介绍一下(useState、userContext、useEffect )

1) useState 状态钩子

import React, { useState } from 'react';

function Example() {
  // 声明一个叫 "count" 的 state 变量
  const [count, setCount] = useState(0);

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

等价的 class 示例

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

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

什么时候我会用 Hook? 如果你在编写函数组件并意识到需要向其添加一些 state,以前的做法是必须将其它转化为 class。现在你可以在现有的函数组件中使用 Hook。

在 useState()中,它接受状态的初始值作为参数,即上例中计数的初始值,它返回一个数组,其中数组第一项为一个变量,指向状态的当前值。类似 this.state,第二项是一个函数,用来更新状态,类似 setState。

上述例子中没有 class 继承、没有 this、没有生命周期、代码更加简洁、这就是使用 hooks 的意义;

2)useContext() 共享状态钩子

如果需要在深层次组件之间共享状态,可以使用 useContext()。context 提供了一种在组件之间共享 props 的方式,而不必显示地通过组件树的逐层传递 props; useContext 钩子比原始 class 组件中使用 context 更为方便;

假设现在有俩个组件 Navbar 和 Messages,我们希望它们之间共享状态。

<div className="test">
  <Navbar />
  <Messages />
</div>

使用方法如下: 第一步在它们的父组件上使用 React 的 Context API,在组件外部建立一个 Context。

import React, { useContext } from 'react';

const TestContext = React.createContext({});

const Navbar = () => {
  const { username } = useContext(TestContext);

  return (
    <div className="navbar">
      <p>{username}</p>
    </div>
  );
};

const Messages = () => {
  const { messageDetail } = useContext(TestContext);

  return (
    <div className="messages">
      <p>1 message for {messageDetail}</p>
    </div>
  );
};

const App = () => {
  return (
    <TestContext.Provider
      value={{
        username: 'superawesome',
        messageDetail: 'hello world',
      }}
    >
      <div className="test">
        <Navbar />
        <Messages />
      </div>
    </TestContext.Provider>
  );
};
export default App;

等价的 class 用例

import React from 'react';

const TestContext = React.createContext({});

const Navbar = () => (
  <TestContext.Consumer>
    {({ username }) => (
      <div className="messages">
        <p>1 message for {username}</p>
      </div>
    )}
  </TestContext.Consumer>
);

const Messages = () => {
  return (
    <TestContext.Consumer>
      {({ messageDetail }) => (
        <div className="messages">
          <p>1 message for {messageDetail}</p>
        </div>
      )}
    </TestContext.Consumer>
  );
};

class App extends React.Component {
  render() {
    return (
      <TestContext.Provider
        value={{
          username: 'superawesome',
          messageDetail: 'hello world',
        }}
      >
        <div className="test">
          <Navbar />
          <Messages />
        </div>
      </TestContext.Provider>
    );
  }
}
export default App;

两种用法相比较 hooks 更加简介易懂、render 中也没有函数式写法!

3)useEffect 副作用钩子

它可以用来更好的处理副作用,如异步请求等;可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。

useEffect(() => {}, [array]);

使用 useEffect 更新网页 title

import React, { 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`;
  },[count]);

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

使用 class (等价)

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

useEffect()接受两个参数,第一个参数是你要进行的异步操作,第二个参数是一个数组,用来给出 Effect 的依赖项。只要这个数组发生变化,useEffect()就会执行。当第二项省略不填时,useEffect()会在每次组件渲染时执行。这一点类似于类组件的 componentDidMount。

我们实现一个 useEffect() 依赖项变化的例子

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

const AsyncCount = ({ countNum }) => {
  const [loading, setLoading] = useState(true);
  const [count, setCount] = useState(0);

  useEffect(() => {
    setLoading(true);
    setTimeout(() => {
      setLoading(false);
      setCount(countNum);
    }, 2000);
  }, [countNum]);
  return <>{loading ? <p>Loading...</p> : <p>{count}</p>}</>;
};

const TestCount = () => {
  const [count, setCount] = useState(0);
  const changeCount = (name) => {
    setCount(name);
  };
  return (
    <>
      <AsyncCount countNum={count} />
      <button
        onClick={() => {
          changeCount(count + 1);
        }}
      >
        增加
      </button>
      <button
        onClick={() => {
          changeCount(count - 1);
        }}
      >
        减少
      </button>
    </>
  );
};

export default TestCount;

再上述例子中,我们把处理 count 异步的操作以及是否渲染 loading,都放在了 AsyncCount hook 中;把复杂操作,通过 hooks 提取出去;将组件中关联部分拆分;

那下面我们在做一个更加细化的拆分,拆出一个自己的 hook
import React, { useState, useEffect } from 'react';

const useCount = (countNum) => {
  const [loading, setLoading] = useState(true);
  const [count, setCount] = useState(0);

  useEffect(() => {
    setLoading(true);
    setTimeout(() => {
      setLoading(false);
      setCount(countNum);
    }, 2000);
  }, [countNum]);
  return [loading, count];
};

const AsyncCount = ({ countNum }) => {
  const [loading, count] = useCount(countNum);
  return <>{loading ? <p>Loading...</p> : <p>{count}</p>}</>;
};

const TestCount = () => {
  const [count, setCount] = useState(0);
  const changeCount = (count) => {
    setCount(count);
  };
  return (
    <>
      <AsyncCount countNum={count} />
      <button
        onClick={() => {
          changeCount(count + 1);
        }}
      >
        增加
      </button>
      <button
        onClick={() => {
          changeCount(count - 1);
        }}
      >
        减少
      </button>
    </>
  );
};

export default TestCount;

我们在上述 AsyncCount 组件中再次将它的副作用操作拆分;在此组件中只关注渲染结果,useCount 接受一个数字,返回一个数组,数组中包括状态,与 count 两个结果;在我们使用 useCount 时,会根据我们传入参数的不同而返回不同的状态;

另外关于 hook 其他注意事项、在使用 useState 和一些其他钩子时、它们彼此的对应关系、是依靠 Hook 调用的顺序、所以我们不要在循环、条件、或嵌套函数中使用 hook、确保总是在你的 React 函数的最顶层调用他们,这让 React 能够在多次的 useState 和 useEffect 调用之间保持 hook 状态的正确;

用代码来解释一下顶层的意思

if (count !== 0) {
    useEffect(function persistForm() {
      ...
    });
}

这样会导致在 count 不等于 0 时,条件值为 true,会执行此次 hook,但如果在某次渲染时、如 count 值变为 0,那么此次 hook 将不在渲染,从而导致 hook 顺序发生了改变,产生不可预知的 bug;

其次在 hooks 中在 setState 值为 Object 或 Array 时,由于为引用类型,setState 通过回调函数的形式赋值,其参数值存的是 obj 的地址,由于 react 中 state 是只读的,所以在操作本身值改变是无法成功的,所以我们应当拷贝出去一份,在重新赋值;还有 hooks 异步获取当前 state 值问题等等,遇到问题多百度;

它为我们本来的开发带来了很大的变化、更加便于我们操作使用;come on!

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐