React复习全攻略:顶级理解Hooks君王秘术

前瞻

好家伙,在重温 React,了解其的历史版本迭代的过程中,发现了一个令人震惊的事情。

就是以前函数组件那般默默无闻,说是类组件的陪衬品也不为过,而如今,ta 的威名已然盖过了类组件。

这期间的转变历程,任由脑海遨游畅想,越发觉得在上演一场宫心大计,想想都觉得恐怖~

那么是什么给了函数组件的勇气?梁静茹吗? NoNoNo,是Hooks

接下来,我们就了解一下什么是 Hooks,以及看白莲花函数组件如何走上神坛!!!

介绍

Hooks,英文翻译为钩子。

ta的意思是组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。

React默认内置了一些常用的钩子 ,不过你也可以自定义封装自己的钩子。

所有的钩子都是为函数引入外部的功能,所以 React 约定,Hooks 统一规定使用use前缀,提升可读性(usexxx)。

由来

在16.8版本之前,我们知道 React 组件分为类组件和函数组件,类组件,受函数组件辅佐,当王称道。

一开始呢,React 是想要一个仅接受属性并可以返回 JSX 的组件(不需帝王之心的能人),类组件以防万一,函数组件便被净了身召入宫中,被封无状态函数组件

但不久之后,函数组件就告诉我们,不要称呼他的全称,我们应该称之为函数组件,因为 ta 有自己的打算~

没曾想到,在 React16.8 版本后,React 引入了 Hooks,助函数组件修炼,在那时 React 内忧外患,函数组件就凭借着 Hooks 这一手完美解决了当时令类组件也头疼的问题。

这一项举动,极大地奠定了 React 写法的转变,逐渐释放了函数组件 ta 隐忍的帝王之心,从此新王登基。

那么,Hooks到底解决了什么痛点呢?

解决了什么

当时有这么几个痛点,官方当时的说法是这样的:

  • 在组件之间复用状态逻辑很难
  • 复杂组件变得难以理解
  • 难以理解的 class

这显然是 class(类组件)的痛点所在,因为其在治理 React 国度时,就存在以下问题:

  • 难以琢磨的 this 指向(治理国度方向迷糊)
  • 关联的逻辑被拆分(大臣们不团结)
  • 众多生命周期,每一个都得适当使用(后宫众多,伤身)
  • 代码量相对更多,尤其简单组件甚是(批奏折需过层层关卡,效率低下)

针对上述问题,React 决定给函数组件带来新的能力 -- Hooks

所谓 React Hooks,就是这些增强函数组件能力特性的钩子,把这些特性「钩」进纯函数。

常用Hooks

React 提供了许多常用的 Hooks,用于在函数组件中添加状态管理、副作用处理和其他功能,下面介绍其中几个常用的,React 还有很多其他 Hooks。

下面是本章主要内容的总览图:

基础Hooks

最基础的Hooks,公认为三个:useStateuseEffectuseContext

ta 们的作用,分别是:

  • useState :向组件引入新的状态变量,这个状态会被保留在 React 中
  • useEffect :向组件提供副作用的处理区域,例如数据获取、订阅、事件处理等
  • useContext :向组件提供跨越组件层级直接传递变量,实现数据共享

useState

useState用于在函数组件中添加局部状态。通过 useState,函数组件可以像类组件一样拥有状态,并且能够响应状态的变化而重新渲染。

顶级理解:Hooks之拉拢人心

基本用法:

js 复制代码
import React, { useState } from 'react';  
  
function Example() {  
  // 声明一个新的状态变量,我们称之为 "count"  
  const [count, setCount] = useState(0);  
  
  return (  
    <div>  
      <p>You clicked {count} times</p>  
      <button onClick={() => setCount(count + 1)}>  
        Click me  
      </button>  
    </div>  
  );  
}

useState 返回一个数组,数组的第一个元素是当前状态的值,第二个元素是一个用于更新状态的函数。通过解构赋值,我们可以很方便地获取这两个值。

更新状态

setCount 函数接受一个新的状态值作为参数,并更新 count 的值。当 count 的值改变时,组件会重新渲染。

注意事项

  1. 只在函数组件的顶层调用 Hooks :确保在组件的顶层调用 useState,不要在循环、条件或嵌套函数中调用它。
  2. Hook 的调用顺序:React 依靠 Hook 的调用顺序来正确更新状态。因此,每次渲染时,Hook 的调用顺序都应该是一致的。
  3. 从 Hook 返回的值 :每次调用 useState 都会返回一个新的状态变量和更新函数,即使状态变量的初始值相同。

