
vite.config.js 中export default defineCOnfig({
plugins: [
vue(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
base: process.env.NODE_ENV === 'production' ? '/bms/' : '/'
})
脚手架:创建项目,选择性安装需要的插件,指定统一的风格,生成demo。
构建项目:建立项目的运行环境,需要手动安装插件。
打包代码:代码写好之后,为了更好的使用,需要打包处理一下。
Vue-cli 一直是 Vue 2 默认官方脚手架工具,Vue-cli 基于 Webpack 开发
Vite 是 下一版的 Vue-Cli,也是 Vue3 推荐的构建工具
Vue CLI(脚手架)
create-vue(脚手架)
npm init vue@latestRollup(打包工具) 是一个 JavaScript 模块打包工具,可以将多个小的代码片段编译为完整的库和应用
webpack(构建工具) 是一个用于现代 JavaScript 应用程序的静态模块打包工具。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个依赖图(dependency graph),然后将你项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源,用于展示你的内容。
Vite(构建工具)
是一种新型前端构建工具,能够显著提升前端开发体验。它主要由两部分组成:
一个开发服务器,它基于原生ES模块提供了丰富的内建功能,如速度快到惊人的模块热更新(HMR)。
一套构建指令,它使用 Rollup 打包你的代码,并且它是预配置的,可输出用于生产环境的高度优化过的静态资源。
是前端构建工具,用于将自己写的代码转换成浏览器能够识别的 js 代码,像 Vue 的单文件组件(SFC)浏览器是不能直接运行的,需要 Vite 做转换。
npm install -g create-vite 安装 Vite 的全局 CLI(命令行界面)
create-vite my-vue-app 创建 Vue 项目
npm run dev 启动开发服务器
项目目录结构:
my-vue-app/
├── README.md
├── package.json
├── tsconfig.json
├── vite.config.ts
└── src/
├── assets/
├── components/
├── main.ts
├── App.vue
└── index.html
src 目录包含源代码文件。main.ts 是应用程序的入口文件。App.vue 是根组件。vite.config.ts 是 Vite 的配置文件。<template>
<div>{{ message }}</div>
</template>
// 以上模板经过编译后成为:
render() {
return h('div', this.message)
}
// h(标签名, 属性, 子内容) 是 createElement 的缩写,用于创建虚拟 DOM 节点。
<div id="app"></div>,而页面上所有的内容,都是 Vue 在运行时动态生成并插入的<script src="app.js" async></script><script src="app.js" defer></script><style scoped>
.my-component {
color: red;
}
</style>
<div data-v-f3f3eg9>...</div>
<style> .my-component[data-v-f3f3eg9] { color: red; } </style>
<script type="module" src="main.js"></script><script setup> 是一个 编译时语法糖,它让 <script> 中的代码自动执行在组件的 setup() 阶段,并自动暴露所有声明的变量、函数、组件给模板使用。 setup() 是组件的“逻辑入口”,Vue 在组件初始化时执行它,收集返回的数据和方法,并建立响应式联系,这样才能让模板“看到”并“自动更新”它们。setup() 在组件创建之前执行,但:在 props 解析之后;在响应式系统建立之前;在模板渲染之前// 传统写法(Options API + Composition API)
<script>
import { ref, reactive } from 'vue'
import MyComponent from './MyComponent.vue'
export default {
components: { MyComponent },
setup() {
const count = ref(0)
const message = reactive({
text: 'Hello'
})
function increment() {
count.value++
}
// 必须手动 return 才能在模板中使用
return {
count,
message,
increment
}
}
}
</script>
<template>
<div>
<h1>{{ message.text }}</h1>
<p>计数:{{ count }}</p>
<button @click="increment">+1</button>
<MyComponent />
</div>
</template>
// 使用 <script setup>(推荐)
<script setup>
import { ref, reactive } from 'vue'
import MyComponent from './MyComponent.vue'
// 不需要 setup() 函数
// 不需要 return
// 所有顶层变量、函数自动暴露给模板
const count = ref(0)
const message = reactive({
text: 'Hello'
})
function increment() {
count.value++
}
</script>
<template>
<div>
<h1>{{ message.text }}</h1>
<p>计数:{{ count }}</p>
<button @click="increment">+1</button>
<MyComponent />
</div>
</template>
组件的本质:一个函数,从底层看,组件其实就是一个返回虚拟 DOM 的函数:Vue 把组件当作“可调用的 UI 函数”,输入 props,输出 UI。
function UserCard(props) {
const { user } = props
return h('div', [
h('img', { src: user.avatar }),
h('h3', user.name),
h('button', { onClick: () => emit('follow') }, '关注')
])
}
template/script/stylesetup() 和模板的逻辑单元,Vue 内部的对象props 接收输入,通过 emit 发送输出根 Vue 实例是整个应用的“总指挥”,它挂载根组件,启动整个组件树;每个组件实例是“执行单元”,它们通过父子关系形成一棵树,共享同一个应用上下文,但拥有独立的状态和生命周期。
组件的创建和销毁时机:
v-show="false" 不会销毁!只是 display: nonev-if 控制“是否创建”,v-show 控制“是否显示v-if 只在为真时才渲染到 DOM,v-show 则会一直渲染,只是不显示JS 文件的作用范围:3 层理解
<script src="...">引入的 JS 才能使用选项式API对比组合式API
// 选项式API:逻辑割裂——一个功能(如“计数器”)的代码分散在多个选项中
export default {
data() { }, // 所有数据放这里
methods: { }, // 所有方法放这里
computed: { }, // 所有计算属性
watch: { }, // 所有监听
mounted() { } // 所有生命周期
}
// 选项式API:一个功能的相关代码集中在一起
setup() {
// 🔹 计数器功能
const count = ref(0)
const square = computed(() => count.value ** 2)
const increment = () => count.value++
watch(() => count.value, logChange)
onMounted(() => init())
// 🔹 另一个功能...
const user = ref(null)
const loadUser = () => { }
return { count, square, increment, user }
}
let body = document.querySelector('body')
let node = document.createElement('script')
node.src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js";
let original = document.getElementsByTagName('script')
if(original.length === 0){
body.appendChild(node);
}
var data = { a: 1 }
var vm = new Vue({
el: '#example',
data: data,
watch: {}
})
$ 开头vm.$data === data // true
vm.$el === document.getElementById('example')` // true
// $watch 是一个实例方法
vm.$watch('a', function (newValue, oldValue) {
// 这个回调将在 `vm.a` 改变后调用
})
beforeCreate:在实例初始化之后,数据观测 (data observer) 和事件/路由等的初始化之前被调用。created:实例创建完成后被立即调用。此时,实例已经完成了数据观测、属性和方法的运算,事件/路由等的初始化。beforeMount:在挂载开始之前被调用。mounted:el 被新创建的 vm.$el 替换,并挂载到实例上之后调用。此时,你可以访问 DOM。beforeUpdate:数据更新之前被调用。updated:数据更改导致的虚拟 DOM 重新渲染和打补丁之后调用。activated:仅存在于 <keep-alive> 内部组件激活时调用。deactivated:仅存在于 <keep-alive> 内部组件停用时调用。beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。destroyed:Vue 实例销毁后调用。当实例被销毁后,所有与之相关的事件监听器和子实例都会被移除。不要在选项 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 之类的错误。

文本插值:适用于插入文本节点 <span> {{ msg }} </span> 支持 JS 表达式 {{ num + 1}}, {{ message.split('').reverse().join('') }}, 每个绑定都只能包含单个表达式
v-bind:attr:适用于属性节点
:::important
使用 v-bind 绑定的响应式变量,数据流是单向的,即从 Vue 实例的 data 属性流向 DOM 元素(DOM 元素跟随 data 属性的变化而变化,而 DOM 元素的变化并不能引起 data 属性的变化。)。
使用 v-model 的绑定是双向流动的。
:::
原始HTML:v-html="rawThml"
指令 (Directives) 是带有 v- 前缀的特殊 attribute,指令 attribute 的值预期是单个 JavaScript 表达式(v-for 例外)
指令参数
<a v-bind:href="url">...</a>。在这里 href 是参数,告知 v-bind 指令将该元素的 href attribute 与表达式 url 的值绑定。v-on 指令的参数是监听的事件名 <a v-on:click="doSomething">...</a>动态参数:<a v-bind:[attributeName]="url"> ... </a>,attributeName 作为 JS 表达式动态求值,求值结果作为最终属性名。<a v-on:[eventName]="doSomething"> ... </a>
null,则表明移除绑定,其他非字符串的值会触发警告修饰符 (modifier) 是以半角句号 . 指明的特殊后缀,用于指出一个指令应该以特殊方式绑定
.prevent 修饰符告诉 v-on 指令对于触发的事件调用 event.preventDefault()缩写:v-bind:href="url" 缩写为 :hred="url",动态参数的缩写 :[attr]="url"。v-on:click="doSomething 缩写为 @click="doSomething",动态参数:@[event]="doSomething"
计算属性:相当于一个新属性(依赖原属性),其结果是由现有的 Vue 属性经过一系列操作得到的。原属性更新时,计算属性同步更新。计算属性是基于它们的响应式依赖进行缓存的,只在相关响应式依赖发生改变时它们才会重新求值。
var vm = new Vue({
el: '#example',
data: {
message: 'Hello'
},
computed: {
// 提供的函数将用作计算属性的 getter 函数
reversedMessage: function () {
// `this` 指向 vm 实例
return this.message.split('').reverse().join('')
}
newMsg: {
// 计算属性也可以有 setter
set: function(newValue) {
message = newValue;
}
}
}
})
vm.reversedMessage // 调用 getter
vm.newMsg = 'sss' // 调用 setter
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar',
fullName: 'Foo Bar'
},
watch: {
firstName: function (newValue, oldValue) {
this.fullName = newValue + ' ' + this.lastName
}
}
})
<div v-bind:class="{ active: isActive, 'text-danger': hasError }"></div>,active 和 text-danger 的class 存在与否取决于 isActive 和 hasError 属性的真假。v-bind:class 指令也可以与普通的 class 属性共存:<div
class="static"
v-bind:class="{ active: isActive, 'text-danger': hasError }"
></div>
<div class="static" v-bind:class="classObject"></div>
data: {
classObject: {
active: true,
'text-danger': false
}
}
// 渲染结果
<div class="static active"></div>
<div v-bind:class="[activeClass, errorClass]"></div>
data: {
activeClass: 'active',
errorClass: 'text-danger'
}
// 渲染为:
<div class="active text-danger"></div>
// 根据条件切换列表中的 class,可以用三元表达式:
<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>
// 直接在数组语法中使用对象语法:
<div v-bind:class="[{ active: isActive }, errorClass]"></div>
Vue.component('my-component', {
template: '<p class="foo bar">Hi</p>'
})
<my-component class="baz boo"></my-component>
// HTML 将被渲染为:
<p class="foo bar baz boo">Hi</p>
// 对于带数据绑定 class 也同样适用:
<my-component v-bind:class="{ active: isActive }"></my-component>
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
data: {
activeColor: 'red',
fontSize: 30
}
// 直接绑定到一个样式对象通常更好,这会让模板更清晰:
<div v-bind:style="styleObject"></div>
data: {
styleObject: {
color: 'red',
fontSize: '13px'
}
}
// 同样适用于数组
<div v-bind:style="[baseStyles, overridingStyles]"></div>
当 v-bind:style 使用需要添加浏览器引擎前缀的 CSS property 时,如 transform,Vue.js 会自动侦测并添加相应的前缀。
条件渲染:v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 truthy 值的时候被渲染。(所有除 false、0、-0、0n、""、null、undefined 和 NaN 以外的皆为真值)。
不推荐同时使用 v-if 和 v-for
<h1 v-if="awesome">Vue is awesome!</h1>
// 也可以用 v-else, v-else-if 添加一个“else-if 块”:
<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>
v-else v-else-if 元素必须紧跟在带 v-if 或者 v-else-if 的元素的后面,否则它将不会被识别。
在 <template> 元素上使用 v-if 条件渲染分组 <template v-if="ok"><h1>Title</h1></template>
v-show 也可用于条件渲染,与 v-if 不同的是带有 v-show 的元素始终会被渲染并保留在 DOM 中。v-show 只是简单地切换元素的 CSS property display。
v-show 不支持 <template> 元素,也不支持 v-else。
用 key 管理可复用的元素:Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。为元素添加一个具有唯一值的 key 属性,来表达“这两个元素是完全独立的,不要复用它们”。
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username" key="username-input">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address" key="email-input">
</template>
如果<input> 元素没有 key 属性,则 loginType 变化时,输入框中的内容不会清除,因为使用了相同的 <input> 元素
用 v-for 把一个数组对应为一组元素 <li/> <ul/>,可以用 of 替代 in 作为分隔符
var example1 = new Vue({
el: '#example-1',
data: {
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})
<ul id="example-1">
<li v-for="(item, index) in|of items" :key="item.message">
{{ item.message }}
</li>
</ul>
// 结果
// · Foo
// · Bar
push() pop() shift() unshift() splice() sort() reverse(),这些方法导致的数组变更会被响应式更新filter() concat() slice(),可以用新数组替换旧数组:example1.items = example1.items.filter(function (item) {
return item.message.match(/Foo/)
})
由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。即数组和对象不能响应式更新。使用 Vue.set 来添加新属性来确保 Vue 能够检测到这些变化
<ul id="v-for-object" class="demo">
<li v-for="(item, key, index) in object">
{{ index }}. {{ key }}: {{ item }}
</li>
</ul>
new Vue({
el: '#v-for-object',
data: {
object: {
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
}
}
})
// 结果
// 0.title: How to do lists in Vue
// 1.author: Jane Doe
// 2.publishedAt: 2016-04-10
在遍历对象时,会按 Object.keys() 的结果遍历,但是不能保证它的结果在不同的 JavaScript 引擎下都一致。
当 Vue 正在更新使用 v-for 渲染的元素列表时,它默认使用“就地更新”的策略。为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key attribute:<div v-for="item in items" :key="item.id"/>
不要使用对象或数组之类的非基本类型值作为 v-for 的 key。请用字符串或数值类型的值。
在组件上使用 v-for:为了把迭代数据传递到组件里,要使用 prop:
<my-component
v-for="(item, index) in items"
v-bind:item="item"
v-bind:index="index"
v-bind:key="item.id"
></my-component>
2.2.0+ 的版本里,当在组件上使用 v-for 时,key 现在是必须的。
$event 是原始的 DOM 事件<button v-on:click="warn('Form cannot be submitted yet.', $event)">
Submit
</button>
methods: {
warn: function (message, event) {
// 现在我们可以访问原生事件对象
if (event) {
event.preventDefault()
}
alert(message)
}
}
事件修饰符:用于修改或增强事件处理的行为
.stop:阻止事件冒泡,即阻止事件继续传播到其他元素。.prevent:阻止事件的默认行为,例如,当点击提交按钮时,可以阻止表单的自动提交。.capture:添加事件监听器时使用捕获模式,即事件从根节点开始传播到目标节点的过程中,先触发捕获模式的监听器。 .self:当事件触发时,只有当事件的事件目标是绑定元素本身时,才会触发事件处理函数。.once:事件处理函数将只被触发一次,之后自动移除监听器。.passive:表示事件处理函数不会调用 event.preventDefault(),通常用于滚动事件,可以提高性能。按键修饰符,在监听键盘事件时,经常需要检查详细的按键
<!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` -->
<input v-on:keyup.enter="submit">
<!-- 67按键码对应的是C,只有按下C时才调用 `vm.submit() -->
<input v-on:keyup.67="submit">
<!-- 系统修饰键 .ctrl .alt .shift .meta -->
<!-- Alt + C -->
<input v-on:keyup.alt.67="clear">
<!-- Ctrl + Click -->
<div v-on:click.ctrl="doSomething">Do something</div>
<!-- .exact 修饰符允许你控制由精确的系统修饰符组合触发的事件。-->
<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button v-on:click.ctrl="onClick">A</button>
<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button v-on:click.ctrl.exact="onCtrlClick">A</button>
<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button v-on:click.exact="onClick">A</button>
<!-- 鼠标按钮修饰符 .left .right .middle -->
基础用法
用 v-model 指令在表单 <input>、<textarea> 及 <select> 元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。
v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:
text 和 textarea 元素使用 value property 和 input 事件;checkbox 和 radio 使用 checked property 和 change 事件;select 字段将 value 作为 prop 并将 change 作为事件。text,textarea,radio 绑定是的 value属性
单个 checkbox 绑定的是布尔值,多个 checkbox 绑定的是 value 到一个数组
select 绑定的是每一个 option 的 value
修饰符
.lazy:在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步。添加 lazy 修饰符后,从而转为在 change 事件之后进行同步:<input v-model.lazy="msg">
.number:自动将用户的输入值转为数值类型,<input v-model.number="age" type="number">
.trim:自动过滤用户输入的首尾空白字符,<input v-model.trim="msg">
// 定义一个名为 button-counter 的新组件
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
// 使用组件
<div id="components-demo">
<button-counter></button-counter>
</div>
一个组件的 data 选项必须是一个函数,这样每个实例才有自己独立的数据副本,相互之间不会影响。data 函数在每次组件实例化时被调用,返回一个对象
可以将组件进行任意次数的复用,每用一次组件,就会有一个它的新 Vue 实例被创建。
全局组件,通过 Vue.component 注册的。全局注册的组件可以用在其被注册之后的任何 (通过 new Vue) 新创建的 Vue 根实例,也包括其组件树中的所有子组件的模板中。
Prop 属性,Prop 是你可以在组件上注册的一些自定义 attribute。当一个值传递给一个 prop attribute 的时候,它就变成了那个组件实例的一个 property。
Vue.component('blog-post', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
})
data: {
posts: [
{ id: 1, title: 'My journey with Vue' },
{ id: 2, title: 'Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun' }
]
}
// 使用 v-bind 来动态传递 prop
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:title="post.title"
></blog-post>
每个组件必须只有一个根元素
template:
'<div class="blog-post">
<h3>{{ title }}</h3>
<div v-html="content"></div>
</div>'
$event 就是 $emit('event-name',value) 抛出的值,被父组件所接收// 子组件通过 $emit('enlarge-text', value) 触发 enlarge-text 事件,并抛出一个值 value
<button v-on:click="$emit('enlarge-text', value)">
Enlarge text
</button>
// 父组件使用 v-on 监听子组件实例的任意事件,使用 $event 接收子组件抛出的值
<blog-post
v-on:enlarge-text="postFontSize += $event"
></blog-post>
// 如果事件处理器是一个函数,子组件抛出的值作为第一个参数传入这个函数
<blog-post
v-on:enlarge-text="onEnlargeText"
></blog-post>
methods: {
onEnlargeText: function (enlargeAmount) { // enlargeAmount = value
this.postFontSize += enlargeAmount
}
}
v-model<input v-model="searchText">
// 等价于:
<input v-bind:value="searchText"
v-on:input="searchText = $event.target.value"> // 这里的 $event 是原始的 DOM 事件
v-modle 实际上是将 input 元素的 value 属性绑定到 searchText 变量上,同时监听 input 事件,当事件发生时,又让该元素的 value 属性赋值给 searchText 变量
只使用 v-bind:value 只是单向绑定,即 searchText 的变化会改变表单的内容,但是表达内容的改变却不能改变 searchText,单向绑定完成数据从 Vue 流向表单,监听 input 事件实现表单数据流向 Vue
当用在自定义组件上时,v-model=searchText 则会这样:
<custom-input
v-bind:value="searchText"
v-on:input="searchText = $event"
></custom-input>
v-modleVue.component('custom-input', {
props: ['value'],
template: `
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
`
})
// 使用组件
<custom-input v-model="searchText"></custom-input>
value attribute 用于不同的目的。model 选项可以用来避免这样的冲突Vue.component('base-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
template: `
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
`
})
Vue.component('alert-box', {
template: `
<div class="demo-alert-box">
<strong>Error!</strong>
<slot></slot>
</div>
`
})
// 使用插槽
<alert-box>
Something bad happened.
</alert-box>
// 结果
// Error!Something bad happened.
<component> 元素加一个特殊的 is attribute 来实现:<component v-bind:is="currentTabComponent"></component> 组件会在 currentTabComponent 改变时改变。currentTabComponent 可以包括:已注册组件的名字,或一个组件的选项对象Vue.component('my-component-name', { /* ... */ }),组件名使用 kebab-case 风格components 选项中定义想要使用的组件:局部注册的组件在其子组件中不可用var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
new Vue({
el: '#app',
components: {
'component-a': ComponentA,
'component-b': ComponentB
}
})
// 只可在当前组件内使用
<component-a></component-a>
// 如果希望 ComponentA 在 ComponentB 中可用
var ComponentA = { /* ... */ }
var ComponentB = {
components: {
'component-a': ComponentA
},
}
import ComponentA from './ComponentA'
import ComponentC from './ComponentC'
export default {
components: {
ComponentA, // 等价于 ComponentA: ComponentA
ComponentC
},
}
// ComponentA 和 ComponentC 都可以在 ComponentB 的模板中使用了。
import Vue from 'vue'
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'
const requireComponent = require.context(
// 其组件目录的相对路径
'./components',
// 是否查询其子目录
false,
// 匹配基础组件文件名的正则表达式
/Base[A-Z]\w+\.(vue|js)$/
)
requireComponent.keys().forEach(fileName => {
// 获取组件配置
const componentConfig = requireComponent(fileName)
// 获取组件的 PascalCase 命名
const componentName = upperFirst(
camelCase(
// 获取和目录深度无关的文件名
fileName
.split('/')
.pop()
.replace(/\.\w+$/, '')
)
)
// 全局注册组件
Vue.component(
componentName,
// 如果这个组件选项是通过 `export default` 导出的,
// 那么就会优先使用 `.default`,
// 否则回退到使用模块的根。
componentConfig.default || componentConfig
)
})
全局注册的行为必须在根 Vue 实例 (通过 new Vue) 创建之前发生
props: ['title', 'likes', 'isPublished', 'commentIds', 'author']props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
callback: Function,
contactsPromise: Promise // or any other constructor
}
<!-- 静态传递 -->
<blog-post title="My journey with Vue"></blog-post>
<!-- 动态赋予一个变量的值 -->
<blog-post v-bind:title="post.title"></blog-post>
<!-- 动态赋予一个复杂表达式的值 -->
<blog-post
v-bind:title="post.title + ' by ' + post.author.name"
></blog-post>
<!-- 传入一个数字 -->
<!-- 即便 `42` 是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post v-bind:likes="42"></blog-post>
<!-- 用一个变量进行动态赋值。-->
<blog-post v-bind:likes="post.likes"></blog-post>
<!-- 传入一个布尔值 -->
<!-- 包含该 prop 没有值的情况在内,都意味着 `true`。-->
<blog-post is-published></blog-post>
<!-- 即便 `false` 是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post v-bind:is-published="false"></blog-post>
<!-- 用一个变量进行动态赋值。-->
<blog-post v-bind:is-published="post.isPublished"></blog-post>
<!-- 传入一个数组 -->
<!-- 即便数组是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post v-bind:comment-ids="[234, 266, 273]"></blog-post>
<!-- 用一个变量进行动态赋值。-->
<blog-post v-bind:comment-ids="post.commentIds"></blog-post>
<!-- 传入一个对象 -->
<!-- 即便对象是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post
v-bind:author="{
name: 'Veronica',
company: 'Veridian Dynamics'
}"
></blog-post>
<!-- 用一个变量进行动态赋值。-->
<blog-post v-bind:author="post.author"></blog-post>
v-bind (取代 v-bind:prop-name)。例如,对于一个给定的对象 post:post: {
id: 1,
title: 'My Journey with Vue'
}
<blog-post v-bind="post"></blog-post>
// 等价于:
<blog-post
v-bind:id="post.id"
v-bind:title="post.title"
></blog-post>
单向数据流:所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行
每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。 :::warning
在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变变更这个对象或数组本身将会影响到父组件的状态。
让子组件的 prop 跟随父组件而被动的更新,不要主动更新子组件 prop,可以在子组件中定义 data 属性,使用 prop 作为初始值。或者定义一个 computed 属性,依赖 prop :::
Prop 验证,当 prop 验证失败的时候,(开发环境构建版本的) Vue 将会产生一个控制台的警告。
Vue.component('my-component', {
props: {
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
propA: Number,
// 多个可能的类型
propB: [String, Number, Boolean, Array, Object, Date, Function, Symbol],
// 必填的字符串
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'].includes(value)
}
}
}
})
prop 会在一个组件实例创建之前进行验证,所以实例的 property (如 data、computed 等) 在 default 或 validator 函数中是不可用的。
type 还可以是一个自定义的构造函数,并且通过 instanceof 来进行检查确认function Person (firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
// 你可以使用:
Vue.component('blog-post', {
props: {
author: Person
}
})
// 来验证 author prop 的值是否是通过 new Person 创建的。
// 即 传递给子组件的 prop 必须是一个 Person 的实例
// 即 person instanceof Person 返回 true
<blog-post :author="person"></blog-post>
template 属性要求根组件必须只有一个根元素!!!)data-date-picker 属性:<bootstrap-date-input data-date-picker="activated"></bootstrap-date-input> 这个 data-date-picker="activated" attribute 就会自动添加到 <bootstrap-date-input> 的根元素上。inheritAttrs: false 属性控制,这样父组件传递过来的属性会被 $attrs 属性接收(是一个对象),然后在子组件的模板中手动确定 $attrs 的绑定。在撰写基础组件的时候是常会用到Vue.component('base-input', {
inheritAttrs: false,
props: ['label', 'value'],
template: `
<label>
{{ label }}
<input
v-bind="$attrs" // v-bind:placeholder="Enter your username"
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
</label>
`
})
// 使用子组件 $attrs={placeholder :'Enter your username'}
<base-input
label="Username:"
v-model="username"
required
placeholder="Enter your username"
></base-input>
注意 inheritAttrs: false 选项不会影响 style 和 class 的绑定。style 和 class 依然绑定到根元素上。
type="text" 就会替换掉 type="date" 并把它破坏!庆幸的是,class 和 style attribute 会稍微智能一些,即两边的值会被合并起来自定义事件
不同于组件和 prop,事件名不存在任何自动化的大小写转换,并且 v-on 事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以 v-on:myEvent 将会变成 v-on:myevent——导致自定义事件 myEvent 不可能被监听到。
将原生事件绑定到组件
如果想在子组件的根元素上监听一个原生事件,只需在事件名后加 .native 修饰符。例如:<base-input v-on:focus.native="onFocus"></base-input>
使用 $listeners 属性(是一个对象,其中包含了作用在这个组件上的所有监听器),可以将事件监听器将指向一个特定的子元素。
this.$listeners.childClick({ message: 'Clicked from child' }) 将调用父组件的事件监听器(会调用父组件的事件处理器),this.$emit('childClick',{ message: 'Clicked from child' }) 触发事件。.sync 修饰符
通过 prop 进行双向绑定:
// 子组件中通过事件来为 `title` prop 更新值
this.$emit('update:title', newTitle)
// 父组件中接收该值来更新与 `title` 绑定的 `doc.title`
<text-document
v-bind:title="doc.title"
v-on:update:title="doc.title = $event"
></text-document>
.sync 修饰符是为这种模式提供的缩写 <text-document v-bind:title.sync="doc.title"></text-document>
:::warning.sync 修饰符的 v-bind 不能和表达式一起使用,例如 v-bind:title.sync = "doc.title + '!'" 是无效的,只能提供想要绑定的属性名<text-document v-bind.sync="doc"></text-document>,这样会把 doc 对象中的每一个 property (如 title) 都作为一个独立的 prop 传进去,然后各自添加用于更新的 v-on 监听器。v-bind.sync 用在一个字面量的对象上,例如 v-bind.sync="{ title: doc.title }",是无法正常工作的
:::// 组件模板
<a v-bind:href="url" class="nav-link">
<slot></slot>
</a>
// 使用组件
<navigation-link url="/profile">
Your Profile
</navigation-link>
// 渲染结果
<a href="/profile" class="nav-link">
Your Profile
</a>
// 插槽内可以包含任何模板代码,包括 HTML, 甚至其它的组件:
<navigation-link url="/profile">
<!-- 添加一个 Font Awesome 图标 -->
<span class="fa fa-user"></span>
<!-- 添加一个图标的组件 -->
<font-awesome-icon name="user"></font-awesome-icon>
Your Profile
</navigation-link>
如果 <navigation-link> 的 template 中没有包含一个 <slot> 元素,则该组件起始标签和结束标签之间的任何内容都会被抛弃。
<navigation-link url="/profile">
Clicking here will send you to: {{ url }}
<!-- 这里的 `url` 会是 undefined,因为其 (指该插槽的) 内容是
_传递给_ <navigation-link> 的而不是
在 <navigation-link> 组件*内部*定义的。 -->
<!-- 这里的数据可以正常访问 -->
Logged in as {{ user.name }}
</navigation-link>
<button type="submit">
<slot>Submit</slot>
</button>
<!-- 不提供插槽内容 -->
<submit-button></submit-button>
<!-- 渲染结果 -->
<button type="submit">
Submit
</button>
<!-- 提供插槽内容 -->
<submit-button>
Save
</submit-button>
<!-- 渲染结果 -->
<button type="submit">
Save
</button>
#header,name 属性必须有参数才可以,默认的写成 #default<div class="container">
<header>
<slot name="header"></slot>
</header>
<!-- 不带 name 的 <slot> 出口会带有隐含的名字“default”。 -->
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
<!-- 使用插槽 在 <template> 元素上使用 v-slot 指令,并提供其名称-->
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<!--
任何没有被包裹在带有 v-slot 的 <template> 中的内容都会被视为默认插槽的内容。
或者使用更明确的写法
<template v-slot:default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
-->
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>
<!-- 渲染结果 -->
<div class="container">
<header>
<h1>Here might be a page title</h1>
</header>
<main>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</main>
<footer>
<p>Here's some contact info</p>
</footer>
</div>
注意 v-slot 只能添加在 <template> 上 (只有一种例外情况),这一点和已经废弃的 slot attribute 不同。
<span>
<!-- user prop 被称为插槽 prop -->
<slot v-bind:user="user">
{{ user.lastName }}
</slot>
</span>
<!-- slotProps = {
user : {
firstName: 'Zhu',
lastName: 'mengyu'
}
} -->
<current-user>
<!-- default 是插槽的默认名 -->
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
</current-user>
<!-- 也可以省略 default,对应默认插槽 -->
<current-user v-slot="slotProps">
{{ slotProps.user.firstName }}
</current-user>
<!-- 对插槽 prop 使用解构 -->
<current-user v-slot="{ user }">
{{ user.firstName }}
</current-user>
<!-- 还允许你重命名 prop,例如将 user 重命名为 person -->
<current-user v-slot="{ user : person }">
{{ person.firstName }}
</current-user>
<!-- 可以为插槽 prop 提供默认值(后备内容),以防它们是 undefined: -->
<current-user v-slot="{ user = { firstName: 'Guest' } }">
{{ user.firstName }}
</current-user>
默认插槽的缩写语法不能和具名插槽混用,因为它会导致作用域不明确,只要出现多个插槽,请始终为所有的插槽使用完整的基于 <template> 的语法
<template v-slot:[dynamicSlotName]><keep-alive> 缓存组件状态<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>
Vue.component('async-example', function (resolve, reject) {
setTimeout(function () {
// 向 `resolve` 回调传递组件定义
resolve({
template: '<div>I am async!</div>'
})
}, 1000)
})
// 定义一个混入对象
var myMixin = {
data: function () {
return {
msg: 'zmy'
}
}
created: function () {
this.hello()
},
methods: {
hello: function () {
console.log('hello from mixin!')
}
}
}
// 定义使用混入对象的组件
var Component1 = Vue.extend({
mixins: [myMixin],
data: function () {
return {
bar: 'def'
}
},
methods: {
say: function () {
console.log('Hi!')
}
}
})
var Component2 = Vue.extend({
mixins: [myMixin],
data: function () {
return {
foo: 'def'
}
},
})
// component1 包含 msg, bar, say(), hello()
var component1 = new Component1()
// component2 包含 msg, foo, helo()
var component2 = new Component2()
Vue.config.optionMergeStrategies 添加一个函数:Vue.config.optionMergeStrategies.myOption = function (toVal, fromVal) {
// 返回合并后的值
}
// 为自定义的选项 'myOption' 注入一个处理器。
Vue.mixin({
created: function () {
var myOption = this.$options.myOption
if (myOption) {
console.log(myOption)
}
}
})
<template/>,通过将一个函数赋值给 Vue 实例或者组件的 render 选项来使用它。这个函数接收一个 createElement 方法作为参数,这个方法用来创建 VNodes。var component = {
render: function (createElement) {
return createElement(
'div', // 标签名
'Hello Vue!' // 子节点
)
}
}
var vm = new Vue(component).$mount('#app')
createElement:创建一个 <div/> 元素,设定它的各种属性,还可以为这个元素设置子元素 <span>Span Text</span> 和文本节点 Text Noderender: function (createElement) {
return createElement(
'div',
{
attrs: { id: 'myDiv' }, // 静态属性
class: 'container', // 类名
style: { color: 'red' }, // 内联样式
on: { // 监听器
click: this.handleClick
}
},
[ // 子节点可以是数组形式
createElement('span', 'Span Text'),
'Text Node'
]
)
}
createElement 函数返回的是一个节点,当作正常的元素来使用(实际返回的是虚拟节点 VNode),下面是 createElement 函数的定义// @returns {VNode}
createElement(
// {String | Object | Function}
// 一个 HTML 标签名、组件选项对象,或者
// resolve 了上述任何一种的一个 async 函数。必填项。
'div',
// {Object}
// 一个与模板中 attribute 对应的数据对象。可选。
{
// (详情见下一节)
},
// {String | Array}
// 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,
// 也可以使用字符串来生成“文本虚拟节点”。可选。
[
'先写一些文字', //文本节点
createElement('h1', '一则头条'), // 子元素 h1
createElement(MyComponent, { // 子元素 MyComponent 是一个组件
props: {
someProp: 'foobar'
}
})
]
)
useRouter() 都将得到undefinedimport router from '../router'; 加 {} 可以导入整个模块,反之则只导入某个函数或变量
router 是路由器实例,即由 createRouter() 返回的对象。route 是当前路由对象,包含当前路由信息this.$router 和 this.$route 访问useRouter() 和 useRoute() 访问let router = useRouter();
let route = useRoute();
router.push 方法会向 history栈 添加一个新的 url 记录router.replace 方法不会向 history栈 添加一个新的记录,而是替换掉当前的 history 记录- router.push('/')
- router.replace('/')
- router.go(-1) 返回上一层
- router.go(1) 前进一层
- router.go(0) 刷新页面
- router.go(2) 前进两层
ref 属性可以用于获取组件实例(DOM元素),this.$refs.multipleTableRef 就表示这个表格组件,即这个 dom 元素<el-table ref="multipleTableRef" :data="tableData" style="width:100%"
max-height="500px" @selection-change="handleSelectionChange">
- #(v-slot的简写) 符号指定插槽的名称
- 子组件
<el-table-column/>中有一个名为 default 的<slot/>,这个 slot 有 row 属性和 index 内置属性#default="attr表示将 row 属性和 index 属性传递给 attr,利用attr 可以访问子组件 slot 的内容(attr.row)<el-table-column label="操作"> <template #default="attr"> <!-- 当前行的索引 scoped.$index 和当前行数据 scoped.row --> <el-button size="small" @click="handleEdit(attr.$index, attr.row)">编辑</el-button> <el-button size="small" type="danger" @click="handleDelete(attr.$index, attr.row)">删除</el-button> </template> </el-table-column>
在 template 中不需要 .value 访问响应式变量,在 script 中需要
index 代表这个组件在地址栏中的路由路径:
http://localhost:5173/company/DeptInfo, 点击组件时会路由到对应的组件deptInfo.vue(定义在 index.js 中,不区分大小写)<el-menu-item index="/company/DeptInfo">部门管理</el-menu-item> <el-menu-item index="/company/EmployeeInformation">员工管理</el-menu-item>
import { createMemoryHistory, createRouter } from 'vue-router'
import HomeView from './HomeView.vue'
import AboutView from './AboutView.vue'
const routes = [
{ path: '/', component: HomeView },
{ path: '/about', component: AboutView },
]
const router = createRouter({
history: createMemoryHistory(),
routes,
})
use() 来完成。createApp(App)
.use(router)
.mount('#app')
// 等价地:
const app = createApp(App)
app.use(router)
app.mount('#app')
use() 插件的职责包括:RouterView 和 RouterLink 组件。$router 和 $route 属性。useRouter() 和 useRoute() 组合式函数。const router = createRouter({ })
router.beforeEach((to, from) => {
// ...
// 返回 false 以取消导航
return false
})
false:取消当前的导航。router.push(),且可以传入诸如 replace: true 或 name: 'home' 之类的选项。它会中断当前的导航,同时用相同的 from 创建一个新导航。router.beforeEach 类似,因为它在每次导航时都会触发,不同的是,解析守卫刚好会在导航被确认之前、所有组件内守卫和异步路由组件被解析之后调用。router.beforeResolve 是获取数据或执行任何其他操作(如果用户无法进入页面时你希望避免执行的操作)的理想位置。// 根据路由在元信息中的 requiresCamera 属性确保用户访问摄像头的权限:
router.beforeResolve(async to => {
if (to.meta.requiresCamera) {
try {
await askForCameraPermission()
} catch (error) {
if (error instanceof NotAllowedError) {
// ... 处理错误,然后取消导航
return false
} else {
// 意料之外的错误,取消导航并把错误传给全局处理器
throw error
}
}
}
})
router.afterEach((to, from) => {
sendToAnalytics(to.fullPath)
})
app.provide()inject() 方法。这在注入像 pinia stores 这样的全局属性时很有用。在 app.provide() 中提供的所有内容都可以在 router.beforeEach()、router.beforeResolve()、router.afterEach() 内获取到:// main.ts
const app = createApp(App)
app.provide('global', 'hello injections')
// router.ts or main.ts
router.beforeEach((to, from) => {
const global = inject('global') // 'hello injections'
// a pinia store
const userStore = useAuthStore()
// ...
})
beforeEnter 守卫:beforeEnter 守卫 只在进入路由时触发,不会在 params、query 或 hash 改变时触发。beforeEnter,这在为不同的路由重用守卫时很有用(代码复用)const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: (to, from) => {
// reject the navigation
return false
},
},
]
beforeRouteLeave 守卫。beforeEach 守卫。beforeRouteUpdate 守卫(2.2+)。beforeEnter。beforeRouteEnter。beforeResolve 守卫(2.5+)。afterEach 钩子。beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。defineStore() 定义,并且它需要一个唯一名称,作为第一个参数传递:reactive 包裹的对象,这意味着不需要在 getter 之后写 .value,但是,就像 setup 中的 props 一样,我们不能对其进行解构:storeToRefs(): const { name, doubleCount } = storeToRefs(store) 直接结构会破坏响应式 const { name, doubleCount } = storeimport { defineStore } from 'pinia'
const useStore = defineStore('storeId', {
// 推荐使用 完整类型推断的箭头函数
state: () => {
return {
// 所有这些属性都将自动推断其类型
counter: 0,
name: 'Eduardo',
isAdmin: true,
}
},
})
可以通过 store 实例访问状态来直接读取和写入状态:const store = useStore(); store.counter++
可以通过调用 store 上的 $reset() 方法将状态 重置 到其初始值:store.$reset()
Getters 类似于 computed 属性,使用 defineStore() 中的 getters 属性定义
export const useStore = defineStore('main', {
state: () => ({
counter: 0,
}),
getters: {
doubleCount: (state) => state.counter * 2,
},
})
defineStore() 中的 actions 属性定义,并且它们非常适合定义业务逻辑:export const useStore = defineStore('main', {
state: () => ({
counter: 0,
}),
actions: {
increment() {
this.counter++
},
randomizeCounter() {
this.counter = Math.round(100 * Math.random())
},
},
})