Vue.js 学习笔记(一)数据绑定与指示器

一、安装与项目初始化

安装 @vue/cli$ npm install -g @vue/cli

安装 gitsudo apt-get install git

创建项目:$ vue create todo --default

项目结构

$ tree todo -I node_modules
todo
├── babel.config.js
├── package.json
├── package-lock.json
├── public
│   ├── favicon.ico
│   └── index.html
├── README.md
└── src
    ├── App.vue
    ├── assets
    │   └── logo.png
    ├── components
    │   └── HelloWorld.vue
    └── main.js
文件 功能
public/index.html 浏览器加载的 HTML 文件。其中包含用于显示 Vue 应用的标签和加载应用文件的 <script>
src/main.js 负责 Vue 应用的基本配置,通常还用于注册应用依赖的第三方组件
src/App.vue Vue 组件,即 Vue 应用的主要构成部分。如需要显示给用户的 HTML 页面、Javascript 代码和 CSS 等

public/index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>todo</title>
  </head>
  <body>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

src/main.js

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

Vue.config.productionTip = false

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

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>

开启测试服务器:$ npm run serve

npm serve

二、数据绑定与指示器

准备工作,添加 CSS 库:
$ npm install bootstrap@4.0.0

修改 src/main.js 导入 Bootstrap

// src/main.js
import Vue from 'vue'
import App from './App.vue'

import "bootstrap/dist/css/bootstrap.min.css";

Vue.config.productionTip = false

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

1. 数据绑定

展示数据给用户是 web 组件最重要的工作之一,数据绑定可以将 <script> 下定义的数据对象关联给 <template> 中的特定元素进行显示。

显示数据

修改 src/App.vue 如下:

// src/App.vue
<template>
  <div id="app" class="bg-primary text-white text-center m-2 p-3">
    <h3>Product: {{ name }}</h3>
  </div>
</template>

<script>
export default {
  name: "app",
  data: function () {
    return {
      name: "Kayak"
    }
  }
}
</script>

通过 JavaScript 模块中的 data 属性定义了可以被绑定的数据值:

data: function () {
  return {
    name: "Kayak"
  }
}

再借助数据绑定机制,使用 {{ name }} 等语法将 <script> 中定义的数据关联到 template 模块的 HTML 元素中。这种形式的绑定也称为文本注入(text interpolation binding)

data binding

绑定中使用表达式

实际上数据绑定不仅仅可以通过预先定义的变量替换 HTML 元素中的文本,还可以直接嵌入复杂的表达式,具体代码如下:

// src/App.vue
<template>
  <div id="app" class="bg-primary text-white text-center m-2 p-3">
    <h3>Product: {{ name }}</h3>
    <h3>Price: ${{ (price + (price * (taxRate / 100))).toFixed(2) }}</h3>
  </div>
</template>

<script>
export default {
  name: "app",
  data: function () {
    return {
      name: "Kayak",
      price: 275,
      taxRate: 12
    }
  }
}
</script>
expression
Computed Properties

在数据绑定中使用过于复杂的表达式并不利于代码的阅读、维护和复用。为了使 template 组件可以足够简单,Vue 提供了计算属性模块,可以从 data 属性提供的数据中生成需要的值,从而减少数据绑定中复杂表达式的使用。

// src/App.vue
<template>
  <div id="app" class="bg-primary text-white text-center m-2 p-3">
    <h3>Product: {{ name }}</h3>
    <h3>Price: ${{ totalPrice.toFixed(2) }}</h3>
  </div>
</template>

<script>
export default {
  name: "app",
  data: function () {
    return {
      name: "Kayak",
      price: 275,
      taxRate: 12
    }
  },
  computed: {
    totalPrice: function() {
      return this.price + (this.price * (this.taxRate / 100));
    }
  }
}
</script>

关键代码:

<h3>Price: ${{ totalPrice.toFixed(2) }}</h3>
...
computed: {
  totalPrice: function() {
    return this.price + (this.price * (this.taxRate / 100));
  }
}
computed properties
Methods

方法相对于计算属性则更加灵活,可以定义自己的参数。

// src/App.vue
<template>
  <div id="app" class="bg-primary text-white text-center m-2 p-3">
    <h3>Product: {{ name }}</h3>
    <h4>Price: ${{ lowTotalPrice.toFixed(2) }} (Low Rate)</h4>
    <h4>Price: ${{ highTotalPrice.toFixed(2) }} (High Rate)</h4>
  </div>
</template>

<script>
export default {
  name: "app",
  data: function () {
    return {
      name: "Kayak",
      price: 275,
      lowTaxRate: 12,
      highTaxRate: 20
    }
  },
  computed: {
    lowTotalPrice: function () {
      return this.getTotalPrice(this.lowTaxRate);
    },
    highTotalPrice: function () {
      return this.getTotalPrice(this.highTaxRate);
    }
},
  methods: {
    getTotalPrice(taxRate) {
      return this.price + (this.price * (taxRate / 100));
    }
  }
}
</script>
methods