useEffect

useEffect 主要用于处理组件中的副作用操作。副作用是指在组件渲染过程中,可能会对外部环境产生影响的操作,比如数据获取、订阅事件、操作 DOM 等。

顶级理解:Hooks之培养暗卫

基本用法: useEffect 接受两个参数:一个副作用函数和一个依赖数组。

js 复制代码
import React, { useEffect } from 'react';  
  
function Example() {  
  useEffect(() => {  
    // 副作用函数,在组件渲染时执行  
    // 可以在这里进行副作用操作,比如数据获取、事件订阅等  
  
    // 如果返回一个函数,这个函数会在组件卸载时执行,用于清理副作用  
    return () => {  
      // 清理函数  
    };  
  }, [依赖数组]); // 依赖数组,用于指定副作用函数的执行时机  
  
  return <div>Example Component</div>;  
}

执行时机

  1. 无依赖数组 :如果 useEffect 的第二个参数(依赖数组)不传或者为空,那么副作用函数会在组件首次渲染后执行,并且在每次组件更新后也会执行。
  2. 有依赖数组:如果依赖数组不为空,并且依赖数组中的值发生变化时,副作用函数会被重新执行。如果依赖数组中的值没有变化,那么副作用函数不会执行。

使用场景

  • 替代生命周期方法useEffect 可以看作是 componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个生命周期方法的组合。当组件挂载后,副作用函数会执行(类似于 componentDidMount);当组件的 props 或 state 更新导致重新渲染后,如果依赖数组中的值有变化,副作用函数也会执行(类似于 componentDidUpdate);当组件卸载时,如果副作用函数返回了一个清理函数,那么这个清理函数会执行(类似于 componentWillUnmount)。
  • 数据获取:在副作用函数中可以进行数据获取操作,比如从 API 获取数据并更新组件的状态。
  • 事件订阅与取消订阅:在副作用函数中可以进行事件订阅,并在清理函数中取消订阅,以避免内存泄漏。

注意事项

  • 不要在副作用函数中直接修改 state :如果需要更新 state,应该使用 setState 函数或通过其他方式触发组件的重新渲染。
  • 注意副作用函数的执行次数:由于副作用函数可能会在每次组件更新后执行,因此需要注意其执行次数和性能影响。如果可能的话,尽量将副作用函数的执行限制在必要的时机。

useContext

useContext 它允许你在组件树中跨多层级访问 context 的值,而无需通过每层手动传递 props。在 React 类组件中,父组件向子组件传递参数可以通过 propscontext,但在函数式组件中,当需要向多层组件传递数据时,useContext 就变得非常有用。

顶级理解:Hooks之飞鸽传书

基本用法

要使用 useContext,首先需要创建一个 context 对象,这可以通过 React.createContext() 方法实现。然后,你可以使用 <Context.Provider> 组件来包裹需要访问该 context 的组件树,并为其提供值。最后,在组件内部,你可以使用 useContext(Context) 来获取该 context 的值。

js 复制代码
import React, { createContext, useContext } from 'react';  
  
// 创建一个 context 对象  
const MyContext = createContext();  
  
function MyComponent() {  
  // 使用 useContext 获取 context 的值  
  const value = useContext(MyContext);  
  
  // 使用 value 做一些事情...  
  return <div>{value}</div>;  
}  
  
function App() {  
  // 定义 context 的值  
  const myValue = 'Hello, World!';  
  
  // 使用 Provider 包裹组件树,并提供值  
  return (  
    <MyContext.Provider value={myValue}>  
      <MyComponent />  
    </MyContext.Provider>  
  );  
}

在这个例子中,MyComponent 能够通过 useContext(MyContext) 访问到 MyContext 中定义的值 myValue

使用场景

useContext 非常适合用于在组件树中共享全局状态或配置,例如用户认证信息、主题设置、语言偏好等。这些全局状态或配置通常不需要在每个组件中手动传递,使用 useContext 可以简化组件的逻辑,使代码更加清晰和易于维护。

注意事项

  1. 不要过度使用 :虽然 useContext 提供了方便的跨层级数据共享方式,但过度使用可能会导致组件间的耦合度增加,使得代码难以理解和维护。
  2. 性能考虑 :当 context 的值发生变化时,所有使用 useContext 的组件都会重新渲染。因此,如果 context 的值经常变化,或者组件树很大,那么这可能会导致性能问题。
  3. 后备值 :如果组件树中没有匹配的 ProvideruseContext 会返回其默认值。在调用 createContext 时,可以提供一个默认值作为后备,这样即使在没有 Provider 的情况下,useContext 也不会返回 undefined

