Skip to content

Vue.js

https://cn.vuejs.org/

evan

Vue.js(发音 /vjuː/,类似 view)是由尤雨溪(Evan You)于2014年发布的一款轻量级、渐进式 JavaScript 前端框架,专注于高效构建用户界面与单页应用(SPA)。

它基于标准 HTML、CSS、JavaScript,核心是响应式数据绑定组件化开发: - 响应式:数据变化时,视图自动同步更新,无需手动操作 DOM。 - 组件化:将界面拆分为独立、可复用的单文件组件(.vue),封装逻辑、模板与样式。 - 渐进式:可按需引入,小到用 <script> 标签增强网页交互,大到配合 Vue Router、Pinia 构建全栈应用。

Vue 语法直观、文档完善、性能优异(虚拟 DOM 优化),兼具易用性、灵活性与高效性,是全球主流前端框架之一。

Javascript开发常见需求

MVVM

组件


MVVM模式

MVVM 全称 Model–View–ViewModel,是一种前端架构设计模式,Vue 就是典型的 MVVM 框架

数据驱动视图,视图同步数据,中间由 ViewModel 自动衔接,开发者不用手动操作 DOM。

mvvm

mvvm-1

MVVM 的核心特点

  • 双向绑定:数据 ↔ 视图自动同步
  • 低耦合:视图和数据分开,互不污染
  • 开发者不用操作 DOM,只关心数据逻辑

数据驱动和组件式编程

数据驱动

不用手动改 DOM,只改数据,页面自动跟着变。

与jQuery对比

  • 传统 jQuery:改文字 → 找到 DOM → 赋值
  • Vue 数据驱动:改数据 → Vue 自动帮你更新页面

开发者只需关心数据是什么,不关心页面怎么刷新。

核心特点 - 以数据为中心 页面展示、样式、列表渲染,都由数据决定。

  • 数据变 → 视图自动更新 修改 data 里的变量,视图自动刷新,不用操作 DOM。

  • 视图操作同步回数据(双向绑定) 输入框输入内容,数据自动跟着变,不用 value、不用监听事件。

  • 减少 DOM 操作 几乎不用 getElementByIdinnerHTML、jQuery 之类。

data driven


组件式编程

把页面拆成一个个独立、可复用的小模块,像搭积木一样拼成完整页面。

核心思想

  • 一个页面 = 多个组件组合而成
  • 每个组件独立封装:自己的 HTML、CSS、JS
  • 组件可以复用、嵌套、互相通信
  • 便于维护、分工协作、单元测试

组件的特点 - 可复用 写一次导航栏、按钮、卡片,到处都能用。

  • 独立作用域 数据和样式默认不互相污染(Vue 单文件组件 + scoped)。

  • 可嵌套 组件里可以再放组件,形成组件树。

  • 可通信 父子组件通过 props、emit 传递数据; 复杂项目用 Pinia/Vuex 统一管理。

component

这个理念不是来源于vue, 把web组件式开发发扬光大的应该是react了,组件开发是一种朴素的开发思想,分而治之,大型系统拆分成一个个的小模块小组件,分配给不同的人。额外的好处是顺便能复用这个组件。


组件理解

理解组件的思想可以类比函数。一个函数包含哪些东西呢?

  1. 形参

  2. 局部变量

  3. 函数名

  4. 返回值

与Vue组件类比

函数 Vue组件
形参 Slot,props
局部变量和局部函数 Data, methods
函数名 Name
Return template

Hello Vue

  • 效果


  • 源代码


    <script src="vue.global.js"></script>
    
    <!-- 1. 定义View -->
    <div id="app">{{ message }}</div>
    
    <script>
      const { createApp, ref } = Vue
    
      // 3. 定义ViewModel
      createApp({
        setup() {
          const message = ref('Hello vue!')
          // 2. 定义Model
          return {
            message
          }
        }
      }).mount('#app')
    </script>
    

生命周期

