Vue CLI
Vue-CLI的使用
介绍
vue脚手架的作用是用来自动一键生成vue+webpack的项目模版,包括依赖库,免去你手动安装各种插件,寻找各种cdn并一个个引入的麻烦。
vue脚手架指的是vue-cli,它是一个专门为单页面应用快速搭建繁杂的脚手架,它可以轻松的创建新的应用程序而且可用于自动生成vue和webpack的项目模板。
vue-cli是有Vue提供的一个官方cli,专门为单页面应用快速搭建繁杂的脚手架。它是用于自动生成vue.js+webpack的项目模板,是为现代前端工作流提供了 batteries-included 的构建设置。只需要几分钟的时间就可以运行起来并带有热重载,保存时 lint 校验,以及生产环境可用的构建版本
安装
Node 版本要求
Vue CLI 4.x 需要 Node.js v8.9 或更高版本 (推荐 v10 以上)。你可以使用 n,nvm 或 nvm-windows 在同一台电脑中管理多个 Node 版本。
可以使用下列任一命令安装这个新的包:
1 | npm install -g @vue/cli |
安装之后,你就可以在命令行中访问 vue
命令。你可以通过简单运行 vue
,看看是否展示出了一份所有可用命令的帮助信息,来验证它是否安装成功。
你还可以用这个命令来检查其版本是否正确:
1 | vue --version |
升级
如需升级全局的 Vue CLI 包,请运行:
1 | npm update -g @vue/cli |
创建项目
切换到你要创建项目的目录,然后运行以下命令来创建一个新项目:
1 | vue create hello-world |
你会被提示选取一个 preset。你可以选默认的包含了基本的 Babel + ESLint 设置的 Vue2或者Vue3,也可以选“手动选择特性”来选取需要的特性。
使用图形化界面
你也可以通过 vue ui
命令以图形化界面创建和管理项目:
1 | vue ui |
上述命令会打开一个浏览器窗口,并以图形化界面将你引导至项目创建的流程。
启动项目
1 | npm run serve |
1 如出现下载缓慢请配置npm 淘宝镜像:npm config set registry https://registry.npm.taobao.org
1 Vue 脚手架隐藏了所有webpack 相关的配置,若想查看具体的webpakc 配置,请执行:vue inspect > output.js
vue.config.js
vue.config.js
是一个可选的配置文件,如果项目的 (和 package.json
同级的) 根目录中存在这个文件,那么它会被 @vue/cli-service
自动加载。你也可以使用 package.json
中的 vue
字段,但是注意这种写法需要你严格遵照 JSON 的格式来写。
这个文件应该导出一个包含了选项的对象:
1 | // vue.config.js |
模板项目的结构
1 | ├── node_modules |
ref属性
ref
被用来给元素或子组件注册引用信息。
- 引用信息将会注册在父组件的
$refs
对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素(id的替代者); - 如果用在子组件上,引用就指向组件实例
VueComponent
:
1 | <!-- `vm.$refs.p` will be the DOM node --> |
当 v-for
用于元素或组件的时候,引用信息将是包含 DOM 节点或组件实例的数组。
获取:this.$refs.xxx
props
props 可以是数组或对象,用于接收来自父组件的数据。
三种读取方式
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34// 简单语法
Vue.component('props-demo-simple', {
props: ['size', 'myMessage']
})
// 指定名称和类型
Vue.component('props-demo-simple', {
props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
callback: Function,
contactsPromise: Promise // or any other constructor
}
})
// 对象语法,提供验证
Vue.component('props-demo-advanced', {
props: {
// 检测类型
height: Number,
// 检测类型 + 其他验证
age: {
type: Number,
default: 0, // 默认值
required: true,
validator: function (value) {
return value >= 0
}
}
}
})
1 备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。
mixin
混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。
当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。
1 | // 定义一个混入对象 |
混入对象的局部使用
1 | // 定义一个使用混入对象的组件 |
如果你的混入包含一个 created 钩子,而创建组件本身也有一个,那么两个函数都会被调用。
Mixin 钩子按照传入顺序依次调用,并在调用组件自身的钩子之前被调用。
全局使用
全局混入:Vue.mixin(xxx)
全局注册一个混入,影响注册之后所有创建的每个 Vue 实例。插件作者可以使用混入,向组件注入自定义的行为。不推荐在应用代码中使用。
插件
插件通常用来为 Vue 添加全局功能。插件的功能范围没有严格的限制——一般有下面几种:
- 添加全局方法或者 property。如:vue-custom-element
- 添加全局资源:指令/过滤器/过渡等。如 vue-touch
- 通过全局混入来添加一些组件选项。如 vue-router
- 添加 Vue 实例方法,通过把它们添加到
Vue.prototype
上实现。 - 一个库,提供自己的 API,同时提供上面提到的一个或多个功能。如 vue-router
开发插件
Vue.js 的插件应该暴露一个 install
方法。这个方法的第一个参数是 Vue
构造器,第二个参数是一个可选的选项对象:
1 | MyPlugin.install = function (Vue, options) { |
使用插件
通过全局方法 Vue.use()
使用插件。它需要在你调用 new Vue()
启动应用之前完成:
1 | // 调用 `MyPlugin.install(Vue)` |
scoped样式
作用:让样式在局部生效,防止冲突。
写法:
1
2
3
4
5
6
7<style scoped>
@media (min-width: 250px) {
.list-container:hover {
background: orange;
}
}
</style>
这个可选 scoped
attribute 会自动添加一个唯一的 attribute (比如 data-v-21e5b78
) 为组件内 CSS 指定作用域,编译的时候 .list-container:hover
会被编译成类似 .list-container[data-v-21e5b78]:hover
。
组件的自定义事件
组件的自定义事件是一种组件间通信方式,适用于:子组件给父组件传递数据。
不同于组件和 prop,事件名不存在任何自动化的大小写转换。而是触发的事件名需要完全匹配监听这个事件所用的名称。
不同于组件和 prop,事件名不会被用作一个 JavaScript 变量名或 property 名,所以就没有理由使用 camelCase 或 PascalCase 了。并且 v-on
事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以 v-on:myEvent
将会变成 v-on:myevent
——导致 myEvent
不可能被监听到。
因此,我们推荐你始终使用 kebab-case 的事件名。
步骤:
先在父组件中给子组件TodoFooter
绑定事件:
1 | <TodoFooter @custom-event="doSomething"></TodoFooter> |
并在methods
中定义doSomething回调方法:
1 | doSomething(msg) { |
在子组件中绑定一个按钮来触发自定义事件:
1 | <button @click="triggerCustomEvent">触发自定义事件</button> |
触发自定义事件:
1 | triggerCustomEvent() { |
上面绑定自定义事件还有第二种方式:
1 | <TodoFooter ref="todofooter"></TodoFooter> |
解绑自定义事件:
vm.$off( [event, callback] )
参数:
{string | Array<string>} event
(只在 2.2.2+ 支持数组){Function} [callback]
用法:
移除自定义事件监听器。
- 如果没有提供参数,则移除所有的事件监听器;
- 如果只提供了事件,则移除该事件所有的监听器;
- 如果同时提供了事件与回调,则只移除这个回调的监听器。
1 | this.$off("custom-event") |
将原生事件绑定到组件
你可能有很多次想要在一个组件的根元素上直接监听一个原生事件。这时,你可以使用 v-on
的 .native
修饰符:
1 | <TodoFooter @click.native="show"></TodoFooter> |
全局事件总线
vue组件中的数据传递最最常见的就是父子组件之间的传递。父传子通过props向下传递数据给子组件;子传父通过$emit发送事件,并携带数据给父组件。而有时两个组件之间毫无关系,或者他们之间的结构复杂,如何传递数据呢?这时就要用到 vue 中的事件总线 EventBus的概念。
使用 EventBus
- 第一种方式: 可以在 main.js中,初始化 EventBus
1 | new Vue({ |
X组件想接收数据,则在X组件中给$bus
绑定自定义事件,事件的回调留在A组件自身。
1 | methods(){ |
Y组件想要给X组件传递数据,则在Y组件中发送事件
1 | this.$bus.$emit('xxxx',param) |
在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件
语法:this.$EventBus.$off(要移除监听的事件名)
$nextTick
vm.$nextTick( [callback] )
参数:
{Function} [callback]
用法:
将回调延迟到下次 DOM 更新循环之后执行。
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16new Vue({
// ...
methods: {
// ...
example: function () {
// 修改数据
this.message = 'changed'
// DOM 还没有更新
this.$nextTick(function () {
// DOM 现在更新了
// `this` 绑定到当前实例
this.doSomethingElse()
})
}
}
})
过渡&动画
单元素/组件的过渡
Vue 提供了 transition
的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡
- 条件渲染 (使用
v-if
) - 条件展示 (使用
v-show
) - 动态组件
- 组件根节点
这里是一个典型的例子:
1 | <div id="demo"> |
1 | new Vue({ |
1 | .fade-enter-active, .fade-leave-active { |
在进入/离开的过渡中,会有 6 个 class 切换。
v-enter
:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。v-enter-active
:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。v-enter-to
:定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时v-enter
被移除),在过渡/动画完成之后移除。v-leave
:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。v-leave-active
:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。v-leave-to
:定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时v-leave
被删除),在过渡/动画完成之后移除。
对于这些在过渡中切换的类名来说,如果你使用一个没有名字的 <transition>
,则 v-
是这些类名的默认前缀。如果你使用了 <transition name="my-transition">
,那么 v-enter
会替换为 my-transition-enter
。
v-enter-active
和 v-leave-active
可以控制进入/离开过渡的不同的缓和曲线。
CSS 过渡
常用的过渡都是使用 CSS 过渡。
下面是一个简单例子:
1 | <div id="example-1"> |
1 | new Vue({ |
1 | /* 可以设置不同的进入和离开动画 */ |
第三方 CSS 动画库
我们可以通过以下 attribute 来自定义过渡类名:
enter-class
enter-active-class
enter-to-class
leave-class
leave-active-class
leave-to-class
他们的优先级高于普通的类名,这对于 Vue 的过渡系统和其他第三方 CSS 动画库,如 Animate.css 结合使用十分有用。
Install with npm:
1 | npm install animate.css |
Import it into your file:
1 | import "animate.css" |
1 | <div id="demo"> |
那么怎么同时渲染整个列表,比如使用 v-for
?在这种场景中,使用 <transition-group>
组件。在我们深入例子之前,先了解关于这个组件的几个特点:
- 不同于
<transition>
,它会以一个真实元素呈现:默认为一个<span>
。你也可以通过tag
attribute 更换为其他元素。 - 过渡模式不可用,因为我们不再相互切换特有的元素。
- 内部元素总是需要提供唯一的
key
attribute 值。 - CSS 过渡的类将会应用在内部的元素中,而不是这个组/容器本身。
Vue-CLI配置代理
如果你的前端应用和后端 API 服务器没有运行在同一个主机上,你需要在开发环境下将 API 请求代理到 API 服务器。这个问题可以通过 vue.config.js
中的 devServer.proxy
选项来配置。
resolve CORS policy
Access-Control-Allow-Origin
devServer.proxy
可以是一个指向开发环境 API 服务器的字符串:
1 | module.exports = { |
这会告诉开发服务器将任何未知请求 (没有匹配到静态文件的请求) 代理到http://localhost:4000
。
说明:
- 优点:配置简单,请求资源时直接发给前端(8080)即可。
- 缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
- 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)
配置多个代理
1 | module.exports = { |
说明:
- 优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
- 缺点:配置略微繁琐,请求资源时必须加前缀。
插槽(slot)
Slot 通俗的理解就是“占坑”,在组件模板中占好了位置,当使用该组件标签时候,组件标签里面的内容就会自动填坑(替换组件模板中slot
位置)
并且可以作为承载分发内容的出口
因为在2.6.0中,具名插槽 和 作用域插槽 引入了一个新的统一的语法 (即v-slot
指令)。它取代了 slot
和 slot-scope
插槽内容
Vue 实现了一套内容分发的 API,这套 API 的设计灵感源自 Web Components 规范草案,将 <slot>
元素作为承载分发内容的出口。
它允许你像这样合成组件:
1 | <HelloWorld> |
然后你在 HelloWorld
组件中可能会写为:
1 | <div class="hello"> |
当组件渲染的时候,<slot></slot>
将会被替换为“Hello World”。插槽内可以包含任何模板代码,包括 HTML。
如果HelloWorld
组件 的 template
中没有包含一个 <slot>
元素,则该组件起始标签<HelloWorld>
和结束标签 </HelloWorld>
之间的任何内容都会被抛弃。
具名插槽
对于这样的情况,<slot>
元素有一个特殊的 attribute:name
1 | <div class="container"> |
一个不带 name
的 <slot>
出口会带有隐含的名字“default”。
在向具名插槽提供内容的时候,我们可以在一个 <template>
元素上使用 v-slot
指令,并以 v-slot
的参数的形式提供其名称:
1 | <HelloWorld> |
现在 <template>
元素中的所有内容都将会被传入相应的插槽。任何没有被包裹在带有v-solt
的 <template>
中的内容都会被视为默认插槽的内容。
然而,如果你希望更明确一些,仍然可以在一个 <template>
中包裹默认插槽的内容:<template>
1 | <HelloWorld> |
任何一种写法都会渲染出:
1 | <div class="container"> |
1 注意 v-slot 只能添加在 <template> 上
具名插槽的缩写
跟 v-on
和 v-bind
一样,v-slot
也有缩写,即把参数之前的所有内容 (v-slot:
) 替换为字符 #
。例如 v-slot:header
可以被重写为 #header
:
1 | <HelloWorld> |
作用域插槽
有时让插槽内容能够访问子组件中才有的数据是很有用的。例如,父组件访问子组件中才有的数据
在子组件HelloWorld
中先定义数据
1 | data() { |
为了让 user
在父级的插槽内容中可用,我们可以将 user
作为 <slot>
元素的一个 attribute 绑定上去:
1 | <span> |
在父组件中使用子组件的数据:
1 | <HelloWorld> |
效果如下: