Skip to content

Vue.js

https://cn.vuejs.org/

evan

Javascript开发常见需求

MVVM

组件


MVVM模式

Vue.js,Angular.js,React.js,Ember.js

mvvm

mvvm-1


数据驱动和组件式编程

数据驱动

data driven


组件式编程

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>

<!-- 定义View -->
<div id="app">{{ message }}</div>

<script>
  const { createApp } = Vue

  //创建ViewModel
  createApp({
    data() {//定义Model
      return {
        message: 'Hello Vue!'
      }
    }
  }).mount('#app')//绑定
</script>

使用Vue的过程就是定义MVVM各个组成部分的过程的过程。

  1. 定义View

  2. 定义Model

  3. 创建一个Vue实例或"ViewModel",它用于连接View和Model

在创建Vue实例时,需要传入一个选项对象,选项对象可以包含数据、挂载元素、方法、模生命周期钩子等等。 在这个示例中,选项对象的el属性指向View,el: '#app'表示该Vue实例将挂载到<div id="app">...</div>这个元素;data属性指向Model,data: exampleData表示我们的Model是exampleData对象。 Vue.js有多种数据绑定的语法,最基础的形式是文本插值,使用一对大括号语法,在运行时{{ message }}会被数据对象的message属性替换,所以页面上会输出"Hello World!"。


生命周期

每个 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常用指令

command


组件化应用构建

全局注册

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
6
7
8
npm -v  //查看版本
npm install -g cnpm --registry=https://registry.npm.taobao.org  //淘宝定制版
cnpm install vue    //安装Vue
cnpm install --global vue-cli   //全局安装 vue-cli
vue init webpack my-project //初始化项目
cd my-project   //进入项目目录
cnpm install    //安装依赖
cnpm 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>