每个 Vue 实例在被创建之前都要经过一系列的初始化过程。例如,实例需要配置数据观测(data observer)、编译模版、挂载实例到 DOM ,然后在数据变化时更新 DOM 。在这个过程中,实例也会调用一些 生命周期钩子 ,这就给我们提供了执行自定义逻辑的机会。 它可以总共分为8个阶段:

  1. beforeCreate:在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用。

  2. created:实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算, watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。

  3. beforeMount:在挂载开始之前被调用:相关的 render 函数首次被调用。该钩子在服务器端渲染期间不被调用。

  4. mounted: el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm.$el 也在文档内。该钩子在服务器端渲染期间不被调用。

  5. beforeUpdate:数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。 你可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。该钩子在服务器端渲染期间不被调用。

  6. updated:由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性或 watcher 取而代之。该钩子在服务器端渲染期间不被调用。

  7. beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。该钩子在服务器端渲染期间不被调用。

  8. destroyed:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。


API风格

  • 用包含多个选项的对象来描述组件的逻辑

  • 适合面向对象

<script>
export default {
  // data() 返回的属性将会成为响应式的状态
  // 并且暴露在 `this` 上
  data() {
    return {
      count: 0
    }
  },
  // methods 是一些用来更改状态与触发更新的函数
  // 它们可以在模板中作为事件监听器绑定
  methods: {
    increment() {
      this.count++
    }
  },
  // 生命周期钩子会在组件生命周期的各个不同阶段被调用
  // 例如这个函数就会在组件挂载完成后被调用
  mounted() {
    console.log(`The initial count is ${this.count}.`)
  }
}
</script>
<template>
  <button @click="increment">Count is: {{ count }}</button>
</template>
  • 使用导入的 API 函数来描述组件逻辑

  • 适合结构化

<script setup>
import { ref, onMounted } from 'vue'
// 响应式状态
const count = ref(0)
// 用来修改状态、触发更新的函数
function increment() {
  count.value++
}
// 生命周期钩子
onMounted(() => {
  console.log(`The initial count is ${count.value}.`)
})
</script>
<template>
  <button @click="increment">Count is: {{ count }}</button>
</template>

模板语法

Vue.js 使用了基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据。所有 Vue.js 的模板都是合法的 HTML,所以能被遵循规范的浏览器和 HTML 解析器解析

  • 文本

    • <span>Message: {{ msg }}</span>
    • <span v-once>这个将不会改变: {{ msg }}</span>
  • 原始 HTML

    • <p>Using v-html directive: <span v-html="rawHtml"></span></p>
  • Attribute

    • <div v-bind:id="dynamicId"></div>
  • 使用 JavaScript 表达式

    • {{ number + 1 }}
    • {{ ok ? 'YES' : 'NO' }}
    • {{ message.split('').reverse().join('') }}
    • <div v-bind:id="'list-' + id"></div>
  • 指令 (Directives) 是带有 v- 前缀的特殊 attribute

    • <p v-if="seen">现在你看到我了</p>
  • 参数

    • <a v-bind:href="url">...</a>

    • <a v-on:click="doSomething">...</a>

  • 修饰符

    • <form v-on:submit.prevent="onSubmit">...</form>
  • v-bind 缩写

    • <!-- 完整语法 --> <a v-bind:href="url">...</a>

    • <!-- 缩写 --> <a :href="url">...</a>

  • v-on 缩写

    • <!-- 完整语法 --> <a v-on:click="doSomething">...</a>

    • <!-- 缩写 --> <a @click="doSomething">...</a>


Vue.js常用指令

graph LR A[

Vue 核心指令] --> B[

条件渲染

] B --> B1[v-if
根据表达式真假删除/插入元素] B --> B2[v-show
不管条件是否成立,都会渲染 html,v-if 仅条件成立时渲染] B --> B3[v-else
与 v-if 或 v-show 配合,v-if 不成立时显示] A --> C[

列表渲染

] C --> C1[v-for
基于数组渲染列表,类似 JavaScript 遍历语法] A --> D[

事件处理

] D --> D1[v-on
监听 DOM 事件,用法类似 v-bind,如给 button 加点击事件] A --> E[

表单输入绑定

] E --> E1[v-model
在表单 input、textarea、select 元素上创建双向数据绑定]

组件化应用构建

全局注册

import { createApp } from 'vue'
const app = createApp({})
app.component(
  // 注册的名字
  'MyComponent',
  // 组件的实现
  {
    /* ... */
  }
)

局部注册

1
2
3
4
5
6
<script setup>
import ComponentA from './ComponentA.vue'
</script>
<template>
  <ComponentA />