进阶Hooks

除了基础Hooks,还有很多进阶的Hooks,这里就详细介绍一下这四个:useReduceruseCallBackuseMemouseRef

ta 们的作用,分别是:

  • useReducer :用于管理组件中复杂的状态逻辑
  • useCallBack :用于返回一个记忆的回调函数,防止不必要的重新渲染
  • useMemo :向组件提供跨越组件层级直接传递变量,实现数据共享
  • useRef :用于保存组件内部的可变数据,这些数据可在组件重新渲染保持不变

useReducer

useReducer 用于管理组件中复杂的状态逻辑。它类似于 Redux 中的 reducer,允许你将状态更新逻辑集中到一个地方,使代码更加清晰和可维护。useReducer 非常适合处理包含多个子状态的状态对象,或者当状态更新逻辑较复杂时。

顶级理解:Hooks之集权管理

基本用法

useReducer 接受两个参数:一个 reducer 函数和一个初始状态。reducer 函数接受当前状态和一个动作(action)对象,并返回一个新的状态。useReducer 返回一个包含当前状态和 dispatch 函数的数组。

下面是一个简单的示例:

js 复制代码
import React, { useReducer } from 'react';  
  
// 定义 reducer 函数  
function counterReducer(state, action) {  
  switch (action.type) {  
    case 'increment':  
      return { count: state.count + 1 };  
    case 'decrement':  
      return { count: state.count - 1 };  
    default:  
      throw new Error();  
  }  
}  
  
function Counter() {  
  // 初始化状态为 { count: 0 }  
  const [state, dispatch] = useReducer(counterReducer, { count: 0 });  
  
  return (  
    <div>  
      Count: {state.count}  
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>  
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>  
    </div>  
  );  
}

在上面的例子中,我们定义了一个 counterReducer 函数来处理状态的更新。然后,在 Counter 组件中,我们使用 useReducer 初始化状态,并获取到当前状态和 dispatch 函数。通过调用 dispatch 并传入一个动作对象,我们可以触发状态的更新。

使用场景

  1. 当多个状态需要一起更新时 :使用 useReducer 可以方便地管理多个相关的状态字段,确保它们以一致的方式更新。
  2. 当状态更新逻辑较复杂时 :对于复杂的状态更新逻辑,使用 useReducer 可以将逻辑集中到一个 reducer 函数中,使代码更加清晰和可维护。
  3. 当下一个状态依赖于之前的状态时 :在某些情况下,新的状态可能依赖于之前的状态。使用 useReducer 可以方便地处理这种情况,因为 reducer 函数会接收当前状态作为参数。

相对于 useState 的优势

useState 相比,useReducer 在处理复杂状态逻辑时具有以下优势:

  1. 更好的描述"如何更新状态"useReducer 通过将状态更新逻辑集中到一个 reducer 函数中,使得代码更加清晰和易于理解。相比之下,useState 的状态更新逻辑通常分散在组件的各个部分。
  2. 代码结构更清晰 :使用 useReducer 可以将状态更新逻辑与组件的渲染逻辑分离,使得代码结构更加清晰。
  3. 更容易进行状态管理扩展 :如果你发现 useReducer 不够用,可以将 reducer 逻辑与 Redux 这样的状态管理库结合使用,从而更容易地扩展到大型应用程序中。

useCallBack

useCallback 它用于返回一个记忆的回调函数。当你把函数传递给经过优化的子组件时,可以防止不必要的重新渲染,从而提高性能。

顶级理解:Hooks之审慎持重

基本用法

useCallback 接收一个函数作为参数,并返回一个记忆版本的该函数。只有当它的依赖项数组中的值发生变化时,这个记忆版本的函数才会更新。

js 复制代码
import React, { useCallback } from 'react';  
  
function MyComponent({ onClick }) {  
  const handleClick = useCallback(() => {  
    console.log('Button clicked!');  
  }, []); // 依赖项数组  
  
  return <button onClick={handleClick}>Click me</button>;  
}

在上面的例子中,handleClick 函数是记忆化的,这意味着只要依赖项数组(在这个例子中是空的)中的值不发生变化,每次渲染 MyComponent 时都会返回相同的 handleClick 函数实例。

