Vue 学习笔记:从传统 JavaScript 到 Vue 开发

前言

笔者在学习 Vue 等前端框架前只接触过基本的前端三件套,即 HTML、CSS、JavaScript(原生),在这之前有尝试接触过一些 Vue 教程,了解一些语法,但并不知道他们背后到底是什么关系。近些日子硬着头皮写了几个 Vue 项目,有所心得。好歹是把 MVVM 和工程化之类的概念过了一遍,便想着把自己对这些概念的理解用教程的方式记录一下!

这篇文章算是一个学习笔记和心得整理(但其实自己是按着入门教程的样子写的),顺带练练文笔。也希望能帮到恰好有类似基础、但还未学习 Vue 的朋友(真的有人看??)。

本文以 Vue 2 进行演示,只着重说明 Vue 的一些基本语法和概念,以及过渡到前端工程化项目相关的内容。具体的语法还是推荐从 Vue 官方文档入手。本文比较适合学习过前端三件套并且对 JavaScript 有大致了解,并想开始进入 Vue 学习的朋友鉴赏!更建议对 JavaScript 有一定了解的(特别是构造函数、对象、原型这一块的)来。欢迎各位朋友勘误!!!

Vue 基本概念和语法

引题

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>简单示例</title>
    <style>
        body {
            font-family: Arial, sans-serif;  // 设置字体
            text-align: center;  // 设置文字对齐方式
            margin-top: 50px;  // 设置上外边距
        }

        #alertButton {
            padding: 10px 20px;  // 设置内边距
            font-size: 16px;  // 设置字体大小
            cursor: pointer;   // 设置指针样式
        }
    </style>
</head>
<body>
    <button id="alertButton">点击我</button>

    <script>
        // 通过 document.getElementById('alertButton') 获取 HTML 元素
        // 随后给这个 HTML 元素添加点击事件的监听器,事件被触发后的逻辑代码在箭头函数内
        document.getElementById('alertButton').addEventListener('click', () => {
            alert('按钮被点击了!');
        });
    </script>
</body>
</html>

在直接开始 Vue 之前,先来重温一下一个网站的页面最基本的三要素:HTML、CSS 和 JavaScript。HTML 用标签来定义页面的结构和内容(如 <p></p><div></div>);CSS 用选择器和声明来定义页面的样式(如p { color: red; });JavaScript 是一门编程语言,用于定义网页的行为和交互。

从语言的定义上来说,HTML 和 CSS 不是编程语言,只有 JavaScript 是编程语言。因为 HTML 和 CSS 只是告诉浏览器如何渲染、生成页面,顶多算标记语言。而 JavaScript 他有逻辑控制、有函数、有变量和数据类型,JavaScript 诞生之初就是为了操作网页页面中的内容而诞生的。

HTML 是最基本的、定义结构的东西,因为不论是 CSS 还是 JavaScript,都是需要通过 HTML 标签引入到页面里的,CSS 是给 HTML 变样子的样式表语言,而 JavaScript 是操作页面内 HTML 的编程语言(是的,JS 不能直接操作 CSS 文件和样式表,它只能通过修改 HTML 的属性来实现对样式的变更,如 class 属性和 style 属性)。

Vue 是什么

复习完基本的知识,让我们回到 Vue。Vue 到底是什么?

Vue 是一个 JavaScript 框架。所谓“框架”,就是在原有的基础上又搭了一个框架,你只需要在这个框架里做你自己想做的东西就好了。换句话说,你甚至不用学习 JavaScript,只学习 Vue 的语法都够了(当然这样学习很不推荐,因为 Vue 本身就是基于 JavaScript 继续开发的),因为 Vue 已经为你封装了大量的常见功能和模式,你只需要在里面用就好了。当然,除了 Vue 以外,还有很多前端框架,这些框架的写法和理念都不太一样(如 React、Angular 等),但最终的目的肯定都是为了方便

Vue 和传统 JavaScript 的区别

传统 JavaScript 实现

欸,为什么说方便?传统的 JavaScript 有什么缺点呢?先不急着说原因,我们先假设自己要做一个 计数器 页面,然后分别用传统的 JavaScript 和 Vue 框架写一段代码,来直接看看它们之间的区别?

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>原生 JavaScript 计数器示例</title>
</head>
<body>
    <h1 id="counter">计数器:0</h1>
    <button id="incrementButton">增加</button>
    <button id="decrementButton">减少</button>

    <script>
        // 初始化计数器
        let count = 0;
        
        // 获取 DOM 元素
        const counter = document.getElementById('counter');
        const incrementButton = document.getElementById('incrementButton');
        const decrementButton = document.getElementById('decrementButton');

        // 更新计数器显示
        function updateCounter() {
            counter.textContent = `计数器:${count}`;
        }

        // 增加计数
        incrementButton.addEventListener('click', () => {
            count++;
            updateCounter();
        });

        // 减少计数
        decrementButton.addEventListener('click', () => {
            count--;
            updateCounter();
        });

        // 初始化显示
        updateCounter();
    </script>
</body>
</html>

这个是传统的 JavaScript 的写法,它的流程大概是这样的:首先在 HTML 里面放一个标签用来显示计数,另外两个按钮用来增加和减少计数;随后在 JS 中放好一个变量用来存值,再根据两个按钮的 id,通过document.getElementById();方法拿到这两个按钮元素,之后给这两个按钮分别添加监听器,一个增加一个减少。最后写一个函数用来更新 HTML 里的内容textContent,每次监听器里面都会调用一下这个函数,这个函数也会对应的修改<h1 id="counter"></h1>里面的内容。