也可以省略掉上面代码中对计算属性的定义,直接在数据绑定时调用方法。这显示了各组件之间组织的灵活性,当然此举也会增加 template 中代码的复杂度:

// src/App.vue
<template>
  <div id="app" class="bg-primary text-white text-center m-2 p-3">
    <h3>Product: {{ name }}</h3>
    <h4>Price: ${{ getTotalPrice(lowTaxRate).toFixed(2) }} (Low Rate)</h4>
    <h4>Price: ${{ getTotalPrice(highTaxRate).toFixed(2) }} (High Rate)</h4>
  </div>
</template>

<script>
export default {
  name: "app",
  data: function () {
    return {
      name: "Kayak",
      price: 275,
      lowTaxRate: 12,
      highTaxRate: 20
    }
  },

  methods: {
    getTotalPrice(taxRate) {
      return this.price + (this.price * (taxRate / 100));
    }
  }
}
</script>
methods 2
Filters

过滤器是一种在 filter 模块下定义的函数,可以对需要显示的表达式进行格式化操作:

// src/App.vue
<template>
  <div id="app" class="bg-primary text-white text-center m-2 p-3">
    <h3>Product: {{ name }}</h3>
    <h4>Price: {{ getTotalPrice(lowTaxRate) | currency }} (Low Rate)</h4>
    <h4>Price: {{ getTotalPrice(highTaxRate) | currency }} (High Rate)</h4>
  </div>
</template>

<script>
export default {
  name: "app",
  data: function () {
    return {
      name: "Kayak",
      price: 275,
      lowTaxRate: 12,
      highTaxRate: 20
    }
  },

  methods: {
    getTotalPrice(taxRate) {
      return this.price + (this.price * (taxRate / 100));
    }
  },
  filters: {
    currency(value) {
      return new Intl.NumberFormat("en-US",
        { style: "currency", currency: "USD" }).format(value);
    }
  }
}
</script>
filters
复杂 Filter
// src/App.vue
<template>
  <div id="app" class="bg-primary text-white text-center m-2 p-3">
    <h3>Product: {{ name | reverse | capitalize }}</h3>
    <h4>Price: {{ getTotalPrice(lowTaxRate) | currency(3) }} (Low Rate)</h4>
    <h4>Price: {{ getTotalPrice(highTaxRate) | currency }} (High Rate)</h4>
  </div>
</template>

<script>
export default {
  name: "app",
  data: function () {
    return {
      name: "Lifejacket",
      price: 48.95,
      lowTaxRate: 12,
      highTaxRate: 20
    }
  },

  methods: {
    getTotalPrice(taxRate) {
      return this.price + (this.price * (taxRate / 100));
    }
  },
  filters: {
    currency(value, places) {
      return new Intl.NumberFormat("en-US",
        {
          style: "currency", currency: "USD",
          minimumFractionDigits: places || 2,
          maximumFractionDigits: places || 2
        }).format(value);
    },
    capitalize(value) {
      return value[0].toUpperCase() + value.slice(1);
    },
    reverse(value) {
      return value.split("").reverse().join("");
    }
  }
}
</script>
complex filter

2. Directives

指示器 是 template 中可以为 HTML 元素添加 Vue.js 功能的一些特殊属性。比如 v-on:click 指示器可以为 <button> 元素添加对鼠标单击事件的监听,v-text 指示器可以设置某个 HTML 元素的文字内容。

v-text

有选择地显示元素

使用 v-if 指示器控制 HTML 元素的显示与隐藏:

// src/App.vue
<template>
  <div id="app" class="container-fluid text-center">
    <div class="bg-primary text-white m-2 p-3">
      <h3>Product: <span v-text="name"></span></h3>
      <h4 v-if="showElements">{{ price }}</h4>
    </div>
    <button v-on:click="handleClick" class="btn btn-primary">
      Press Me
    </button>
  </div>
</template>

<script>
export default {
  name: "app",
  data: function () {
    return {
      name: "Lifejacket",
      price: 275,
      showElements: true
    }
  },

  methods: {
    handleClick() {
      this.showElements = !this.showElements;
    }
  },
}
</script>

关键代码:

<h4 v-if="showElements">{{ price }}</h4>
<button v-on:click="handleClick" class="btn btn-primary">
...
methods: {
    handleClick() {
      this.showElements = !this.showElements;
    }
  }
