将 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
我选择了 TypeScript,因为我们喜欢安全类型,不需要任何预处理器,因为 PostCSS 是默认内置的;我还选择了Vuex和Vue-router,因为它们是 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"
,所以现在你可以轻松地使用你的value
或key
(我听到“嗯,为什么value
先这样?”,嗯,你通常会这样做value in yourArray
)v-if
,v-else-if
并且v-else
对于条件渲染来说,它是 JSX 中三元运算符的完美替代品。使用 likev-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>
这里没什么问题吧?也许只是这些动态数据是从哪里来的,比如……user
或者类似的函数goToNewPostPage
?
让我们定义数据和逻辑
现在我们可以转到脚本标签了。为了更容易从 React 过渡,我们选择了基于类的语法,并且为了好玩,我们还添加了 TypeScript 支持。开始吧:
<script lang="ts">
</script>
现在让我们来看看正文:
// 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;
}
这样,我们在组件和方法中定义了数据$emits
。还记得吗@customEvent="handlerForIt"
?现在,我们的父组件header
可以监听事件@openCreatePostModal="handlerForIt"
,处理程序会将其event
作为参数接收。这样,我们就可以向父组件传递任何我们想要的数据了。
一些 vue 特定的方法或数据总是从$
sign 开始。
问:我们的 在哪里componentDidMount
?
好吧,只需定义一个mounted
方法:
// ...
async mounted() {
this.user = await fetchUserData()
}
// ...
用户更新 -> 组件更新 -> 查看更新。很简单。
问:那怎么样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;
// ...
}
所有这些都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;
}
// ...
所以,我们根据属性的变化来改变状态,这是最常见的情况吗getDerivedStateFromProps
?是的,你也可以监视“状态”数据属性。监视者非常强大💪。
但是我们该如何用 Vue 的方式处理它呢?计算属性!由于我们不需要更改组件中的任何其他数据,没有复杂的逻辑,也不需要发出任何异步请求,因此拥有一个基于 而变化的简单属性是有意义的username
。计算属性是最佳选择,它们性能卓越,具有缓存功能,并且易于编写和使用:
// remove photoSrcPath data property
// define computed property:
get photoSrcPath():string {
return `/user/${this.username}/data/avatar.png`
}
现在我们的img
标签:
<img v-if="username" :src="photoSrcPath" alt="User avatar" />
当然,你可以在计算中拥有任何类型的东西,就像我曾经为同一个输入集合设置了一堆过滤器一样:
// ...
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);
}
// ...
无需将其存储在状态、实现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 {}
现在我们向装饰器传递一些选项,特别是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>
我们传递了“Alex”作为username
prop,并在组件上附加了一个监听器。我们的 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 的响应式系统,你不再需要thunks
、sagas
和connect
。你只需像示例项目中一样定义 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 上或者直播中见到您..?