react和vue3使用hook对比

本文探讨了React和Vue3中Hooks的使用及其差异。React的Hooks提供了灵活的状态管理和生命周期控制,而Vue3则通过组合式API实现了类似的功能。文章还深入介绍了自定义Hooks的实现方法,并对比了两者的优缺点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

react和vue3使用hook对比

hook最开始是react函数式提出并引用的概念,react函数式组件(FC)和hook的推出让react的写法更加灵活简便,个人认为vue的新版本vue3在很多方便都借鉴了react函数式组件的优点,当然也就包括了hook。

对比

react有很多内置的hook,比如最常用的useCallback, useMemo,useState,useEffect,以及相对不常用的useReducer等等,vue3新推出了组合式API概念,不过在我看来,实际上的使用和设计理念和react的hook是有很多相似之处的。

react通过useEffect来实现类组件中才有的声明周期方法来控制组件的生命周期,通过useState或者useReducer来创建和操作组件内状态,这里不得不说useEffect这个钩子的秒处,他可以通过不同的用法操作处理各种副作用,实现控制react函数式组件不同的生命周期(函数式组件本没有生命周期概念,这里这样讲是为了类比类组件,更好理解)。

vue通过组合式Api来实现对vue组件的生命周期以及组件内状态的控制,比如使用ref, reactive来管理vue的组件内状态,使用computedwatch, watchEffect来监听状态变化,使用onMounted, onBeforeMount, onBeforeUpdate, onUnmounted等来在组件的各个生命周期插入操作。

Vue优点
  • 仅调用 setup()<script setup> 的代码一次。这使得代码更符合日常 JavaScript 的直觉,不需要担心闭包变量的问题。组合式 API 也并不限制调用顺序,还可以有条件地进行调用。
  • Vue 的响应性系统运行时会自动收集计算属性和侦听器的依赖,因此无需手动声明依赖。
  • 无需手动缓存回调函数来避免不必要的组件更新。Vue 细粒度的响应性系统能够确保在绝大部分情况下组件仅执行必要的更新。对 Vue 开发者来说几乎不怎么需要对子组件更新进行手动优化。

虽然个人更喜欢react这个框架,但是这里必须承认vue上述优点是客观存在的,react函数式组件中useCallbackuseMemo等钩子需要我们手动填入依赖项来追踪依赖,当然这依靠插件的提示是可以很好地完成依赖追踪的,但总的说来确实不如vue自动收集计算=属性和侦听器的依赖来得那么方便。

自定义hook

既然hook这么好用,当然有时候需要自己根据自己的业务需求来封装自己的hook来时先代码复用,这点我认为vue和react差距不大,都很好用。

hook的本质就是函数,普通函数不同的是hook的具有状态的,hook函数内部是可以调用其他的hook的

vue自定义hook

比如现在我们有个简单的需求,整个系统的loading状态存在store(vuex创建的store)里面,我们需要用这个loading状态以及改变store的方法,然而每次都直接从store里面取状态和操作状态是十分不方便的,而我们之前也提到hook是有状态的,我们可以封装hook来实现代码的简单复用:

import { reactive, toRef } from "vue";
import { useStore } from "vuex";

export default function useLoading() {
  const store = useStore();
  const loadingData = reactive({
    loading: false,
    changeLoading(value) {
      store.commit("setLoading", value);
    },
  });

  loadingData.loading = toRef(store.state, "contentLoading");

  return loadingData;
}

这样useLoading这个hook的简单封装就已经完成了,可以看到。在这个hook中我们也使用了useStore这个外部hook和 toRef这个组合式API,使用:

const { loading, changeLoading } = useLoading();

直接在组件中引入并且使用即可。

react自定义hook

react自定义hook同样简单,比如现在有需求,要求在组件中判断用户权限,一般地,这种需要判断权限的地方很多,我们如果每次从store(redux创建的)去取比较麻烦,封装为hook更加方便:

import { useSelector } from "react-redux";
import { GENERAL_ADMIN, GROUP_OBSERVE_ADMIN, OBSERVE_ADMIN, TENANT_ADMIN } from "../models/user.model";
import { selectAuthority } from "../store/selectors";

