Vue 和 React 中的hook

 一、为什么会有Hook

Vue 和 React 中的 "hook" 都是为了解决在组件中复用逻辑的问题。主要为了解决以下两个问题:

1.逻辑复用困难

在 Hook 出现之前,React 和 Vue 都有各自的代码复用方式,但都存在一些问题:

  • React:主要使用高阶组件(Higher-Order Components, HOCs)渲染属性(Render Props)。这二种方式往往会导致组件嵌套层级过深,形成所谓的“Wrapper Hell”(包裹地狱),让代码变得难以阅读和维护。

  • Vue:主要使用混入(Mixins)。当一个组件使用了多个混入时,开发者很难判断某个属性或方法是来自哪个混入,容易发生命名冲突。同时,由于数据来源不清晰,代码的可维护性也会降低。

Hook 的出现提供了一种更优雅、更清晰的方式来封装复用状态的逻辑。将一组相关的状态、副作用和方法封装成一个自定义 Hook(React)或组合式函数(Vue),然后在多个组件中引用,避免了复杂的组件嵌套或命名冲突。

1.状态逻辑分散且难以组织

在传统的类组件(React)或选项式 API(Vue)中,一个功能的完整逻辑往往被拆分到不同的地方。

  • React 类组件:比如一个组件需要处理数据获取,相关的代码会分散在 componentDidMountcomponentDidUpdatecomponentWillUnmount 等不同的生命周期方法中,使一个功能(比如数据订阅)的逻辑不集中。

  • Vue 选项式 API:和react类似,一个功能的逻辑会被分散到 datamethodscomputedmounted 等不同的选项中。当组件变得越来越复杂时,开发者需要来回切换不同的代码块才能理解一个完整的功能,可读性大大降低。

Hook 解决了这个问题,它允许开发者将相关联的逻辑代码组织在一起

  • React:通过 useStateuseEffect 等 Hook,你可以把与某个功能相关的状态和副作用代码写在一起。

  • Vue:通过组合式 API,你可以将同一功能的 refcomputedwatch 和生命周期钩子集中在一个 setup 函数中,甚至提取到一个独立的组合式函数中。

这种方式让代码更易于复用维护,提高了可读性,也让复杂组件的维护变得更简单。

二、React Hook

React Hook 是在 React 16.8 中引入的。在此之前,如果你需要在组件中使用状态(state)或生命周期方法,必须使用类组件。Hook 的出现打破了这一限制,让开发者可以在函数组件中使用这些功能。

  • 核心思想: React 的函数组件每次渲染时都会重新执行。Hook 就像一个 "钩子",将状态和副作用(side effects)"钩住"在这次渲染和下一次渲染之间。

  • 常用 Hook:

    • useState: 用于给函数组件添加状态。每次调用 setState 更新状态时,组件都会重新渲染。

    • useEffect: 用于处理副作用,例如数据请求、DOM 操作、订阅事件等。它在组件渲染后执行,可以根据依赖数组来控制何时重新执行。

    • useContext: 用于在组件树中共享数据,避免一层层地通过 props 传递。

    • useRef: 用于引用 DOM 元素或在组件多次渲染之间保持一个可变的值,且该值的改变不会引起组件重新渲染。

    • useCallback, useMemo: 用于性能优化,分别缓存函数和计算结果,避免不必要的重新创建和计算。

  • 使用规则:

    • 只能在顶层调用: Hook 必须在函数组件或自定义 Hook 的顶层调用,不能在循环、条件判断或嵌套函数中调用。这是为了保证每次渲染时 Hook 的调用顺序是固定的。

    • 自定义 Hook: 可以将多个 Hook 组合成一个自定义 Hook,用来复用复杂的逻辑。

举个例子

假设我们要创建一个自定义 Hook 来追踪鼠标在屏幕上的位置:

// useMousePosition.js - 这是一个自定义 Hook
import { useState, useEffect } from 'react';

function useMousePosition() {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  useEffect(() => {
    const handleMouseMove = (e) => {
      setPosition({ x: e.clientX, y: e.clientY });
    };

    window.addEventListener('mousemove', handleMouseMove);

    // 清理副作用
    return () => {
      window.removeEventListener('mousemove', handleMouseMove);
    };
  }, []); // 依赖数组为空,只在组件挂载时执行一次

  return position; // 返回一个对象,包含 x 和 y
}

export default useMousePosition;

然后在一个函数组件中调用它:

// App.js - 这是一个函数组件
import useMousePosition from './useMousePosition';

function App() {
  const position = useMousePosition(); // 在函数组件中调用自定义 Hook

  return (
    <div>
      <h1>The mouse is at:</h1>
      <p>
        x: {position.x}, y: {position.y}
      </p>
    </div>
  );
}

export default App;

从上面的例子可以看出,useMousePosition 是一个独立的函数,它负责处理逻辑。而 App 才是一个函数组件,它调用 useMousePosition 来获取数据,并利用这些数据来渲染 UI。

三、Vue Composition API(Vue Hook)

