React Hooks 入门

Avatar of Kingsley Silas
Kingsley Silas 发布

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

Hooks 使得在组件中组织逻辑成为可能,使它们变得小巧且可重用,而无需编写类。从某种意义上说,它们是 React 倾向于函数的方式,因为在它们之前,我们必须在组件中编写它们,而组件本身已被证明是强大且功能性的,但它们必须在前端渲染某些内容。在某种程度上,这一切都很好,但结果是 DOM 上充斥着 div,这使得在 DevTools 中进行挖掘和调试变得棘手。

好吧,React Hooks 改变了这一点。我们无需依赖组件的自上而下的流程或以各种方式抽象组件(例如 高阶组件),而可以在组件内部调用和管理流程。Dan Abramov 在他的 理解 React Hooks 文章中很好地解释了这一点

Hooks 将 React 的理念(显式数据流和组合)应用于组件内部,而不仅仅是组件之间 这就是为什么我认为 Hooks 非常适合 React 组件模型。

与渲染属性或高阶组件等模式不同,Hooks 不会在您的组件树中引入不必要的嵌套。它们也不会受到混合体的缺点的影响。

Dan 的文章的其余部分提供了许多有用的上下文,说明 React 团队为何朝着这个方向发展(它们现在在 React v16.7.0-alpha 中可用)以及 Hooks 设计用来解决的各种问题。React 文档有一个 Hooks 入门,该入门反过来包含一个关于 促使团队创建 Hooks 的原因 的部分。我们更关心如何使用它们,所以让我们继续看一些例子吧!

在我们开始时需要注意的重要一点是,目前有九个可用的 Hooks,但我们将看看 React 文档中所说的三个基本 Hooks:useState()useEffectsetContext()。我们将在本文中深入探讨每一个 Hooks,并在最后总结高级 Hooks。

使用 useState() 定义状态

如果您在任何层面上都使用过 React,那么您可能熟悉状态是如何定义的:编写一个类并使用 this.state 初始化一个类

class SomeComponent extends React.component {
  constructor(props)
  super(props);
  this.state = {
    name: Barney Stinson // Some property with the default state value    
  }
}

React Hooks 允许我们抛弃所有这些类的东西,而是使用 useState() Hook。类似这样

import { useState } from 'react';
    
function SomeComponent() {
  const [name, setName] = useState('Barney Stinson'); // Defines state variable (name) and call (setName) -- both of which can be named anything
}

怎么说?!就是这样!请注意,我们正在类之外工作。Hooks 不在类内部工作,因为它们用于代替类。我们直接在组件中使用 Hook

import { useState } from 'react';
    
function SomeComponent() {
  const [name, setName] = useState('Barney Stinson');
  
  return
    <div>
      <p>Howdy, {name}</p>
    </div>
}

哦,您想更新 name 的状态?让我们在输出中添加一个输入框和提交按钮,并调用 setName 来在提交时更新默认名称。

import { useState } from 'react'
    
function SomeComponent() {
  const [input, setValue] = useState("");
  const [name, setName] = useState('Barney Stinson');
  
  handleInput = (event) => {
    setValue(event.target.value);
  }
  
  updateName = (event) => {
    event.preventDefault();
    setName(input);
    setValue("");
  }
  
  return (
    <div>
      <p>Hello, {name}!</p>
      <div>
        <input type="text" value={input} onChange={handleInput} />
        <button onClick={updateName}>Save</button>
      </div>
    </div>
  )
}

在这个例子中注意到其他什么了吗?我们正在构建两个不同的状态(input 和 name)。这是因为 useState() Hook 允许在同一个组件中管理多个状态!在本例中,input 是属性,setValue 持有输入元素的状态,它由 handleInput 函数调用,然后触发 updateName 函数,该函数获取输入值并将其设置为新的 name 状态。

使用 useEffect() 创建副作用

因此,定义和设置状态都很好,但是还有一个名为 useEffect() 的 Hook 可用于——您猜对了——直接在组件中定义和重用效果,而无需类或为每个方法的生命周期使用冗余代码(即 componentDidMountcomponentDidUpdatecomponentWillUnmount)。

当我们谈论效果时,我们指的是 API 调用、DOM 更新和事件监听器等内容。 React 文档 引用了数据获取、设置订阅和更改 DOM 等示例作为此 Hook 的可能用例。也许与 useState() 最大的区别在于 useEffect() 在渲染后运行。将其视为向 React 发出指令,让它保留传递的函数,然后在渲染发生以及之后的任何更新后对 DOM 进行调整。同样,React 文档很好地解释了这一点

默认情况下,它在第一次渲染后和每次更新后都会运行。[…]与其考虑“挂载”和“更新”,您可能会发现更容易理解效果发生在“渲染之后”。React 保证在运行效果时 DOM 已经更新。

好的,那么我们如何运行这些效果呢?好吧,我们首先像对 useState() 做的那样导入 Hook。

import { useEffect } from 'react';

事实上,我们可以在同一个导入中调用 useState()useEffect()

import { useState, useEffect } from 'react';

或者,构造它们

const { useState, useEffect } = React;

因此,让我们偏离我们之前的名称示例,通过连接到包含用户数据的外部 API 使用 axiosuseEffect() Hook 内,然后将该数据渲染到用户列表中。

首先,让我们引入我们的 Hooks 并初始化 App。

const { useState, useEffect } = React

const App = () => {
  // Hooks and render UI
}

现在,让我们使用 useState()users 定义为一个变量,该变量包含一个 setUsers 状态,一旦数据被获取,我们将把用户数据传递给它,以便它准备好进行渲染。