export function useObSelector() {
  const auth = useSelector(selectAuthority);

  return [OBSERVE_ADMIN, GROUP_OBSERVE_ADMIN].includes(auth);
}

export function useTenantSelector() {
  const auth = useSelector(selectAuthority);
  return auth === TENANT_ADMIN;
}

export function useGeneralSelector() {
  const auth = useSelector(selectAuthority);
  return [GENERAL_ADMIN, TENANT_ADMIN].includes(auth);
}

使用,同样的组件中直接引入并调用即可,react自定义hook同样可以调用其他hook, 例子如下:

import * as echarts from "echarts/core";
import {
  BarChart,
  // 系列类型的定义后缀都为 SeriesOption
  BarSeriesOption,
  LineChart,
  LineSeriesOption,
  PieChart,
  PieSeriesOption,
} from "echarts/charts";
import {
  TitleComponent,
  // 组件类型的定义后缀都为 ComponentOption
  TitleComponentOption,
  TooltipComponent,
  TooltipComponentOption,
  GridComponent,
  GridComponentOption,
  // 数据集组件
  // 内置数据转换器组件 (filter, sort)
  TransformComponent,
  DataZoomComponent,
  DataZoomComponentOption,
  LegendComponentOption,
  LegendComponent,
} from "echarts/components";
import { LabelLayout, UniversalTransition } from "echarts/features";
import { SVGRenderer } from "echarts/renderers";
import { useCallback, useEffect, useState } from "react";
import { selectCollapsed, selectHeight, selectWidth } from "../store/selectors";
import { useSelector } from "react-redux";

// 通过 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型
export type ECOption = echarts.ComposeOption<
  | LineSeriesOption
  | TitleComponentOption
  | TooltipComponentOption
  | GridComponentOption
  | BarSeriesOption
  | TitleComponentOption
  | TooltipComponentOption
  | GridComponentOption
  | DataZoomComponentOption
  | LegendComponentOption
  | PieSeriesOption
>;

// 注册必须的组件
echarts.use([TitleComponent, LegendComponent, TooltipComponent, GridComponent, TransformComponent, BarChart, LineChart, PieChart, LabelLayout, UniversalTransition, SVGRenderer, DataZoomComponent]);

export default function useChart(props: { id: string; option?: ECOption }) {
  const [chart, setChart] = useState<echarts.ECharts>();

  const windowWidth = useSelector(selectWidth);
  const windowHeight = useSelector(selectHeight);
  const collapsed = useSelector(selectCollapsed);

  const initChart = useCallback(() => {
    const myChart = echarts.init(document.getElementById(props.id));
    if (props.option) {
      myChart.setOption<ECOption>(props.option);
    }
    setChart(myChart);
  }, [props]);

  useEffect(() => {
    chart?.resize && chart?.resize();
  }, [windowWidth, windowHeight, chart]);

  useEffect(() => {
    setTimeout(() => {
      chart?.resize && chart?.resize();
    }, 400);
  }, [collapsed, chart]);

  useEffect(() => {
    initChart();
  }, [initChart]);

  const setOption = useCallback(
    (option: ECOption) => {
      if (!chart || !option) {
        return;
      }
      console.log("in");

      chart.setOption(option);
    },
    [chart]
  );

  return { chart, setOption };
}

这个例子是我对echarts这个第三方库的使用封装成hook的案例,因为系统中很多的组件都会用到echarts,因此每次引入或者使用都很麻烦,特别是其他的操作,比如我们需要监听屏幕宽高的变化来调用chart实例的resize方法来重绘图表,如果在每个图表中都去注册hook去实现是很麻烦的,因此我们只需把这些方法都写在hook中,封装之后我们只需要暴露chart实例和setOption方法即可,使用:

import { Box } from "@mui/material";
import moment from "moment";
import { useEffect, useMemo } from "react";
import { useTranslation } from "react-i18next";
import useChart from "../../../../hooks/useChart";
import { ChartViewRange, LicenseMonthlyCount } from "../../../../models/license.model";

const elId = "subscription-bar-chart";

