Vue基础学习笔记

102 阅读16分钟

Vue组件实例

1.应用和组件实例

  • 每个Vue应用都由createApp函数创建一个新的应用实例开始的
const app = Vue.createApp({
  /* 选项 */
})

2.组件实例 property

  • data中定义的property是通过组件的实例暴露的:
const app = Vue.createApp({
  data() {
    return { count: 4 }
  }
})

const vm = app.mount('#app')

console.log(vm.count) // => 4

还有各种其他的组件选项,可以将用户定义的 property 添加到组件实例中,例如 methodspropscomputedinject 和 setup。组件实例的所有 property,无论如何定义,都可以在组件的模板中访问。

Vue 还通过组件实例暴露了一些内置 property,如 $attrs 和 $emit。这些 property 都有一个 $ 前缀,以避免与用户定义的 property 名冲突。

3.生命周期钩子

  • 每个组件在被创建的时候都有一系列的初始化过程,这个过程中会运行一些生命周期钩子函数
Vue.createApp({
  data() {
    return { count: 1}
  },
  created() {
    // `this` 指向 vm 实例
    console.log('count is: ' + this.count) // => "count is: 1"
  }
})
  • 生命周期图示

image.png

模板语法

1.插值

  • 文本 数据最常见的形式是使用双大括号的文本插值
<span>Message: {{ msg }}</span>

绑定的组件实例上 msg property 发生了改变,插值处的内容都会更新。

  • 原始HTML 双大括号中的的数据会被解释为普通文本,如果想输出真正的HTML文本,可以使用v-html指令
<p>Using mustaches: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>

2.指令

指令 (Directives) 是带有 v- 前缀的特殊 attribute。指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。比如用 v-if 依据seen的值得真假来插入/移除<p>元素:

<p v-if="seen">现在你看到我了</p>
  • 参数 一些指令能够接收一个“参数”,在指令名称之后以冒号表示。例如,v-bind 指令可以用于响应式地更新 HTML attribute:
<a v-bind:href="url"> ... </a>

-动态参数 也可以在指令参数中使用 JavaScript 表达式,方法是用方括号括起来:

<a v-on:[eventName]="doSomething"> ... </a>

当 eventName 的值为 "focus" 时,v-on:[eventName] 将等价于 v-on:focus

  • 修饰符 修饰符 (modifier) 是以半角句号 . 指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。例如,.prevent 修饰符告诉 v-on 指令对于触发的事件调用 event.preventDefault()
<form v-on:submit.prevent="onSubmit">...</form>

3.缩写

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

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

<!-- 动态参数的缩写 -->
<a :[key]="url"> ... </a>
  • v-on 缩写
<!-- 完整语法 -->
<a v-on:click="doSomething"> ... </a>

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

<!-- 动态参数的缩写 -->
<a @[event]="doSomething"> ... </a>

data 和 methods

1.data

  • data是一个函数,Vue在创建新组件的时候会调用此函数。它应该返回一个对象,然后Vue会把这通过响应式系统将其包裹起来,并以$data的形式存储在组件实例中:
const app = Vue.createApp({
  data() {
    return { count: 4 }
  }
})

const vm = app.mount('#app')

console.log(vm.$data.count) // => 4
console.log(vm.count)       // => 4

// 修改 vm.count 的值也会更新 $data.count
vm.count = 5
console.log(vm.$data.count) // => 5

// 反之亦然
vm.$data.count = 6
console.log(vm.count) // => 6

2.methods

  • 使用methods向组件实例添加方法,是一个包含所需方法的对象:
const app = Vue.createApp({
  data() {
    return { count: 4 }
  },
  methods: {
    increment() {
      // `this` 指向该组件实例
      this.count++
    }
  }
})

const vm = app.mount('#app')

console.log(vm.count) // => 4

vm.increment()

console.log(vm.count) // => 5

Vue 自动为 methods 绑定 this,以便于它始终指向组件实例。在定义 methods 时应避免使用箭头函数,因为这会阻止 Vue 绑定恰当的 this 指向。

计算属性和侦听器

1.计算属性

  • 基本例子 在模板中放入太多的逻辑会让模板过重且难以维护:
Vue.createApp({
  data() {
    return {
      author: {
        name: 'John Doe',
        books: [
          'Vue 2 - Advanced Guide',
          'Vue 3 - Basic Guide',
          'Vue 4 - The Mystery'
        ]
      }
    }
  }
})

根据author是否有些书来显示不同的信息

<div id="computed-basics">
  <p>Has published books:</p>
  <span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span>
</div>

所以对于包含响应式数据的复杂逻辑,我们应该使用计算属性

Vue.createApp({
  data() {
    return {
      author: {
        name: 'John Doe',
        books: [
          'Vue 2 - Advanced Guide',
          'Vue 3 - Basic Guide',
          'Vue 4 - The Mystery'
        ]
      }
    }
  },
  computed: {
    // 计算属性的 getter
    publishedBooksMessage() {
      // `this` 指向 vm 实例
      return this.author.books.length > 0 ? 'Yes' : 'No'
    }
  }
}).mount('#computed-basics')

我们可以像普通属性一样将数据绑定到模板中的计算属性。Vue 知道 vm.publishedBookMessage 依赖于 vm.author.books,因此当 vm.author.books 发生改变时,所有依赖 vm.publishedBookMessage 的绑定也会更新。而且最妙的是我们以声明的方式创建了这个依赖关系:计算属性的 getter 函数没有副作用,它更易于测试和理解。

  • 计算属性缓存 vs 方法 上例中,我们可以调用方法来达到相同的效果:
// 在组件中
methods: {
  calculateBooksMessage() {
    return this.author.books.length > 0 ? 'Yes' : 'No'
  }
}

然而,不同的是计算属性将基于它们的响应依赖关系缓存。计算属性只会在相关响应式依赖发生改变时重新求值。这就意味着只要 author.books 还没有发生改变,多次访问 publishedBookMessage 时计算属性会立即返回之前的计算结果,而不必再次执行函数。相比之下,每当触发重新渲染时,调用方法将始终会再次执行函数。

  • 计算属性的 setter
// ...
computed: {
  fullName: {
    // getter
    get() {
      return this.firstName + ' ' + this.lastName
    },
    // setter
    set(newValue) {
      const names = newValue.split(' ')
      this.firstName = names[0]
      this.lastName = names[names.length - 1]
    }
  }
}

现在再运行 vm.fullName = 'John Doe' 时,setter 会被调用,vm.firstName 和 vm.lastName 也会相应地被更新。

2.侦听器

  • Vue 通过 watch 选项提供了一个更通用的方法来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
watch: {
      // 每当 question 发生变化时,该函数将会执行
      question(newQuestion, oldQuestion) {
        if (newQuestion.indexOf('?') > -1) {
          this.getAnswer()
        }
      }
    },

在这个示例中,使用 watch 选项允许我们执行异步操作 (访问一个 API),并设置一个执行该操作的条件。这些都是计算属性无法做到的。

  • Vue 提供了一种更通用的方式来观察和响应当前活动的实例上的数据变动:侦听属性。当你有一些数据需要随着其它数据变动而变动时,watch 很容易被滥用,通常更好的做法是使用计算属性而不是命令式的 watch 回调。
const vm = Vue.createApp({
  data() {
    return {
      firstName: 'Foo',
      lastName: 'Bar',
      fullName: 'Foo Bar'
    }
  },
  watch: {
    firstName(val) {
      this.fullName = val + ' ' + this.lastName
    },
    lastName(val) {
      this.fullName = this.firstName + ' ' + val
    }
  }
}).mount('#demo')

上面代码是命令式且重复的。将它与计算属性的版本进行比较:

const vm = Vue.createApp({
  data() {
    return {
      firstName: 'Foo',
      lastName: 'Bar'
    }
  },
  computed: {
    fullName() {
      return this.firstName + ' ' + this.lastName
    }
  }
}).mount('#demo')

很明显计算属性的版本更好。

class 与 style 绑定

1.绑定 HTML Class

  • 对象语法 我们可以传给 :class (v-bind:class 的简写) 一个对象,以动态地切换 class:
<div :class="{ active: isActive }"></div>

上面的语法表示active这个class的存在与否取决于isActive的true或者false。 我们还可以在对象中传入更多的字段来动态切换多个class,:class 指令也可以与普通的 class共存:

<div
  class="static"
  :class="{ active: isActive, 'text-danger': hasError }"
></div>
data() {
  return {
    isActive: true,
    hasError: false
  }
}

则渲染的结果为:

<div class="static active"></div>

当 isActive 或者 hasError 变化时,class 列表将相应地更新。例如,如果 hasError 的值为 true,class 列表将变为 "static active text-danger"。 还可以这样:

<div :class="classObject"></div>
data() {
  return {
    classObject: {
      active: true,
      'text-danger': false
    }
  }
}

此外,还可以绑定一个返回对象的计算属性:

<div :class="classObject"></div>
data() {
  return {
    isActive: true,
    error: null
  }
},
computed: {
  classObject() {
    return {
      active: this.isActive && !this.error,
      'text-danger': this.error && this.error.type === 'fatal'
    }
  }
}
  • 数组语法 我们可以把一个数组传给 :class,以应用一个 class 列表:
<div :class="[activeClass, errorClass]"></div>
data() {
  return {
    activeClass: 'active',
    errorClass: 'text-danger'
  }
}

渲染的结果为:

<div class="active text-danger"></div>

如果想根据条件切换列表中的class,可以使用三元表达式:

<div :class="[isActive ? activeClass : '', errorClass]"></div>

这样写将始终添加errorClass,但是只有在 isActive 为 truthy 时才添加 activeClass。 当有多个条件 class 时这样写有些繁琐。所以在数组语法中也可以使用对象语法:

<div :class="[{ active: isActive }, errorClass]"></div>
  • 在组件上使用 当你在带有单个根元素的自定义组件上使用 class attribute 时,这些 class 将被添加到该元素中。此元素上的现有 class 将不会被覆盖。
const app = Vue.createApp({})

app.component('my-component', {
  template: `<p class="foo bar">Hi!</p>`
})

然后在使用它的时候添加一些class:

<div id="app">
  <my-component class="baz boo"></my-component>
</div>

此时HTML将被渲染为:

<p class="foo bar baz boo">Hi</p>

对于带数据绑定 class 也同样适用:

<my-component :class="{ active: isActive }"></my-component>

当 isActive 为 true 的时候:

<p class="foo bar active">Hi</p>

2.绑定内联样式

  • 对象语法
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
data() {
  return {
    activeColor: 'red',
    fontSize: 30
  }
}

直接绑定到一个样式对象通常更好,这会让模板更清晰:

<div :style="styleObject"></div>
data() {
  return {
    styleObject: {
      color: 'red',
      fontSize: '13px'
    }
  }
}
  • 数组语法 :style 的数组语法可以将多个样式对象应用到同一个元素上:
<div :style="[baseStyles, overridingStyles]"></div>
  • 多重值 可以为 style 绑定中的 property 提供一个包含多个值的数组,常用于提供多个带前缀的值,例如:
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>

这样写只会渲染数组中最后一个被浏览器支持的值。在本例中,如果浏览器支持不带浏览器前缀的 flexbox,那么就只会渲染 display: flex

条件渲染

1.v-if

  • v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 truthy 值的时候被渲染。
<h1 v-if="awesome">Vue is awesome!</h1>

2.v-show

  • 另一个用于条件性展示元素的选项是 v-show 指令。用法大致一样:
<h1 v-show="ok">Hello!</h1>

不同的是带有 v-show 的元素始终会被渲染并保留在 DOM 中。v-show 只是简单地切换元素的 display CSS property。 注意,v-show 不支持 <template> 元素,也不支持 v-else

  • v-if vs v-show

v-if是真正的条件渲染,因为会在切换过程中,条件块内的事件监听器和子组件适当的被销毁和重建。 v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。

相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。

一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。

列表渲染

1.v-for把数组映射为元素

  • 我们可以用v-for指令将数组渲染成列表。v-for 指令需要使用 item in items 形式的特殊语法,其中 items 是源数据数组,而 item 则是被迭代的数组元素的别名
<ul id="array-rendering">
  <li v-for="item in items">
    {{ item.message }}
  </li>
</ul>
Vue.createApp({
  data() {
    return {
      items: [{ message: 'Foo' }, { message: 'Bar' }]
    }
  }
}).mount('#array-rendering')

