将 Vue 添加到你的已知堆栈

2025-06-04

将 Vue 添加到你的已知堆栈

TL;DR

Vue.js 不能说“和 React 一样好”(甚至“更好”)。React 作为一个代码库,它的技巧和架构决策(例如 Fiber 或时间片、Suspense 和 Hooks)推动了 JS 开发的发展,其发展速度远超我们的预期。它还教会了我函数式思维,这对使用任何技术编写应用程序都大有裨益。但就我而言,Vue.js 的方法略有不同。它让你专注于开发的产品,而不是编写的代码。同时,我相信 99% 的项目都可以用 Vue 而不是 React 来开发,功能和性能上没有任何差异。但 Vue 会让你感到快乐。它提供了大量的小助手、提示和技巧,以至于当你再次尝试使用 React 构建东西时,你会想:“我为什么要一遍又一遍地写这些样板代码?” Vuex 是核心库之一(明白它的意思了吧),它通过极其便捷的方式为您提供了单一可信来源的存储,减少了您的代码库,从而减少了出现 bug 的地方。vue-router 是另一个核心库,它以最少的设置为您提供了所需的一切,但如果您需要一些复杂的东西,它非常灵活。我甚至不会提及 Vue 提供的开箱transition即用的强大的 UI 和 UX 改进transition-groups,这些改进使任何应用程序都变得更好。我认为 Vue 比 React 好吗?不,React 仍然更受欢迎,每年都会让我大吃一惊(再次是 Fiber、Suspense)。但我会在下一个项目中使用 React 吗?不,不,不会。由于 Vue.js 开发人员的体验要好得多,我宁愿使用它。

让我们开始吧

好吧,我知道 React 开发者很忙,没时间多介绍。我们先创建一个新的 Vue 项目:

npx @vue/cli create simple-sample
Enter fullscreen mode Exit fullscreen mode

我们现在可以在设置中选择我们想要的功能:
Vue 功能设置

我选择了 TypeScript,因为我们喜欢安全类型,不需要任何预处理器,因为 PostCSS 是默认内置的;我还选择了VuexVue-router,因为它们是 Vue 生态系统的重要组成部分。我们想使用类语法(是的,它不是默认语法),因为类语法很熟悉,而且看起来也很漂亮。所以我们的设置如下:
vue 项目设置

快速依赖项安装,现在我们可以看到项目结构:

初始项目结构

shims-这只是 TS 的一个设置,用于在.vue单文件组件中使用这种强大的 JavaScript 类型。你可能已经听说过单文件组件 (SFC):我们没必要这么做,但我们可以在一个文件中编写组件,并且乐在其中!
为什么?因为你的组件通常由骨架(模板)、行为(脚本)和外观(样式)组成。所以,让我们在componentsvue文件夹*中创建一个文件并编写组件。我把它命名为.DevToHeader.vue

(快速提示:Vetur是 VS Code 的 Vue 语法助手)