</template>

风格指南

https://cn.vuejs.org/v2/style-guide/

组件名为多个单词

Vue.component('todo-item', {
            props: ['todo'],
            template: '<li>{{ todo.text }}</li>'
})

单文件组件文件的大小写

  • 单文件组件的文件名应该要么始终是单词大写开头 (PascalCase)

  • 要么始终是横线连接 (kebab-case)


Vue.js快速起步

https://cn.vuejs.org/guide/quick-start.html

安装

1
2
3
4
5
npm -v  //查看版本
npm create vue@latest
cd <your-project-name>
npm install
npm run dev

Vue.js目录结构

目录 说明
build 项目构建(webpack)相关代码
config 配置目录,包括端口号等。我们初学可以使用默认的。
node_modules npm 加载的项目依赖模块
src 这里是我们要开发的目录,基本上要做的事情都在这个目录里。
src/assets 放置一些图片,如logo等。
src/components 目录里面放了一个组件文件,可以不用。
src/App.vue 项目入口文件,我们也可以直接将组件写这里,而不使用 components 目录。
src/main.js 项目的核心文件。
static 静态资源目录,如图片、字体等。
test 初始测试目录,这些是一些配置文件,包括语法配置,git配置等。
index.html 首页入口文件,你可以添加一些 meta 信息或统计代码啥的。
package.json 项目配置文件。
README.md 项目的说明文档,markdown 格式

不同技术选型对比

js-version.html
<!DOCTYPE html>
<html>

<body onload="onInput()">
  <div id="app">
    <h4>数据的双向绑定示例:加法器----js的实现</h4>
    <div class="item">
      <span>数1:</span>
      <input id="d1" type="number" onchange="onInput()" />
    </div>

    <div class="item">
      <span>数2:</span>
      <input id="d2" type="number" onchange="onInput()" />
    </div>

    <div class="item">
      <span>结果:</span>
      <span id="result"></span>
    </div>

    <div id="observer1">
      <span>用户操作:</span>数1:<span id="observer1_d1"></span>,数2:<span id="observer1_d2"></span>,结果是:<span
        id="observer1_result"></span>
    </div>

    <div id="observer2">
      <span>也就是说:</span><span id="observer2_d1"></span>+<span id="observer2_d2"></span>=<span
        id="observer2_result"></span>
    </div>
  </div>
</body>

</html>

<script>
  function onInput() {
    //提取数据
    var $d1 = document.getElementById("d1");
    var $d2 = document.getElementById("d2");

    //观察者
    var $result = document.getElementById("result");

    var $observer1 = document.getElementById("observer1");
    var $observer1_d1 = document.getElementById("observer1_d1");
    var $observer1_d2 = document.getElementById("observer1_d2");
    var $observer1_result = document.getElementById("observer1_result");

    var $observer2 = document.getElementById("observer2");
    var $observer2_d1 = document.getElementById("observer2_d1");
    var $observer2_d2 = document.getElementById("observer2_d2");
    var $observer2_result = document.getElementById("observer2_result");

    var $d1_value = parseFloat($d1.value);
    var $d2_value = parseFloat($d2.value);

    if (!isNaN($d1_value) && !isNaN($d2_value)) {
      //反馈数据
      var $result_value = $d1_value + $d2_value;
      $result.innerText = $result_value;

      $observer1.setAttribute("class", "show observer1");
      $observer1_d1.innerText = $d1_value;
      $observer1_d2.innerText = $d2_value;
      $observer1_result.innerText = $result_value;

      $observer2.setAttribute("class", "show observer2");
      $observer2_d1.innerText = $d1_value;
      $observer2_d2.innerText = $d2_value;
      $observer2_result.innerText = $result_value;
    } else {
      $result.innerText = "";
      $observer1.setAttribute("class", "hide");
      $observer2.setAttribute("class", "hide");
    }
  }

</script>

<style>
  .item {
    margin: 10px;
  }

  .show {
    display: block
  }

  .hide {
    display: none
  }

  .observer1 {
    background-color: dimgray;
    color: white;
    margin: 40px;
    font-size: 20px;
    padding: 40px;
  }

  .observer2 {
    background-color: bisque;
    color: black;
    margin: 40px;
    padding: 40px;
    font-size: 20px;
  }