export default function SubscriptionBarChart(props: { data: LicenseMonthlyCount; viewRange: ChartViewRange }) {
  const { t } = useTranslation();
  const chart = useChart({
    id: elId,
  });

  const option = useMemo(() => {
    if (!props.data?.licenseMonthlyCount) {
      return null;
    }
    const timeLabelFormater = props.viewRange === ChartViewRange.MONTH ? "YYYY-MM" : "YYYY-MM-DD";
    const xAxisData = Object.keys(props.data?.licenseMonthlyCount).map((item) => moment(item).format(timeLabelFormater));
    // const seriesData = Object.values(props.data?.licenseMonthlyCount).map((item) => item.totalNum);

    const seriesData = [
      { name: t("license.total"), type: "bar", data: [], barMaxWidth: 32 },
      { name: t("license.online"), type: "bar", data: [], barMaxWidth: 32 },
      { name: t("license.offline"), type: "bar", data: [], barMaxWidth: 32 },
    ];
    Object.values(props.data?.licenseMonthlyCount).forEach((item) => {
      seriesData[0].data.push(item.totalNum);
      seriesData[1].data.push(item.onlineNum);
      seriesData[2].data.push(item.offlneNum);
    });

    return { xAxisData, seriesData };
  }, [props, t]);

  useEffect(() => {
    chart.setOption({
      tooltip: {
        trigger: "axis",
      },
      legend: { icon: "roundRect", right: 0 },
      xAxis: {
        type: "category",
        data: option.xAxisData,
      },
      yAxis: {
        type: "value",
        name: t("license.deviceUnit"),
        nameTextStyle: {
          fontWeight: "bold",
          fontSize: 14,
        },
      },
      // @ts-ignore
      series: option.seriesData,
      grid: {
        bottom: 60,
        left: "8%",
        right: "8%",
        top: 40,
      },
      dataZoom: [{ bottom: 16, height: 20 }],
    });
  }, [chart, option, t]);

  return <Box sx={{ height: 1 }} id={elId}></Box>;
}

如代码所示,我们使用只需调用这个hook

const chart = useChart({ id: elId, option: null });

调用hook传参的时候option是可选属性我们可以传入在option也可以通过chart.setOption方法来设置option,这里我的option是通过useMemo缓存的(类似于vue的计算属性),因此我选择使用useEffect监听option然后动态设置option,这样基于这个hook的封装和使用都一级完成了。

总结

hook的推出让react函数式组件大放异彩,同时vue3也对这个概念有所借鉴,个人而言,react函数式组件和hook的语法自己更加喜欢,然而上面说说的vue3的组合式API和hook的优点都是客观存在的,因此两者都是非常优秀的设计,都值得大家学习和使用。

### 如何在 Vue 3使用 HookVue 3 的 Composition API 中,可以创建类似于 React Hooks 的自定义逻辑复用机制。通过编写自己的 `use` 函数来封装特定功能的业务逻辑,从而实现代码的模块化可维护性。 #### 创建自定义 Hook 假设有一个需求是要获取鼠标的实时位置并将其显示出来。为此,先构建一个名为 `useMousePosition.js` 文件,在其中定义了一个返回鼠标 X Y 坐标的函数: ```javascript // useMousePosition.js import { ref, onMounted, onUnmounted } from &#39;vue&#39; export default function useMousePosition () { let x = ref(0); let y = ref(0); function update(event) { x.value = event.pageX; y.value = event.pageY; } onMounted(() => window.addEventListener(&#39;mousemove&#39;, update)); onUnmounted(() => window.removeEventListener(&#39;mousemove&#39;, update)); return { x, y } } ``` 此部分实现了监听全局 mousemove 事件,并更新响应式的坐标变量[^2]。 #### 使用自定义 Hook 接着可以在组件内部调用这个钩子方法。下面展示了如何在一个简单的 Vue 单文件组件 (`index.vue`) 中引入并应用上述编写的 hook 来展示当前光标的位置: ```html <!-- index.vue --> <template> <p>mouse position {{x}} {{y}}</p> </template> <script> import { reactive } from &#39;vue&#39; import useMousePosition from &#39;./useMousePosition&#39; export default { name: &#39;MousePosition&#39;, setup() { const { x, y } = useMousePosition() return { x, y } } } </script> ``` 这段代码片段说明了怎样利用组合式API中的 setup 方法导入外部定义好的 hook 并将结果暴露给模板渲染[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值