v-if
v-if2
v-if-else
// src/App.vue
<template>
  <div id="app" class="container-fluid text-center">
    <div class="bg-primary text-white m-2 p-3">
      <h3 v-if="counter % 3 == 0">Product: {{name}}</h3>
      <h3 v-else-if="counter % 3 == 1">Price: {{price}}</h3>
      <h3 v-else>Category: {{category}}</h3>
    </div>
    <button v-on:click="handleClick" class="btn btn-primary">
      Press Me
    </button>
  </div>
</template>

<script>
export default {
  name: "app",
  data: function () {
    return {
      name: "Lifejacket",
      price: 275,
      category: "Waterspots",
      counter: 0
    }
  },

  methods: {
    handleClick() {
      this.counter++;
    }
  },
}
</script>
v-if-else

v-if-else2
设置元素的 Class 属性
v-bind-class

通过 v-bind:class 设置 HTML 元素的 Class 属性:

// src/App.vue
<template>
  <div id="app" class="container-fluid text-center">
    <div class="bg-primary text-white m-2 p-3">
      <h3 v-bind:class="elemClasses">Product: {{name}}</h3>
    </div>
    <button v-on:click="handleClick" class="btn btn-primary">
      Press Me
    </button>
  </div>
</template>

<script>
export default {
  name: "app",
  data: function () {
    return {
      name: "Lifejacket",
      highlight: false
    }
  },
  computed: {
    elemClasses() {
      return this.highlight
        ? ["bg-light", "text-dark", "display-4"]
        : ["bg-dark", "text-light", "p-2"];
    }
  },
  methods: {
    handleClick() {
      this.highlight = !this.highlight;
    }
  },
}
</script>
v-bind-class
v-bind-class2
设置多个属性

v-bind 指示器可以同时设置 HTML 元素的 class、style 等属性,甚至还可以包括原本不存在的由用户自行定义的属性(如下面代码中的 data-size)。

// src/App.vue
<template>
  <div id="app" class="container-fluid text-center">
    <div class="bg-primary text-white m-2 p-3">
      <h3 v-bind="attrValues">Product: {{name}}</h3>
    </div>
    <button v-on:click="handleClick" class="btn btn-primary">
      Press Me
    </button>
  </div>
</template>

<script>
export default {
  name: "app",
  data: function () {
    return {
      name: "Lifejacket",
      highlight: false
    }
  },
  computed: {
    attrValues() {
      return {
        class: this.highlight ? ["bg-light", "text-dark"] : [],
        style: {
          border: this.highlight ? "5px solid red" : ""
        },
        "data-size": this.highlight ? "big" : "small"
      }
    }
  },
  methods: {
    handleClick() {
      this.highlight = !this.highlight;
    }
  },
}
</script>
<style>
  [data-size=big] { font-size: 40pt; }
  [data-size=small] { font-size: 20pt; }
</style>
multiple attributes
multiple attributes2
设置 HTMLElement 属性(不常用)
// src/App.vue
<template>
  <div id="app" class="container-fluid text-center">
    <div class="bg-primary text-white m-2 p-3">
      <h3 v-bind:text-content.prop="textContent"></h3>
    </div>
    <button v-on:click="handleClick" class="btn btn-primary">
      Press Me
    </button>
  </div>
</template>

<script>
export default {
  name: "app",
  data: function () {
    return {
      name: "Lifejacket",
      highlight: false
    }
  },
  computed: {
    textContent() {
      return this.highlight ? "Highlight!" : `Product: ${this.name}`;
    }
  },
  methods: {
    handleClick() {
      this.highlight = !this.highlight;
    }
  },
}
</script>
HTMLElement
HTMLElement2

3. Repeater Directive

Vue.js 中的 v-for 指示器可以用来操作数组格式的数据、生成表格和 Grid 布局等。

遍历数组

v-for 指示器可以遍历数组结构中的数据对象并以循环的方式绑定给多个 HTML 元素。

// src/App.vue
<template>
  <div id="app" class="container-fluid text-center">
    <h2 class="bg-primary text-while text-center p-3">Products</h2>
    <table class="table table-sm table-bordered table-striped text-left">
      <tr><th>Index</th><th>Name</th><th>Price</th></tr>
      <tbody>
        <tr v-for="(p, i) in products" v-bind:key="p.name">
          <td>{{ i + 1 }}</td>
          <td>{{ p.name }}</td>
          <td>{{ p.price }}</td>
        </tr>
      </tbody>
    </table>
    <div>
      <button v-on:click="handleClick" class="btn btn-primary">
        Press Me
      </button>
    </div>
  </div>
</template>

<script>
export default {
  name: "app",
  data: function () {
    return {
      products: [
        { name: "Kayak", price: 275 },
        { name: "Lifejacket", price: 48.95 },
        { name: "Soccer Ball", price: 19.50 },
      ]
    }
  },
  methods: {
    handleClick() {
      this.products.push(this.products.shift());
    }
  },
}
</script>