</style>

vue-version.html
<!DOCTYPE html>
<html>

<head>
  <script src="js\petite-vue.iife.js"></script>
</head>

<body>
  <div id="app">
    <h4>数据的双向绑定示例:加法器----Vue.js的实现</h4>
    <div class="item">
      <span>数1:</span>
      <input type="number" v-model="d1" />
    </div>
    <div class="item">
      <span>数2:</span>
      <input type="number" v-model="d2" />
    </div>

    <div class="item">
      <span>结果:</span>
      <span>{{result}}</span>
    </div>

    <div v-if="result" class="observer1">
      <span>用户操作:</span>数1:<span>{{d1}}</span>,数2:<span>{{d2}}</span>,结果是:<span>{{result}}</span>
    </div>

    <div v-if="result" class="observer2">
      <span>也就是说:</span><span>{{d1}}</span>+<span>{{d2}}</span>=<span>{{result}}</span>
    </div>
  </div>
</body>

</html>

<script>
  PetiteVue.createApp().mount("#app")
  new Vue({
    el: '#app',
    data() {
      return {
        d1: null,
        d2: null,
        result: null
      }
    },
    watch: {
      d1() {
        this.onInput();
      },
      d2() {
        this.onInput();
      }
    },
    methods: {
      onInput() {
        var $d1_value = parseFloat(this.d1);
        var $d2_value = parseFloat(this.d2);

        if (!isNaN($d1_value) && !isNaN($d2_value)) {
          //反馈数据
          this.result = $d1_value + $d2_value;
        } else {
          this.result = null;
        }
      }
    }
  })
</script>

<style>
  .item {
    margin: 10px;
  }

  .show {
    display: block
  }

  .hide {
    display: none
  }

  .observer1 {
    background-color: dimgray;
    color: white;
    margin: 40px;
    font-size: 20px;
    padding: 40px;
  }

  .observer2 {
    background-color: bisque;
    color: black;
    margin: 40px;
    padding: 40px;
    font-size: 20px;
  }
</style>

vuetify-version.html
<!DOCTYPE html>
<html>

<head>
  <link href="css/fonts.css" rel="stylesheet">
  <link href="css/materialdesignicons.min.css" rel="stylesheet">
  <link href="css/vuetify.min.css" rel="stylesheet">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
</head>

<body>
  <div id="app">
    <v-app>
      <v-main>
        <v-card class="mx-auto" max-width="500">
          <v-toolbar color="deep-purple accent-4" dark>
            <v-toolbar-title>数据的双向绑定示例:加法器----Vuetify的实现</v-toolbar-title>
          </v-toolbar>

          <v-card-text>
            <v-text-field label="数1:" :rules="rules" hide-details="auto" v-model="d1"></v-text-field>
            <v-text-field label="数2:" :rules="rules" hide-details="auto" v-model="d2"></v-text-field>
            <v-text-field label="结果:" v-model="result" readonly></v-text-field>
          </v-card-text>
        </v-card>

      </v-main>
    </v-app>


  </div>
</body>

</html>

<script src="js/vue.js"></script>
<script src="js/vuetify.js"></script>

<script>
  new Vue({
    el: '#app',
    vuetify: new Vuetify(),
    data() {
      return {
        d1: null,
        d2: null,
        result: null,
        rules: [
          value => !!value || '必填项.'
        ],
      }
    },
    watch: {
      d1() {
        this.onInput();
      },
      d2() {
        this.onInput();
      }
    },
    methods: {
      onInput() {
        var $d1_value = parseFloat(this.d1);
        var $d2_value = parseFloat(this.d2);

        if (!isNaN($d1_value) && !isNaN($d2_value)) {
          //反馈数据
          this.result = $d1_value + $d2_value;
        } else {
          this.result = null;
        }
      }
    }
  })
</script>

<style>
  .item {
    margin: 10px;
  }

  .show {
    display: block
  }

  .hide {
    display: none
  }

  .observer1 {
    background-color: dimgray;
    color: white;
    margin: 40px;
    font-size: 20px;
    padding: 40px;
  }

  .observer2 {
    background-color: bisque;
    color: black;
    margin: 40px;
    padding: 40px;
    font-size: 20px;
  }
</style>