Vue2.x vue-cli,生命周期,computed,watch,自定义指令
vue-cli环境配置、项目安装、目录树
我们使用webpack也可以手动安装配置vue脚手架环境,但是有一套更简单一点的vue-cli的构建工具,大大降低了webpack的使用难度。
一、配置vue的脚手架环境
npm install -g vue-cli
完成之后查看vue版本:
vue -V
如果终端承认vue命令,那么脚手架安装成功,否则要么脚手架安装失败,要么就是需要配置vue的path变量。
二、生成项目模板
1. 查看所有vue命令
vue -h
运行结果:
Options:
-V, --version output the version number
-h, --help output usage information
Commands:
init generate a new project from a template
list list available official templates
build prototype a new project
create (for v3 warning only)
help [cmd] display help for [cmd]
2. 构建vue项目
vue init webpack my-pro
在安装过程中,会出现以下一些选择:
? Project name 输入项目名称
? Project description 输入项目描述
? Author 作者
? Vue build 打包方式,回车就好了
? Install vue-router? 选择 Y 使用 vue-router,输入 N 不使用
? Use ESLint to lint your code? 代码规范,推荐 N
? Setup unit tests with Karma + Mocha? 单元测试,推荐 N
? Setup e2e tests with Nightwatch? E2E测试,N
3. 项目的目录结构
├── README.md // 项目说明文档
├── node_modules // 依赖包目录
├── build // webpack相关配置文件(都已配置好,一般无需再配置)
│ ├── build.js //生成环境构建
│ ├── check-versions.js //版本检查(node,npm)
│ ├── dev-client.js //开发服务器热重载 (实现页面的自动刷新)
│ ├── dev-server.js //构建本地服务器(npm run dev)
│ ├── utils.js // 构建相关工具
│ ├── vue-loader.conf.js //csss 加载器配置
│ ├── webpack.base.conf.js //webpack基础配置
│ ├── webpack.dev.conf.js // webpack开发环境配置
│ └── webpack.prod.conf.js //webpack生产环境配置
├── config // vue基本配置文件(可配置监听端口,打包输出等)
│ ├── dev.env.js // 项目开发环境配置
│ ├── index.js // 项目主要配置(包括监听端口、打包路径等)
│ └── prod.env.js // 生产环境配置
├── index.html // 项目入口文件
├── package-lock.json // npm5 新增文件,优化性能
├── package.json // 项目依赖包配置文件
├── src // 项目核心文件(存放我们编写的源码文件)
│ ├── App.vue // 根组件
│ ├── assets // 静态资源(样式类文件、如css,less,和一些外部的js文件)
│ │ └── css //样式
│ │ └── font //字体
│ │ └── images //图片
│ ├── components // 组件目录
│ │ └── Hello.vue // 测试组件
│ ├── main.js // 入口js文件
│ └── router // 路由配置文件夹
│ └── index.js // 路由配置文件
└── static // 静态资源目录(一般存放图片类)
注:assets和static文件夹的区别
assets目录中的文件会被webpack处理解析为模块依赖,只支持相对路径形式。例如,在 <img src="./logo.png">
和 background: url(./logo.png)中
,"./logo.png"
是相对的资源路径,将由Webpack解析为模块依赖。
static/
目录下的文件并不会被Webpack处理:它们会直接被复制到最终的打包目录(默认是dist/static
)下。必须使用绝对路径引用这些文件,这是通过在 config.js
文件中的 build.assetsPublicPath
和 build.assetsSubDirectory
连接来确定的。
任何放在 static/
中文件需要以绝对路径的形式引用:/static/[filename]
。
在我们实际的开发中,总的来说:static放不会变动的文件 assets放可能会变动的文件。
4. 安装好了之后启动项目
npm run dev
Component的封装、调用
vue-cli开发的是单页面的应用,只有一个 html 文件,在开发时,我们将 整个项目按功能划分成模块,每个模块都可以被任意的复用。高效率,低耦合。
模块的具体呈现形式就是组件,一个模块就是一个组件,每个组件有自己的数据、函数、周期等。
项目默认安装好了之后,可以在 src 文件夹中看到有一个默认的HelloWorld.vue文件。这就是默认安装的组件,一个组件就是一个 vue 文件。
我们在 src 的 components 文件夹中创建组件。
一、组件的基本构成
<template>
<div class="home">
<!-- 当前组件中的结构 -->
</div>
</template>
<script>
// 当前组件的脚本
export default {
// 当前组件的名称
name: 'Home',
data () {
return {
// 当前组件中所有的数据
}
},
methods: {
// 当前组件中所有的函数
}
}
</script>
<style>
/* 当前组件的样式 */
</style>
注:
- 组件中的结构必须包含在
<template>
标签中。 - 组件所有的子结构都必须包含在一个父级标签中,建议使用block标签。
- 组件的
<style>
标签中的样式,是全局的样式,如果这个组件被其他组件调用,那么这个组件中的style样式会作用于当前页面中的所用同选择器元素。 <style>
标签加了scoped(如<style scoped></style>
)属性之后,其样式就是局部的,只作用于当前组件。
二、组件的调用
组件的调用方式有多种,作为子组件被调用,路由调用,插槽调用等,在这里我们先说传统的调用方式,即作为子组件被调用。
调用组建时,需要这么几个步骤:
- 导出当前组件,
exprot default {}
。 - 在要使用当前组件的父级组件中,导入当前组件。
- 将当前组件传入父组件的components中。
- 在父组件中使用标签形式调用组件。
<!-- App.vue -->
<template>
<div id="app">
<!--
在App.vue中调用Home.vue
注: App.vue是根组件,它是和index.html有直接关系,在main.js中被实例化
-->
<Home/>
</div>
</template>
<script>
// 导入Home.vue
import Home from './components/home'
export default {
name: 'App',
components: {
// 在此声明
Home
}
}
</script>
<style></style>
生命周期钩子函数
每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等,我们把这一系列的过程称为组件的生命周期(组件从注册到销毁的整个过程)。我们有时候需要在组件生命周期的某个过程中,执行某些代码,基于此,vue 提供了生命周期钩子函数,给了用户在不同阶段添加自己的代码的机会。
但是在此之前,我们要详细的介绍下组件的声明周期,以及生命周期中每个阶段组件完成和未完成的部分。
一、组件的生命周期
从图中可以看到,vue 为生命周期提供了 8 个钩子函数:
- beforeCreate: 创建前
- created: 创建后
- beforeMount: 挂载前
- mounted: 挂载后
- beforeUpate: 更新前
- upated: 更新后
- beforeDestoy: 销毁前
- destoyed: 销毁后
二、每个钩子函数执行时的组件形态
我们可以在组件的生命周期钩子函数中执行一些代码。
<template>
<div id="app">
<button @click="flag = !flag">点击</button>
<Home v-if="flag"/>
</div>
</template>
<script>
import Home from './components/home'
export default {
name: 'App',
components: {
Home
},
data() {
return {
flag: true,
info: 'i am info of component'
}
},
//数据没有 元素没有
beforeCreate:function(){
console.log("创建前========");
console.log(this.info);
console.log(this.$el);
},
//数据有 元素没有
created:function(){
console.log("已创建========");
console.log(this.info);
console.log(this.$el);
},
//数据有 元素有 虚拟dom
beforeMount:function(){
console.log("mount之前========");
console.log(this.info);
console.log(this.$el);
},
//数据有 元素有,真实dom
mounted:function(){
console.log("mounted========");
console.log(this.info);
console.log(this.$el);
},
beforeUpdate:function(){
console.log("更新前========");
console.log(this.flag);
console.log(this.$el);
},
updated:function(){
console.log("更新完成========");
console.log(this.flag);
console.log(this.$el);
},
beforeDestroy:function(){
console.log("销毁前========");
console.log(this.info);
console.log(this.$el);
},
destroyed:function(){
console.log("已销毁========");
console.log(this.info);
console.log(this.$el);
}
}
</script>
<style></style>
注:
- 因为 vue 是通过控制数据来控制页面元素的,所以当数据发生变化时,页面中的元素也会随之响应,所以更新发生在数据变化时。beforeUpdate时,数据和节点都已经更新了。
- 可以在 beforeCreate 中,执行一些loading效果。
- created中结束loading,还可以做一些初始化操作,可以将一些页面刚开始加载就执行的代码放在这里,但是要注意,这个时候是没有节点的。
- mounted:在这发起axios请求,拿回数据,配合路由钩子做一些事情,这个时候有节点又有数据,可以实例化一些插件效果。
- beforeDestory: destoryed :当前组件已被删除,清空相关内容。
computed和watch
computed(计算数据) 和 watch(观察者) 是 vue 组件中非常重要的两个功能。
一、computed
创建一个组建时,组件中的数据都声明在 data 中,但是有些数据需要依赖 data 中的其他数据计算,这个数据会随着依赖数据的变化而变化(比如购物车中被选中的商品总价,需要通过购物车中的商品列表计算,当购物车中商品发生变化,这个总价也会变化)。
我们把这种需要依赖其他变量进行处理之后产生的数据都声明在computed(计算属性)中。 计算属性允许我们对指定的视图,复杂的值计算。这些值将绑定到依赖项值,只在需要时更新。
计算属性示例:
<template>
<div class="home">
count: <input type="number" v-model.number="current.count">
price: <input type="number" v-model.number="current.price">
<button @click="addGood">添加</button>
<h3>{{ sum }}</h3>
</div>
</template>
<script>
export default {
name: "Home",
data() {
return {
current: {
count: 0,
price: 0
},
list: [{
count: 3,
price: 39.79
},{
count: 1,
price: 109.34
},{
count: 3,
price: 0.45
}]
}
},
methods: {
addGood() {
this.list.push(this.current)
}
},
computed: {
sum() {
let sum_ = 0;
for(let l of this.list) {
sum_ += l.count * l.price;
}
return sum_;
}
}
}
</script>
注:
computed和methods有时候从代码角度而言,没有区别,但是底层的机制是完全不同的。
methods中的函数,在每次使用这个数据时都要调用,computed依赖于data中的数据,只有在它的相关依赖数据发生改变时才会重新求值,如果依赖数据不变,计算属性会缓存这个数据上次的值,不会重复调用复杂的计算过程。
官方文档反复强调对于任何复杂逻辑,都应当使用computed计算属性
computed和methods的区别示例:
<template>
<div class="home">
<button @click="n++">a变化</button>
<button @click="m++">b变化</button>
<h3>{{ sum_of_c }}</h3>
<h3>{{ sum_of_c }}</h3>
<h3>{{ sum_of_c }}</h3>
<hr>
<h3>{{ sum_of_m() }}</h3>
<h3>{{ sum_of_m() }}</h3>
<h3>{{ sum_of_m() }}</h3>
</div>
</template>
<script>
export default {
name: "Home",
data() {
return {
n: 0,
m: 0
}
},
methods: {
sum_of_m() {
console.log('methods中的sum执行了');
return this.n + this.m;
}
},
computed: {
sum_of_c() {
console.log('computed中的sum执行了');
return this.n + this.m;
}
}
}
</script>
二、watch
有时候我们需要在 data 中的数据发生变化时做一些操作,那就需要监听这个数据的变化,vue 提供了 watch(侦听器),来监听数据的变化。
computed 计算依赖于某些数据的值。watch 监听数据本身。
虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
方式一:
<template>
<div class="test2">
<button @click="num = 1000">点击</button>
</div>
</template>
<script>
export default {
name: "Test2",
data() {
return {
num: 100
}
},
watch: {
num(newVal, oldVal) {
console.log(newVal, oldVal);
}
}
}
</script>
这种写法也可以改写成这样:
// ...
methods: {
numChange(newVal, oldVal) {
console.log(newVal, oldVal);
}
},
watch: {
num: 'numChange'
}
// ...
方式二:
方式一使用 watch 时有一个特点,就是当值第一次绑定的时候,不会执行监听函数,只有值发生改变才会执行。如果我们需要在最初绑定值的时候也执行函数,则就需要用到immediate属性。
// ...
watch: {
num: {
handler(newVal, oldVal) {
console.log(newVal, oldVal)
},
// immediate表示在watch中首次绑定的时候,是否执行handler,值为true则表示在watch中声明的时候,就立即执行handler方法,值为false,则和一般使用watch一样,在数据发生变化的时候才执行handler。
immediate: true
}
}
// ...
watch监听对象时,对象的属性和属性的属性发生变化时,默认不会触发对于这个对象的监听。想要在对象的任意属性(无论嵌套多复杂)改变时,执行对象的watch函数,需要开启deep。
watch: {
obj: {
handler(newVal, oldVal) {
// code
},
deep: true
}
}
// ...
只是想监听对象的某个属性的变化:
watch: {
'obj.key': {
handler(newVal, oldVal) {
// code
}
}
}
// ...
自定义指令
除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也允许注册自定义指令。注意,在 Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,Vue提供了一种更简单的方式-自定义指令,方便我们对DOM的操作。
自定义指令声明之后的调用方式和使用方式和 Vue 提供的默认指令一样。
一、自定义指令的小示例:
全局的自定义指令,在 main.js 中创建
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时
inserted: function (el) {
// 聚焦元素
el.focus();
}
});
局部的自定义指令,创建在组件中:
export default {
name: 'Home',
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
}
}
无论是局部的还是全局的,调用方式都相同,只是全局的指令可以被任意组件调用,局部的指令只能被当前组件调用:
<input v-focus type="text">
二、钩子函数和其参数
一个指令定义对象可以提供如下几个钩子函数 (均为可选):
- bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
- inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
- update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
- componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
- unbind:只调用一次,指令与元素解绑时调用。
指令钩子函数会被传入以下参数:
- el:指令所绑定的元素,可以用来直接操作 DOM 。
- binding:一个对象,包含以下属性:
- name:指令名,不包括 v- 前缀。
- value:指令的绑定值,例如:v-my-directive=“1 + 1” 中,绑定值为 2。
- oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
- expression:字符串形式的指令表达式。例如 v-my-directive=“1 + 1” 中,表达式为 “1 + 1”。
- arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 “foo”。
- modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。
- vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
- oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
更加具体的示例:
Vue.directive('demo', {
bind: function (el, binding, vnode) {
var s = JSON.stringify
el.innerHTML =
'name: ' + s(binding.name) + '<br>' +
'value: ' + s(binding.value) + '<br>' +
'expression: ' + s(binding.expression) + '<br>' +
'argument: ' + s(binding.arg) + '<br>' +
'modifiers: ' + s(binding.modifiers) + '<br>' +
'vnode keys: ' + Object.keys(vnode).join(', ');
}
})
调用:
<template>
<div class="test3" v-demo:foo.a.b="message"></div>
</template>
<script>
export default {
name: "Test3",
data() {
return {
message: 'hello!'
}
}
}
</script>
注:
- 指令的参数可以是动态的。例如,在 v-mydirective:[argument]=“value” 中,argument 参数可以根据组件实例数据进行更新!这使得自定义指令可以在应用中被灵活使用。
- 在很多时候,你可能想在 bind 和 update 时触发相同行为,而不关心其它的钩子。比如这样写:
Vue.directive('color-swatch', function (el, binding) {
el.style.backgroundColor = binding.value;
})
- 如果指令需要多个值,可以传入一个 JavaScript 对象字面量。记住,指令函数能够接受所有合法的 JavaScript 表达式。
Vue.directive('demo', function (el, binding) {
console.log(binding.value.color) // => "white"
console.log(binding.value.text) // => "hello!"
})
<div v-demo="{ color: 'white', text: 'hello!' }"></div>
参考文档
Vue中的computed
Vue中computed和methods中的区别
Vue中的watch详解