关键代码:

<tr v-for="(p, i) in products" v-bind:key="p.name">
  <td>{{ i + 1 }}</td>
  <td>{{ p.name }}</td>
  <td>{{ p.price }}</td>
</tr>
v-for
遍历对象

注意此处遍历的数据结构是 JavaScript 对象而不是上面代码中的数组。

// src/App.vue
<template>
  <div id="app" class="container-fluid text-center">
    <h2 class="bg-primary text-while text-center p-3">Products</h2>
    <table class="table table-sm table-bordered table-striped text-left">
      <tr><th>Index</th><th>Key</th><th>Name</th><th>Price</th></tr>
      <tbody>
        <tr v-for="(p, key, i) in products" v-bind:key="p.name">
          <td>{{ i + 1 }}</td>
          <td>{{ key }}</td>
          <td>{{ p.name }}</td>
          <td>{{ p.price }}</td>
        </tr>
      </tbody>
    </table>
    <div>
      <button v-on:click="handleClick" class="btn btn-primary">
        Press Me
      </button>
    </div>
  </div>
</template>

<script>
import Vue from "vue";

export default {
  name: "app",
  data: function () {
    return {
      products: {
        "kayak": { name: "Kayak", price: 275 },
        22: { name: "Lifejacket", price: 48.95 },
        3: { name: "Soccer Ball", price: 19.50 },
        4: { name: "Corner Flags", price: 39.95 }
      }
    }
  },
  methods: {
    handleClick() {
      Vue.set(this.products, 5, { name: "Running Shoes", price: 100 });
    }
  },
}
</script>

PS:更新表格数据应使用 Vue.set(),不要使用类似 this.products[index] = xx 这样的形式。

Vue.set(this.products, 5, { name: "Running Shoes", price: 100 });
object
object2
v-for 与 Computed Properties

v-for 指示器可以搭配计算属性和方法等一起使用,参考(细品)下面的分页示例:

// src/App.vue
<template>
  <div id="app" class="container-fluid text-center">
    <h2 class="bg-primary text-while text-center p-3">Products</h2>
    <table class="table table-sm table-bordered table-striped text-left">
      <tr><th>Name</th><th>Price</th></tr>
      <tbody>
        <tr v-for="p in pageItems" v-bind:key="p.name">
          <td>{{ p.name }}</td>
          <td>{{ p.price }}</td>
        </tr>
      </tbody>
    </table>
    <div>
      <!-- eslint-disable-next-line vue/require-v-for-key -->
      <button v-for="i in pageCount" v-on:click="selectPage(i)"
              class="btn btn-secondary m-1"
              v-bind:class="{'bg-primary': currentPage == i}">
        {{ i }}
      </button>
    </div>
  </div>
</template>

<script>
export default {
  data: function () {
    return {
      pageSize: 3,
      currentPage: 1,
      products: [
        { name: "Kayak", price: 275  },
        { name: "Lifejacket", price: 48.95  },
        { name: "Soccer Ball", price: 19.50  },
        { name: "Corner Flags", price: 39.95  },
        { name: "Stadium", price: 79500  },
        { name: "Thinking Cap", price: 16  },
        { name: "Unsteady Chair", price: 29.95  },
        { name: "Human Chess Board", price: 75  },
        { name: "Bling Bling King", price: 1200  }
      ]
    }
  },
  computed: {
    pageCount() {
      return Math.ceil(this.products.length / this.pageSize);
    },
    pageItems() {
      let start = (this.currentPage - 1) * this.pageSize;
      return this.products.slice(start, start + this.pageSize);
    }
  },
  methods: {
    selectPage(page) {
      this.currentPage = page;
    }
  }
}
</script>
paging
paging2

参考资料

Pro Vue.js 2

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容

  • Vue 实例 属性和方法 每个 Vue 实例都会代理其 data 对象里所有的属性:var data = { a:...
    云之外阅读 2,124评论 0 6
  • 主要还是自己看的,所有内容来自官方文档。 介绍 Vue.js 是什么 Vue (读音 /vjuː/,类似于 vie...
    Leonzai阅读 3,269评论 0 25
  • 一、了解Vue.js 1.1.1 Vue.js是什么? 简单小巧、渐进式、功能强大的技术栈 1.1.2 为什么学习...
    蔡华鹏阅读 3,293评论 0 3
  • 下载安装搭建环境 可以选npm安装,或者简单下载一个开发版的vue.js文件 浏览器打开加载有vue的文档时,控制...
    冥冥2017阅读 5,977评论 0 42
  • 33、JS中的本地存储 把一些信息存储在当前浏览器指定域下的某一个地方(存储到物理硬盘中)1、不能跨浏览器传输:在...
    萌妹撒阅读 2,014评论 0 2