学Vue3这一篇就够了!

news2024/10/5 16:25:43

目录

  • 学习Vue的前提是掌握 HTML,CSS,Js中级知识
  • vue介绍
    • 声明式渲染
    • 条件与循环
    • 处理用户输入
    • 组件化应用构建
      • Vue与自定义元素的关系
  • 应用和组件实例
    • Vue实例
    • 根组件
    • 组件实例 property
    • 生命周期钩子
    • 实例的生命周期图
  • 模板语法
    • 插值
      • 文本
      • 原始 HTML
      • Attribute
      • 使用 JavaScript 表达式
    • 指令
      • 参数
      • 动态参数
      • 修饰符
    • 缩写
      • v-bind 缩写
      • v-on 缩写
  • Data Property 和方法
    • Data Property
  • 方法
      • 防抖和节流
  • 计算属性和侦听器
    • 计算属性
      • 计算属性缓存 vs 方法
    • 计算属性的 Setter
    • 侦听器
      • 计算属性 vs 侦听器
  • Class 与 Style 绑定
    • 绑定 HTML Class
      • 对象语法
      • 数组语法
      • 在组件上使用
    • 绑定内联样式
      • 对象语法
      • 数组语法
      • 多重值
  • 条件渲染
    • v-if
    • v-show
    • v-if vs v-show
  • 列表渲染
    • v-for
    • 维护状态
    • 显示过滤/排序后的结果
  • 事件处理
    • 多事件处理器
    • 按键修饰符
      • .exact 修饰符
      • 鼠标按钮修饰符
  • 表单输入绑定
    • 复选框 (Checkbox)
      • 单选框(radio)
  • 值绑定
    • 修饰符
      • .lazy
      • .number
      • .trim
  • 组件基础
    • 通过 Prop 向子组件传递数据
      • 在组件上使用 v-model
    • 通过插槽分发内容
    • 动态组件
  • 解析 DOM 模板时的注意事项
    • 单文件组件
  • 深入组件
    • 组件名
      • 组件名大小写
    • 全局注册
    • 局部注册
  • Prop 类型
    • 传递静态或动态的 Prop
    • 单向数据流
    • Prop 验证
    • Prop 的大小写命名 (camelCase vs kebab-case)
  • 非 Prop 的 Attribute
    • Attribute 继承
    • 禁用 Attribute 继承
    • 多个根节点上的 Attribute 继承
  • 自定义事件
    • 定义自定义事件
      • 验证抛出的事件
  • v-model 参数
    • 功能快捷

学习Vue的前提是掌握 HTML,CSS,Js中级知识

vue介绍

Vue是一套用于构建用户界面的渐进式框架
(渐进式框架允许用最小的配置和功能逐渐添加更多的功能和插件)

声明式渲染

Vue的核心是一个允许采用简洁的模板语法来声明式的将数据渲染进DOM的系统

//test.html
<div id="app">
  {{ message }}
</div>

//test.js
var app = new Vue({
    el: '#app',
    data: {
      message: 'Hello Vue!'
    }
  })
  //现在数据和 DOM 已经被建立了关联,所有东西都是响应式的。
  //打开你的浏览器的 JavaScript 控制台,并修改 app.message 的值,你将看到上例相应地更新
//test.html
<div id="app-2">
  <span v-bind:title="message">
    鼠标悬停几秒钟查看此处动态绑定的提示信息!
  </span>
</div>
//test.js
var app2 = new Vue({
    el: '#app-2',
    data: {
      message: '页面加载于 ' + new Date().toLocaleString()
    }
  })
//   v-bind 被称为指令,指令带有前缀 v-,以表示它们是 Vue 提供的特殊 attribute(属性
//   它们会在渲染的 DOM 上应用特殊的响应式行为,在这里,该指令的意思是:“将这个元素节点的 title attribute 和 Vue 实例的 message property 保持一致”。
//   再次打开浏览器的 JavaScript 控制台,输入 app2.message = '新消息',就会再一次看到这个绑定了 title attribute 的 HTML 已经进行了更新

条件与循环

//test.html
<div id="app-3">
  <p v-if="seen">现在你看到我了</p>
</div>
//test.js
var app3 = new Vue({
    el: '#app-3',
    data: {
      seen: true
    }
  })
//在控制台输入 app3.seen = false,你会发现之前显示的消息消失了
//test.html
<div id="app-4">
  <ol>
    <li v-for="todo in todos">
      {{ todo.text }}
    </li>
  </ol>
</div>
//test.js
var app4 = new Vue({
    el: '#app-4',
    data: {
      todos: [
        { text: '学习 JavaScript' },
        { text: '学习 Vue' },
        { text: '整个牛项目' }
      ]
    }
  })//在控制台里,输入 app4.todos.push({ text: '新项目' }),你会发现列表最后添加了一个新项目。

v-for 指令可以绑定数组的数据来渲染一个项目列表

处理用户输入

可以用 v-on 指令添加一个事件监听器,通过它调用在 Vue 实例中定义的方法

//test.html
<div id="app-5">
  <p>{{ message }}</p>
  <button v-on:click="reverseMessage">反转消息</button>
</div>
//test.js
var app5 = new Vue({
    el: '#app-5',
    data: {
      message: 'Hello Vue.js!'
    },
    methods: {
      reverseMessage: function () {
        this.message = this.message.split('').reverse().join('')
      }
    }
  })//在 reverseMessage 方法中,我们更新了应用的状态,但没有触碰 DOM——所有的 DOM 操作都由 Vue 来处理,你编写的代码只需要关注逻辑层面即可

Vue 还提供了 v-model 指令,它能轻松实现表单输入和应用状态之间的双向绑定。

//test.html
<div id="app-6">
  <p>{{ message }}</p>
  <input v-model="message"></input>
</div>
//test.js
var app6 = new Vue({
  el: '#app-6',
  data: {
    message: 'Hello Vue!'
  }
})

组件化应用构建

组件系统是 Vue 的另一个重要概念,因为它是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用。几乎任意类型的应用界面都可以抽象为一个组件树
在 Vue 里,一个组件本质上是一个拥有预定义选项的一个 Vue 实例。

// 定义名为 todo-item 的新组件
Vue.component('todo-item', {
  template: '<li>这是个待办项</li>'
})

var app = new Vue(...)
//现在你可以用它构建另一个组件模板:
<ol>
  <!-- 创建一个 todo-item 组件的实例 -->
  <todo-item></todo-item>
</ol>
但是这样会为每个待办项渲染同样的文本,
我们应该能从父作用域将数据传到子组件才对。
可以修改一下组件的定义,使之能够接受一个 prop:
Vue.component('todo-item', {
  // todo-item 组件现在接受一个
  // "prop",类似于一个自定义 attribute。
  // 这个 prop 名为 todo。
  props: ['todo'],
  template: '<li>{{ todo.text }}</li>'
})


可以使用 v-bind 指令将待办项传到循环输出的每个组件中:

//test.html
<div id="app-7">
  <ol>
    <!--
      现在我们为每个 todo-item 提供 todo 对象
      todo 对象是变量,即其内容可以是动态的。
      我们也需要为每个组件提供一个“key”,稍后再
      作详细解释。
    -->
    <todo-item
      v-for="item in groceryList"
      v-bind:todo="item"
      v-bind:key="item.id"
    ></todo-item>
  </ol>
