Vue Hooks 的意义

Avatar of Sarah Drasner
Sarah Drasner

DigitalOcean 为您旅程的每个阶段提供云产品。 立即开始使用 $200 免费赠送额度!

不要与 生命周期钩子 混淆,Hooks 在 React v16.7.0-alpha 中引入,并且几天后发布了 Vue 的概念证明。 即使它是由 React 提出的,但它实际上是一种重要的组合机制,对 JavaScript 框架生态系统具有优势,因此我们今天将花一些时间讨论这意味着什么。

主要的是,Hooks 提供了一种更明确的方式来思考可重用的模式 - 一种避免对组件本身进行重写并允许状态逻辑的不同部分无缝协同工作的模式。

最初的问题

就 React 而言,问题在于:类是在表达状态概念时最常见的组件形式。 无状态函数组件也很受欢迎,但由于它们只能真正渲染,因此它们的用途仅限于演示性任务。

类本身也存在一些问题。 例如,随着 React 的普及,新手的绊脚石也随之出现。 为了理解 React,也必须理解类。 绑定使代码冗长,因此可读性降低,并且需要了解 JavaScript 中的 this。 类还存在一些优化障碍,此处有讨论

就逻辑的重用而言,使用渲染道具和高阶组件之类的模式很常见,但我们会发现自己陷入了类似的“死亡金字塔” - 嵌套过度利用的风格实现地狱,使组件难以维护。 这导致我对 Dan Abramov 醉酒咆哮,而没有人想要那样。

Hooks 通过允许我们仅使用函数调用来定义组件的状态逻辑来解决这些问题。 这些函数调用变得更加可组合、可重用,并允许我们在函数中表达组合,同时仍然访问和维护状态。 当 Hooks 在 React 中发布时,人们都很兴奋 - 您可以在此处看到一些说明其如何减少代码和重复的优势。

就维护而言,简单是关键,Hooks 提供了一种单一的、功能性的方法来处理共享逻辑,并有可能减少代码量。

为什么在 Vue 中使用 Hooks?

您可能通读这篇文章,想知道 Hooks 在 Vue 中能提供什么。 这似乎是一个不需要解决的问题。 毕竟,Vue 主要不使用类。 Vue 提供无状态函数组件(如果您需要),但为什么我们需要在函数组件中携带状态? 我们有 mixins 用于组合,我们可以将相同的逻辑重用于多个组件。 问题解决。

我也有同样的想法,但在与 Evan You 交谈后,他指出了我错过的主要用例:mixins 无法从一个消费和使用另一个的状态,但 Hooks 可以。 这意味着如果我们需要链式封装逻辑,现在可以使用 Hooks 了。

Hooks 实现了 mixins 的功能,但避免了 mixins 带来的两个主要问题

  • 它们允许我们将状态从一个传递到另一个。
  • 它们明确了逻辑的来源。

如果我们使用多个 mixins,则不清楚哪个属性是由哪个 mixin 提供的。 使用 Hooks,函数的返回值记录了正在使用的值。

那么,它在 Vue 中是如何工作的呢? 我们之前提到过,在使用 Hooks 时,逻辑是在函数调用中表达的,这些函数调用变得可重用。 在 Vue 中,这意味着我们可以将数据调用、方法调用或计算调用组合到另一个自定义函数中,并使它们可以自由组合。 数据、方法和计算现在可以在函数组件中使用。

示例

让我们来看一个非常简单的钩子,以便在我们继续了解 Hooks 中组合的示例之前,我们可以理解构建块。

useWat?

好的,这里是我们有的,你可以称之为 React 和 Vue 之间的交叉事件。 use 前缀是 React 的约定,因此如果您在 React 中查找 Hooks,您会发现 useStateuseEffect 等。 更多信息在此处。

Evan 的现场演示 中,您可以看到他从哪里访问 useStateuseEffect 以进行渲染函数。

如果您不熟悉 Vue 中的渲染函数,可能会有所帮助 看看它

但是,当我们使用 Vue 风格的 Hooks 时,我们会得到 - 你猜对了 - 像:useDatauseComputed 等。

所以,为了让我们看看如何在 Vue 中使用 Hooks,我创建了一个示例应用程序供我们探索。

在 src/hooks 文件夹中,我创建了一个钩子,它可以防止在 useMounted 钩子上滚动,并在 useDestroyed 上重新启用。 这有助于我在打开对话框以查看内容时暂停页面,并在我们完成查看对话框时允许再次滚动。 这对于抽象来说是一个很好的功能,因为它可能在整个应用程序中多次有用。