const { useState, useEffect } = React

const App = () => {
  const [users, setUsers] = useState([]);
  // Our effects come next
}

这里就是 useEffect() 发挥作用的地方。我们将使用它来连接到 API 并从中获取数据,然后将该数据映射到我们在渲染时可以调用的变量。

const { useState, useEffect } = React

const App = () => {
  const [users, setUsers] = useState([]);
  
  useEffect(() => {
    // Connect to the Random User API using axios
    axios("https://randomuser.me/api/?results=10")
      // Once we get a response, fetch name, username, email and image data
      // and map them to defined variables we can use later.
      .then(response =>
        response.data.results.map(user => ({
          name: `{user.name.first} ${user.name.last}`,
          username: `{user.login.username}`,
          email: `{user.email}`,
          image: `{user.picture.thumbnail}`
        }))
      )
      // Finally, update the `setUsers` state with the fetched data
      // so it stores it for use on render
      .then(data => {
        setUsers(data);
      });
  }, []);
  
  // The UI to render
}

好的,现在让我们渲染我们的组件!

const { useState, useEffect } = React

const App = () => {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    axios("https://randomuser.me/api/?results=10")
      .then(response =>
        response.data.results.map(user => ({
          name: `{user.name.first} ${user.name.last}`,
          username: `{user.login.username}`,
          email: `{user.email}`,
          image: `{user.picture.thumbnail}`
        }))
      )
      .then(data => {
        setUsers(data);
      });
  }, []);
  
  return (
    <div className="users">
      {users.map(user => (
        <div key={user.username} className="users__user">
          <img src={user.image} className="users__avatar" />
          <div className="users__meta">
            <h1>{user.name}</h1>
            <p>{user.email}</p>
          </div>
        </div>
      ))}
    </div>
  )
}

以下是我们得到的结果

值得注意的是,useEffect() 能够做到更多,例如链接效果并在条件下触发它们。此外,在某些情况下,我们需要在效果运行后进行清理(例如订阅外部资源)以防止内存泄漏。完全值得在 React 文档中详细了解 带有清理的效果

Context 和 useContext()

React 中的 Context 使得能够将 props 从父组件传递到子组件。这使您免于处理 props 逐层传递的麻烦。但是,您只能在类组件中使用 Context,但现在您可以使用 useContext() 在函数组件中使用 Context。让我们创建一个计数器示例,我们将使用 useContext() 将用于增加或减少计数的状态和函数从父组件传递到子组件。首先,让我们创建我们的 Context

const CountContext = React.createContext();

我们将在 App 组件中声明计数器状态和增加/减少方法,并设置包含组件的包装器。我们将在稍后在实际的计数器组件中使用 Context Hook。

const App = () => {
  // Use `useState()` to define a count variable and its state
  const [count, setCount] = useState(0);
  
  // Construct a method that increases the current `setCount` variable state by 1 with each click
  const increase = () => {
    setCount(count + 1);
  };
  
  // Construct a method that decreases the current `setCount` variable state by 1 with each click.
  const decrease = () => {
    setCount(count - 1);
  };

  // Create a wrapper for the counter component that contains the provider that will supply the context value.
  return (
    <div>
      <CountContext.Provider
        // The value is takes the count value and updates when either the increase or decrease methods are triggered.
        value={{ count, increase, decrease }}
      >
        // Call the Counter component we will create next
        <Counter />
      </CountContext.Provider>
    </div>
  );
};

好了,进入 Counter 组件!useContext() 接受一个对象(我们正在传递 CountContext 提供程序)并允许我们告诉 React 我们想要什么值(`count`)以及哪些方法触发更新的值(increasedecrease)。然后,当然,我们将通过渲染组件来完成所有操作,该组件由 App 调用。

const Counter = () => {
  const { count, increase, decrease } = useContext(CountContext);
  return (
    <div className="counter">
      <button onClick={decrease}>-</button>
      <span className="count">{count}</span>
      <button onClick={increase}>+</button>
    </div>
  );
};

然后!看看我们强大的计数器,其计数由 Context 对象和值提供支持。

总结

我们仅仅触及了 React Hooks 能够做的事情的表面,但希望这能为您奠定坚实的基础。例如,除了我们在本文中介绍的基本 Hooks 之外,还有更多高级 Hooks 可用。以下是这些 Hooks 的列表以及文档提供的描述,以便您在掌握基础知识后能够更上一层楼

Hook描述
userReducer()useState 的替代方案。接受类型为 (state, action) => newState 的 reducer,并返回当前状态以及一个 dispatch 方法。
useCallback()返回一个记忆化的回调函数。传递一个内联回调函数和一个输入数组。useCallback 将返回回调函数的记忆化版本,该版本仅在输入之一发生更改时才会更改。
useMemo()返回一个记忆化的值。传递一个“创建”函数和一个输入数组。useMemo 仅当输入之一发生更改时才会重新计算记忆化的值。
useRef()useRef 返回一个可变的 ref 对象,其 .current 属性初始化为传递的参数(initialValue)。返回的对象将在组件的整个生命周期中持续存在。
useImperativeMethodsuseImperativeMethods 自定义了在使用 ref 时公开给父组件的实例值。与往常一样,在大多数情况下应避免使用 ref 的命令式代码。useImperativeMethods 应与 forwardRef 一起使用。
useLayoutEffect

useLayoutEffect 的签名与 useEffect 相同,但它会在所有 DOM 变更之后同步触发。使用它来读取 DOM 中的布局并同步重新渲染。在 useLayoutEffect 内部调度的更新将同步刷新,在浏览器有机会绘制之前。