</div>
//test.js
Vue.component('todo-item', {
    props: ['todo'],
    template: '<li>{{ todo.text }}</li>'
  })
  
  var app7 = new Vue({
    el: '#app-7',
    data: {
      groceryList: [
        { id: 0, text: '猫' },
        { id: 1, text: '狗' },
        { id: 2, text: '为什么不能狗在前面' }
      ]
    }
  })

在一个大型应用中,有必要将整个应用程序划分为组件,以使开发更易管理。在后续教程中我们将详述组件,不过这里有一个 (假想的) 例子,以展示使用了组件的应用模板是什么样的:

<div id="app">
  <app-nav></app-nav>
  <app-view>
    <app-sidebar></app-sidebar>
    <app-content></app-content>
  </app-view>
</div>

Vue与自定义元素的关系

Vue 组件非常类似于自定义元素——它是 Web 组件规范的一部分,这是因为 Vue 的组件语法部分参考了该规范。例如 Vue 组件实现了 Slot API 与 is attribute.
虽然 Vue 内部没有使用自定义元素,不过在应用使用自定义元素、或以自定义元素形式发布时,依然有很好的互操作性。Vue CLI 也支持将 Vue 组件构建成为原生的自定义元素。

应用和组件实例

Vue实例

每个 Vue 应用都是通过用 createApp 函数创建一个新的应用实例开始的:

const app = Vue.createApp({ /* 选项 */ })
app.component('SearchInput', SearchInputComponent)
app.directive('focus', FocusDirective)
app.use(LocalePlugin)
该应用实例是用来在应用中注册“全局”组件的。
我们将在后面的指南中详细讨论,

应用实例暴露的大多数方法都会返回该同一实例,允许链式

Vue.createApp({})
  .component('SearchInput', SearchInputComponent)
  .directive('focus', FocusDirective)
  .use(LocalePlugin)

根组件

传递给 createApp 的选项用于配置根组件。当我们挂载应用时,该组件被用作渲染的起点

一个应用需要被挂载到一个 DOM 元素中。例如,如果我们想把一个 Vue 应用挂载到 <div id="app"></div>
,我们应该传递 #app:
const RootComponent = { /* 选项 */ }
const app = Vue.createApp(RootComponent)
const vm = app.mount('#app')
与大多数应用方法不同的是,mount 不返回应用本身。
相反,它返回的是根组件实例

虽然没有完全遵循 MVVM 模型 (opens new window),但是 Vue 的设计也受到了它的启发。因此在文档中经常会使用 vm (ViewModel 的缩写) 这个变量名表示组件实例。
尽管本页面上的所有示例都只需要一个单一的组件就可以,但是大多数的真实应用都是被组织成一个嵌套的、可重用的组件树。举个例子,一个 todo 应用组件树可能是这样的:
在这里插入图片描述每个组件将有自己的组件实例 vm。对于一些组件,如 TodoItem,在任何时候都可能有多个实例渲染。这个应用中的所有组件实例将共享同一个应用实例。
现在,你只需要明白根组件与其他组件没什么不同,配置选项是一样的,所对应的组件实例行为也是一样的

组件实例 property

在 data 中定义的 property 是通过组件实例暴露的:

const app = Vue.createApp({
  data() {
    return { count: 4 }
  }
})

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

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

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

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

生命周期钩子

每个组件在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。

比如 created 钩子可以用来在一个实例被创建之后执行代码:

Vue.createApp({
  data() {
    return { count: 1}
  },
  created() {
    // `this` 指向 vm 实例
    console.log('count is: ' + this.count) // => "count is: 1"
  }
})

也有一些其它的钩子,在实例生命周期的不同阶段被调用,如 mounted、updated 和 unmounted。生命周期钩子的 this 上下文指向调用它的当前活动实例。
不要在选项 property 或回调上使用箭头函数,比如 created: () => console.log(this.a) 或 vm.$watch(‘a’, newValue => this.myMethod())。因为箭头函数并没有 this,this 会作为变量一直向上级词法作用域查找,直至找到为止,经常导致 Uncaught TypeError: Cannot read property of undefined 或 Uncaught TypeError: this.myMethod is not a function 之类的错误。

实例的生命周期图

不需要立马弄明白所有的东西,不过随着你的不断学习和使用,它的参考价值会越来越高。
在这里插入图片描述

模板语法

Vue.js 使用了基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层组件实例的数据。所有 Vue.js 的模板都是合法的 HTML,所以能被遵循规范的浏览器和 HTML 解析器解析。
在底层的实现上,Vue 将模板编译成虚拟 DOM 渲染函数。结合响应性系统,Vue 能够智能地计算出最少需要重新渲染多少组件,并把 DOM 操作次数减到最少。

如果你熟悉虚拟 DOM 并且偏爱 JavaScript 的原始力量,你也可以不用模板,直接写渲染 (render) 函数,使用可选的 JSX 语法。

插值

文本

数据绑定最常见的形式就是使用“Mustache”(双大括号) 语法的文本插值:

<span>Message: {{ msg }}</span>
无论何时,绑定的组件实例上 msg 属性发生了改变,插值处的内容都会更新。

原始 HTML

双大括号会将数据解释为普通文本,而非 HTML 代码。为了输出真正的 HTML,你需要使用v-html 指令:
<p>Using mustaches: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>

这个 span 的内容将会被替换成为 property 值 rawHtml,直接作为 HTML——会忽略解析 property 值中的数据绑定。注意,你不能使用 v-html 来复合局部模板,因为 Vue 不是基于字符串的模板引擎。反之,对于用户界面 (UI),组件更适合作为可重用和可组合的基本单位。

在你的站点上动态渲染任意的 HTML 是非常危险的,因为它很容易导致 XSS 攻击 (opens new window)。请只对可信内容使用 HTML 插值,绝不要将用户提供的内容作为插值。

Attribute

Mustache 语法不能在 HTML attribute 中使用 ,然而,可以使用 v-bind 指令:

<div v-bind:id="dynamicId"></div>

对于布尔 attribute (它们只要存在就意味着值为 true),v-bind 工作起来略有不同,

<button v-bind:disabled="isButtonDisabled">按钮</button>
如果 isButtonDisabled 的值是 nullundefined,则 disabled attribute 甚至不会被包含在渲染出来的 <button> 元素中

使用 JavaScript 表达式

对于所有的数据绑定,Vue.js 都提供了完全的 JavaScript 表达式支持。每个绑定都只能包含单个表达式

指令

指令 (Directives) 是带有 v- 前缀的特殊 attribute。指令 attribute 的值预期是单个 JavaScript 表达式 (v-for 和 v-on 是例外情况,稍后我们再讨论)。指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。回顾我们在介绍中看到的例子:

<p v-if="seen">现在你看到我了</p>
v-if 指令将根据表达式 seen 的值的真假来插入/移除 <p> 元素。

参数

一些指令能够接收一个“参数”,在指令名称之后以冒号表示。例如,v-bind 指令可以用于响应式地更新 HTML

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

v-on 指令,它用于监听 DOM 事件:

<a v-on:click="doSomething"> ... </a>
这里参数是监听的事件名。我们也会更详细地讨论事件处理

动态参数

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

<a v-bind:[attributeName]="url"> ... </a>
这里的 attributeName 会被作为一个 JavaScript 表达式进行动态求值,求得的值将会作为最终的参数来使用。例如,如果你的组件实例有一个 data property attributeName,其值为 "href",那么这个绑定将等价于 v-bind:href。

