React Hooks: 深入探究

Avatar of Blessing Ene Anyebe
Blessing Ene Anyebe 发布

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

Hooks 是可重用的函数。 它们允许您使用**状态**和其他功能(例如生命周期方法等),而无需编写类。 Hook 函数让我们能够使用函数组件“钩入”React 状态生命周期,从而允许我们操作函数组件的状态,而无需将其转换为类组件。

React 在 16.8 版本中引入了 Hooks,并且此后一直在不断添加更多 Hooks。 其中一些比其他的使用更广泛、更流行,例如useEffectuseStateuseContext Hooks。 如果你使用 React,我相信你一定用过这些 Hooks。

但我想关注的是鲜为人知的 React Hooks。 虽然所有 React Hooks 都很有趣,但我真正想向你展示其中五个,因为它们可能不会出现在你的日常工作中——或者也许会,了解它们会给你一些额外的超能力。

useReducer

useReducer Hook 就像其他 Hooks 一样,是一个状态管理工具。 更具体地说,它是useState Hook 的替代方案。

如果您使用useReducer Hook 更改两个或多个状态(或操作),则无需分别操作这些状态。 该 Hook 会跟踪所有状态并集体管理它们。 换句话说:它管理和重新渲染状态更改。 与useState Hook 不同,useReducer 在处理复杂项目中的多个状态时更容易。

使用案例

useReducer 可以帮助降低处理多个状态的复杂性。 当您发现自己需要集体跟踪多个状态时,请使用它,因为它允许您将组件的状态管理和渲染逻辑视为独立的关注点。

语法

useReducer 接受三个参数,其中一个参数是可选的

  • 一个 reducer 函数
  • initialState(初始状态)
  • 一个init函数(可选)
const [state, dispatch] = useReducer(reducer, initialState)
const [state, dispatch] = useReducer(reducer, initialState initFunction) // in the case where you initialize with the optional 3rd argument

示例

下面的示例是一个包含文本输入、计数器和按钮的界面。 与每个元素交互都会更新状态。 请注意,useReducer 允许我们一次定义多个 case,而不是单独设置它们。

import { useReducer } from 'react';
const reducer = (state, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 };
    case 'DECREMENT':
      return { ...state, count: state.count - 1 };
    case 'USER_INPUT':
      return { ...state, userInput: action.payload };
    case 'TOGGLE_COLOR':
      return { ...state, color: !state.color };
    default:
      throw new Error();
  }
}

function App() {
  const [state, dispatch] = useReducer(reducer, { count: 0, userInput: '', color: false })

  return (
    <main className="App, App-header" style={{ color: state.color ? '#000' : '#FF07FF'}}>
      <input style={{margin: '2rem'}}
        type="text"
        value={state.userInput}
        onChange={(e) => dispatch({ type: 'USER_INPUT', payload: e.target.value })}
      />
      <br /><br />
      <p style={{margin: '2rem'}} >{state.count}</p>
      <section style={{margin: '2rem'}}>
        <button  onClick={(() => dispatch({ type: 'DECREMENT' }))}>-</button>
        <button onClick={(() => dispatch({ type: 'INCREMENT' }))}>+</button>
        <button onClick={(() => dispatch({ type: 'TOGGLE_COLOR' }))}>Color</button>
      </section>
      <br /><br />
      <p style={{margin: '2rem'}}>{state.userInput}</p>
    </main>
  );
}
export default App;

从上面的代码中,注意到我们能够轻松地在**reducer**(switch-case)中管理多个状态,这展示了useReducer 的优势。 这是它在处理具有多个状态的复杂应用程序时提供的强大功能。

useRef

useRef Hook 用于在元素上创建 refs 以访问 DOM。 但不仅如此,它还返回一个带有.current属性的对象,该属性可以在组件的整个生命周期中使用,允许数据持久化而不会导致重新渲染。 因此,useRef 值在渲染之间保持不变;更新引用不会触发重新渲染。

使用案例

当您想要以下操作时,可以使用useRef Hook:

  • 使用存储的可变信息操作 DOM。
  • 访问子组件(嵌套元素)中的信息。
  • 将焦点设置到某个元素上。

它在存储应用程序中的可变数据而不导致重新渲染时最为有用。

语法

useRef 仅接受一个参数,即**初始值**。

const newRefComponent = useRef(initialValue);

示例

在这里,我使用了useRefuseState Hook 来显示在文本输入中键入时应用程序重新渲染更新状态的次数。

import './App.css'

function App() {
  const [anyInput, setAnyInput] = useState(" ");
  const showRender = useRef(0);
  const randomInput = useRef();
  const toggleChange = (e) => {
    setAnyInput (e.target.value);
    showRender.current++;
  
  }
  const focusRandomInput = () => {
    randomInput.current.focus();
  }

  return (
    <div className="App">
      <input className="TextBox" 
        ref ={randomInput} type="text" value={anyInput} onChange={toggleChange}
      />
      <h3>Amount Of Renders: {showRender.current}</h3>
      <button onClick={focusRandomInput}>Click To Focus On Input </button>
    </div>
  );
}

export default App;

请注意,在文本字段中键入每个字符都会更新应用程序的状态,但永远不会触发完全重新渲染。

useImperativeHandle

您知道子组件如何访问从父组件传递给它们的函数吗? 父组件通过 props 将它们传递下来,但这种传递在某种意义上是“单向的”,因为父组件无法调用子组件中的函数。

好吧,useImperativeHandle 使父组件能够访问子组件的函数成为可能。