使用场景

  1. 优化子组件渲染 :当你有一个经过优化的子组件(例如,使用 React.memouseMemo),并且你传递一个新的回调函数给它作为 prop 时,每次父组件渲染都会导致子组件重新渲染,即使回调函数的内容没有变化。使用 useCallback 可以防止这种情况。
  2. 避免不必要的渲染和计算 :在复杂的应用中,你可能需要避免不必要的计算和渲染。使用 useCallback 可以确保只有当依赖项发生变化时,相关的计算和渲染才会发生。

注意事项

  • 不要过度使用 useCallback。在大多数情况下,React 的性能已经足够好,不需要额外的优化。
  • 只有在确定回调函数的变化会导致性能问题时,才使用 useCallback
  • 不要仅仅为了使用 useCallback 而增加不必要的依赖项。这可能会导致回调函数比预期更频繁地更新,从而抵消了其性能优势。

useMemo

useMemo 它允许你"记住"一些计算结果,并在依赖项没有变化时避免重新进行计算。这可以帮助优化应用性能,特别是在处理复杂或计算密集型的任务时。

顶级理解:Hooks之方案化治国

基本用法

useMemo 接受两个参数:一个函数(用于执行计算)和一个依赖项数组。如果依赖项数组中的任何值发生变化,该函数将重新执行,并返回新的计算结果。

js 复制代码
import React, { useMemo } from 'react';  
  
function ExampleComponent({ a, b }) {  
  // 使用 useMemo 来"记住"计算结果  
  const computedValue = useMemo(() => {  
    // 这里执行一些计算密集型的任务  
    const result = a * b;  
    return result;  
  }, [a, b]); // 依赖项数组  
  
  return <div>{computedValue}</div>;  
}

使用场景

  1. 避免不必要的渲染 :当组件的 props 或 state 发生变化时,如果某些计算不是基于这些变化的,那么使用 useMemo 可以避免这些不必要的计算。
  2. 优化性能 :对于复杂或计算密集型的任务,使用 useMemo 可以提高应用的响应速度和性能。

注意事项

  1. 不要过度使用useMemo 并不是在所有情况下都会带来性能提升。在某些情况下,它甚至可能导致性能下降,因为 React 需要跟踪额外的依赖项和计算。因此,在使用之前,最好先确定是否真的需要它。
  2. 正确设置依赖项 :如果遗漏了某个依赖项,那么当该依赖项发生变化时,useMemo 可能不会重新执行计算,从而导致错误的结果。另一方面,如果包含了不必要的依赖项,那么可能会导致不必要的重新计算。
  3. 与其他 Hooks 配合使用useMemo 通常与其他 React Hooks(如 useStateuseEffect 等)一起使用,以构建更复杂和高效的组件。

useRef

useRef 它返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。

顶级理解:Hooks之坚守本心

useRef 主要有以下几个用途:

  1. 访问 DOM 元素或 React 元素
    当你需要直接操作 DOM 元素,或者获取 React 元素(如子组件的实例)时,可以使用 useRef。例如,你可能需要手动触发某个输入框的 focus,或者获取某个子组件的状态。
js 复制代码
import React, { useRef, useEffect } from 'react';  
  
function Example() {  
  const inputRef = useRef(null);  
  
  useEffect(() => {  
    inputRef.current.focus();  
  }, []);  
  
  return <input ref={inputRef} type="text" />;  
}
  1. 保持可变的值
    useRef 也可以用来保存一个可以在渲染之间保持不变的值。这与 useState 不同,因为 useState 会触发组件的重新渲染,而 useRef 不会。
js 复制代码
function Timer() {  
  const countRef = useRef(0);  
  
  function tick() {  
    countRef.current += 1;  
    console.log(countRef.current);  
  }  
  
  const intervalId = setInterval(tick, 1000);  
  
  return (  
    <div>  
      <h1>This is a Timer</h1>  
    </div>  
  );  
  
  // 清理函数,在组件卸载时执行  
  return () => clearInterval(intervalId);  
}
  1. 传递 ref 到子组件
    当父组件需要将 ref 传递给子组件时,可以使用 forwardRefuseRef 结合。
js 复制代码
import React, { forwardRef, useRef, useImperativeHandle } from 'react';  
  
const FancyInput = forwardRef((props, ref) => {  
  const inputRef = useRef();  
  
  useImperativeHandle(ref, () => ({  
    focus: () => {  
      inputRef.current.focus();  
    }  
  }));  
  
  return <input ref={inputRef} {...props} />;  
});  
  