还可以使用动态参数为一个动态的事件名绑定处理函数:

<a v-on:[eventName]="doSomething"> ... </a>
当 eventName 的值为 "focus" 时,v-on:[eventName] 将等价于 v-on:focus

修饰符

修饰符 (modifier) 是以半角句号.指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。

<form v-on:submit.prevent="onSubmit">...</form>
.prevent 修饰符告诉 v-on 指令对于触发的事件调用 event.preventDefault()

缩写

v-bind: (属性绑定)简写为 :
v-on : (事件绑定)简写为 @
v-model(双向绑定)

v-bind 缩写

v-bind的基本用法是动态更新HTML元素上的属性,如id,class,href,src等。

<!-- 完整语法 -->
<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>

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

: 与 @ 对于 attribute 名来说都是合法字符,在所有支持 Vue 的浏览器都能被正确地解析。而且,它们不会出现在最终渲染的标记中。缩写语法是完全可选的

之后将在示例中使用缩写,因为这是 Vue 开发者最常用的用法。

Data Property 和方法

Data Property

组件的 data 选项是一个函数。它应该返回一个对象,然后 Vue 会通过响应性系统将其包裹起来,并以 $data 的形式存储在组件实例中。该对象的任何顶级 property 也直接通过组件实例暴露出来:

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

这些实例 property 仅在实例首次创建时被添加,所以你需要确保它们都在 data 函数返回的对象中。必要时,要对尚未提供所需值的 property 使用 null、undefined 或其他占位的值。。

Vue 使用 $ 前缀通过组件实例暴露自己的内置 API。它还为内部 property 保留 _ 前缀。避免使用这两个字符开头的的顶级 data property 名称

方法

用 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

这些 methods 和组件实例的其它所有 property 一样可以在组件的模板中被访问。在模板中,它们通常被当做事件监听使用:

<button @click="increment">Up vote</button>
点击 <button> 时,会调用 increment 方法。

从模板调用的方法不应该有任何副作用,比如更改数据或触发异步进程。如果想这么做,应该换做生命周期钩子

防抖和节流

Vue 没有内置支持防抖和节流,但可以使用 Lodash (opens new window) 等库来实现

如果某个组件仅使用一次,可以在 methods 中直接应用防抖:

<script src="https://unpkg.com/lodash@4.17.20/lodash.min.js"></script>
<script>
  Vue.createApp({
    methods: {
      // 用 Lodash 的防抖函数
      click: _.debounce(function() {
        // ... 响应点击 ...
      }, 500)
    }
  }).mount('#app')
</script>

但是,这种方法对于可复用组件有潜在的问题,因为它们都共享相同的防抖函数。为了使组件实例彼此独立,可以在生命周期钩子的 created 里添加该防抖函数:

app.component('save-button', {
  created() {
    // 用 Lodash 的防抖函数
    this.debouncedClick = _.debounce(this.click, 500)
  },
  unmounted() {
    // 移除组件时,取消定时器
    this.debouncedClick.cancel()
  },
  methods: {
    click() {
      // ... 响应点击 ...
    }
  },
  template: `
    <button @click="debouncedClick">
      Save
    </button>
  `
})

计算属性和侦听器

计算属性

对于任何包含响应式数据的复杂逻辑,你都应该使用计算属性。

//.html
<div id="computed-basics">
  <p>Has published books:</p>
  <span>{{ publishedBooksMessage }}</span>
</div>
//.js
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')
这里声明了一个计算属性 publishedBooksMessage。

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

计算属性缓存 vs 方法

我们可以通过在表达式中调用方法来达到同样的效果:

//.html
<p>{{ calculateBooksMessage() }}</p>

//.js
// 在组件中
methods: {
  calculateBooksMessage() {
    return this.author.books.length > 0 ? 'Yes' : 'No'
  }
}
可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。
然而,不同的是计算属性是基于它们的反应依赖关系缓存的。
计算属性只在相关响应式依赖发生改变时它们才会重新求值。
这就意味着只要 author.books 还没有发生改变,
多次访问 publishedBookMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。
相比之下,每当触发重新渲染时,调用方法将总会再次执行函数。

计算属性的 Setter

计算属性默认只有 getter,不过在需要时你也可以提供一个 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]
    }
  }
}

侦听器

虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。

//.html
<div id="watch-example">
  <p>
    Ask a yes/no question:
    <input v-model="question" />
  </p>
  <p>{{ answer }}</p>
</div>
//.js
<!-- 因为 AJAX 库和通用工具的生态已经相当丰富,Vue 核心代码没有重复 -->
<!-- 提供这些功能以保持精简。这也可以让你自由选择自己更熟悉的工具。 -->
<script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
<script>
  const watchExampleVM = Vue.createApp({
    data() {
      return {
        question: '',
        answer: 'Questions usually contain a question mark. ;-)'
      }
    },
    watch: {
      // whenever question changes, this function will run
      question(newQuestion, oldQuestion) {
        if (newQuestion.indexOf('?') > -1) {
          this.getAnswer()
        }
      }
    },
    methods: {
      getAnswer() {
        this.answer = 'Thinking...'
        axios
          .get('https://yesno.wtf/api')
          .then(response => {
            this.answer = response.data.answer
          })
          .catch(error => {
            this.answer = 'Error! Could not reach the API. ' + error
          })
      }
    }
  }).mount('#watch-example')
</script>
使用 watch 选项允许我们执行异步操作 (访问一个 API),限制我们执行该操作的频率,
并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

计算属性 vs 侦听器

Vue 提供了一种更通用的方式来观察和响应当前活动的实例上的数据变动:侦听属性。当你有一些数据需要随着其它数据变动而变动时,你很容易滥用 watch——特别是如果你之前使用过 AngularJS。然而,通常更好的做法是使用计算属性而不是命令式的 watch 回调。

//.html
<div id="demo">{{ fullName }}</div>
//.js
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 绑定

操作元素的 class 列表和内联样式是数据绑定的一个常见需求。
它们都是 attribute,所以我们可以用 v-bind 处理它们:只需要通过表达式计算出字符串结果即可。不过,字符串拼接麻烦且易错。因此,在将 v-bind 用于 class 和 style 时,Vue.js 做了专门的增强。表达式结果的类型除了字符串之外,还可以是对象或数组。

绑定 HTML Class

对象语法

我们可以传给 :class (v-bind:class 的简写) 一个对象,以动态地切换 class:

//.html
<div :class="{ active: isActive }"></div>
表示 active 这个 class 存在与否将取决于数据 property isActive 的 truthiness 

:class 指令也可以与普通的 class attribute 共存

数组语法

可以把一个数组传给 :class,以应用一个 class 列表:

//.html
<div :class="[activeClass, errorClass]"></div>

//.js
data() {
  return {
    activeClass: 'active',
    errorClass: 'text-danger'
  }
}
如果你想根据条件切换列表中的 class,可以使用三元表达式:
<div :class="[isActive ? activeClass : '', errorClass]"></div>

在数组语法中也可以使用对象语法:
<div :class="[{ active: isActive }, errorClass]"></div>

在组件上使用

当你在带有单个根元素的自定义组件上使用 class attribute 时,这些 class 将被添加到该元素中。此元素上的现有 class 将不会被覆盖。

//.html
<div id="app">
  <my-component class="baz boo"></my-component>
</div>
//.js
const app = Vue.createApp({})