Vue Composition API(组合式 API)是在 Vue 3 中正式推出的。它的出现是为了解决 Vue 2 中 Options API 在处理大型复杂组件时,代码分散、逻辑难以组织和复用的问题。

  • 核心思想: Vue 的组合式 API 基于 Vue 的响应式系统。它允许你将同一功能的逻辑代码组织在一起,而不是像 Options API 那样把数据、方法、生命周期钩子等选项分散在不同地方。

  • 常用 API:

    • ref, reactive: 用于创建响应式数据。ref 主要用于基本数据类型,而 reactive 用于对象和数组。当数据改变时,所有使用到该数据的组件都会自动更新。

    • computed: 用于创建计算属性,其值会根据依赖的响应式数据自动更新。

    • watch, watchEffect: 用于监听响应式数据的变化并执行特定的回调函数。

    • 生命周期钩子: 如 onMounted, onUpdated, onUnmounted 等,用于在组件的不同生命周期阶段执行代码。

  • 使用方式:

    • <script setup>: 这是 Vue 3 中使用组合式 API 的推荐方式,它简化了语法,让代码更简洁。

    • 可组合性: 你可以轻松地将多个逻辑片段(称为 "Composable" 或 "组合式函数")提取到单独的文件中,并在多个组件中复用。这类似于 React 的自定义 Hook。

定义一个自定义hook:

// useCounter.js
import { ref, computed } from 'vue';

export function useCounter() {
  const count = ref(0);
  const double = computed(() => count.value * 2);

  const increment = () => {
    count.value++;
  };

  return { count, double, increment };
}

使用

// main.js
import { useCounter } from './useCounter';

const { count, double, increment } = useCounter();

console.log(count.value); // 输出: 0
increment();
console.log(count.value); // 输出: 1
console.log(double.value); // 输出: 2

四、React Hook和Vue Composition API(Vue Hook)限制

1. React Hook 的限制

React Hook 的限制主要源于其依赖于调用顺序的设计。React 内部通过一个链表来存储每个 Hook 的状态,并根据 Hook 的调用顺序来匹配状态。因此,如果调用顺序改变,React 就无法正确地找到对应的状态,导致程序出错。

主要有以下两个核心规则:

  1. 只在 React 函数的顶层调用 Hook

    • 限制:你不能在循环、条件语句(if)、嵌套函数或回调函数中调用 Hooks。

    • 原因:这保证了每次组件渲染时,Hook 的调用顺序是完全一致的。例如,如果 useState 被包裹在 if 语句中,那么当条件不满足时,Hook 的调用顺序就会中断,导致后续的 Hook 状态错乱。

    • 解决方案:如果需要在条件渲染中管理状态,应该在条件语句外部调用 Hook,然后根据条件来决定如何使用它们返回的值。

  2. 只在 React 函数组件或自定义 Hook 中调用 Hook

    • 限制:你不能在普通的 JavaScript 函数或类组件中调用 Hook。

    • 原因:Hook 依赖于 React 的上下文(即组件的渲染过程)来绑定状态和副作用。在普通函数中调用它们没有意义,因为没有组件可以关联这些状态。

    • 解决方案:如果你想复用 Hook 的逻辑,应该将它封装在一个以 use 开头的自定义 Hook 中,然后在函数组件里调用这个自定义 Hook。

2.Vue Composition API 的限制

Vue 组合式 API 的限制主要源于其需要正确的响应式上下文。它的核心是数据响应式,所有响应式数据、计算属性和监听器都需要在特定的上下文中创建。

主要有以下两个核心规则:

  1. setup 阶段调用 API

    • 限制:大多数组合式 API(如 ref, reactive, computed, watch 等)必须在 Vue 组件的 setup 函数或 <script setup> 块中同步调用。

    • 原因:Vue 内部在 setup 阶段会为组件实例建立响应式上下文。在这个阶段创建的响应式数据才能被正确追踪,并且与组件的生命周期绑定。如果你在异步操作(如 setTimeoutPromisethen 回调)中调用它们,响应式上下文可能已经失效,导致 API 无法正常工作。

    • 解决方案:将异步操作放在生命周期钩子(如 onMounted)或 watch 中,并在其内部处理数据更新,而不是在异步回调中创建新的响应式数据。

  2. 避免在 setup 中使用 this

    • 限制:在 setup 函数中,this 不再指向组件实例。

    • 原因:在 setup 执行时,组件实例还没有被创建和挂载,所以 this 是一个 undefined。这是 Vue 3 的设计改变,目的是鼓励开发者使用组合式 API 来组织逻辑,而不是依赖于传统的 this

    • 解决方案:如果你需要访问组件的 props、attrs、slots 或 emit 方法,它们会作为参数传递给 setup 函数。所有在 setup 中声明的响应式数据和方法,都会被返回并在模板中直接使用,不再需要通过 this 来访问。

总结

尽管两者有一些相似的限制,但背后的原理有所不同:

  • React 的限制 是为了保持 Hook 调用顺序的一致性,这对于其基于单向数据流的渲染机制至关重要。

  • Vue 的限制 是为了确保响应式数据的正确绑定,所有响应式操作必须在组件的初始设置阶段完成。

五、什么是自定义Hook