import { useDestroyed, useMounted } from "vue-hooks";

export function preventscroll() {
  const preventDefault = (e) => {
    e = e || window.event;
    if (e.preventDefault)
      e.preventDefault();
    e.returnValue = false;
  }

  // keycodes for left, up, right, down
  const keys = { 37: 1, 38: 1, 39: 1, 40: 1 };

  const preventDefaultForScrollKeys = (e) => {
    if (keys[e.keyCode]) {
      preventDefault(e);
      return false;
    }
  }

  useMounted(() => {
    if (window.addEventListener) // older FF
      window.addEventListener('DOMMouseScroll', preventDefault, false);
    window.onwheel = preventDefault; // modern standard
    window.onmousewheel = document.onmousewheel = preventDefault; // older browsers, IE
    window.touchmove = preventDefault; // mobile
    window.touchstart = preventDefault; // mobile
    document.onkeydown = preventDefaultForScrollKeys;
  });

  useDestroyed(() => {
    if (window.removeEventListener)
      window.removeEventListener('DOMMouseScroll', preventDefault, false);

    //firefox
    window.addEventListener('DOMMouseScroll', (e) => {
      e.stopPropagation();
    }, true);

    window.onmousewheel = document.onmousewheel = null;
    window.onwheel = null;
    window.touchmove = null;
    window.touchstart = null;
    document.onkeydown = null;
  });
} 

然后我们可以像这样在 AppDetails.vue 中的 Vue 组件中调用它

<script>
import { preventscroll } from "./../hooks/preventscroll.js";
...

export default {
  ...
  hooks() {
    preventscroll();
  }
}
</script>

我们正在那个组件中使用它,但现在我们可以在整个应用程序中使用相同的功能!

两个 Hooks,相互理解

我们之前提到过,Hooks 和 mixins 的主要区别之一是 Hooks 实际上可以将值从一个传递到另一个。 让我们用一个简单的,虽然有点牵强的例子来看看。

假设在我们的应用程序中,我们需要在一个钩子中进行计算,这些计算将在其他地方重复使用,并且某些其他内容需要使用该计算。 在我们的示例中,我们有一个钩子,它获取窗口宽度并将其传递到动画中,以告知它仅在我们使用较大屏幕时才触发。

在第一个钩子中

import { useData, useMounted } from 'vue-hooks';

export function windowwidth() {
  const data = useData({
    width: 0
  })

  useMounted(() => {
    data.width = window.innerWidth
  })

  // this is something we can consume with the other hook
  return {
    data
  }
}

然后,在第二个中,我们使用它来创建一个条件,该条件触发动画逻辑

// the data comes from the other hook
export function logolettering(data) {
  useMounted(function () {
    // this is the width that we stored in data from the previous hook
    if (data.data.width > 1200) {
      // we can use refs if they are called in the useMounted hook
      const logoname = this.$refs.logoname;
      Splitting({ target: logoname, by: "chars" });

      TweenMax.staggerFromTo(".char", 5,
        {
          opacity: 0,
          transformOrigin: "50% 50% -30px",
          cycle: {
            color: ["red", "purple", "teal"],
            rotationY(i) {
              return i * 50
            }
          }
        },
        ...

然后,在组件本身中,我们将一个传递给另一个

<script>
import { logolettering } from "./../hooks/logolettering.js";
import { windowwidth } from "./../hooks/windowwidth.js";

export default {
  hooks() {
    logolettering(windowwidth());
  }
};
</script>

现在我们可以使用 Hooks 在整个应用程序中组合逻辑! 再次强调,这是一个为了演示目的而设计的牵强示例,但您可以看到这对于大型应用程序将事物保持在更小、可重用的函数中可能有多么有用。

未来计划

Vue Hooks 现在可以使用 Vue 2.x,但仍然是实验性的。 我们计划将 Hooks 集成到 Vue 3 中,但可能会在我们的实现中偏离 React 的 API。 我们发现 React Hooks 非常鼓舞人心,并且正在考虑如何将它的优势引入 Vue 开发人员。 我们希望以一种补充 Vue 的惯用用法的方式来做到这一点,因此还有很多实验要做。

您可以通过 查看此处的仓库 来开始。 Hooks 很可能将成为 mixins 的替代品,因此尽管该功能仍处于早期阶段,但这可能是一个值得在中期探索的概念。

(衷心感谢 Evan You 和 Dan Abramov 校对本文。)