app.component('my-component', {
  template: `<p class="foo bar">Hi!</p>`
})
渲染结果:
	<p class="foo bar baz boo">Hi</p>
对于带数据绑定 class 也同样适用:
	<my-component :class="{ active: isActive }"></my-component>
当 isActive 为 truthy[1] 时,HTML 将被渲染成为:
	<p class="foo bar active">Hi</p>

如果你的组件有多个根元素,你需要定义哪些部分将接收这个类。可以使用 $attrs 组件属性执行此操作

//.html
<div id="app">
  <my-component class="baz"></my-component>
</div>
//.js
const app = Vue.createApp({})

app.component('my-component', {
  template: `
    <p :class="$attrs.class">Hi!</p>
    <span>This is a child component</span>
  `
}

绑定内联样式

对象语法

:style 的对象语法十分直观——看着非常像 CSS,但其实是一个 JavaScript 对象。CSS property 名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case

//.html
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
直接绑定到一个样式对象通常更好,这会让模板更清晰:
<div :style="styleObject"></div>
//.js
data() {
  return {
    styleObject: {
      color: 'red',
      fontSize: '13px'
    }
  }
}
对象语法常常结合返回对象的计算属性使用

数组语法

:style 的数组语法可以将多个样式对象应用到同一个元素上:

<div :style="[baseStyles, overridingStyles]"></div>

在 :style 中使用需要 (浏览器引擎前缀) vendor prefixes(opens new window) 的 CSS property 时,如 transform,Vue 将自动侦测并添加相应的前缀。

多重值

可以为 style 绑定中的 property 提供一个包含多个值的数组,常用于提供多个带前缀的值

这样写只会渲染数组中最后一个被浏览器支持的值。
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>
如果浏览器支持不带浏览器前缀的 flexbox,那么就只会渲染 display: flex

条件渲染

v-if

v-if指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 truthy 值的时候被渲染。**

<div v-if="type === 'A'"></div>
<div v-else-if="type === 'B'"></div>
<div v-else-if="type === 'C'"></div>
<div v-else>Not A/B/C</div>

v-show

<h1 v-show="ok">Hello!</h1>

不同的是带有 v-show 的元素始终会被渲染并保留在 DOM 中。v-show 只是简单地切换元素的 CSS property display。

注意,v-show 不支持 元素,也不支持 v-else

v-if vs v-show

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

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

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

不推荐同时使用 v-if 和 v-for,当 v-if 与 v-for 一起使用时,v-if 具有比 v-for 更高的优先级

列表渲染

v-for

用 v-for 指令基于一个数组来渲染一个列表。v-for 指令需要使用 item in items 形式的特殊语法,其中 items 是源数据数组,而 item 则是被迭代的数组元素的别名。

v-for你也可以提供第二个的参数为 property 名称 (也就是键名 key):还可以用第三个参数作为索引

//.html
<li v-for="(value, name, index) in myObject">
  {{ index }}. {{ name }}: {{ value }}
</li>

//.js
Vue.createApp({
  data() {
    return {
      myObject: {
        title: 'How to do lists in Vue',
        author: 'Jane Doe',
        publishedAt: '2016-04-10'
      }
    }
  }
}).mount('#v-for-object')

可以用 of 替代 in 作为分隔符,因为它更接近 JavaScript 迭代器的语法:

<div v-for="item of items"></div>

维护状态

为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key attribute:

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

建议尽可能在使用 v-for 时提供 key attribute
不要使用对象或数组之类的非基本类型值作为 v-for 的 key。请用字符串或数值类型的值。

显示过滤/排序后的结果

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

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

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

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

类似于 v-if,你也可以利用带有 v-for 的 来循环渲染一段包含多个元素的内容。比如:

<ul>
  <template v-for="item in items">
    <li>{{ item.msg }}</li>
    <li class="divider" role="presentation"></li>
  </template>
</ul>

不推荐在同一元素上使用 v-if 和 v-for

当它们处于同一节点,v-if 的优先级比 v-for 更高,这意味着 v-if 将没有权限访问 v-for 里的变量

可以把 v-for 移动到 标签中来修正:

<template v-for="todo in todos">
  <li v-if="!todo.isComplete">
    {{ todo }}
  </li>
</template>

在自定义组件上,你可以像在任何普通元素上一样使用 v-for
然而,任何数据都不会被自动传递到组件里,因为组件有自己独立的作用域。为了把迭代数据传递到组件里,我们要使用 props:

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

不自动将 item 注入到组件里的原因是,这会使得组件与 v-for 的运作紧密耦合。明确组件数据的来源能够使组件在其他场合重复使用。

事件处理

使用 v-on 指令 (通常缩写为 @ 符号) 来监听 DOM 事件,并在触发事件时执行一些 JavaScript。用法为 v-on:click=“methodName” 或使用快捷方式 @click=“methodName”

v-on 还可以接收一个需要调用的方法名称。

//.html
<div id="event-with-method">
  <!-- `greet` 在下面定义的方法名 -->
  <button @click="greet">Greet</button>
</div>
//.js
Vue.createApp({
  data() {
    return {
      name: 'Vue.js'
    }
  },
  methods: {
    greet(event) {
      // `this` 内部 `methods` 指向当前活动实例
      alert('Hello ' + this.name + '!')
      // `event` 是原生 DOM event
      if (event) {
        alert(event.target.tagName)
      }
    }
  }
}).mount('#event-with-method')

也可以在内联 JavaScript 语句中调用方法

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

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

//.html
<button @click="warn('Form cannot be submitted yet.', $event)">
  Submit
</button>
//.js
methods: {
  warn(message, event) {
    // now we have access to the native event
    if (event) {
      event.preventDefault()
    }
    alert(message)
  }
}

多事件处理器

//.html
<button @click="one($event), two($event)">
  Submit
</button>
//.js
methods: {
  one(event) {
    //
  },
  two(event) {
  //
  }
}

在事件处理程序中调用 event.preventDefault() 或 event.stopPropagation() 是非常常见的需求。尽管我们可以在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。

为了解决这个问题,Vue.js 为 v-on 提供了事件修饰符。之前提过,修饰符是由点开头的指令后缀来表示的。

//.html
!-- 阻止单击事件继续传播 -->
<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>

使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 v-on:click.prevent.self 会阻止所有的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击。
不像其它只能对原生的 DOM 事件起作用的修饰符,.once 修饰符还能被用到自定义的组件事件上。如果你还没有阅读关于组件的文档,现在大可不必担心。
不要把 .passive 和 .prevent 一起使用,因为 .prevent 将会被忽略,同时浏览器可能会向你展示一个警告。请记住,.passive 会告诉浏览器你不想阻止事件的默认行为。

按键修饰符

在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on 或者 @ 在监听键盘事件时添加按键修饰符:

<!-- 只有在 `key``Enter` 时调用 `vm.submit()` -->
<input @keyup.enter="submit" />
可以直接将 KeyboardEvent.key (opens new window) 暴露的任意有效按键名转换为 kebab-case 来作为修饰符。
<input @keyup.page-down="onPageDown" />
处理函数只会在 $event.key 等于 'PageDown' 时被调用

Vue 为最常用的键提供了别名:

.enter
.tab
.delete (捕获“删除”和“退格”键)
.esc
.space
.up
.down
.left
.right

可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。

.ctrl
.alt
.shift
.meta

在 Mac 系统键盘上,meta 对应 command 键 (⌘)。在 Windows 系统键盘 meta 对应 Windows 徽标键 (⊞)。

.exact 修饰符

.exact 修饰符允许你控制由精确的系统修饰符组合触发的事件。

<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button @click.ctrl="onClick">A</button>

<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>

<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button @click.exact="onClick">A</button>

鼠标按钮修饰符

.left
.right
.middle

这些修饰符会限制处理函数仅响应特定的鼠标按钮

表单输入绑定

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

v-model会忽略所有的表单元素的value,checked,selected的初始值而总是将当前活动实例的数据作为数据来源,通过js在组件的data选项中声明初始值
v-model在内部为不同的输入元素使用不同的property并抛出不同的事件:
text和textarea元素使用value property和inout事件
checkbox和radio使用checked property和change事件
select字段将value作为prop并将change作为事件

v-model 不会在输入法组织文字过程中得到更新。如果你也想处理这个过程,请使用 input 事件。

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

复选框 (Checkbox)

//单个复选框
<input type="checkbox" id="checkbox" v-model="checked" />
<label for="checkbox">{{ checked }}</label>
//多个复选框
<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>
//.js
Vue.createApp({
  data() {
    return {
      checkedNames: []
    }
  }
}).mount('#v-model-multiple-checkboxes')

单选框(radio)

//.html
<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>
//.js
Vue.createApp({
  data() {
    return {
      picked: ''
    }
  }
}).mount('#v-model-radiobutton')

值绑定

对于单选按钮,复选框及选择框的选项,v-model 绑定的值通常是静态字符串 (对于复选框也可以是布尔值)

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

<!-- `toggle`truefalse -->
<input type="checkbox" v-model="toggle" />

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

有时我们可能想把值绑定到当前活动实例的一个动态 property 上,这时可以用 v-bind 实现,此外,使用 v-bind 可以将输入值绑定到非字符串

修饰符

.lazy

在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步
可以添加 lazy 修饰符,从而转为在 change 事件_之后_进行同步

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

.number

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

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

.trim

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

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

组件基础

组件是可复用的组件实例,可以将组件进行任意次数的复用
创建一个 Vue 组件的示例:

// 创建一个Vue 应用
const app = Vue.createApp({})

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

在典型的 Vue 应用程序中,我们使用单个文件组件而不是字符串模板
可以将组件进行任意次数的复用,每用一次组件,就会有一个它的新实例被创建
通常一个应用会以一颗嵌套的组件树来组织,例如,可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。
为了能在模板中使用,这些组件必须先注册以便 Vue 能够识别。这里有两种组件的注册类型:全局注册和局部注册。至此,我们的组件都只是通过 component 全局注册的:

const app = Vue.createApp({})

app.component('my-component-name', {
  // ... 选项 ...
})

通过 Prop 向子组件传递数据

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

//.html
<div id="blog-post-demo" class="demo">
//prop 被注册之后,你就可以像这样把数据作为一个自定义 attribute 传递进来
  <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>
//.js
const app = Vue.createApp({})
app.component('blog-post', {
//一个组件默认可以拥有任意数量的 prop,任何值都可以传递给任何 prop
  props: ['title'],
  template: `<h4>{{ title }}</h4>`
})

app.mount('#blog-post-demo')

在组件上使用 v-model

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


<input v-model="searchText" />
等价于
<input :value="searchText" @input="searchText = $event.target.value" />


当用在组件上时,v-model 
<custom-input
  :model-value="searchText"
  @update:model-value="searchText = $event"
></custom-input>

为了让它正常工作,这个组件内的 必须:
将其 value attribute 绑定到一个名叫 modelValue 的 prop 上
在其 input 事件被触发时,将新的值通过自定义的 update:modelValue 事件抛出

//.html
<custom-input v-model="searchText"></custom-input>
//.js
app.component('custom-input', {
  props: ['modelValue'],
  emits: ['update:modelValue'],
  template: `
    <input
      :value="modelValue"
      @input="$emit('update:modelValue', $event.target.value)"
    >
  `
})

在自定义组件中创建 v-model 功能的另一种方法是使用 computed property 的功能来定义 getter 和 setter。

app.component('custom-input', {
  props: ['modelValue'],
  emits: ['update:modelValue'],
  template: `
    <input v-model="value">
  `,
  computed: {
    value: {
      get() {
        return this.modelValue
      },
      set(value) { this.$emit('update:modelValue', value)
      }
    }
  }
})
get 方法应返回 modelValue property,或用于绑定的任何 property,set 方法应为该 property 触发相应的 $emit

通过插槽分发内容

有时需要向一个组件传递内容,Vue 自定义的 slot 元素让这变得非常简单,只要在需要的地方加入插槽就行了

//.html
<alert-box>
  Something bad happened.
</alert-box>
//.js
app.component('alert-box', {
  template: `
    <div class="demo-alert-box">
      <strong>Error!</strong>
      <slot></slot>
    </div>
  `
  文本 Something bad happened.会显示在slot中
})

动态组件

在不同组件之间进行动态切换是非常有用的,比如在一个多标签的界面里:

//.html
<div id="dynamic-component-demo" class="demo">
  <button
     v-for="tab in tabs"
     v-bind:key="tab"
     v-bind:class="['tab-button', { active: currentTab === tab }]"
     v-on:click="currentTab = tab"
   >
    {{ tab }}
  </button>

  <component v-bind:is="currentTabComponent" class="tab"></component>
</div>
currentTabComponent 可以包括已注册组件的名字,或一个组件的选项对象
//.css
.demo {
  font-family: sans-serif;
  border: 1px solid #eee;
  border-radius: 2px;
  padding: 20px 30px;
  margin-top: 1em;
  margin-bottom: 40px;
  user-select: none;
  overflow-x: auto;
}

.tab-button {
  padding: 6px 10px;
  border-top-left-radius: 3px;
  border-top-right-radius: 3px;
  border: 1px solid #ccc;
  cursor: pointer;
  background: #f0f0f0;
  margin-bottom: -1px;
  margin-right: -1px;
}
.tab-button:hover {
  background: #e0e0e0;
}
.tab-button.active {
  background: #e0e0e0;
}
.demo-tab {
  border: 1px solid #ccc;
  padding: 10px;
}
//.js
const app = Vue.createApp({
  data() {
    return {
      currentTab: 'Home',
      tabs: ['Home', 'Posts', 'Archive']
    }
  },
  computed: {
    currentTabComponent() {
      return 'tab-' + this.currentTab.toLowerCase()
    }
  }
})

app.component('tab-home', {
  template: `<div class="demo-tab">Home component</div>`
})
app.component('tab-posts', {
  template: `<div class="demo-tab">Posts component</div>`
})
app.component('tab-archive', {
  template: `<div class="demo-tab">Archive component</div>`
})

app.mount('#dynamic-component-demo')

解析 DOM 模板时的注意事项

有些 HTML 元素,诸如 ul、ol、table 和 select,对于哪些元素可以出现在其内部是有严格限制的。而有些元素,诸如 li、tr 和 option,只能出现在其它某些特定的元素内部。

这会导致我们使用这些有约束条件的元素时遇到一些问题

<table>
  <blog-post-row></blog-post-row>
</table>
这个自定义组件 <blog-post-row> 会被作为无效的内容提升到外部,并导致最终渲染结果出错

特殊的 v-is attribute 给了我们一个变通的办法,v-is 值应为 JavaScript 字符串文本:

<table>
  <tr v-is="'blog-post-row'"></tr>
</table>


<!-- 错误的,这样不会渲染任何东西 -->
<tr v-is="blog-post-row"></tr>

<!-- 正确的 -->
<tr v-is="'blog-post-row'"></tr>

HTML 属性名不区分大小写,因此浏览器将把所有大写字符解释为小写。这意味着当你在 DOM 模板中使用时,驼峰 prop 名称和 event 处理器参数需要使用它们的 kebab-cased (横线字符分隔) 等效值:

//  在JavaScript中的驼峰

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

<!--HTML则是横线字符分割 -->

<blog-post post-title="hello!"></blog-post>

单文件组件

在很多 Vue 项目中,我们使用 app.component 来定义全局组件,紧接着用 app.mount(‘#app’) 在每个页面内指定一个容器元素。
这对于中小型项目非常有效,在这些项目里 JavaScript 只被用来增强特定的视图。但当在更复杂的项目中,或者你的前端完全由 JavaScript 驱动的时候,下面这些缺点将变得非常明显:

  • 全局定义强制要求每个component中的命名不得重复
  • 字符串模板缺乏高亮
  • 不支持css意味着HTML和JS组件化时,CSS明显被遗漏
  • 没有构建步骤限制只能使用 HTML 和 ES5 JavaScript,而不能使用预处理器

所有这些都可以通过扩展名为 .vue 的 single-file components (单文件组件) 来解决,并且还可以使用 webpack 或 Browserify 等构建工具。

这是一个文件名为 Hello.vue 的简单实例:
在这里插入图片描述

深入组件

组件名

注册一个组件的时候,我们始终需要给它一个名字。比如在全局注册的时候我们已经看到了:

const app = Vue.createApp({...})

app.component('my-component-name', {
  /* ... */
})
组件名就是 app.component 的第一个参数

当直接在 DOM 中使用一个组件 (而不是在字符串模板或单文件组件) 的时候,

强烈推荐遵循 W3C 规范 (opens new window)中的自定义组件名:

全部小写
包含连字符 (及:即有多个单词与连字符符号连接)

这样会帮助你避免与当前以及未来的 HTML 元素发生冲突。

组件名大小写

在字符串模板或单个文件组件中定义组件时,定义组件名的方式有两种:

使用 短横线分隔命名( kebab-case)
app.component('my-component-name', {
  /* ... */
})
当使用 kebab-case (短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用 kebab-case,例如 <my-component-name>


使用首字母大写命名 (PascalCase)
app.component('MyComponentName', {
  /* ... */
})
当使用 PascalCase (首字母大写命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。
也就是说 <my-component-name><MyComponentName> 都是可接受的。注意,尽管如此,直接在 DOM (即非字符串的模板) 中使用时只有短横线分隔命名是有效的

全局注册

用app.component 来创建的组件是全局注册的。也就是说它们在注册之后可以用在任何新创建的组件实例的模板中。比如:

//.html
<div id="app">
  <component-a></component-a>
  <component-b></component-b>
  <component-c></component-c>
</div>

//.js
const app = Vue.createApp({})

app.component('component-a', {
  /* ... */
})
app.component('component-b', {
  /* ... */
})
app.component('component-c', {
  /* ... */
})
app.mount('#app')
在所有子组件中也是如此,也就是说这三个组件在各自内部也都可以相互使用。

局部注册

如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。

在这些情况下,你可以通过一个普通的 JavaScript 对象来定义组件:

const ComponentA = {
  /* ... */
}
const ComponentB = {
  /* ... */
}
const ComponentC = {
  /* ... */
}
然后在 components 选项中定义你想要使用的组件:
const app = Vue.createApp({
  components: {
    'component-a': ComponentA,
    'component-b': ComponentB
  }
})

对于 components 对象中的每个 property 来说,其 property 名就是自定义元素的名字,其 property 值就是这个组件的选项对象

注意局部注册的组件在其子组件中不可用。例如,如果你希望 ComponentA 在 ComponentB 中可用,则你需要这样写:

const ComponentA = {
  /* ... */
}

const ComponentB = {
  components: {
    'component-a': ComponentA
  }
  // ...
}

Prop 类型

以字符串数组形式列出的 prop:

props: ['title', 'likes', 'isPublished', 'commentIds', 'author']

通常每个 prop 都有指定的值类型。可以以对象形式列出 prop,这些 property 的名称和值分别是 prop 各自的名称和类型:

props: {
  title: String,
  likes: Number,
  isPublished: Boolean,
  commentIds: Array,
  author: Object,
  callback: Function,
  contactsPromise: Promise // 或任何其他构造函数
}

传递静态或动态的 Prop

可以像这样给 prop 传入一个静态的值

//.html
<blog-post title="My journey with Vue"></blog-post>

prop 还可以通过 v-bind 或简写 : 动态赋值,例如:

<!-- 动态赋予一个变量的值 -->
<blog-post :title="post.title"></blog-post>

<!-- 动态赋予一个复杂表达式的值 -->
<blog-post :title="post.title + ' by ' + post.author.name"></blog-post>

在上述两个示例中,传入的值都是字符串类型的,但实际上任何类型的值都可以传给一个 prop

传入一个数字

//.html

<!-- 即便 `42` 是静态的,我们仍然需要 `v-bind` 来告诉 Vue     -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。             -->
<blog-post :likes="42"></blog-post>

<!-- 用一个变量进行动态赋值。-->
<blog-post :likes="post.likes"></blog-post>



传入一个布尔值


<!-- 包含该 prop 没有值的情况在内,都意味着 `true`-->
<blog-post is-published></blog-post>

<!-- 即便 `false` 是静态的,我们仍然需要 `v-bind` 来告诉 Vue  -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。             -->
<blog-post :is-published="false"></blog-post>

<!-- 用一个变量进行动态赋值。                                -->
<blog-post :is-published="post.isPublished"></blog-post>

传入一个数组

!-- 即便数组是静态的,我们仍然需要 `v-bind` 来告诉 Vue        -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。             -->
<blog-post :comment-ids="[234, 266, 273]"></blog-post>

<!-- 用一个变量进行动态赋值。                                -->
<blog-post :comment-ids="post.commentIds"></blog-post>

传入一个对象

<!-- 即便对象是静态的,我们仍然需要 `v-bind` 来告诉 Vue        -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。             -->
<blog-post
  :author="{
    name: 'Veronica',
    company: 'Veridian Dynamics'
  }"
></blog-post>

<!-- 用一个变量进行动态赋值。                                 -->
<blog-post :author="post.author"></blog-post>

传入一个对象的所有 property

如果你想要将一个对象的所有 property 都作为 prop 传入,你可以使用不带参数的 v-bind (取代 v-bind:prop-name)。例如,对于一个给定的对象 post:

//.html
<blog-post v-bind="post"></blog-post>
//.js
post: {
  id: 1,
  title: 'My Journey with Vue'
}

上述代码等价于
<blog-post v-bind:id="post.id" v-bind:title="post.title"></blog-post>

单向数据流

所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态
每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。
有两种常见的试图变更一个 prop 的情形:
**1. 这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。**在这种情况下,最好定义一个本地的 data property 并将这个 prop 作为其初始值:

props: ['initialCounter'],
data() {
  return {
    counter: this.initialCounter
  }
}

2.这个 prop 以一种原始的值传入且需要进行转换。在这种情况下,最好使用这个 prop 的值来定义一个计算属性:

props: ['size'],
computed: {
  normalizedSize: function () {
    return this.size.trim().toLowerCase()
  }
}

Prop 验证

可以为组件的 prop 指定验证要求。如果有一个需求没有被满足,则 Vue 会在浏览器控制台中警告你。
为了定制 prop 的验证方式,你可以为 props 中的值提供一个带有验证需求的对象,而不是一个字符串数组。

app.component('my-component', {
  props: {
    // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
    propA: Number,
    // 多个可能的类型
    propB: [String, Number],
    // 必填的字符串
    propC: {
      type: String,
      required: true
    },
    // 带有默认值的数字
    propD: {
      type: Number,
      default: 100
    },
    // 带有默认值的对象
    propE: {
      type: Object,
      // 对象或数组默认值必须从一个工厂函数获取
      default: function() {
        return { message: 'hello' }
      }
    },
    // 自定义验证函数
    propF: {
      validator: function(value) {
     
// 这个值必须匹配下列字符串中的一个
        return ['success', 'warning', 'danger'].indexOf(value) !== -1
      }
    },
    // 具有默认值的函数
    propG: {
      type: Function,
      // 与对象或数组默认值不同,这不是一个工厂函数 —— 这是一个用作默认值的函数
      default: function() {
        return 'Default function'
      }
    }
  }
})

当 prop 验证失败的时候,(开发环境构建版本的) Vue 将会产生一个控制台的警告。

注意那些 prop 会在一个组件实例创建之前进行验证,所以实例的 property (如 data、computed 等) 在 default 或 validator 函数中是不可用的。

type 可以是下列原生构造函数中的一个:

String
Number
Boolean
Array
Object
Date
Function
Symbol

Prop 的大小写命名 (camelCase vs kebab-case)

HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名:

//.html
<blog-post post-title="hello!"></blog-post>
//.js
const app = Vue.createApp({})

app.component('blog-post', {
  // camelCase in JavaScript
  props: ['postTitle'],
  template: '<h3>{{ postTitle }}</h3>'
})

非 Prop 的 Attribute

一个非 prop 的 attribute 是指传向一个组件,但是该组件并没有相应 props 或 emits 定义的 attribute。常见的示例包括 class、style 和 id 属性

Attribute 继承

当组件返回单个根节点时,非 prop attribute 将自动添加到根节点的 attribute 中。

如果我们需要通过 data status property 定义 <date-picker> 组件的状态,它将应用于根节点 (即 div.date-picker)<!-- 具有非prop attribute的Date-picker组件-->
//.html
<date-picker data-status="activated"></date-picker>

<!-- 渲染 date-picker 组件 -->
<div class="date-picker" data-status="activated">
  <input type="datetime" />
</div>
//.js
app.component('date-picker', {
  template: `
    <div class="date-picker">
      <input type="datetime" />
    </div>
  `
})

同样的规则适用于事件监听器:

//.html
<date-picker @change="submitChange"></date-picker>
//.js
app.component('date-picker', {
  template: `
    <select>
      <option value="1">Yesterday</option>
      <option value="2">Today</option>
      <option value="3">Tomorrow</option>
    </select>
  `
})
在这种情况下,change 事件监听器从父组件传递到子组件,它将在原生 select 的 change 事件上触发。

禁用 Attribute 继承

如果不希望组件的根元素继承 attribute,你可以在组件的选项中设置 inheritAttrs: false

禁用 attribute 继承的常见情况是需要将 attribute 应用于根节点之外的其他元素。

app.component('date-picker', {
  inheritAttrs: false,
  template: `
    <div class="date-picker">
      <input type="datetime" v-bind="$attrs" />
    </div>
  `
})

通过将 inheritAttrs 选项设置为 false,你可以访问组件的 $attrs property,该 property 包括组件 props 和 emits property 中未包含的所有属性 (例如,class、style、v-on 监听器等)。

多个根节点上的 Attribute 继承

与单个根节点组件不同,具有多个根节点的组件不具有自动 attribute 回退行为。如果未显式绑定 $attrs,将发出运行时警告。

//.html
<custom-layout id="custom-layout" @click="changeValue"></custom-layout>
//.js
// 这将发出警告
app.component('custom-layout', {
  template: `
    <header>...</header>
    <main>...</main>
    <footer>...</footer>
  `
})

// 没有警告,$attrs被传递到<main>元素
app.component('custom-layout', {
  template: `
    <header>...</header>
    <main v-bind="$attrs">...</main>
    <footer>...</footer>
  `
})

自定义事件

不同于组件和 prop,事件名不存在任何自动化的大小写转换。而是触发的事件名需要完全匹配监听这个事件所用的名称。

//.html
<my-component @my-event="doSomething"></my-component>
//.js
this.$emit('my-event')

如果我们触发一个 camelCase 名字的事件:

//.js
this.$emit('myEvent')

则监听这个名字的 kebab-case 版本是不会有任何效果的:

<!-- 没有效果 -->
<my-component @my-event="doSomething"></my-component>

不同于组件和 prop,事件名不会被用作一个 JavaScript 变量名或 property 名,所以就没有理由使用 camelCase 或 PascalCase 了
并且 v-on 事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以 @myEvent 将会变成 @myevent——导致 myEvent 不可能被监听到。

因此,推荐始终使用 kebab-case 的事件名

定义自定义事件

可以通过 emits 选项在组件上定义已发出的事件

app.component('custom-form', {
  emits: ['in-focus', 'submit']
})

当在 emits 选项中定义了原生事件 (如 click) 时,将使用组件中的事件替代原生事件侦听器。

建议定义所有发出的事件,以便更好地记录组件应该如何工作

验证抛出的事件

与 prop 类型验证类似,如果使用对象语法而不是数组语法定义发出的事件,则可以验证它。

要添加验证,将为事件分配一个函数,该函数接收传递给 $emit 调用的参数,并返回一个布尔值以指示事件是否有效。

app.component('custom-form', {
  emits: {
    // 没有验证
    click: null,

    // 验证submit 事件
    submit: ({ email, password }) => {
      if (email && password) {
        return true
      } else {
        console.warn('Invalid submit event payload!')
        return false
      }
    }
  },
  methods: {
    submitForm() {
      this.$emit('submit', { email, password })
    }
  }
})

v-model 参数

默认情况下,组件上的 v-model 使用 modelValue 作为 prop 和 update:modelValue 作为事件。我们可以通过向 v-model 传递参数来修改这些名称:

功能快捷

撤销:Ctrl/Command + Z
重做:Ctrl/Command + Y
加粗:Ctrl/Command + B
斜体:Ctrl/Command + I
标题:Ctrl/Command + Shift + H
无序列表:Ctrl/Command + Shift + U
有序列表:Ctrl/Command + Shift + O
检查列表:Ctrl/Command + Shift + C
TOC语法后生成一个完美的目录。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/416596.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

不黑学孙溟㠭匪石社寒山寺游记

昔日寒山问拾得曰&#xff1a;世间谤我、欺我、辱我、笑我、轻我、贱我、恶我、骗我、如何处治乎&#xff1f;拾得云&#xff1a;只是忍他、让他、由他、避他、耐他、敬他、不要理他、再待几年你且看他。 经典对话显修行。是否文殊、普贤再来都不重要。因缘使然&#xff0c;朋友…

C++ :JSON 解析系统的设计与实现

JSON JavaScript Object Notation(JavaScript 对象表示法) 定义 存储和交换文本信息的语法&#xff0c;类似 XML&#xff1b; 比 XML 更小、更快&#xff0c;更易解析&#xff0c;属于轻量级的文本数据交换格式&#xff1b; 使用 Javascript语法来描述数据对象&#xff0c;但…

PMP学习笔记

day01 裸考1 题目1: 下列哪一项不属于项目的特征? D A:独特性 B:临时性 C:渐进明细 D:重复性 解析:重复性属于运营 题目2: 在确定项目目标时,应该更加关注哪一个? D A:进度要求 B:成本预算 C:质量标准 D:同等关注进度,成本和质量要求 前沿 管理的基础 三个学…

Hashicorp Vault(金库)

什么是Vault? HashiCorp Vault 是一个基于身份的秘密和加密管理系统。机密是您想要严格控制访问的任何内容,例如 API 加密密钥、密码和证书。Vault 提供由身份验证和授权方法门控的加密服务。使用 Vault 的 UI、CLI 或 HTTP API,可以安全地存储和管理、严格控制(限制)和审…

微服务高级篇【1】之微服务保护

文章目录前言一 初识Sentinel1.1 雪崩问题1.2 解决方法1.3 小结1.4 服务保护技术对比1.5 Sentinel介绍1.6 Sentinel安装1.7 微服务整合Sentinel二 测试工具&#xff1a;Jmeter2.1 Jmeter安装和配置2.2 Jmeter快速入门2.2.1 设置中文语言2.2.2 设置Jmeter桌面快捷图标2.3 Jmeter…

Spring————java的反射机制,Spring的IOC和DI

一、认识Spring 1.1、Spring家族 SpringFramework&#xff1a; Spring框架&#xff1a;是Spring中最早核心的技术&#xff0c;也是所有其他技术及的基础。 SpringBoot:Spring是用来简化开发。而SpringBoot是来帮助Spring在简化的基础上能更快速进行开发。 SpringCloud&#xf…

FreeRTOS 任务相关 API 函数(一)

文章目录一、任务创建和删除 API 函数1. xTaxkCreate()2. xTaskCreateStatic()3. xTaskCreateRestricted()4. vTaskDelete()二、任务创建和删除实验(动态方法)三、任务创建和删除实验(静态方法)一、任务创建和删除 API 函数 FreeRTOS 最基本的功能就是任务管理&#xff0c;而任…

AcWing4957.飞机降落——学习笔记

目录 题目 代码 AC结果 思路 〇、例子 一、获取数据 二、深度优先遍历&#xff08;DFS&#xff09; 1.入参 2.出口 3.判断是否找到安全降落方案 4.递推过程 5.回溯过程 6.DFS完整代码 三、输出打印 题目 4957. 飞机降落 - AcWing题库https://www.acwing.com/pr…

打开 plist 文件

​ 对于使用苹果的进阶或资深玩家来说&#xff0c;有时候要编辑plist文件&#xff0c;比如要弄两个qq&#xff0c;要修改info.plist下的sku&#xff0c;而要是没有工具&#xff0c;就不能随意查看和编辑plist文件了。再有&#xff0c;可能要通过修改plist来破解某些游戏&#x…

windows服务器自带IIS搭建网站并发布公网访问【内网穿透】

文章目录1.前言2.Windows网页设置2.1 Windows IIS功能设置2.2 IIS网页访问测试3. Cpolar内网穿透3.1 下载安装Cpolar3.2 Cpolar云端设置3.3 Cpolar本地设置4.公网访问测试5.结语转载自远程源码文章&#xff1a;【IIS搭建网站】本地电脑做服务器搭建web站点并公网访问「内网穿透…

协同数据交换平台详细设计方案(word)

本资料来源公开网络&#xff0c;仅供个人学习&#xff0c;请勿商用&#xff0c;如有侵权请联系删除 1 架构设计 1.1 总体架构协同数据交换平台利用企业服务总线、数据抽取ETL、消息中间件、大文件传输等相关技术&#xff0c;包括文件适配器、数据库适配器、Web服务中间件等在内…

【排序算法(四)】归并排序计数排序(非比较排序)以及八大排序算法的总结

​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;数据结构 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 文章目录1、归并排序1.1 算法…

图像处理数据集

BSDS500 Berkeley Segmentation Dataset 500 是第一个用于评估超像素算法的数据集。对于参数优化&#xff0c;使用了验证集。 500张数据集200训练集train100验证集val200测试集test 每张图像有 5 个不同的高质量地面真值分割&#xff08;groundTruth,是.mat文件&#xff09; …

Android 中的混音器 AudioMixer 实现分析

Android framework 的音频处理模库 libaudioprocessing (位于 frameworks/av/media/libaudioprocessing) 提供了混音器组件 AudioMixer&#xff0c;它主要用在 audioflinger 里&#xff0c;用来将多路音频源数据混音&#xff0c;以方便送进音频设备播放出来。 音频混音操作本身…

MyBatis(九)MyBatis小技巧

一、#{}和${} #{}&#xff1a;先编译sql语句&#xff0c;再给占位符传值&#xff0c;底层是PreparedStatement实现。可以防止sql注入&#xff0c;比较常用。 ${}&#xff1a;先进行sql语句拼接&#xff0c;然后再编译sql语句&#xff0c;底层是Statement实现。存在sql注入现象。…

第09章_异常处理

第09章_异常处理 讲师&#xff1a;尚硅谷-宋红康&#xff08;江湖人称&#xff1a;康师傅&#xff09; 官网&#xff1a;http://www.atguigu.com 本章专题与脉络 1. 异常概述 1.1 什么是生活的异常 男主角小明每天开车上班&#xff0c;正常车程1小时。但是&#xff0c;不出…

计网第五章.运输层—TCP流量控制与可靠传输

以下来自湖科大计算机网络公开课笔记及个人所搜集资料 目录一、流量控制死锁死锁的解决&#xff1a;二、超时重传时间的选择解决方案Karn算法三、可靠传输补充&#xff1a;其实TCP的流量控制&#xff0c;可靠传输&#xff0c;拥塞控制&#xff0c;都是围绕滑动窗口机制来实现的…

SpringBoot的统一功能处理

目录 1.统一用户的的登录权限校验 最开始的用户登录 Spring拦截器 2.统一数据返回格式 统一数据的返回格式意义 统一数据返回格式的实现 3.统一异常处理 在上篇博客中我介绍了Spring AOP的基础知识,这篇博客则是AOP的实践练习,通过借助AOP实现三个目标 1.统一用户登录权…

VContainer 初体验

IOC 控制反转 IOC 提供一个对象生成容器&#xff0c;在我们需要取得某个对象时&#xff0c;不再使用New关键字进行对象生成操作&#xff0c;而是通过IOC容器内部控制来获得对象。 使用这种思想方式&#xff0c;可以让我们无需关心对象的生成方式&#xff0c;只需要告诉容器我需…

xmanager连接linux桌面教程 xmanager连接之后黑屏

xmanager 是一款专业的远程服务器管理软件&#xff0c;但习惯了使用Windows系统下的桌面&#xff0c;一时会无法适应linux服务器的命令行界面。下面我就为大家介绍xmanager连接linux桌面教程&#xff0c;xmanager连接之后黑屏的相关内容&#xff0c;让大家在使用linux更加方便。…