Vue 和 React 的自定义 Hook(或 Vue 中的“组合式函数”)都是为了在函数式组件中封装和复用有状态的逻辑自定义 Hook 本质上就是一个普通函数,但具体实现方式和设计理念存在一些关键差异。

React 的自定义 Hook

在 React 中,自定义 Hook 是一个以 use 开头的 JavaScript 函数。它内部可以调用其他内置 Hook(如 useStateuseEffect),并返回数据和方法。 

核心理念

React 的自定义 Hook 是一种逻辑共享机制,它将与 UI 无关的逻辑从组件中剥离出来。每次组件渲染时,调用自定义 Hook 就像是在组件内部“插入”了一段有状态的逻辑。

限制

  • 必须在 React 函数组件或另一个自定义 Hook 的顶层调用。这是因为 React 依赖于 Hook 的调用顺序来正确匹配内部状态。

  • 不能在普通 JavaScript 函数中使用。脱离了 React 的渲染上下文,Hook 无法工作。

Vue 的自定义 Hook (组合式函数)

在 Vue 中,通常称之为组合式函数 (Composable)。它同样是一个以 use 开头的 JavaScript 函数,用于封装可复用的状态逻辑。

核心理念

Vue 的组合式函数利用其响应式系统来创建和管理状态。它的核心是返回一个包含响应式数据和方法的对象,这些数据在任何使用它的组件中都会保持响应。

优点

  • 与 Vue 组件松耦合。组合式函数本质上是响应式工具的集合,因此它们可以在任何普通的 JavaScript 函数中使用,不局限于 Vue 组件。

  • 更直观的依赖追踪。Vue 的响应式系统会自动追踪依赖,开发者通常不需要手动维护依赖数组来防止重复执行,这在某些情况下简化了代码。

六、自定义Hook是否可以在普通函数中使用?

Vue 的组合式函数(Composables)可以在普通的 JavaScript 函数中使用,而 React 的自定义 Hook 不行。这是一个非常重要的区别,也是两种框架设计的体现。

为什么 Vue 可以?

Vue 的组合式函数本质上是封装了响应式逻辑的普通 JavaScript 函数。它的核心是 Vue 的响应式系统,而不是组件的渲染上下文。

因为 refcomputed 是独立的响应式工具,它们不依赖于 Vue 组件的特定渲染流程。它们只是创建了可追踪的数据,当数据变化时,会通知依赖它的地方。在这个例子中,通知的对象就是 console.log,但如果是在 Vue 组件中,通知的对象就是模板。

在普通函数中由于它没有被任何 Vue 模板“绑定”住,所以它的变化不会自动引发任何 UI 的重新渲染,也不能访问 Vue 框架提供的特定上下文(如路由、状态管理)。

考虑一个简单的 Vue 组合式函数:

// useCounter.js
import { ref, computed } from 'vue';

export function useCounter() {
  const count = ref(0);
  const double = computed(() => count.value * 2);

  const increment = () => {
    count.value++;
  };

  return { count, double, increment };
}

这个 useCounter 函数内部创建了响应式数据 count 和计算属性 double。当你在一个 Vue 组件的 <script setup> 中调用它时,Vue 知道将这些响应式数据与该组件实例关联起来。

但你也可以在普通 JavaScript 文件中调用它:

// main.js
import { useCounter } from './useCounter';

const { count, double, increment } = useCounter();

console.log(count.value); // 输出: 0
increment();
console.log(count.value); // 输出: 1
console.log(double.value); // 输出: 2

为什么 React 不行?

React 的自定义 Hook 必须在 React 函数组件的顶层或另一个自定义 Hook 中调用。这是因为 React 的 Hook 依赖于 React 内部的渲染上下文调用顺序

React 自定义 Hook:

// useCounter.js
import { useState } from 'react';

export function useCounter() {
  const [count, setCount] = useState(0);

  const increment = () => {
    setCount(c => c + 1);
  };

  return { count, increment };
}

如果你在普通的 JavaScript 文件中调用这个 useCounter 函数,比如:

// main.js
import { useCounter } from './useCounter';

const { count, increment } = useCounter(); // ❌ 错误!

这段代码会抛出错误,因为 useState 找不到与之关联的 React 组件。它依赖于 React 在组件渲染时建立的“内部状态链表”,如果脱离这个环境,useState 就会失去它的作用,无法分配和管理状态。

总结

这个区别揭示了 Vue 和 React 的根本差异:

  • Vue 的组合式 API 更像是一套独立的、可插拔的响应式工具库。这些工具可以被任何 JavaScript 环境使用,与组件框架本身是松耦合的,普通函数中只是会丢失自动触发 UI 的更新和获取组件上下文的功能。

  • React 的 Hook 是紧密集成在 React 渲染流程中的特性。它们是 React 组件模型的扩展,旨在解决类组件的痛点,因此它们必须依赖于 React 的渲染上下文才能工作。

简而言之,Vue 的 Composable 更多是独立逻辑上的封装,而 React 的 Custom Hook 是渲染上下文中的状态管理

posted @ 2025-08-21 22:01  雪旭  阅读(42)  评论(0)    收藏  举报