Vue 与 Vanilla JavaScript - 初学者指南

2025-06-09

Vue 与 Vanilla JavaScript - 初学者指南

原帖发布于michaelzanggl.com。订阅我的新闻通讯,不错过任何新内容。

今天我们将编写一个非常简单的应用程序,并比较 VueJs 和 Vanilla JavaScript 的实现。对于 Vue,我们将使用单文件组件,这意味着每个组件都位于自己的.vue文件中。

如果您更喜欢包含所有基本步骤的交互式教程,请查看通过 vueing 进行学习

我们要构建的应用程序有一个按钮,单击该按钮时就会计数。

让我们看一下 Vanilla JavaScript 解决方案。

<button id="counter">0</button>
Enter fullscreen mode Exit fullscreen mode
const counterBtn = document.getElementById('counter')

counterBtn.addEventListener('click', function incrementCounter() {
    const count = Number(counterBtn.innerText) + 1
    counterBtn.innerText = count
})
Enter fullscreen mode Exit fullscreen mode

好的,到目前为止一切顺利。我们还可以把当前计数保存在一个变量/状态中,然后递增它并更新 DOM。让我们看看如何实现。

<button id="counter"></button>
Enter fullscreen mode Exit fullscreen mode
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()

Enter fullscreen mode Exit fullscreen mode

此方法的一个问题是,我们必须在初始化期间调用该方法,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>
Enter fullscreen mode Exit fullscreen mode

这里是我们counter.vue将花费大部分时间的组件。

<template>
<div>
    <button @click="counter++">{{ counter }} </button>
</div>
</template>

<script>
export default {
    data() {
        return {
            counter: 0,
        }
    },
}
</script>
Enter fullscreen mode Exit fullscreen mode

在 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>
Enter fullscreen mode Exit fullscreen mode
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()
Enter fullscreen mode Exit fullscreen mode

让我们添加这个 CSS 类:

.hidden {
    display: none;
}
Enter fullscreen mode Exit fullscreen mode

好了,我们现在需要添加一个始终存在于 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>
Enter fullscreen mode Exit fullscreen mode

哇,这太简单了!我们用一行代码就搞定了所有事情。而且,如果我们检查 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>
Enter fullscreen mode Exit fullscreen mode
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()
Enter fullscreen mode Exit fullscreen mode
.hidden {
    display: none;
}
Enter fullscreen mode Exit fullscreen mode

好吧,为了一个简单的减少按钮,我们做了很多改变......

这是 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>
Enter fullscreen mode Exit fullscreen mode

两行!只有两行代码!

好吧,我得公平地说,那个原生 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()
Enter fullscreen mode Exit fullscreen mode

好多了!
现在项目经理又来找我们了。这次,他要求根据计数值的不同,提供不同的激励信息。
具体要求如下:

< 10 -> Go on with it
10-15 -> 頑張って
16 - 25 -> Sauba!
25 - 50 -> Good Job! 
Enter fullscreen mode Exit fullscreen mode

温度不能低于零或高于 50。

目前,在 Vanilla JavaScript 中有很多方法可以实现这一点,很难选择其中一种......这个怎么样?

<button id="increment-counter">+</button>
<span id="counter"></span>
<button id="decrement-counter">-</button>
<div id="inspirational-message"></div>
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

这样就应该完成了。我们重构后的 JavaScript 现在更易于扩展了。我们只需要修改constructorrender方法和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>
Enter fullscreen mode Exit fullscreen mode

在 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>
Enter fullscreen mode Exit fullscreen mode

看看这两种实现,现在看来,它们都很容易理解。这很好!不过,我们在使用原生 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>
Enter fullscreen mode Exit fullscreen mode

我们必须摆脱所有 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'))
Enter fullscreen mode Exit fullscreen mode

让我们看一下 Vue 的实现。实际上,我们需要做的就是修改App.vue

<template>
<div>
    <app-counter />
    <app-counter />
</div>
</template>

<script>
import AppCounter from './Counter'

export default {
    components: { AppCounter }
}
</script>
Enter fullscreen mode Exit fullscreen mode

没错,就是它!我们只需要复制粘贴即可<app-counter />。Vue 组件内部的状态只能在该组件内访问。

结论

本文旨在展示 Vue 的可读性和易扩展性。比较一下原生 JavaScript 和 Vue 解决方案的每个步骤。在所有情况下,Vue 解决方案所需的更改都少得多。Vue
虽然有些固执己见,但它会强制你遵循清晰的结构。
也请花一点时间比较一下最终结果。你认为哪一个更易读,因此也更易于维护?

最后,你可以看到,在我们的应用中添加另一个计数器组件是多么容易。这正是 Vue 的闪光点所在,它拥有令人惊叹的组件设计。原生 JavaScript 解决方案在可读性和可扩展性方面远远落后。不过,那是另一篇文章的主题了 ;) 我们只是触及了 Vue 的皮毛。


如果这篇文章对您有帮助,我这里还有更多关于简化编写软件的提示

鏂囩珷鏉ユ簮锛�https://dev.to/michi/vue-vs-vanilla-javascript---beginners-guide-h35
PREV
8 个值得关注的 DevOps YouTube 频道 Bret Fisher Docker 和 DevOps TechWorld 与 Nana @techworld_with_nana Stephane Maarek @simplesteph KodeKloud @kodekloud Jeff Geerling tutoriaLinux Gaurav Sen DevOps Directive
NEXT
跨站点脚本 (XSS),您的 SPA 真的安全吗?