Vue入门
1.Mac下快速搭建Vue项目
sudo npm install -g vue //下载vue
sudo npm install -g vue-cli //下载vue-cli脚手架
vue init webpack VueTest //用脚手架创建VueTest项目
项目创建成功后,进入项目目录,tnpm run dev
即可启动项目。
问题描述
mac下出现:bash: vue: command not found
解决方法
- 重新安装vue,拷贝红色部分路径
- 在终点输入
vi .bash_profile
打开环境编辑器。 - i进入编辑模式
- 输入
export PATH="$PATH:(复制的地址)"
esc
退出编辑模式,:wq
保存环境变量source .bash_profile
,立即生效环境变量
2.Vue对象属性
vue常用的对象属性有:
el
:实例对象的dom元素id
data
:实例对象数据
methods
:实例对象定义的方法
filters
:实例对象定义的过滤器
computed
:以缓存的方式定义直接放回结果的方法
watch
:监听data中某个属性值变更,相较于updated更加灵活
components
:引入的组件
template
:模板代码
router
:路由配置
8大生命周期
:beforeCreate/created
、beforeMount/mounted
、beforeUpdate/updated
、beforeDestroy/destrotyed
main.js
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import ElementUI from 'element-ui'
import moment from 'moment'
import 'element-ui/lib/theme-chalk/index.css'
Vue.config.productionTip = false
Vue.use(ElementUI)
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>',
data: {
msg: "全局数据初始化"
},
methods: {
mfn: function () {
//mfn本身代码一个函数mfn()===cfn
//methods使用时可以参数,但是不进行缓存,每次都需执行函数获得结果
//methods中的函数不能是箭头函数,不然找不到当前实例对象
console.log("全局函数方法")
return "全局函数"
}
},
computed: {
cfn: function () {
//cfn代表的是函数返回结果而不是函数本身,在使用的时候不能传参
//计算属性是为性能优化而设计的,调用时会和本地data进行比较,有变更则采用新增渲染
//computed中的函数不能是箭头函数,不然找不到当前实例对象
console.log("计算属性方法,只返回纯粹的结果")
return "全局函数结果"
}
}
})
HelloWorld.vue
<template>
<div>
<h1>Vue学习</h1>
<h2>模板修改</h2>
<div>{{msg}}</div>
<div class="test">
<div class="testItem">当前值:{{value}}</div>
<i @click="addItem" class="el-icon-circle-plus-outline testIcon"></i>
<i @click="subItem" class="el-icon-remove-outline testIcon"></i>
</div>
<div>
<h2>根节点测试</h2>
<div>当前日期:{{date|DFormat('YYYY年MM月DD日')}}</div>
<div>{{this.$root.msg}}</div>
<div>mfn是一个函数,返回的函数体:{{this.$root.mfn}}</div>
<div>cfn是函数结果:{{this.$root.cfn}}</div>
<div>mfn()是函数执行结果:{{this.$root.mfn()}}</div>
</div>
</div>
</template>
<script>
let moment = require("moment");
export default {
//组件名称
name: "HelloWorld",
/*组件数据定义start*/
data() {
return {
msg: "欢迎来到我的vue讲堂!",
value: 0,
date: moment().format("YYYY-MM-DD")
};
},
/*组件数据定义end*/
/*组件方法定义start*/
methods: {
addItem() {
this.value = this.value + 1;
},
subItem() {
this.value = this.value - 1;
}
},
/*组件方法定义end*/
/*过滤器*start*/
filters: {
DFormat: (date, format) => {
//过滤器,这里面的this为undefined
return moment(date).format(format);
}
},
/*过滤器*end*/
/*监听器*start*/
watch: {
value: function(newValue, oldValue) {
//监听data中value值的变更,一般用于异步数据变更的联动处理。
//只有声明的值才会监听到,updated只要数据变更均将执行
console.log("新值:" + newValue, "旧值:" + oldValue);
}
},
/*监听器器*end*/
/*组件生命周期start*/
beforeCreate() {
//主要进行对data的监听和事件的初始化,此时data和methods等都还无法访问。
console.log("******组件创建前******");
},
created() {
//数据双向绑定完成,此时只是vue对象的实例化,组件还未开始解析,data和methods等完成了初始化。
console.log("******组件创建完成******");
},
beforeMount() {
//编译模板,将模板和数据结合生成真实的html,生成的html只是虚拟dom还未渲染到真实页面上
console.log("******组件挂载前******");
},
mounted() {
//此时可以进行初始化数据请求和dom节点操作
console.log("******组件挂载完成******");
},
beforeUpdate() {
//数据变更之前,可以进行新旧数据对比,避免重复渲染
console.log("******数据变更前******");
},
updated() {
//数据变更完成,此时可以监听变更数据实现联动
console.log("******数据变更完成******");
},
beforeDestroy() {
//移除事件和定时器之内的内存消耗资源
console.log("******组件销毁前******");
},
destroyed() {
console.log("******组件销毁完成******");
}
/*组件生命周期end*/
};
</script>
<style>
.test {
border: 1px solid #d6cece;
padding: 15px;
display: flex;
border-radius: 5px;
}
.test .testItem {
padding: 5px 8px;
}
.test .testIcon {
font-size: 24px;
padding: 5px 8px;
color: #cac6c6;
}
.test .testIcon:hover {
color: #a7a7a7;
}
</style>
效果图
3.Vue常用指令
指令 (Directives) 是带有 v-
前缀的特殊特性。指令的作用是其表达式的值改变时,响应式的作用于DOM元素。
3.1常用指令
{{ 表达式 }}
:以文本的形式显示表达式的值,只能包含单一表单式。
v-html=参数
:解析参数值为html代码作为dom元素的子元素
v-if=表达式
:根据表达式的值判断是否渲染该指令对应的dom元素
v-show=表达式
:以css样式改变dom元素的显隐,相当于是否添加display:none
样式。
v-bind:属性=参数
:接收data中一个参数的值最为dom元素某一属性的值。缩写为:属性=参数
。
v-model=参数
:只适用于表单,相当于v-bind:value=参数
和v-on:input="参数=表单值"
的语法糖。
v-on:事件名=事件处理函数
:用于进行事件绑定。缩写为@事件名=事件处理函数
。
v-model
在内部为不同的输入元素使用不同的属性并抛出不同的事件:
- text 和 textarea 元素使用
value
属性和input
事件; - checkbox 和 radio 使用
checked
属性和change
事件; - select 字段将
value
作为 prop 并将change
作为事件。
3.2条件渲染
v-if
、v-else-if
、v-else
可组合使用,用于dom元素按条件渲染。
v-show
:dom元素必定渲染,只是控制display属性来实现显隐。
v-for="(item,index) in items"
:item为数组元素、index为元素索引(可省略)、items为数组。用于遍历数组。
v-for="(value,name,index) in obj"
:value为对象属性值,name:对象属性名(可省略),index:对象属性索引(可省略)。
注意
- vue在进行dom比较时,如果模板元素结构一致,并不会删除模板重建,而是修改已存在的模板,变更其不同的地方,这样会大大节省资源,但也存在问题,之前模板的遗留数据可能会影响当前模板,所有可通过给每个相同的元素定义不同的key,这样模板渲染的时候会当成一个新的模板去处理。
- vue不能检测到通过索引直接设置数组项的值和直接修改数组长度的数据变动。例如:
vm.items[indexOfItem] = newValue
;vm.items.length = newLength
设置数组中某项的值可以通过:
vm.$set(vm.items, indexOfItem, newValue)
修改数组长度可以通过:
vm.items.splice(newLength)
-
vue不能检测到对象属性的增删变动。
可以用
Object.assign({},对象,{新的属性})
,以一个新的对象替换原对象。
3.3自定义指令
自定义命令有5个钩子函数:
bind
:只调用一次,将指令和dom元素绑定
inserted
:绑定的dom元素插入父元素节点时调用,此时不一定已插入了文档流中。
update
:绑定的dom元素更新时调用,其子节点不一定完成更新。
componentUpdated
:绑定的dom元素和其子元素均更新完成时调用
unbind
:解除指令和元素间的绑定,只执行一次
每个钩子函数均有2个参数
el
:绑定的dom元素
bind
:绑定对象bind:{
name:指令名
value:指令绑定值
oldValue:旧的绑定值
expression:指令值表达式
arg:传入指令的参数
modifiers:修饰符
}
vnode
:编译后生成的虚拟dom
oldVnode
:编译前的虚拟dom
Test.vue
<template>
<div>
<h1>自定义指令</h1>
<div>
<input :value="value" placeholder="请输入内容" v-blur="{set:this}" />
<div>{{value}}</div>
</div>
</div>
</template>
<script>
export default {
name: "Test",
data() {
return {
value: "你好!"
};
},
directives: {
blur: {
bind: function(el, bindObj) {
el.onblur = function() {
//bindObj.value.set(el.value);不能生效,
//需在html中手动传入{set:this},set才会生效
bindObj.value.set["value"] = el.value;
};
console.log("指令绑定到dom元素上时调用,只执行一次");
},
inserted: function(el, bindObj) {
console.log("被绑定的dom元素插入父节点时调用");
},
update: function(el, bindObj) {
console.log("绑定组件节点变更时调用,有可能子组件还未更新");
},
componentUpdated: function() {
console.log("绑定组件及其子组件变更完成时调用");
},
unbind: function() {
console.log("解除指令和dom元素间的绑定");
}
}
}
};
</script>
这里自定义了v-blur指令实现输入框失去焦点时变更输入框的值,相当于
v-model="value"
3.4 插槽(slot)
插槽就是一个占位符,方便将定义好的内容替换到定义的插槽位置。
<el-collapse-item>
<template slot="title">
<div>aaa</div>
</template>
</el-collapse-item>
以手风琴组件的一个子元素为例,el-collapse-item具有一个title属性,这里使用
slot="title"
标识title属性值就是模板定义的<div>aaa</div>
vue2.6.0后slot
和slot-scope
被v-slot
取代。
v-slot可以用#缩写,但仅限于具名插槽,即有name属性的可以缩写,没有的不行
v-slot:header
可以缩写为:#header
子组件
<template>
<div>
<div>
<h1>头部标题</h1>
<!-- 插入头部内容 -->
<slot name="header"></slot>
</div>
<p>我是第一行内容</p>
<p>我是第二行内容</p>
<div>
<h1>尾部标题</h1>
<!-- 插入尾部内容 -->
<slot name="footer"></slot>
</div>
</div>
</template>
<script>
export default {
name: "child"
};
</script>
父组件
<template>
<div>
<div>slot演示</div>
<Child>
<template v-slot:header>页面头部内容在这里</template>
<template v-slot:footer>页面头部内容在这里</template>
</Child>
</div>
</template>
<script>
import Child from './child.vue'
export default {
name: "father",
components:{
Child
}
};
</script>
效果
由上可以看出
<slot name="header"></slot>
定义了占位位置,<template v-slot:header>页面头部内容在这里</template>
定义了占位内容,通过name属性来将其对应替换
4.模板语法
vue是基于html特性进行模板设计的,所有其第大小写不敏感,浏览器会统一将之转换为小写。所有的驼峰命名会以-拆分。
{{ testName }}
这种形式的字符串模板不会被转换为小写,而是直接查找data中的testName。
4.1插入值
{{表达式}}
:表达式只能由唯一一个,支撑常见的javaScript表达式
不支持流程表达,例如
if(true){return "a"}
,if流程可用三目运算替代,其他流程需用指令多个处理结果可以使用过滤器,例如
{{name | fullName | useFilter}}
4.2指令
指令以v-开头
,可以接收一个参数,例如
<a v-if:href="url"></div>
href是接收参数,是元素上的一个属性,url为数据值,与data中的url相绑定
2.6版本后支持动态参数,需用
[]
包裹,例如v-if[attr]="url"
,attr可动态变化。
4.3修饰符
用于指定指令该以怎样的方式和元素进行绑定。
常用的事件修饰符有:.stop
(阻止事件冒泡)、.pervent
(阻止浏览器默认行为)、.capture
、.self
、.once
、.passive
常用的按键修饰符有:.enter
、.tab
、.delete
、.esc
、.space
、.up
、.down
、.left
、.right
常用的系统修饰符有:.ctrl
、.alt
、.shift
、.meta
常用鼠标修饰符有:.left
、.right
、.middle
4.4css和style
可以通过v-bind:class=对象
,实现class的动态切换。例如:
<div v-bind:class="{active:isActive}">模板代码</div>
active为样式名,isActive为data中的属性,当isActive为true时则添加样式class=active,否则则class为空。
v-bind:class可以和已声明的class样式进行合并。例如:
<div class="a" v-bind:class="{active:isActive}">模板代码</div>
解析后的结果为:
<div class="a active">模板代码</div>
可以通过v-bind:style={样式名:样式值}
。例如:
<div class="a" v-bind:class="{active:isActive}" v-bind:style="{fontWeight:700}">父组件</div>
4.5 scoped穿透
vue中<style>
包含有一个scoped
属性,其可以让样式只在当前组件作用域生效。
原理是为每个定义的样式添加了一个唯一标识,并且为使用该样式的元素添加这个标识属性,只有元素属性和样式标识相同,才能生效。
<style scoped>
.example {
color: red;
}
</style>
<template>
<div class="example">hi</div>
</template>
</style>
转译后
<style>
.example[data-v-5558831a] {
color: red;
}
</style>
<template>
<div class="example" data-v-5558831a>hi</div>
</template>
可以看到.example后跟了一个标识,且,使用该样式的div也多了一个data-v-5558831a属性。
虽然scoped解决了多层级之间的样式相互影响,但是,如果我们引用第三方组件,并且需要修改第三方组件时,就会存在样式无法覆盖问题。究其原因就是样式作用域的限制。
解决方案
<style scoped>
外层 >>> 第三方组件 {
样式
}
</style>
>>>
该符号可以进行样式穿透,让我们定义的作用域样式,穿透到第三方组件去。
另外也可以定义一个不包含scoped的style,定义全局样式,通过全局样式进行覆盖。
<style>
//全局样式,直接在这里进行第三方组件样式覆盖
</style>
5.组件使用
5.1组件构成
<template>
<!-- 模板html代码 -->
<div class="a" v-bind:class="{active:isActive}" v-bind:style="{fontWeight:700}">父组件</div>
</template>
<script>
//数据和事件定义
export default {
name: "father",
data() {
return {
isActive: true
};
}
};
</script>
<style>
/* 样式定义 */
.active {
color: red;
font-size: 20px;
}
</style>
组件中的data必须是一个函数,vue实例中的data是个对象
5.2生命周期
5.3数据传递
父组件通过prop
向子组件传递数据,其所定义的prop
都将可被子组件利用。
所有绑定在父组件上的属性我们都可以在子组件中通过
this.$props.属性名获取
子组件通过$emit
向父组件传递参数。
在模板中可通过
$event
获取子组件传递的参数。父组件接收参数的属性必须是个事件,可为自定义事件,即子组件传递参数给父组件是通过回调函数完成的。
-
子组件可通过
this.$parent
访问父组件实例 -
父组件可通过
this.$children
访问子组件实例数组
this.$children
返回一个数组,但是数组的顺序并不能得到保证,所有最后通过父组件定义的数组来确子组件渲染顺序。
- 所有组件均可通过
this.$root
访问根组件实例 this.$refs
可访问ref
注册过的dom元素和组件实例this.$options
可获取实例初始化选项this.$data
可获取当前实例的data对象this.$forceUpdate()
可强制实例重新渲染this.$nextTick(fn)
可以实现dom元素更新后再调用fn,即我们数据变更完成后再调用定义的fn方法。
5.4示例
Father.vue
<template>
<div class="father">
<div v-bind:class="{active:isActive}">父组件</div>
<div class="child">
<Child :formList="formList" @submit-form="childData+=$event" />
</div>
<div>提交次数:{{childData}}</div>
</div>
</template>
<script>
//数据和事件定义
import Child from "./Child";
export default {
name: "father",
components: {
Child: Child
},
data() {
return {
isActive: true,
childData:1,
formList: [
{
id: 1,
name: "name",
label: "姓名",
type: "input",
defaultValue: "张三"
},
{
id: 2,
name: "like",
label: "爱好",
type: "select",
defaultValue: "a",
option: [
{ name: "a", label: "篮球" },
{ name: "b", label: "足球" },
{ name: "c", label: "羽毛球" },
{ name: "d", label: "乒乓球" },
{ name: "e", label: "网球" },
{ name: "f", label: "排球" },
{ name: "g", label: "棒球" }
]
},
{
id: 3,
name: "sex",
label: "性别",
type: "radio",
defaultValue: "1",
option: [{ name: "0", label: "男" }, { name: "1", label: "女" }]
}
]
};
}
};
</script>
<style>
/* 样式定义 */
.father {
background-color: #e8e8e8;
padding: 20px;
}
.child {
background-color: #fff;
padding: 15px;
border: 1px solid #bfbfbf;
border-radius: 5px;
text-align: left;
}
.active {
color: #333;
font-size: 20px;
padding-bottom: 15px;
}
</style>
Child.vue
<template>
<el-form ref="form" :model="form" label-width="80px">
<el-form-item :label="item.label" v-for="item in formList" :key="item.id" :prop="item.name">
<el-input
v-model="form.name"
style="width:200px;"
v-if="item.type == 'input'"
:rules="[{ required: true, message: '请输入姓名'}]"
></el-input>
<el-select v-model="form.like" v-if="item.type == 'select'">
<el-option :label="opt.label" :value="opt.name" v-for="opt in item.option" :key="opt.name"></el-option>
</el-select>
<el-radio-group v-model="form.sex" v-if="item.type == 'radio'">
<el-radio :label="opt.name" v-for="opt in item.option" :key="opt.name">{{opt.label}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm()">提交</el-button>
<el-button @click="resetForm()">重置</el-button>
</el-form-item>
</el-form>
</template>
<script>
export default {
name: "child",
props: {
formList: Array
},
data() {
return {
form: {},
initForm: {}
};
},
methods: {
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
console.log(this.form);
//$emit可以将附加参数上抛给父组件的指定监听函数,上报参数可以通过$event获取
this.$emit("submit-form",1)
} else {
return false;
}
});
},
resetForm() {
// this.$refs["form"].resetFields();
// Object.assign(this.$data, this.$options.data.call(this))
//上面2种方法均可实现数据重置,但重置的数据为初始化定义的数据,不是请求后的数据
this.form = Object.assign({}, this.initForm);
}
},
mounted() {
let formList = this.$props.formList;
let formObj = {};
for (let item of formList) {
formObj[item.name] = item.defaultValue;
}
let initForm = Object.assign({}, this.form, formObj);
this.form = initForm;
this.initForm = Object.assign({}, initForm);
}
};
</script>
效果图
6.插件引用
vue插件使用常见的有2种方法。
Vue.use(插件)
,例如:Vue.use(ElementUI)
Vue.prototype.定义插件名= 插件名
,例如:Vue.prototype.$moment= moment
vue插件暴露了install
方法,可实现自定义插件开发
MyPlugin.install = function (Vue, options) {
// 1. 添加全局方法或属性
Vue.myGlobalMethod = function () {
// 逻辑...
}
// 2. 添加全局资源
Vue.directive('my-directive', {
bind (el, binding, vnode, oldVnode) {
// 逻辑...
}
...
})
// 3. 注入组件选项
Vue.mixin({
created: function () {
// 逻辑...
}
...
})
// 4. 添加实例方法
Vue.prototype.$myMethod = function (methodOptions) {
// 逻辑...
}
}
7.数据双向绑定
vue的数据双向绑定是利用数据劫持和发布-订阅者模式实现的。
- 定义了一个监听器
Observer
,利用Object.defineProperty(obj, prop, descriptor)
来劫持各属性的setter
和getter
。当数据有变动时就通知给订阅者。 - 定义了一个阅读者
Watcher
,可以接受属性的变化通知并执行相应的函数,来更新视图。 - 定义了一个解析器
Compile
,对每个元素节点的指令进行了扫描和解析,根据指令初始化视图和订阅器。订阅者变化时,通知订阅器添加订阅者。