Vue 实现

看着很正常,那么 Vue 写起来是啥样子的呢?让我们来看看:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vue 2 计数器示例</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
</head>
<body>
    <div id="app">
        <h1>计数器:{{ count }}</h1>
        <button @click="increment">增加</button>
        <button @click="decrement">减少</button>
    </div>

    <script>
        new Vue({
            el: '#app',
            data: {
                count: 0
            },
            methods: {
                increment() {
                    this.count++;
                },
                decrement() {
                    this.count--;
                }
            }
        });
    </script>
</body>
</html>

这看起来和传统的 JS 差距有点大,让我来结合传统的 JS 穿插着解释一下。

刚开始,Vue 框架通过<script></script>标签中 src 的地址被浏览器引入了!(是的啊,就是这么简单直接引入的,这在 Vue 的文档中被称为 CDN 引入。再深入了解一点的朋友可能会问了,那 NPM 呢,Vue CLI 呢,SFC 呢,这个别急,后面再说 )

然后,还是老样子,HTML 上留好一个标签就好了!一个<h1></h1>和两个按钮<button></button>。但是奇怪的是,为什么到了 Vue 这里好像有点不一样呢?<h1></h1>里面的“计数器”后面为什么多了个{{ count }}?为什么 button 的后面多了个 @click?为什么外面还要套一层<div id="app"></div>???

Vue 语法

这其实都是 Vue 自己的语法!很简单,{{ count }}@click都被称为模板语法{{ count }}其实就是把一个名为 count 的数据的值直接放到 HTML 内,这种写法被称为插值表达式

是的,Vue 支持直接把数据通过一对花括号{{}}放到 HTML 中哦!和传统的 JS 相比,你不用费时费力再去直接操作 HTML(DOM)了,哪里还需要写什么 textContent、innerHTML?Vue 的数据绑定系统已经帮你处理好了。你担心,如果 count 的值增加或者减少了怎么办?没关系啊,HTML 内的数据也会自动更新的,因为已经和这个变量双向绑定了。

那么@click又是什么?你可能会觉得很有点眼熟又有些陌生,这不是传统 JS 里的onclick=""吗。是的!一个意思,这就是绑定按钮到了一个方法上。前面的 “@” 是 v-on 的缩写(因为用的太频繁,所以有缩写),如果完全去掉缩写,那么他的样子就是v-on:click="increment"。你又会惊讶,这个 “v-” 打头的 v-on 是啥?这叫 Vue 指令,用来对 HTML(DOM)元素进行动态操作和数据绑定。这里的 v-on:click 就是给被添加的 HTML 标签添加一个 click 事件

v-on 除了 click,还有 mousedown、mouseup 等事件。v-on 以外还有另一种指令叫 v-bind,用于动态绑定值,它的缩写是一个冒号 “:”。如<div v-bind:class="className"></div>,也就是绑定一个 CSS 类到一个名为 className 的数据中,这个 div 的类会随着数据 className 值的变化而变化,并且,除了绑定 class,如果在 img 标签上,你还可以动态绑定图片链接 src <img :src="imageURL" />

在 v-on、v-bind 以外,还有 v-for(循环用的,遍历数组或对象,并渲染列表项的)、v-if、v-show。当然,还有非常非常多的 Vue 指令,这里是说不完的,如果你有兴趣,可以去 Vue 的官方文档看看,那边有详细的解释和案例。强行总结一下就是,指令就是丢 HTML 标签里面,结合 JS 改变 HTML 的。

Vue 实例

那最外层的 <div id="app"></div>又是什么?这个需要结合 script 标签的代码部分一起看。让我们把视角聚焦到代码块。

new Vue({
    el: '#app',
    data: {
        count: 0
    },
    methods: {
        increment() {
            this.count++;
        },
        decrement() {
            this.count--;
        }
    }
});

不同于直接在 script 标签内编写代码,Vue 是通过创建 Vue 实例然后在其中传入一个对象后的。可以看作是往new Vue()里面传递了一个配置对象,这个对象包括了各种选项。比如 el、data、methods 之类的。

如果你分得清函数、方法、构造函数、对象实例、类,这段话就不用看了。如果分不清,最好去补全一下 JavaScript 基础,这里只能简单说一下。总而言之,只需要把new Vue()的过程当作是告诉 Vue 如何去做就比较好理解了,我们只是通过script标签引入了 Vue,但是并没有去使用它,所以需要一个方式来构造它。所以,new Vue()是调用一个名为 Vue 的构造函数,在这个函数内部传入一个负责配置的对象,即{ el: '#app', data: { count: 0 }, methods: { increment() {} } }。这里面包含了挂载点el、数据data等内容。传递完后,构造函数会创建对象实例。本质上,new Vue()创建 Vue 实例的过程就等于在写配置文件,去告诉 Vue.js 要做的事情。