function ParentComponent() {  
  const fancyInputRef = useRef();  
  
  const handleClick = () => {  
    fancyInputRef.current.focus();  
  };  
  
  return (  
    <>  
      <FancyInput ref={fancyInputRef} />  
      <button onClick={handleClick}>Focus the input</button>  
    </>  
  );  
}

自定义Hooks

React 的自定义 Hooks 是一种允许你在组件之间重用状态逻辑的技术。通过自定义 Hooks,你可以提取组件中共享的逻辑,并在其他组件中重复使用,从而提高代码的可读性和可维护性。

顶级理解:Hooks之心有万象

基本规则

  • use 开头:自定义 Hooks 必须以 "use" 开头。这个约定有助于区分是否为 Hook,并提醒开发者这个特定的函数遵循 Hook 的规则。
  • 函数内部调用其他 Hooks :自定义 Hooks 本质上就是函数,但是在这些函数内部,你需要调用其他的 React Hooks(如 useStateuseEffect 等)。
  • 自定义 Hook 的名字应该是描述性的 :Hook 应该根据其功能命名,例如 useFetchuseInputValidation 等。

下面是一个简单的自定义 Hook 示例,它使用 useStateuseEffect 来处理异步数据获取:

js 复制代码
import { useState, useEffect } from 'react';  
  
function useFetch(url) {  
  const [data, setData] = useState(null);  
  const [error, setError] = useState(null);  
  const [isLoading, setIsLoading] = useState(false);  
  
  useEffect(() => {  
    let didCancel = false;  
  
    const fetchData = async () => {  
      setIsLoading(true);  
      try {  
        const response = await fetch(url);  
        if (!response.ok) {  
          throw new Error('Network response was not ok');  
        }  
        const json = await response.json();  
        if (!didCancel) {  
          setData(json);  
        }  
      } catch (error) {  
        if (!didCancel) {  
          setError(error);  
        }  
      } finally {  
        setIsLoading(false);  
      }  
    };  
  
    fetchData();  
  
    return () => {  
      didCancel = true;  
    };  
  }, [url]);  
  
  return { data, error, isLoading };  
}  
  
// 使用自定义 Hook  
function MyComponent() {  
  const { data, error, isLoading } = useFetch('https://api.example.com/data');  
  
  if (isLoading) {  
    return <div>Loading...</div>;  
  }  
  
  if (error) {  
    return <div>Error: {error.message}</div>;  
  }  
  
  return (  
    <div>  
      {/* 显示数据 */}  
    </div>  
  );  
}

在这个示例中,useFetch 是一个自定义 Hook,它封装了异步数据获取的逻辑。你可以在多个组件中重复使用 useFetch,而无需在每个组件中重复编写相同的逻辑。

结语

至此,所有常用的Hooks君王秘术已展现完毕。这些秘术不仅展示了技术的深度和广度,更为我们在实际开发中提供了无穷的灵感和可能。

无论是数据处理的巧妙,还是状态管理的智慧,亦或是组件复用的创新,它们都以其独特的方式,提升了我们的开发效率和代码质量。

然而,秘术虽好,也需善加运用。在未来的开发中,我们应根据具体场景,结合现有秘术方案,灵活运用Hooks君王秘术,创造出更加优雅、高效、可维护的代码。同时,我们也应保持对新技术的探索热情,不断拓宽视野,提升自我,以应对日新月异的技术挑战。

在此,感谢各位对我的关注和支持。愿我们在技术的海洋中,乘风破浪,勇往直前,共同书写更加辉煌的篇章!

相关推荐
yuhaiqiang32 分钟前
为什么这道初中数学题击溃了所有 AI
前端·后端·面试
djk888834 分钟前
支持手机屏幕的layui后台html模板
前端·html·layui
紫_龙36 分钟前
最新版vue3+TypeScript开发入门到实战教程之watch详解
前端·javascript·typescript
默默学前端1 小时前
ES6模板语法与字符串处理详解
前端·ecmascript·es6
lxh01131 小时前
记忆函数 II 题解
前端·javascript
我不吃饼干1 小时前
TypeScript 类型体操练习笔记(三)
前端·typescript
华仔啊1 小时前
除了防抖和节流,还有哪些 JS 性能优化手段?
前端·javascript·vue.js
CHU7290352 小时前
随时随地学新知——线上网课教学小程序前端功能详解
前端·小程序
清粥油条可乐炸鸡2 小时前
motion入门教程
前端·css·react.js
这是个栗子2 小时前
【Vue3项目】电商前台项目(四)
前端·vue.js·pinia·表单校验·面包屑导航