在 v-for 块中,我们可以访问所有父作用域的 property。v-for 还支持一个可选的第二个参数,即当前项的索引。

<ul id="array-with-index">
  <li v-for="(item, index) in items">
    {{ parentMessage }} - {{ index }} - {{ item.message }}
  </li>
</ul>
Vue.createApp({
  data() {
    return {
      parentMessage: 'Parent',
      items: [{ message: 'Foo' }, { message: 'Bar' }]
    }
  }
}).mount('#array-with-index')

结果:

image.png

2.在 v-for 里使用对象

  • 可以用 v-for 来遍历一个对象的 property。
<ul id="v-for-object" class="demo">
  <li v-for="value in myObject">
    {{ value }}
  </li>
</ul>
Vue.createApp({
  data() {
    return {
      myObject: {
        title: 'How to do lists in Vue',
        author: 'Jane Doe',
        publishedAt: '2016-04-10'
      }
    }
  }
}).mount('#v-for-object')
  • 也可以提供第二个的参数为 property 名称 (也就是键名 key):
<li v-for="(value, name) in myObject">
  {{ name }}: {{ value }}
</li>
  • 还可以用第三个参数作为索引:
<li v-for="(value, name, index) in myObject">
  {{ index }}. {{ name }}: {{ value }}
</li>

提示:在遍历对象时,会按 Object.keys() 的结果遍历

3.维护状态

当 Vue 正在更新使用 v-for 渲染的元素列表时,它默认使用“就地更新”的策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。

这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出。 为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一的 key attribute:

<div v-for="item in items" :key="item.id">
  <!-- 内容 -->
</div>

建议尽可能在使用 v-for 时提供 key attribute,除非遍历输出的 DOM 内容非常简单。

4.数组更新检测

  • 变更方法 Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:

    push()

    pop()

    shift()

    unshift()

    splice()

    sort()

    reverse()

  • 替换数组 变更方法,顾名思义,会变更调用了这些方法的原始数组。相比之下,也有非变更方法,例如 filter()concat() 和 slice()。它们不会变更原始数组,而总是返回一个新数组。当使用非变更方法时,可以用新数组替换旧数组:Vue 为了使得 DOM 元素得到最大范围的重用而实现了一些智能的启发式方法,所以用一个含有相同元素的数组去替换原来的数组是非常高效的操作。

5.显示过滤/排序后的结果

有时,我们想要显示一个数组经过过滤或排序后的版本,而不实际变更或重置原始数据。在这种情况下,可以创建一个计算属性,来返回过滤或排序后的数组。

<li v-for="n in evenNumbers" :key="n">{{ n }}</li>
data() {
  return {
    numbers: [ 1, 2, 3, 4, 5 ]
  }
},
computed: {
  evenNumbers() {
    return this.numbers.filter(number => number % 2 === 0)
  }
}

在计算属性不适用的情况下 (例如,在嵌套的 v-for 循环中) 你可以使用一个方法:

<ul v-for="numbers in sets">
  <li v-for="n in even(numbers)" :key="n">{{ n }}</li>
</ul>
data() {
  return {
    sets: [[ 1, 2, 3, 4, 5 ], [6, 7, 8, 9, 10]]
  }
},
methods: {
  even(numbers) {
    return numbers.filter(number => number % 2 === 0)
  }
}

6.在组件上使用v-for

在自定义的组件上,可以像再正常元素上使用v-for

<my-component v-for="item in items" :key="item.id"></my-component>

但是,任何数据都不会被自动传递到组件里。因为组件有自己独立的作用域,为了传递数据到组件里,我们需要用props:

<my-component
  v-for="(item, index) in items"
  :item="item"
  :index="index"
  :key="item.id"
></my-component>

事件处理

1.监听事件

  • 我们可以使用 v-on 指令 (通常缩写为 @ 符号) 来监听 DOM 事件,并在触发事件时执行一些 JavaScript。用法为 v-on:click="methodName" 或使用快捷方式 @click="methodName"
<div id="basic-event">
  <button @click="counter += 1">Add 1</button>
  <p>The button above has been clicked {{ counter }} times.</p>
</div>

2.事件处理方法

  • v-on 还可以接收一个需要调用的方法名称。
