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君王秘术,创造出更加优雅、高效、可维护的代码。同时,我们也应保持对新技术的探索热情,不断拓宽视野,提升自我,以应对日新月异的技术挑战。

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

相关推荐
也无晴也无风雨34 分钟前
深入剖析输入URL按下回车,浏览器做了什么
前端·后端·计算机网络
Martin -Tang1 小时前
Vue 3 中,ref 和 reactive的区别
前端·javascript·vue.js
FakeOccupational3 小时前
nodejs 020: React语法规则 props和state
前端·javascript·react.js
小牛itbull3 小时前
ReactPress:构建高效、灵活、可扩展的开源发布平台
react.js·开源·reactpress
放逐者-保持本心,方可放逐3 小时前
react 组件应用
开发语言·前端·javascript·react.js·前端框架
曹天骄4 小时前
next中服务端组件共享接口数据
前端·javascript·react.js
阮少年、4 小时前
java后台生成模拟聊天截图并返回给前端
java·开发语言·前端
郝晨妤5 小时前
鸿蒙ArkTS和TS有什么区别?
前端·javascript·typescript·鸿蒙
AvatarGiser6 小时前
《ElementPlus 与 ElementUI 差异集合》Icon 图标 More 差异说明
前端·vue.js·elementui
喝旺仔la6 小时前
vue的样式知识点
前端·javascript·vue.js