Vue 与 Vanilla JavaScript - 初学者指南
原帖发布于michaelzanggl.com。订阅我的新闻通讯,不错过任何新内容。
今天我们将编写一个非常简单的应用程序,并比较 VueJs 和 Vanilla JavaScript 的实现。对于 Vue,我们将使用单文件组件,这意味着每个组件都位于自己的.vue
文件中。
如果您更喜欢包含所有基本步骤的交互式教程,请查看通过 vueing 进行学习。
我们要构建的应用程序有一个按钮,单击该按钮时就会计数。
让我们看一下 Vanilla JavaScript 解决方案。
<button id="counter">0</button>
const counterBtn = document.getElementById('counter')
counterBtn.addEventListener('click', function incrementCounter() {
const count = Number(counterBtn.innerText) + 1
counterBtn.innerText = count
})
好的,到目前为止一切顺利。我们还可以把当前计数保存在一个变量/状态中,然后递增它并更新 DOM。让我们看看如何实现。
<button id="counter"></button>
const counterBtn = document.getElementById('counter')
let count = 0
function renderCount() {
counterBtn.innerText = count
}
counterBtn.addEventListener('click', function incrementCounter() {
count = count + 1
renderCount()
})
// on init
renderCount()
此方法的一个问题是,我们必须在初始化期间调用该方法,renderCount
以确保计数与 DOM 保持同步。
如你所见,从一开始,设计应用程序就有多种方法。
第一种方法很简单,但略显粗糙,且不易扩展。
第二种方法则相对简洁一些,但会带来一些开销。
但需要记住的是,DOM 不应该被用作数据存储。所以,我们暂时先使用第二个版本。
让我们看看 Vue 单文件组件中的等效方法。由于我们使用单文件组件,您需要使用 Vue Cli、Laravel Mix 等工具将 Vue 文件转换为普通的 JavaScript。或者,您也可以在在线编辑器
中尝试一下。
假设我们有以下包装器组件App.vue
<template>
<div>
<app-counter />
</div>
</template>
<script>
import AppCounter from './Counter'
export default {
components: { AppCounter }
}
</script>
这里是我们counter.vue
将花费大部分时间的组件。
<template>
<div>
<button @click="counter++">{{ counter }} </button>
</div>
</template>
<script>
export default {
data() {
return {
counter: 0,
}
},
}
</script>
在 Vue 中,你永远不会找到类似的东西counterBtn.innerText = count
。UI 与其状态/数据同步。让我再说一遍
UI 与其状态/数据同步
对于我们这个简单的计数器来说,这可能无关紧要,但想象一下,如果你有一个可以添加、编辑和删除记录的表。想象一下,你用 JavaScript 更新了数组,然后又得想办法更新 HTML 表。你会重新加载整个表吗?还是在 HTML 中找到元素,然后编辑/删除它?当然,这会很乱。Vue 会帮我们处理整个 UI 部分。
但目前为止,仅仅为了这个目的安装 Vue 有点小题大做。让我们看看添加更多功能后,我们的应用会如何发展。
Good Job!
我们希望我们的应用程序在计数器至少为 10 时显示文本。
这将是 Vanilla 方法。
<button id="counter"></button>
<div id="inspirational-message" class="hidden">Good Job!</div>
const counterBtn = document.getElementById('counter')
const inspirationalMessageEl = document.getElementById('inspirational-message')
let count = 0
function renderCount() {
counterBtn.innerText = count
if (count >= 10) {
inspirationalMessageEl.classList.remove('hidden')
}
}
counterBtn.addEventListener('click', function incrementCounter() {
count = count + 1
renderCount()
})
// on init
renderCount()
让我们添加这个 CSS 类:
.hidden {
display: none;
}
好了,我们现在需要添加一个始终存在于 DOM 中的新元素、一个 CSS 类和一个 if 条件。
让我们看看 Vue 组件中的代码库是如何增长的。
<template>
<div>
<button @click="counter++">{{ counter }} </button>
<div v-if="counter >= 10">Good Job!</div>
</div>
</template>
<script>
export default {
data() {
return {
counter: 0,
}
},
}
</script>
哇,这太简单了!我们用一行代码就搞定了所有事情。而且,如果我们检查 DOM,如果计数器小于 10,甚至连隐藏的 div 都没有。这是因为 Vue 使用了虚拟 DOM,因此可以只将必要的 HTML 传递给实际的 DOM。
但现在,我们这个价值数百万美元的应用程序的项目经理来找我们,说他们也想要一个减量按钮。我们看看谁实现起来更吃力?
为了制作一个减量按钮,我们必须从增量按钮的标签中移除当前计数,并将其添加到增量和减量按钮之间。
让我们看看 JavaScript 实现
<button id="increment-counter">+</button>
<span id="counter"></span>
<button id="decrement-counter">-</button>
<div id="inspirational-message" class="hidden">Good Job!</div>
const counterEl = document.getElementById('counter')
const incrementCounterEl = document.getElementById('increment-counter')
const decrementCounterEl = document.getElementById('decrement-counter')
const inspirationalMessageEl = document.getElementById('inspirational-message')
let count = 0
function renderCount() {
counterEl.innerText = count
const forceToggle = count < 10
inspirationalMessageEl.classList.toggle('hidden', forceToggle)
}
incrementCounterEl.addEventListener('click', function incrementCounter() {
count = count + 1
renderCount()
})
decrementCounterEl.addEventListener('click', function decrementCounter() {
count = count - 1
renderCount()
})
// on init
renderCount()
.hidden {
display: none;
}
好吧,为了一个简单的减少按钮,我们做了很多改变......
这是 Vue 中的全部内容
<template>
<div>
<button @click="counter--">-</button>
{{ counter }}
<button @click="counter++">+</button>
<div v-if="counter >= 10">Good Job!</div>
</div>
</template>
<script>
export default {
data() {
return {
counter: 0,
}
},
}
</script>
两行!只有两行代码!
好吧,我得公平地说,那个原生 JavaScript 代码有点失控了。所以,我们先重构一下再继续,毕竟我也不是想把它搞砸。
class Counter {
constructor() {
this.count = 0
this.cacheDOM()
this.bindEvents()
this.render()
}
cacheDOM() {
this.counterEl = document.getElementById('counter')
this.incrementCounterEl = document.getElementById('increment-counter')
this.decrementCounterEl = document.getElementById('decrement-counter')
this.inspirationalMessageEl = document.getElementById('inspirational-message')
}
bindEvents() {
this.incrementCounterEl.addEventListener('click', () => this.countUp(1))
this.decrementCounterEl.addEventListener('click', () => this.countUp(-1))
}
render() {
this.counterEl.innerText = this.count
const forceToggle = this.count < 10
this.inspirationalMessageEl.classList.toggle('hidden', forceToggle)
}
countUp(value) {
this.count += value
this.render()
}
}
new Counter()
好多了!
现在项目经理又来找我们了。这次,他要求根据计数值的不同,提供不同的激励信息。
具体要求如下:
< 10 -> Go on with it
10-15 -> 頑張って
16 - 25 -> Sauba!
25 - 50 -> Good Job!
温度不能低于零或高于 50。
目前,在 Vanilla JavaScript 中有很多方法可以实现这一点,很难选择其中一种......这个怎么样?
<button id="increment-counter">+</button>
<span id="counter"></span>
<button id="decrement-counter">-</button>
<div id="inspirational-message"></div>
class Counter {
constructor() {
this.count = 0
this.messages = [
{ start: 0, end: 9, message: 'Go on with it!' },
{ start: 10, end: 15, message: '頑張って!' },
{ start: 16, end: 25, message: 'Sauba' },
{ start: 26, end: 50, message: 'Good Job' },
]
this.cacheDOM()
this.bindEvents()
this.render()
}
cacheDOM() {
this.counterEl = document.getElementById('counter')
this.incrementCounterEl = document.getElementById('increment-counter')
this.decrementCounterEl = document.getElementById('decrement-counter')
this.inspirationalMessageEl = document.getElementById('inspirational-message')
}
bindEvents() {
this.incrementCounterEl.addEventListener('click', () => this.countUp(1))
this.decrementCounterEl.addEventListener('click', () => this.countUp(-1))
}
render() {
this.counterEl.innerText = this.count
const { message } = this.messages.find(({start, end}) => this.count >= start && this.count <= end)
this.inspirationalMessageEl.innerText = message
}
countUp(value) {
const newCount = this.count + value
if (newCount < 0 || newCount > 50) return
this.count = newCount
this.render()
}
}
new Counter()
这样就应该完成了。我们重构后的 JavaScript 现在更易于扩展了。我们只需要修改constructor
、render
方法和count
方法。让我们看看 Vue 的实现。
<template>
<div>
<button @click="counter > 0 && counter--">-</button>
{{ counter }}
<button @click="counter < 50 && counter++">+</button>
<div>{{ message }}</div>
</div>
</template>
<script>
export default {
data() {
return {
counter: 0,
messages: [
{ start: 0, end: 9, message: 'Go on with it!' },
{ start: 10, end: 15, message: '頑張って!' },
{ start: 16, end: 25, message: 'Sauba' },
{ start: 26, end: 50, message: 'Good Job' },
],
}
},
computed: {
message() {
return this.messages
.find(({start, end}) => this.counter >= start && this.counter <= end)
.message
}
}
}
</script>
在 Vanilla JavaScript 实现中,我们必须扩展渲染方法。Vue 提供了一个更优雅的解决方案,即计算字段。
计算字段获取现有数据,运行同步方法(在我们的例子中是message()
缓存数据,并使其像实际数据一样可用data
)。
我们还可以将递减和递增提取到一种方法中。
<template>
<div>
<button @click="decrement">-</button>
{{ counter }}
<button @click="increment">+</button>
<div>{{ message }}</div>
</div>
</template>
<script>
export default {
data() {
return {
counter: 0,
messages: [
{ start: 0, end: 9, message: 'Go on with it!' },
{ start: 10, end: 15, message: '頑張って!' },
{ start: 16, end: 25, message: 'Sauba' },
{ start: 26, end: 50, message: 'Good Job' },
],
}
},
computed: {
message() {
return this.messages
.find(({start, end}) => this.counter >= start && this.counter <= end)
.message
}
},
methods: {
decrement() {
if (this.counter > 0) this.counter--
},
increment() {
if (this.counter < 50) this.counter++
},
}
}
</script>
看看这两种实现,现在看来,它们都很容易理解。这很好!不过,我们在使用原生 JavaScript 实现时遇到了一些问题。从一开始,我们就必须决定实现计数器的最佳方式。在规范修改之后,我们很早就不得不将其重构为模块化结构,以保持代码的可读性。总的来说,进行必要的修改更加困难。Vue 的
优点在于,一切都各得其所。
现在我们正准备发布计数器,突然经理敲门告诉我们,一个页面上可以有多个计数器。很简单吧,复制一些 HTML 代码就行。但是等等……我们一直用的是 ID。这意味着一个页面上只能有一个计数器……不过还好,我们已经模块化了代码,所以只需要做一些小改动。我们来看看具体实现。
<div class="counter-wrapper" id="counter1">
<button class="increment-counter">+</button>
<span class="counter"></span>
<button class="decrement-counter">-</button>
<div class="inspirational-message"></div>
</div>
<div class="counter-wrapper" id="counter2">
<button class="increment-counter">+</button>
<span class="counter"></span>
<button class="decrement-counter">-</button>
<div class="inspirational-message"></div>
</div>
我们必须摆脱所有 ID,并用类代替它们。
class Counter {
constructor(wrapperEl) {
this.count = 0
this.messages = [
{ start: 0, end: 9, message: 'Go on with it!' },
{ start: 10, end: 15, message: '頑張って!' },
{ start: 16, end: 25, message: 'Sauba' },
{ start: 26, end: 50, message: 'Good Job' },
]
this.cacheDOM(wrapperEl)
this.bindEvents()
this.render()
}
cacheDOM(wrapperEl) {
this.wrapperEl = wrapperEl
this.counterEl = this.wrapperEl.querySelector('.counter')
this.incrementCounterEl = this.wrapperEl.querySelector('.increment-counter')
this.decrementCounterEl = this.wrapperEl.querySelector('.decrement-counter')
this.inspirationalMessageEl = this.wrapperEl.querySelector('.inspirational-message')
}
bindEvents() {
this.incrementCounterEl.addEventListener('click', () => this.countUp(1))
this.decrementCounterEl.addEventListener('click', () => this.countUp(-1))
}
render() {
this.counterEl.innerText = this.count
const { message } = this.messages.find(({start, end}) => this.count >= start && this.count <= end)
this.inspirationalMessageEl.innerText = message
}
countUp(value) {
const newCount = this.count + value
if (newCount < 0 || newCount > 50) return
this.count = newCount
this.render()
}
}
new Counter(document.getElementById('counter1'))
new Counter(document.getElementById('counter2'))
让我们看一下 Vue 的实现。实际上,我们需要做的就是修改App.vue
<template>
<div>
<app-counter />
<app-counter />
</div>
</template>
<script>
import AppCounter from './Counter'
export default {
components: { AppCounter }
}
</script>
没错,就是它!我们只需要复制粘贴即可<app-counter />
。Vue 组件内部的状态只能在该组件内访问。
结论
本文旨在展示 Vue 的可读性和易扩展性。比较一下原生 JavaScript 和 Vue 解决方案的每个步骤。在所有情况下,Vue 解决方案所需的更改都少得多。Vue
虽然有些固执己见,但它会强制你遵循清晰的结构。
也请花一点时间比较一下最终结果。你认为哪一个更易读,因此也更易于维护?
最后,你可以看到,在我们的应用中添加另一个计数器组件是多么容易。这正是 Vue 的闪光点所在,它拥有令人惊叹的组件设计。原生 JavaScript 解决方案在可读性和可扩展性方面远远落后。不过,那是另一篇文章的主题了 ;) 我们只是触及了 Vue 的皮毛。
如果这篇文章对您有帮助,我这里还有更多关于简化编写软件的提示。
鏂囩珷鏉ユ簮锛�https://dev.to/michi/vue-vs-vanilla-javascript---beginners-guide-h35