el处的值是 #app,即<div id="app"></div>,这也是解答了我们上方的疑问。el 即 Vue 的挂载点。我们先前所说的数据双向绑定、HTML 指令都是 Vue 框架所带的功能,你如果直接写在 HTML,原生 JavaScript 环境是不支持也无法识别的。简单来说,只有将内容写在 Vue 的挂载点内,才能被解析,实现想要的效果(也就是说除了 #app 以外的地方都不会被 Vue 渲染,你随便写东西不会被 Vue 捕获的,这也是为什么 Vue 也被称为“渐进式框架”,因为你可以指在页面的一小块地方引入他,如果有需要再渐进式的增加)。

顺带一提,文章从此处开始,不会在继续使用 HTML 指代页面结构,而是使用更加准确的 DOM(文档对象模型)一词称呼。DOM 也是 JavaScript 组成的一部分,除了 DOM 以外,还有 BOM(文档对象模型)及 ECMAScript。

回归正题,data就是定义数据值的地方。简单理解为在这里声明变量,就能实现前面所说的,双向绑定的效果。methods则是定义方法的地方,也就是传统 JavaScript 编程中的函数(对象的函数),写法也与函数声明类似,只是不需要 function 开头了。此外,你可能会注意到,increment() 和 decrement() 调整 count 值的写法似乎有些不同——它的前面有一个 this。在 Vue 内部,this 指向的是当前 Vue 实例,因此你可以通过 this 访问和操作实例的数据和方法。this.count 代表实例上的 count 属性,这样就可以实现数据的修改和视图的更新。

你可能会惊讶,那不是做什么都要创建一个方法了吗?并不是,Vue 也有类似于传统 JavaScript 编程中“页面加载后执行”的方法,这被称为钩子 Hooks(实际上这种给“某件事做完以后插入自定义逻辑”的操作都叫 Hooks,不管是 Vue 还是其他语言,在编程中这是一种通用的概念)。Vue 中比较常用的 Hooks 有 created()、mounted()、beforeDestroy()。

new Vue({
    el: '#app',
    data: {
        count: 0
    },
    methods: {
        increment() {
            this.count++;
        },
        decrement() {
            this.count--;
        }
    },
    created() {
        this.count = 0;  // 闲着没事写一下,其实我只是想示范一下 Hooks 在 Vue 里面咋写
    },
    mounted() {
        console.log(this.count);
    }
})

mounted() 是示例挂载之后,有点类似传统 JavaScript 编程中的 window.onload。created() 是实例已经创建,但 DOM 还没渲染的时候,有点类似传统 JavaScript 编程中,把 script 部分代码放到 DOM 的上面一样。见下示例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>错误示例</title>
</head>
<body>
    <script>
        // 把代码放在 DOM 结构前面,代码加载的比 DOM 还快,只会导致报错,因为 DOM 还没出来代码先加载了
        const counter = document.getElementById('counter');
        const incrementButton = document.getElementById('incrementButton');
        const decrementButton = document.getElementById('decrementButton');
    </script>

    <h1 id="counter">计数器:0</h1>
    <button id="incrementButton">增加</button>
    <button id="decrementButton">减少</button>
</body>
</html>

但总的来说,created() 发生的时机是比 mounted() 早的。除了这几个 Hooks 以外,Vue 还提供了很多 Hooks 用于开发,具体可参见文档

小结

最后,了解完 Vue 的这么多东西可能信息量有点爆炸,没关系,让我们最后再来看一下传统 JavaScript 和 Vue 的区别。我们还是以先前的例子计数器为案例,我分别把传统和 Vue 的两段代码放在一起,用来对比。

<script>
    // 初始化计数器
    let count = 0;
    
    // 获取 DOM 元素
    const counter = document.getElementById('counter');
    const incrementButton = document.getElementById('incrementButton');
    const decrementButton = document.getElementById('decrementButton');

    // 更新计数器显示
    function updateCounter() {
        counter.textContent = `计数器:${count}`;
    }

    // 增加计数
    incrementButton.addEventListener('click', () => {
        count++;
        updateCounter();
    });

    // 减少计数
    decrementButton.addEventListener('click', () => {
        count--;
        updateCounter();
    });

    // 初始化显示
    updateCounter();
</script>
<script>
    new Vue({
        el: '#app',
        data: {
            count: 0
        },
        methods: {
            increment() {
                this.count++;
            },
            decrement() {
                this.count--;
            }
        }
    });
</script>

相比之下,你会发现 Vue 的代码相比传统 JavaScript 更加简洁、方便。传统 JavaScript 开发中,我们需要事无巨细的去操作 DOM,去添加监听器、修改 textContent,还要控制每一个变量,十分繁杂。而 Vue 恰恰不同,你根本不需要关注具体怎么操作 DOM,你只需要简单的写几个标签,里面放个 {{ count }} 以实现和 data 处的数据绑定,写几个放在 methods 里的方法,就好了,几行下来就解决了。这也是 Vue 的特点:简化 DOM 操作数据绑定。专注于开发逻辑的编写就好了。