<div id="event-with-method">
  <!-- `greet` 是在下面定义的方法名 -->
  <button @click="greet">Greet</button>
</div>
Vue.createApp({
  data() {
    return {
      name: 'Vue.js'
    }
  },
  methods: {
    greet(event) {
      // `methods` 内部的 `this` 指向当前活动实例
      alert('Hello ' + this.name + '!')
      // `event` 是原生 DOM event
      if (event) {
        alert(event.target.tagName)
      }
    }
  }
}).mount('#event-with-method')

3.内联处理器中的方法

除了直接绑定到一个方法,也可以在内联 JavaScript 语句中调用方法:除了直接绑定到一个方法,也可以在内联 JavaScript 语句中调用方法:

<div id="inline-handler">
  <button @click="say('hi')">Say hi</button>
  <button @click="say('what')">Say what</button>
</div>
Vue.createApp({
  methods: {
    say(message) {
      alert(message)
    }
  }
}).mount('#inline-handler')

有时也需要在内联语句处理器中访问原始的 DOM 事件。可以用特殊变量 $event 把它传入方法:

<button @click="warn('Form cannot be submitted yet.', $event)">
  Submit
</button>
// ...
methods: {
  warn(message, event) {
    // 现在可以访问到原生事件
    if (event) {
      event.preventDefault()
    }
    alert(message)
  }
}

4.事件修饰符

  • 修饰符是由点开头的指令后缀来表示的。
<!-- 阻止单击事件继续冒泡 -->
<a @click.stop="doThis"></a>

<!-- 提交事件不再重载页面 -->
<form @submit.prevent="onSubmit"></form>

<!-- 修饰符可以串联 -->
<a @click.stop.prevent="doThat"></a>

<!-- 只有修饰符 -->
<form @submit.prevent></form>

<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 -->
<div @click.capture="doThis">...</div>

<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div @click.self="doThat">...</div>

tip:使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 @click.prevent.self 会阻止元素本身及其子元素的点击的默认行为,而 @click.self.prevent 只会阻止对元素自身的点击的默认行为。

表单输入绑定

1.基础用法

  • 可以用 v-model 指令在表单<input><textarea> 及 <select> 元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。但 v-model 本质上不过是语法糖。它负责监听用户的输入事件来更新数据,并在某种极端场景下进行一些特殊处理。

    v-model 会忽略所有表单元素的 valuecheckedselected attribute 的初始值。它将始终将当前活动实例的数据作为数据来源。应该通过 JavaScript 在组件的 data 选项中声明初始值。

    v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:

    • text 和 textarea 元素使用 value property 和 input 事件;
    • checkbox 和 radio 使用 checked property 和 change 事件;
    • select 字段将 value 作为 prop 并将 change 作为事件。

文本 (Text)

<input v-model="message" placeholder="edit me" />
<p>Message is: {{ message }}</p>

多行文本 (Textarea)

插值在 textarea 中不起作用,请使用 v-model 来代替。

<!-- bad -->
<textarea>{{ text }}</textarea>

<!-- good -->
<textarea v-model="text"></textarea>

复选框 (Checkbox)

<div id="v-model-multiple-checkboxes">
  <input type="checkbox" id="jack" value="Jack" v-model="checkedNames" />
  <label for="jack">Jack</label>
  <input type="checkbox" id="john" value="John" v-model="checkedNames" />
  <label for="john">John</label>
  <input type="checkbox" id="mike" value="Mike" v-model="checkedNames" />
  <label for="mike">Mike</label>
  <br />
  <span>Checked names: {{ checkedNames }}</span>
</div>

单选框 (Radio)

<div id="v-model-radiobutton">
  <input type="radio" id="one" value="One" v-model="picked" />
  <label for="one">One</label>
  <br />
  <input type="radio" id="two" value="Two" v-model="picked" />
  <label for="two">Two</label>
  <br />
  <span>Picked: {{ picked }}</span>
</div>

选择框 (Select)

单选:

<div id="v-model-select" class="demo">
  <select v-model="selected">
    <option disabled value="">Please select one</option>
    <option>A</option>
    <option>B</option>
    <option>C</option>
  </select>
  <span>Selected: {{ selected }}</span>
</div>