它是如何工作的?

  • 在子组件中定义一个函数。
  • 在父组件中添加一个ref
  • 我们使用forwardRef,允许将定义的ref传递给子组件。
  • useImperativeHandle 通过ref公开子组件的函数。

使用案例

当您希望父组件受到子组件更改的影响时,useImperativeHandle 效果很好。 因此,诸如更改焦点、递增和递减以及元素模糊等情况可能是您使用此 Hook 的情况,以便父组件可以相应地更新。

语法

useImperativeHandle (ref, createHandle, [dependencies])

示例

在此示例中,我们有两个按钮,一个在父组件中,一个在子组件中。 单击父组件按钮将从子组件检索数据,允许我们操作父组件。 它被设置为单击子组件按钮不会将任何内容从父组件传递到子组件,以帮助说明我们如何以相反的方向传递内容。

// Parent component
import React, { useRef } from "react";
import ChildComponent from "./childComponent";
import './App.css';

function useImperativeHandle() {
  const controlRef = useRef(null);
  return (
    onClick={
      () => {
        controlRef.current.controlPrint();
      }
    }
    >
    Parent Box
  );
}
export default useImperativeHandle;
// Child component
import React, { forwardRef, useImperativeHandle, useState } from "react";

const ChildComponent = forwardRef((props, ref) => {
  const [print, setPrint] = useState(false);
  useImperativeHandle(ref, () => ({
    controlPrint() 
    { setPrint(!print); },
  })
  );

  return (
    <>
    Child Box
    { print && I am from the child component }
  );
});

export default ChildComponent;

输出

useMemo

useMemo 是最不常用但最有趣的 React Hooks 之一。 它可以提高性能并降低延迟,尤其是在应用程序中进行大量计算时。 如何做到这一点? 每次组件的状态更新和组件重新渲染时,useMemo Hook 都会阻止 React 重新计算值。

您会看到,函数会响应状态更改。 useMemo Hook 接收一个函数并**返回该函数的返回值**。 它缓存该值以防止花费额外的精力重新渲染它,然后在其中一个依赖项发生更改时返回它。

此过程称为记忆化,它有助于通过记住先前请求的值来提高性能,以便可以再次使用它而无需重复所有这些计算。

使用案例

最佳用例将是您在进行大量计算时想要存储该值并在后续状态更改中使用它。 它可以带来不错的性能提升,但过度使用它可能会产生完全相反的效果,占用应用程序的内存。

语法

useMemo( () => 
  { // Code goes here },
  []
)

示例

单击按钮时,此小型程序会指示数字是偶数还是奇数,然后对该值求平方。 我在循环中添加了许多零以提高其计算能力。 它以毫秒为单位返回该值,并且由于useMemo Hook 而仍然运行良好。

// UseMemo.js
import React, { useState, useMemo } from 'react'

function Memo() {
  const [memoOne, setMemoOne] = useState(0);
  const incrementMemoOne = () => { setMemoOne(memoOne + 1) }
  const isEven = useMemo(() => { 
    let i = 0 while (i < 2000000000) i++ return memoOne % 2 === 0
  },
  [memoOne]);
  
  const square = useMemo(()=> { 
    console.log("squared the number"); for(var i=0; i < 200000000; i++);
    return memoOne * memoOne;
  },
  [memoOne]);

  return (
    Memo One - 
    { memoOne }
    { isEven ? 'Even' : 'Odd' } { square } 
  );
}
export default Memo

输出

useMemo 有点像useCallback Hook,但区别在于useMemo 可以存储来自函数的记忆化值,而useCallback 存储记忆化的函数本身。

useCallback

useCallback 钩子是另一个有趣的钩子,上一节有点剧透了它的作用。

正如我们刚刚看到的,useCallback 的工作方式类似于 useMemo 钩子,它们都使用记忆化来缓存某些内容以供以后使用。useMemo 将函数的计算结果存储为缓存值,而 useCallback 则存储并返回一个函数。

使用案例

useMemo 一样,useCallback 是一种不错的性能优化,因为它存储并返回一个记忆化的回调及其任何依赖项,而无需重新渲染。

语法

const getMemoizedCallback = useCallback (
  () => { doSomething () }, []
);

示例


{ useCallback, useState } from "react";
import CallbackChild from "./UseCallback-Child";
import "./App.css"

export default function App() {
  const [toggle, setToggle] = useState(false);
  const [data, setData] = useState("I am a data that would not change at every render, thanks to the useCallback");
  const returnFunction = useCallback(
    (name) => 
    { return data + name; }, [data]
  );
  return (
    onClick={() => {
      setToggle(!toggle);
    }}
    >
    {" "}

    // Click To Toggle
    { toggle && h1. Toggling me no longer affects any function } 
  ); 
}
// The Child component
import React, { useEffect } from "react";

function CallbackChild(
  { returnFunction }
) {
  useEffect(() => 
    { console.log("FUNCTION WAS CALLED"); },
    [returnFunction]);
  return { returnFunction(" Hook!") };
}
export default CallbackChild;

输出

最终想法

就是这样!我们刚刚介绍了五个非常方便的 React 钩子,我认为它们经常被忽视。与许多类似的总结一样,我们只是触及了这些钩子的表面。它们各自都有自己的细微差别和需要在使用时考虑的事项。但希望您对它们是什么以及何时比您可能更常使用的其他钩子更适合有一个很好的高级概念。

完全理解它们的最佳方法是通过实践。因此,我鼓励您在您的应用程序中练习使用这些钩子以更好地理解它们。为此,您可以通过查看以下资源了解更多详细信息