再给出一个案例,分别是通过 v-for 指令遍历数组内容以渲染表格的。从这里开始,为了省事,我会直接把 <!DOCTYPE html> 以及 <head></head> 之类的标签省略掉,不然太多了鼠标滚的累!(看到后面你也会觉得我这么做很合理喵,很像 template 标签 + script 标签组成的单文件组件

<div id="app">
  <h1>我的任务列表</h1>
  <ul>
    <!-- 使用 v-for 渲染列表 -->
    <li v-for="task in tasks" :key="task.id">
      {{ task.name }}
    </li>
  </ul>
</div>

<script>
  new Vue({
    el: '#app',
    data: {
      // 定义一个简单的任务列表
      tasks: [
        { id: 1, name: '学习 Vue' },
        { id: 2, name: '完成作业' },
        { id: 3, name: '去超市' }
      ]
    }
  });
</script>

这段代码生效后的生成的结果就是:

<ul>
  <li>学习 Vue</li>
  <li>完成作业</li>
  <li>去超市</li>
</ul>

简单吧。v-for 循环 tasks 数组里的信息,如果我们有传统 JavaScript 就要写这么多了:

<ul id="task-list"></ul>

<script>
  // 定义任务数组
  const tasks = [
    { id: 1, name: '学习 Vue' },
    { id: 2, name: '完成作业' },
    { id: 3, name: '去超市' }
  ];

  // 获取 ul 元素
  const ul = document.getElementById('task-list');

  // 遍历任务数组并创建列表项
  for (const task of tasks) {
    const li = document.createElement('li');
    li.textContent = task.name;
    ul.appendChild(li);
  }
</script>

Vue 最大的意义就是简化了前端开发和提高开发效率,虽然看起来,Vue 只是简化了开发人员的操作,但是对普通的用户而言呢?Vue 的一些特性能让最终的页面更流畅(虚拟 DOM),这也涉及到很多的 Vue 的特性,这里就不多赘述。总之,你甚至根本不需要学习传统 JavaScript,直接从 Vue 开始都可以,你也不需要再去用一堆 document.querySelector() 之类语句的去获取元素、修改元素了。你只需要直接在 HTML 中使用 Vue 的模板语法,就能快速实现数据绑定和动态更新。当然,如果你没有一些网站的开发经验,可能对这些总结无感,如果可以的话,可以去尝试多写一些实际的项目,项目积累起来的经验对于自己的未来也是十分有帮助的,毕竟计算机是一门十分看技术和经验的行业~

至于更多的,关于 Vue 的功能和语法,你可以查阅 Vue 的官方文档进行学习,本文只是引入门!到目前为止,本文都是以 Vue 2 为例进行演示,笔者我个人也建议,如果要学习的话还是先从 Vue 2 开始,简单好上手,方便理解。等有了一定的经验以后再学习 Vue 3 和 React 等进阶内容。(如果把所有东西讲一遍那就成文档了(草)

前端工程化和 Vue 项目

进入第二部分,工程化!如果你有看过 B 站或者其他地方的一些 Vue 教程,一定离不开创建 Vue 项目这个步骤。那么这个“项目”到底是什么?我们前面一直是在一个单页 index.html 中进行演示,这个所谓的“项目”和我们以前传统的,只在一个 .html 页面内开发又有什么区别?这些问题是本章要讲的一个核心,前端工程化

前端怎么“工程化”?

在上一章中,我们从传统的 JavaScript,过渡到简单的 Vue。在我们前面提到的例子中,Vue 是通过<script></script>标签被引入的,所谓的“Vue”其实也只是一个 JavaScript 文件。在 Vue 的官方文档中,这种方式被称为 CDN 引入

所有的代码都只在一个 .html 文件内。如果有别的内容怎么办?那就新建一个 .html 文件,通过一个 a 标签互相链接,互相跳转。但是这种办法会不会太麻烦太不规范了?而且,如果代码一多,这个 .html 文件的大小会越拉越长,看着都累。

那么,有没有其它的办法来更好的来组织代码?我们能不能用工程化的方法来改善这些问题?有!前端工程化正是为了解决这些问题而产生的。这也是我们这一章的内容。

当然,我们先不急着直接入手和前端工程化相关的概念,我们讲抛弃传统的 CDN 引入的方式,而是使用各种构建工具,从新建一个 Vue 项目开始。项目不再是只在一个 index.html 中,而是有了入口 JS 文件、根组件…… 还有组件、路由、状态等各种新的概念。让我们开始吧!

开始

创建项目

  • 配置使用 Vue 2 + Vue CLI。

创建项目的过程可能不是本文主要的,如果有需要可以去其他站点查询,这里只能简单说一下。在你安装好 Node.js 以后,使用npm install -g @vue/cli安装脚手架工具,然后通过vue create vue-test-project创建一个 Vue 项目。这里的 vue-test-project 是项目的名字。之后,我们选择 Vue 2 + NPM 包管理器,项目会自动下载需要的依赖,配置好后的提示如下。


🎉  Successfully created project vue-test-project.
👉  Get started with the following commands:

 $ cd vue-test-project
 $ npm run serve

随后,使用npm run serve运行开发服务器就可以开始编写了。

 DONE  Compiled successfully in 4643ms                                                                                                                                            20:25:15


  App running at:
  - Local:   http://localhost:8080/ 
  - Network: http://192.168.31.208:8080/

  Note that the development build is not optimized.
  To create a production build, run npm run build.

在项目创建完了以后,我们来进入文件夹看一下,这个“构建工具”构建了个什么?

粗略看一下整个项目的结构,会发现为什么 index.html 跑到了 src/public 下?根目录下的 main.js 和 src/App.vue 有什么区别?node_modules 文件夹是什么?其它的 package.json、vue.config.js 都是什么东西?别着急,我们一个一个看。

先从最外层说起。最外层有src文件夹、public文件夹、node_modules文件夹、package.jsonvue.config.js。最前面的 src 文件夹是存放源码的,可以说,在项目的开发中,大多数文件都只在这个文件夹里面。src 文件夹的内部可以看到有几个的 .vue 结尾的文件,这是 Vue 单文件组件(SFC),还有存放图片的地方。除此之外,CSS 文件也是放到 src 目录下的(虽然这个默认项目没有 CSS)。

而 public 文件夹就只有一个 index.html 和网站图标(favicon.ico),这里面的东西是不会被构建和打包的。欸?!“构建”和“打包”是什么?

构建和打包过程

构建和打包这两个虽然听起来是很高深的词汇,其实不然。在前端开发中,构建打包是最常见的操作,也就是把我们写在 src 里的源码转换成浏览器能够识别的文件。为什么要构建和打包?项目文件不能直接被浏览器读取吗?答案是肯定的,确实不能。在传统的开发中,我们只需要写好一个 index.html 文件,然后将相关的 JavaScript 和 CSS 文件直接引入到这个 HTML 文件中,浏览器就能够加载和渲染这些文件。

可是,现代的前端开发远不止于此。需求是会越变越多的,如果接触过 Java,用过 IDEA 创建过项目,会发现代码其实不仅仅只有一个 .java 文件。给开发人员写的代码和最终导出的结果是完全不一样的,这就好比通过 PS 编辑图像,这其中你可能会新建、修改很多图层,但是最终的结果就只是一张 .png 或者其他格式的图片。所以,计算机中的项目开发也是同理,工程化是必然的,还是那个结论,写出来的和最终导出来的不是一个东西。浏览器只能接受 JavaScript 代码,不论是什么框架,他们都需要把最终的东西变成 JS 引擎能执行的代码。

此外,不同的浏览器对 JS 和 CSS 的支持有所不同,如 IE、Chromium 系(Chrome、Edge)、Firefox、Safari 等。如果依靠手动兼容不同平台,工作量是十分巨大的。但通过构建工具,我们可以将代码打包成各种浏览器都能理解的格式!对于开发效率的提示是巨大的。

并且,现在的前端在打包过程中都会压缩图片、CSS 和其它内容。我们先执行一下构建命令npm run build


> vue-test-project@0.1.0 build
> vue-cli-service build

All browser targets in the browserslist configuration have supported ES module.
Therefore we don't build two separate bundles for differential loading.


⠋  Building for production...

 DONE  Compiled successfully in 11084ms                                                                                                                                           22:14:23

  File                                 Size                                                                    Gzipped

  dist\js\chunk-vendors.b52409db.js    91.36 KiB                                                               32.11 KiB
  dist\js\app.b9180ba3.js              13.11 KiB                                                               8.43 KiB
  dist\css\app.2cf79ad6.css            0.33 KiB                                                                0.23 KiB

  Images and other types of assets omitted.
  Build at: 2024-08-30T14:14:24.033Z - Hash: c6070512a6d23152 - Time: 11084ms

 DONE  Build complete. The dist directory is ready to be deployed.
 INFO  Check out deployment instructions at https://cli.vuejs.org/guide/deployment.html

打包后的文件会在项目的 dist 文件夹,我们来具体看一下文件:

打包后会新增一个 dist 文件夹,先不用管一些我们不认识的内容。里面有一个 js 文件夹,css 文件夹还有一个我们最熟悉的 index.html,又是我们最熟悉的样子了!项目源码被打包一个可以随便打开的文件了!你只需要打开 index.html,和你在开发时的样子一模一样!你可能会担心 index.html 怎么导入 JavaScript 文件的;Vue 项目里面所需要的<div id="app"></div>这个里面也有吗?我们来看项目代码中的 public/index.html:

<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

构建打包后的内容是这样的:

<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><title>vue-test-project</title><script defer="defer" src="/js/chunk-vendors.b52409db.js"></script><script defer="defer" src="/js/app.b9180ba3.js"></script><link href="/css/app.2cf79ad6.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but vue-test-project doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>

经过打包后,HTML 内部结构已经被压缩到一行了。但是我们还是能看出几个 script 标签和引入 CSS 的 link 标签。在执行完npm run build后,负责打包的会自动帮你处理好这些问题,要分几个 JS 文件、CSS 文件,怎么引入到 index.html 里面,JS 怎么再引用 JS 文件……这都完全不用担心,它帮你处理好了。在 Vue 里面打包的工具叫 Webpack,Webpack 是集成在 Vue CLI(脚手架工具)中的,也就是我们在一开始新建 Vue 项目时输入的指令vue create vue-test-project,这条命令就是 Vue 官方的一个快速构建 Vue 项目的工具 Vue CLI(Vue 3 一般使用 Vite 了)。所以vue.config.js文件是控制 Vue CLI 的配置文件。

当然啊!Webpack 打包工具其实是独立的一个,Vue 只是让你创建项目更简单一点,省得你再去安装 Webpack(npm install webpack)。这种方便你直接开始写东西的工具被称为脚手架工具。除了 Vue 框架有这个玩意,React 也有,叫 Create React App。

说回我们之前提到的“而 public 文件夹就只有一个 index.html 和网站图标(favicon.ico),这里面的东西是不会被构建和打包的”,此时我们再看打包后的文件,public 文件夹(index.html 和 favicon.ico)里的东西和其中是对的上的。

所以,构建和打包是编写 Vue 项目前必须要理解的一个环节,在上面的篇幅中,我并没有直接开始讲解 Vue 单文件组件的内容,而是先介绍了前端工程化相关的知识。Vue 开发不一定非得用命令行编译,它也可以通过 CDN 引入到一个 index.html 里面。你甚至可以创建一个没有任何框架和库的空白工程项目,只需要输入npm init初始化一个空项目就好了。如果你不嫌麻烦,你可以不用vue create创建项目,而是使用 npm init 之后再一点一点的用npm install vue把 Vue 和其他你想要的包导入进来。

包和包管理器

啊!让我们用命令来整理一下刚才说的一堆东西。

npm install -g @vue/cli           # 安装 Vue CLI,用来更方便的创建 Vue 项目

vue create vue-test-project     # 用 Vue CLI 这个脚手架工具创建 Vue 项目

npm run serve                         # 开发测试项目用的,打开测试服务器。前文没有提到,但是也是非常常用的命令

npm run build                          # 代码写得差不多了,要打包了,就输入这个命令开始构建。通过 Vue CLI 内部集成的 Webpack 打包工具进行打包~

看完代码和总结,相信你大概对构建流程、打包工具和脚手架工具有了一定的了解。接下来我们继续把视角放到npm上。在前面提过的命令中,“npm”是一个被用到了很多次的东西,这是个啥?这叫包管理器,负责安装各种的。我们前面说的 Webpack(打包工具)、Vue CLI(脚手架工具)都是通过npm install安装到我们计算机里的。NPM 仿佛一个应用市场、软件商店一般,什么东西都可以被安装进来。我们现在回归正题,继续观察 Vue 项目中的文件,我们来观察一下和 NPM 相关的 package.json。

{
  "name": "vue-test-project",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "core-js": "^3.8.3",
    "vue": "^2.6.14"
  },
  "devDependencies": {
    "@babel/core": "^7.12.16",
    "@babel/eslint-parser": "^7.12.16",
    "@vue/cli-plugin-babel": "~5.0.0",
    "@vue/cli-plugin-eslint": "~5.0.0",
    "@vue/cli-service": "~5.0.0",
    "eslint": "^7.32.0",
    "eslint-plugin-vue": "^8.0.3",
    "vue-template-compiler": "^2.6.14"
  },
  "eslintConfig": {
    "root": true,
    "env": {
      "node": true
    },
    "extends": [
      "plugin:vue/essential",
      "eslint:recommended"
    ],
    "parserOptions": {
      "parser": "@babel/eslint-parser"
    },
    "rules": {}
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not dead"
  ]
}

这个文件里面存储着其实就是项目相关的内容,包括名字name、版本version、运行脚本scripts、项目依赖dependencies、开发环境的项目依赖devDependencies和各种其他的内容。我只挑脚本和依赖两个部分简单说一下,方便大家理解包之类的东西。

首先是脚本scripts那个部分。里面有 build、serve 和 lint。其实,我们执行npm run build就等于执行vue-cli-service build,只是通过 NPM 被简化了。如果你愿意,在 JSON 内新增一个"yuanshen": "vue-cli-service build",以npm run yuanshen来替换npm run build都是可以的。

另外一个是依赖部分。项目依赖 dependencies 是项目被打包后还保留的包,开发环境的项目依赖 devDependencies 是只在开发环境保留的,比如语法检测一类的。如果要安装依赖,通过命令npm install <包名>就可以了,具体的命令语法可以去别的地方查!这里说太多就写偏题了。不了解“包”的朋友可能会感到困惑,包是什么?

很简单,把 NPM 当作软件商店,“包”就是软件商店里面的 APP。开发项目的时候,总不可能全部东西都你自己来写吧?(666,要是有能力当我没说)很多时候,为了省事,或多或少都会调用一些库。大名鼎鼎的 UI 库 Element UI,或者图标库,或者是展示 Minecraft 皮肤的库 skinview3d。很多代码不需要自己来写,只需要引入,调用,就可以了!项目文件夹里的node_modules就是包管理器在这个项目安装的依赖,全部都放在这个里面。

当然啊!NPM 可不止能安装这些东西,像打包工具、脚手架工具都要通过 NPM 来安装,可以说,没 NPM 这种包管理器就没法继续了!NPM 换句话说其实是 Node.js 的包管理器,但是 Node.js 和本文没太大关系,就先不说。

除了 NPM 以外,常见的包管理器还有 Yarn 等。

Vue 项目

 DONE  Compiled successfully in 59ms                                                                                                                                              00:39:47


  App running at:
  - Local:   http://localhost:8080/ 
  - Network: http://192.168.31.208:8080/

在说了那么多前置知识,终于可以继续开始我们的 Vue 项目学习了!我们输入npm run serve之后就可以开启运行开发服务器(和构建打包后不同,这个开发环境是给你看的,类似于传统 JavaScript 编程中打开 index.html 查看效果),根据 CLI 提示的信息在浏览器内打开相应地链接即可。

和 CDN 引入方式不同,Vue 项目因为在前端工程化的基础上,多了许多 CDN 引入没有的概念。最重要的就是单文件组件。接下来我们会围绕单文件组件结合 Vue 的项目进行解释。

单文件组件

让我们回归 Vue 的项目结构,src 目录下有一个名为 App.vue 的文件,这是 Vue 的根组件,而 src/components 下又有另一个名为 HelloWorld.vue 的文件。这些.vue结尾的文件是 Vue 的单文件组件。我们打开 App.vue 观察一下它的结构。

<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js App"/>
  </div>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue'

export default {
  name: 'App',
  components: {
    HelloWorld
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

一眼望去,似乎和 HTML 差不多?有些熟悉,又有一些陌生。为什么存在着三个并立的标签?<!DOCTYPE html><head>这些基本的标签去哪了?

最上方的标签<template></template>被称为模板,里面简单来说,就是写 HTML 内容的。其他两个标签<script></script><style></style>便是 JS 代码和 CSS 样式。这三个标签共同构成了一个 Vue 组件,如果你愿意叫他全称,你也可以说“单文件组件”,单文件组件以 .vue 结尾,你可以把组件理解成 HTML 中的<div></div>,一个 div 里面可以放其他的 div。同理,你可以在组件里面放其它的组件,一直嵌套嵌套都可以,只要你愿意,你可以把一些重复率很高的代码单独拿出来,单独抽象成一个组件。这样也能让代码看得更直观,看的不累。

而我们拿来举例子的这个文件 App.vue 则是 Vue 的根组件,它是最上层的组件,Vue 只会有一个根组件实例,所有其他组件都是作为它的子组件进行嵌套的。在 Vue 应用中,App.vue 作为根组件负责定义应用的总体结构,并且是所有子组件的起点。如果你要写一些通用的 CSS 样式,你只需要写在 App.vue 中的 style 即可。

每个组件之间的 HTML、CSS、JavaScript 都是独立的,是互不干扰的。在传统的 JavaScript 开发中,经常容易碰到作用域互相干扰的问题,在 Vue 中则很少碰到这个问题。

(组件文件里面可以给 CSS 的 style 加上一个 “scoped”,也就是<style scoped>。这样就会给打包后自动给组件内的 CSS 加上作用域,避免不同组件之间相同的 CSS 类互相干扰。你可以看一些网站,他们的 HTML 都是<div data-v-3eef872a class="content">这个样子的,里面的这个 data-v-3eef872a 就是添加了 CSS 作用域后的实现效果)

JavaScript 和入口文件

import HelloWorld from './components/HelloWorld.vue'

export default {
  name: 'App',
  components: {
    HelloWorld
  }
}

每个组件的 script 部分和我们第一章提到的 CDN 引入方式的类似。前者是直接把配置对象写在构造函数内了,CDN 引入这种方式适用于较小的项目或学习示例,但在实际开发中,通常不够灵活和模块化。而后者的组件里是通过export default导出一个配置对象,这种方式使得我们能够将组件的定义与其他模块分开,从而提高了代码的组织性和可维护性。每个组件都可以配置它们独属的 data、methods 之类的参数。

exportimport是 JavaScript 的新标准 ES6(ES2015)添加的内容。export 即导出,import 即导入,这些新特性主要用于实现模块化,具体的格式和使用方法可以搜索一下 ES6 相关的文章。在 Vue 中,这两个关键字是实现组件模块化管理的,在其他组件中导入另一个组件就是通过import来实现。

这一点可以看 App.vue 是如何导入 HelloWorld.vue 的。App.vue 在最上方通过import HelloWorld from './components/HelloWorld.vue'导入组件,随后在 App.vue 配置对象的 components 参数中引入了这个模块。最后在 template 中通过<HelloWorld msg="Welcome to Your Vue.js App"/>调用组件。这里的msg是 HelloWorld.vue 暴露出来的数据,用于给子组件传值,父子组件传值这一点先不急着了解,下次再说。接着看被导入调用的组件 src/components/HelloWorld.vue 的 script 部分。

export default {
  name: 'HelloWorld',
  props: {
    msg: String
  }
}

HelloWorld.vue 通过 export 使得自己可以被 App.vue 导入,而每一个 Vue 组件又必须有 export。毕竟组件通过 export 导出一个配置对象,虽然不一定是export default { ... }的形式,也可以是命名导出,但总之必须 export 出去。import 不是必须的,毕竟如果功能简单或者其他什么因素,也不需要用到别的组件。

不过,你可能会问了,那 App.vue 已经是最外一层了,还能被 import 导入吗?会,它会被入口文件调用。入口文件也就是我们先前看项目结构时看到的 main.js 文件。

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')

在 main.js 中,它通过import App from './App.vue'导入了根组件,最后放到new Vue({ ... })中进行渲染。main.js 的配置对象很简洁,就一个 render。render 是渲染函数,但这里不做具体说明,可以参阅官方文档。随后通过$mount('#app')挂载到 DOM 元素上。另外,这个 JS 入口文件是全局性的,App.vue 虽然是根组件,但是只是在层级上是最顶层,main.js 是整个应用的启动和配置文件,确保所有组件的渲染和管理。

最后,import 也用来导入你装的其它的包,比如 UI 库 Element UI 就是需要你通过import ElementUI from 'element-ui'import 'element-ui/lib/theme-chalk/index.css'来导入到你的项目中的,具体的使用方法每个项目都会说明。并且,调用组件不一定非要写到 export 的外部。像这样导入也是可以的(见下),这种方式被称为动态导入,也就是允许你在运行时按需加载模块,而不是在应用启动时立即加载所有模块。如果你直接写在最外面,那就是静态导入,不管你有没有触发这个组件里的内容,都会被立刻加载。

export default {
  name: 'App',
  components: {
    HelloWorld: () => import('./components/HelloWorld.vue')
  }
}

总结一下,在 Vue 中,.vue 文件是单文件组件,包含 template、script 和 style 三部分,分别定义组件的模板、逻辑和样式。App.vue 是应用的根组件,负责整体结构,其他组件通过 import 语法被引入和使用,实现了组件之间的模块化。组件的 script 部分通过 export default 导出配置对象,定义了组件的功能和数据。这种方式使得组件可以被导入和组织,从而提高了代码的模块化和可维护性。Vue 还支持动态导入,即在需要时按需加载组件,从而优化应用性能和加载速度。

(CDN 引入也能用模板,也有组件这一说,但是很麻烦)

大多数时候说的 “Vue 开发”,指的都是通过 NPM 一类的包管理器创建的 Vue 工程项目,而非 CDN 引入的 Vue。

小结

不多说,直接放上一个 Vue 项目结构解析~

vue-test-project/
│
├── node_modules/           # 项目依赖的 npm 模块
│
├── public/                 # 公共静态资源目录
│   ├── favicon.ico         # 网站图标
│   └── index.html          # 主 HTML 文件
│
├── src/                    # 源代码目录
│   ├── assets/             # 静态资源(如图片、字体等)
│   ├── components/         # Vue 组件
│   ├── views/              # 视图组件(通常用于路由)
│   ├── router/             # 路由配置
│   ├── store/              # Vuex 状态管理
│   ├── App.vue             # 根组件
│   └── main.js             # 项目入口文件
│
├── .gitignore              # Git 忽略文件配置
├── babel.config.js         # Babel 配置
├── package.json            # 项目描述文件及依赖配置
├── README.md               # 项目说明文件
└── vue.config.js           # Vue CLI 配置文件(可选)

这个里面又多了几个文件夹,store 和 route。这两个分别是状态管理和路由配置的,先不管~

Vue 3 和 Vue 2 的区别

在文章的最后,来说一下 Vue 3 和 Vue 2 的区别。Vue 2 其实已经是 2016 年的作品了。2023年末,Vue 2 已经被官方终止技术支持了。但是对于新手来说(至少对笔者我来说),Vue 2 相比 Vue 3,会更好上手和入门一些,因此,很多入门教程都是从 Vue 2 开始做起(要从 Vue 3 也可以)。随后再进入到 Vue 3 的开发,这样也能更好地理解两者的不同之处。Vue 3 发布于 2020 年,距今为止也有四年了。

要说 Vue 3 和 Vue 2 除了性能方面的优化和底层的一些改变,最大的改变可能是 Composition API,也被称作组合式 API。我们以往导入配置对象,创建 Vue 实例都是通过这样:

<script>
export default {
  data() {
    return {
      count: 0
    };
  },
  methods: {
    increment() {
      this.count += 1;
    }
  }
}
</script>

这种往里面丢个配置对象的方式被称为选项式 API。而 Vue 3 的方式是:

<script setup>
import { ref } from 'vue';

const count = ref(0);

const increment = () => {
  count.value += 1;
}
</script>

这种将代码一行一行组合起来写的方式也就是 Vue 3 的新特性之一:组合式 API(Composition API),它有点接近传统 JavaScript,但是这种方式依旧保留着 Vue 的核心特性(其实这种写法感觉有点 React)。但这也只是一直风格,Vue 3 的官方文档是可以切换 API 风格的,如果你不习惯这种新的写法,也可以使用老写法:选项式 API。

除了组合式 API 以外,响应式系统的变化也很新颖,具体可以去其他文章查询,就不多说了!

后记

如果这篇文章对你有帮助,我也很高兴!如果你大概都了解完了这些东西,那么恭喜你,可以正式开始前端学习了!可以尝试着去通过 Vue 的官方文档、技术博客等网站的教程自己学习了。

另外,对于原生 JS 的学习是很重要的,JS 的三座大山:this、原型、异步;标准库;闭包、原型链、表驱动、最小知识原则、DRY 原则、API 正交原则、重载、链式调用…… 如果能大致掌握,不管学习什么框架乃至其他语言都游刃有余。

最后,附上一个网上搜集来的前端总结(主要给我自己看的)。

类型 项目名
包管理工具 npm、Yarn 等
打包工具 Webpack、Parcel、Rollup 等
脚手架工具 Create React App、Vue CLI、Angular CLI 等
构建工具 Vite、Snowpack 等
前端框架 React、Angular、Vue.js、Ember.js、Backbone.js 等
状态管理库 Redux、MobX、Vuex、Zustand、Recoil 等
前端路由 React Router、Vue Router、Angular Router、Reach Router、Next.js Router 等
UI 库 Bootstrap、Ant Design、Material UI、Element UI 等
CSS 预处理器 Sass、Less、Stylus 等
代码格式化工具 Prettier、ESLint 等
测试工具 Jest、Mocha、Chai 等
模板引擎 Handlebars、Pug、EJS 等
静态类型检查器 TypeScript、Flow 等
posted @ 2024-08-31 03:12  AurLemon  阅读(609)  评论(0编辑  收藏  举报