模板简介

  • 模板是有效的 html
  • 如果您需要将一些数据绑定到模板,您可以使用v-bind(没有人这样做**,使用:),例如:prop="{ react: 'cool' }"(与 React 相同,:prop="true"等于prop
  • 如果你需要听某个事件,你可以v-on简短地使用或@。例如,感受或的@click="functionName"力量@customEvent="handlerOfThisEventName"@click="$event => handlerFuncName($event, 'my custom data')"@mousedown="mouseDownDataInYourComponent = true"
  • 您只需记住几个指令:
    • v-for指令是 for 循环,像下面这样遍历你的集合:v-for="(value, key) in youObjectOrArray",所以现在你可以轻松地使用你的valuekey(我听到“嗯,为什么value先这样?”,嗯,你通常会这样做value in yourArray
    • v-ifv-else-if并且v-else对于条件渲染来说,它是 JSX 中三元运算符的完美替代品。使用 like v-if="userLoggedIn"(或者简单地v-show使用display: none;(!) 挂载组件),你很快就会发现这个助手有多棒,现在不需要 CSS 或内联样式了!
    • v-model- 你的英雄让你无需为每个动态输入编写方法setState。现在你可以拥有<input v-model="searchText" />与以下相同的功能<input :value="searchText" @input="updateSearchTextValue)" />(你能猜出文档中这个例子的作用<input v-model.number="age" type="number">:)
    • 您可以查看或创建自定义的,它们通常从一些很酷的功能开始v-*并添加一些很酷的功能。
  • 要呈现某些数据,请使用 mustaches: <h2>{{ variableName }}</h2>,而仅使用 text: 则不需要它们<h2>search</h2>

基本上就是这样!了解了这些之后,我们来定义模板:

<template>
  <header class="main-header">
    <img src="../assets/logo.png" alt="logo" />
    <input placeholder="search" v-model="searchText" />
    <button @click="openModal">Write a post</button>
    <img v-if="user" :src="user.photo" alt="User avatar" />
    <button v-else>Login</button>
  </header>
</template>
Enter fullscreen mode Exit fullscreen mode

这里没什么问题吧?也许只是这些动态数据是从哪里来的,比如……user或者类似的函数goToNewPostPage

让我们定义数据和逻辑

现在我们可以转到脚本标签了。为了更容易从 React 过渡,我们选择了基于类的语法,并且为了好玩,我们还添加了 TypeScript 支持。开始吧:

<script lang="ts">
</script>
Enter fullscreen mode Exit fullscreen mode

现在让我们来看看正文:

// think about this as import React from "react"
import { Component, Vue } from "vue-property-decorator";

// with this decorator we're saying to compile regular Vue component from our class
@Component
export default class DevToHeader extends Vue {
    user:User = null;
    searchText:string = ""; // two-way binding in v-model works with this guy

    openModal(event: Event) {
      this.$emit('openCreatePostModal', event);
    }
}

type User = IUser | null;

interface IUser {
  photo: string;
  name: string;
}
Enter fullscreen mode Exit fullscreen mode

这样,我们在组件和方法中定义了数据$emits。还记得吗@customEvent="handlerForIt"?现在,我们的父组件header可以监听事件@openCreatePostModal="handlerForIt",处理程序会将其event作为参数接收。这样,我们就可以向父组件传递任何我们想要的数据了。

一些 vue 特定的方法或数据总是从$sign 开始。

问:我们的 在哪里componentDidMount
好吧,只需定义一个mounted方法:

  // ...
  async mounted() {
    this.user = await fetchUserData()
  }
  // ...
Enter fullscreen mode Exit fullscreen mode

用户更新 -> 组件更新 -> 查看更新。很简单。

问:那怎么样static getDerivedStateFromProps(props, state)
好吧,假设我们username从父级获取,并且我们想根据更改头像的路径username。为此,我们需要进行一些更改:

import { Component, Vue, Prop } from "vue-property-decorator";

@Component
export default class DevToHeader extends Vue {
    @Prop({
      type: String, // your `prop-types` checks out of the box
      default: null // we don't really need that
    })
    username:string | null = null; // now for TypeScript
    // our photo src path that we will use as img :src
    photoSrcPath: string | null = null;
  // ...
}
Enter fullscreen mode Exit fullscreen mode

所有这些都props可以作为实例属性使用,就像我们自定义的数据一样。现在让我们添加路径:

// import Watch decorator
import { Component, Vue, Prop, Watch } from "vue-property-decorator";

// ... or component class
    // watch for 'username' property
    @Watch('username', {
      immediate: true // call this function also on component mount
    })
    changePhotoPath(username:string | null) { // takes (newValue, oldValue)
      this.photoSrcPath = username ? `/user/${username}/data/avatar.png` : null;
    }
// ...
Enter fullscreen mode Exit fullscreen mode

所以,我们根据属性的变化来改变状态,这是最常见的情况吗getDerivedStateFromProps?是的,你也可以监视“状态”数据属性。监视者非常强大💪。

但是我们该如何用 Vue 的方式处理它呢?计算属性!由于我们不需要更改组件中的任何其他数据,没有复杂的逻辑,也不需要发出任何异步请求,因此拥有一个基于 而变化的简单属性是有意义的username。计算属性是最佳选择,它们性能卓越,具有缓存功能,并且易于编写和使用:

  // remove photoSrcPath data property
  // define computed property:
  get photoSrcPath():string {
    return `/user/${this.username}/data/avatar.png`
  }
Enter fullscreen mode Exit fullscreen mode

现在我们的img标签:

  <img v-if="username" :src="photoSrcPath" alt="User avatar" />
Enter fullscreen mode Exit fullscreen mode

当然,你可以在计算中拥有任何类型的东西,就像我曾经为同一个输入集合设置了一堆过滤器一样:

// ...
    get filteredByA() {
      return this.collection.filter(filterByA).map(setFlags);
    }

    get filteredByB() {
      return this.collection.filter(filterByB)
    }

    get filteredByC() {
      return this.collection.filter(filterByC).map(setFlags);
    }
// ...
Enter fullscreen mode Exit fullscreen mode

无需将其存储在状态、实现shouldComponentUpdate或其他东西中。而且,它们的性能非常出色。

添加我们的组件

让我们去views/Home.vue那里添加我们的组件:

import { Component, Vue } from "vue-property-decorator";
import HelloWorld from "@/components/HelloWorld.vue"; // @ is an alias to /src
import DevToHeader from "@/components/DevToHeader.vue";

@Component({
  components: {
    HelloWorld,
    DevToHeader // becomes 'DevToHeader': DevToHeader
  }
})
export default class Home extends Vue {}
Enter fullscreen mode Exit fullscreen mode

现在我们向装饰器传递一些选项,特别是components。这样,我们就告诉 Vue 编译器我们将在模板中使用哪些组件。Vue 会自动将 PascalCase 缩写转换为 kebab-case 缩写以便在模板中使用(或者你可以自己命名,例如'hello-w': HelloWorld)。因此,Home.vue我们可以在模板中使用我们的组件:

  <div class="home">
    <dev-to-header
      username="Alex"
      @openCreatePostModal="$router.push('/newPost')"
    />
    <img alt="Vue logo" src="../assets/logo.png">
    <hello-w msg="Welcome to Your Vue.js + TypeScript App"/>
  </div>
Enter fullscreen mode Exit fullscreen mode

我们传递了“Alex”作为usernameprop,并在组件上附加了一个监听器。我们的 header 不知道,但是没有模态框,我们应该直接跳转到另一个页面(没错,我们现在应该重命名这个事件),所以我在这里写了一个内联函数。还记得内联函数吗🔝?从 DX 的角度来看,它们不太好用,但对于一些简单的东西,为了避免重复,为什么不呢?毕竟我们是人……

所以这个内联器实际上调用了this.$router.push('/newPost'),那么是什么$router

vue-router

你有没有因为 React-Router 升级而多次重写路由器配置的经历?看看这个几乎没有随着时间而改变的配置:

通过动态导入,您已经可以在页面级别看到捆绑拆分了吗?

Vue.use(Router)为你添加了几个全局组件,你可以在模板中像<router-view/>和 一样使用它们<router-link to="/about">About</router-link>。此外,它还为你的 Vue 实例添加了超属性:$route包含你当前的路由信息​​,例如参数、查询、元数据,并$router提供了以编程方式操作路由器的方法。好东西,好东西。

vuex

得益于 Vue.js 的响应式系统,你不再需要thunkssagasconnect。你只需像示例项目中一样定义 store,并将其作为this.$store组件中的另一个超属性使用即可。异步操作、突变、模块、中间件——一切都已实现。你还需要一个非常棒的抽象来精简你的代码库—— vuex-pathify看起来很漂亮。

你是个怪人,喜欢 JSX

支持 JSX,它是 babel 抽象,Vue 使用render与 React 相同的方法。

React.createContext?

是的,也在那里。您可以provide在父组件和inject: ['nameOfPropertyToInject']任意深度的子组件中定义属性。

尝试一下

拒绝尝试新工具毫无意义。我常常无法理解那些即使没怎么用过也不喜欢 Vue 的人。说到底,Vue 是提升生产力和用户满意度的工具。如果它不适合你,那就放弃它,但不要轻易放弃。我之前的想法是,一切都应该是 immutable 的,但后来又改变了主意,这this.smth = ...让我感觉自己做错了什么,或者像是在作弊。不,这只是因为我们以前写过 React 代码(没错,就是 JS 🙃)。不得不说,我还开始通过添加过渡效果来提升任何应用程序的用户体验,因为它们在 Vue 中非常易于设置和使用。

感谢您的阅读,希望在 Twitter 上或者直播中见到您..?

*(我收到了关于如何知道何时将组件放入views文件夹以及何时放入文件夹的问题components。好吧,如果您的组件被重复使用,比如在不同的页面/视图或其他组件上,那么就将其放入components文件夹中。
** 是的,我知道一些事情,比如v-bind="$attrs"让我把这篇文章写短一点?:)
封面照片:https://blog.pusher.com/building-external-modules-vuejs/
文章来源:https://dev.to/alvechy/add-vue-to-your-acknowledged-stack-2gc4
PREV
那时你以为你了解 Y(A)ML 😵
NEXT
Flutter 中的依赖注入