如果 v-model 表达式的初始值未能匹配任何选项,<select> 元素将被渲染为“未选中”状态。在 iOS 中,这会使用户无法选择第一个选项。因为这样的情况下,iOS 不会触发 change 事件。因此,更推荐像上面这样提供一个值为空的禁用选项。

多选 (绑定到一个数组):

<select v-model="selected" multiple>
  <option>A</option>
  <option>B</option>
  <option>C</option>
</select>
<br />
<span>Selected: {{ selected }}</span>

2.值绑定

单选按钮

<!-- 当选中时,`picked` 为字符串 "a" -->
<input type="radio" v-model="picked" value="a" />

<!-- `toggle` 为 true 或 false -->
<input type="checkbox" v-model="toggle" />

<!-- 当选中第一个选项时,`selected` 为字符串 "abc" -->
<select v-model="selected">
  <option value="abc">ABC</option>
</select>

复选框 (Checkbox)

<input type="checkbox" v-model="toggle" true-value="yes" false-value="no" />
// 当选中时:
vm.toggle === 'yes'
// 当未选中时:
vm.toggle === 'no'

单选框 (Radio)

<input type="radio" v-model="pick" v-bind:value="a" />
// 当选中时
vm.pick === vm.a

选择框选项 (Select Options)

<select v-model="selected">
  <!-- 内联对象字面量 -->
  <option :value="{ number: 123 }">123</option>
</select>
// 当选中时
typeof vm.selected // => 'object'
vm.selected.number // => 123

3.修饰符

.lazy

在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步(除了输入法组织文字时)。你可以添加 lazy 修饰符,从而转为在 change 事件之后进行同步:

<!-- 在“change”时而非“input”时更新 -->
<input v-model.lazy="msg" />

.number

如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符:

<input v-model.number="age" type="text" />

当输入类型为 text 时这通常很有用。如果输入类型是 number,Vue 能够自动将原始字符串转换为数字,无需为 v-model 添加 .number 修饰符。如果这个值无法被 parseFloat() 解析,则返回原始的值。

.trim

如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符

<input v-model.trim="msg" />

组件基础

1.基本示例

// 定义一个名为 button-counter 的新全局组件
app.component('button-counter', {
  data() {
    return {
      count: 0
    }
  },
  template: `
    <button @click="count++">
      You clicked me {{ count }} times.
    </button>`
})
<div id="components-demo">
  <button-counter></button-counter>
</div>

因为组件是可复用的实例,所以它们与根实例接收相同的选项,例如 datacomputedwatchmethods 以及生命周期钩子等。

2.组件的复用

可以将组件进行任意次数的复用:

<div id="components-demo">
  <button-counter></button-counter>
  <button-counter></button-counter>
  <button-counter></button-counter>
</div>

注意当点击按钮时,每个组件都会各自独立维护它的 count。因为你每用一次组件,就会有一个它的新实例被创建。

3.通过Prop向子组件传递数据

Prop 是你可以在组件上注册的一些自定义 attribute。为了给博文组件传递一个标题,我们可以用 props 选项将其包含在该组件可接受的 prop 列表中:

app.component('blog-post', {
  props: ['title'],
  template: `<h4>{{ title }}</h4>`
})

一个组件可以拥有任意数量的 prop,并且在默认情况下,无论任何值都可以传递给 prop。

<div id="blog-post-demo" class="demo">
  <blog-post title="My journey with Vue"></blog-post>
  <blog-post title="Blogging with Vue"></blog-post>
  <blog-post title="Why Vue is so fun"></blog-post>
</div>

然而在一个典型的应用中,可能在 data 里有一个博文的数组:

const App = {
  data() {
    return {
      posts: [
        { id: 1, title: 'My journey with Vue' },
        { id: 2, title: 'Blogging with Vue' },
        { id: 3, title: 'Why Vue is so fun' }
      ]
    }
  }
}

const app = Vue.createApp(App)

app.component('blog-post', {
  props: ['title'],
  template: `<h4>{{ title }}</h4>`
})

并想要为每篇博文渲染一个组件:

<div id="blog-posts-demo">
  <blog-post
    v-for="post in posts"
    :key="post.id"
    :title="post.title"
  ></blog-post>
</div>

3.监听子组件事件

