五个React hook的介绍及实例

266 阅读6分钟

钩子是可重用的函数。它们允许你在不写类的情况下使用状态和其他功能(如生命周期方法等)。钩子函数让我们使用功能组件 "钩住 "React的状态生命周期,让我们能够操作我们的功能组件的状态,而不需要将它们转换为类组件。

React早在16.8版就引入了钩子,此后一直在增加。有些钩子比其他钩子更常用、更流行,比如useEffectuseStateuseContext 。我毫不怀疑,如果你使用React工作,你已经接触到了这些钩子。

但我感兴趣的是那些不太知名的React钩子。虽然所有的React钩子都有自己的有趣之处,但有五个钩子我真的想告诉你,因为它们可能不会在你的日常工作中出现--或者它们确实出现了,而了解它们会给你带来一些额外的超级力量。

目录

  • useReducer
  • useRef
  • useImperativeHandle
  • useMemo
  • 使用回调
  • 最后的想法

useReducer

useReducer 钩子和其他钩子一样是一个状态管理工具。具体来说,它是useState 钩子的一个替代品。

如果你使用useReducer 钩子来改变两个或更多的状态(或动作),你就不必单独操作这些状态了。这个钩子会跟踪所有的状态,并对它们进行集体管理。换句话说:它管理并重新呈现状态变化。与useState 钩子不同,useReducer 在处理复杂项目中的许多状态时更容易。

用例

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

语法

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

  • 一个还原器函数
  • 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 ,它允许我们一次定义多个案例,而不是单独设置它们。

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;

从上面的代码中,注意到我们是如何在还原器(switch-case)中轻松管理多个状态的,这显示了useReducer 的好处。这是它在处理具有多个状态的复杂应用时给予的力量。

useRef

useRef 钩子被用来在元素上创建引用,以便访问DOM。但不仅如此,它还会返回一个带有.current 属性的对象,该对象可以在组件的整个生命周期中使用,允许数据持续存在而不引起重新渲染。因此,useRef 的值在两次渲染之间保持不变;更新引用不会触发重新渲染。

使用案例

当你想要的时候,可以使用useRef 钩子:

  • 用存储的可变信息操纵DOM。
  • 从子组件(嵌套元素)获取信息。
  • 在一个元素上设置焦点。

当在你的应用程序中存储可改变的数据时,它是最有用的,不会导致重新渲染。

语法

useRef 只接受一个参数,那就是初始值

const newRefComponent = useRef(initialValue);

例子

在这里,我使用useRefuseState 钩子来显示一个应用程序在输入一个文本输入时渲染更新状态的次数。

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

你知道一个子组件是如何访问从父组件传递下来的函数的吗?父组件通过道具传递这些功能,但这种传递是 "单向的",也就是说,父组件无法调用子组件中的某个功能。

好吧,useImperativeHandle ,使父类可以访问子类组件的功能。

那是怎么做的呢?

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

使用案例

useImperativeHandle 当你想让父级组件受到子级组件变化的影响时,这个方法很有效。因此,像焦点改变、增量和减量以及模糊的元素等,都可能是你发现自己要使用这个钩子的情况,这样父组件就可以相应地被更新。

语法

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钩子之一。它可以提高性能,减少延迟,特别是在你的应用程序的大型计算中。怎么说呢?每当一个组件的状态更新和组件重新渲染时, 钩子可以防止React不得不重新计算数值。useMemo

你看,函数会对状态变化做出反应。useMemo 钩子接收一个函数并返回该函数的返回值。它缓存该值以防止花费额外的精力重新渲染它,然后在其中一个依赖关系发生变化时返回它。

这个过程被称为 记忆化它有助于提高性能,因为它记住了前一个请求的值,所以它可以再次使用而不需要重复所有的数学运算。

使用案例

最好的用例是在你处理繁重的计算时,你想存储数值并在随后的状态变化中使用它。它可以是一个很好的性能优势,但如果使用得太多,就会产生完全相反的效果,占用你的应用程序的内存。

语法

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

例子

当点击按钮时,这个小程序会指示一个数字是偶数还是奇数,然后将数值平方化。我在循环中加入了很多零,以增加其计算能力。由于有了useMemo 这个钩子,它以秒为单位返回数值,并且仍然运行良好。

// 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 钩子,但不同的是,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钩子,我认为它们经常被忽视。就像很多这样的综述一样,我们只是在这些钩子的表面上做了一些文章。当你使用它们时,它们都有各自的细微差别和考虑因素。但希望你对它们有一个很好的高层次的概念,知道它们是什么,以及什么时候它们可能比你可能更经常使用的另一个钩子更适合。