在我们开发 <blog-post> 组件时,它的一些功能可能要求我们和父级组件进行沟通。例如我们可能会引入一个辅助功能来放大博文的字号,同时让页面的其它部分保持默认的字号。 在其父组件中,我们可以通过添加一个 postFontSize 数据 property 来支持这个功能:

new Vue({
  el:'#blog-posts-events-demo',
  data:{
    posts:[/*...*/],
    postFontSize: 1
  }
})

它可以在模板中用来控制所有博文的字号:

<div id="blog-posts-events-demo">
  <div :style="{ fontSize: postFontSize + 'em' }">
    <blog-post
      v-for="post in posts"
      v-bind:key="post.id"
      v-bind:post="post"
    ></blog-post>
  </div>
</div>

现在我们在每篇博文正文之前添加一个按钮来放大字号:

  props: ['post'],
  template: `
    <div class="blog-post">
      <h3>{{ post.title }}</h3>
      <button>
        Enlarge text
      </button>
      <div v-html="post.content"></div>
    </div>
  `
})

此时这个按钮还不会做任何事情,当我们点击这个按钮的时候,我们需要告诉父组件放大所有博文的字号。Vue实例提供了一个自定义事件的系统来解决这个问题。父级组件可以像处理native DOM 事件一样通过v-on监听子组件实例的任意事件:


<blog-post
  ...
  v-on:enlarge-text="postFontSize += 0.1"
></blog-post>

与此同时,子组件可以通过调用内建的$emit方法并传入事件名来触发一个事件:

<button v-on:click = "$emit('enlarge-text')">
  Enlarge text
</button>

有了这个v-on:enlarge-text="postFontSize += 0.1" 监听器,父级组件就会接受该事件并更新postFontSize 的值

使用事件抛出一个值

有的时候用一个事件来抛出一个特定的值是非常有用的。例如我们可能想让 <blog-post> 组件决定它的文本要放大多少。这时可以使用 $emit 的第二个参数来提供这个值:

<button v-on:click="$emit('enlarge-text', 0.1)">
  Enlarge text
</button>

当父级组件监听这个事件的时候,我们可以通过$event访问到被抛出的这个值:

<blog-post
  ...
  v-on:enlarge-text="postFontSize += $event"
></blog-post>

还有一种情况,如果这个事件处理函数是一个方法:

<blog-post
  ...
  v-on:enlarge-text="onEnlargeText"
></blog-post>

那么这个值将会作为第一个参数传入这个方法:

methods: {
  onEnlargeText: function (enlargeAmount) {
    this.postFontSize += enlargeAmount
  }
}

在组件上使用v-model

自定义事件也可以用于创建支持v-model的自定义输入组件。可以记住:

<input v-model="searchText"

以上等价于:

<input
  v-bind:value="searchText"
  v-on:input="searchText = $event.target.value"
>

当用于组件上的时候,v-model 则会这样:

<custom-input
  v-bind:value="searchText"
  v-on:input="searchText = $event"
></custom-input>

而为了让其正常工作,这个组件内的 <input> 必须:

  • 将其 value attribute 绑定到一个名叫 value 的 prop 上
  • 在其 input 事件被触发时,将新的值通过自定义的 input 事件抛出 写成代码之后:
Vue.component('custom-input', {
  props: ['value'],
  template: `
    <input
      v-bind:value="value"
      v-on:input="$emit('input', $event.target.value)"
    >
  `
})

现在 v-model 就应该可以在这个组件上完美地工作起来了:

<custom-input v-model="searchText"></custom-input>

通过插槽分享内容

我们经常需要向一个组件传递内容:

<alert-box>
  Something bad happened.
</alert-box>

而我们想渲染的东西:

image.png

Vue自定义的<slot> 元素让这变得非常简单:

Vue.component('alert-box', {
  template: `
    <div class="demo-alert-box">
      <strong>Error!</strong>
      <slot></slot>
    </div>
  `
})

动态组件

有时候,不同的组件之间进行动态切换是非常有用的,比如常见的tabs切换,而这些可以通过Vue的<component> 元素加一个特殊的 is attribute 来实现:

<!-- 组件会在 `currentTabComponent` 改变时改变 -->
<component v-bind:is="currentTabComponent"></component>