data
在组件内,data 必须是返回一个对象的函数
xxcomponent.vue
<script>
export default {
data(){
return {}
}
}
</>
特别的,cli 中 App.vue 的 data 也是一个函数。
data 中的属性值不被期望为函数,如果写成函数不会被执行,需要手动调用。
如果需要对数据进行一些操作或者变化,可以写在 computed 或者 methods 中改变或者
template
<div> {{xx()}} </div>
/template
<script>
export default {
data (){
return {
xx : () => 'hello, vue'
}
}
}
</script>
深入响应式原理
-
深入响应式原理:数据劫持和订阅发布
-
底层原理:
数据劫持:watch底层将data内的属性遍历使用Object.defienProperty()
方法,将所有属性设置get(){return value}
和set(val){}
函数变成可监听的响应式。
订阅发布:自定义事件声明vm.$on
和触发vm.emit
-
表现:
v-model
默认绑定表单元素的value值,当get
或者set
表单的value值时触发watcher
,更新VDOM,然后通过render
函数生成真实DOM
响应变化。 -
手动实现:Vue响应式实现 、深入Vue实现原理,实现一个响应式框架
非响应式属性
- 概念:
普通数据在绑定到DOM后再发生改变,DOM并不会响应随着改变。
只有在data内的才是响应式,在data外的都不是响应式的。 - 转换方法:
实例外:Vue.set(Obj,key,value)
实例内:this.$set
||vm.$set
- 底层原理:利用
Object.assign()
方法合并对象
数组检测
数组更新检测不到直接的操作,如 arr[0]=‘A’ 不会检测到更新。
由于 js 本身的一些原因, vue 中
watch
官网描述:
一个对象,键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。
Vue 实例将会在实例化时调用 $watch(),遍历 watch 对象的每一个属性。
思考:watch对象中的key就是响应式数据,value为函数,有可能是写入setter内的东西,因为
- watch内所有属性名都是data 中对应的属性名
- 属性值可以是一个函数,字符串,数组,对象
new Vue({
el:'#app',
data:{
msg:'msg',
list:[]
},
methods:{
logChange(){console.log('methods')}
},
watch:{
watch内所有属性名都为data中对应的属性名,属性值可以为methods内的方法,可以为函数,也可以为对象或者数组
msg(val){},
msg:'logChange',
list:{
deep:true,
handler(val,oldval){}
}
}
})
computed
vs watch
-
computed内变量名不能与data内变量重复,而watch内变量名要与data一致
-
computed=data中数据+一些逻辑计算,然后return出一个新值,然后将变量当作data内的变量一样来使用
-
异步操作,开销较大,逻辑复杂,强调点对点数据变化用
watch
-
操作逻辑,并不复杂选
computed
-
坑:
watch
内不写get
和set
,有handle
函数(虽然说用了监听机制,setter
和getter
什么的)computed
内像get
一样需要return
出值computed
中只通过get获取参数值,没有set无法改变参数值。当v-model的是计算属性时改变inptu的value会报错:Computed property "xxx" was assigned to but it has no setter.
组件
Vue.extend()
Vue.extend
返回一个子类构造函数,也就是带有预设参数的组件实例构造器。它的目的是创建一个Vue的子类并且返回相应的 constructor 。- Vue.extend是构造一个组件的语法器. 你给它参数 他给你一个组件 然后这个组件你可以作用到Vue.component 这个全局注册方法里, 也可以在任意vue模板里使用apple组件
- 后可使用vue.component进行实例化,或new extendName().$mount(query)方式进行实例化
- Vue.component()实际上是一个类似于Vue.directive() 和 Vue.filter()的注册方法,它的目的是给指定的一个constructor一个String类型的ID。实际上当你直接传递选项给Vue.component()的时候,它会在背后调用Vue.extend()
Vue.extend()
继承于Vue()
,所以他们用法类似,都有data
methods
钩子函数等属性
标准写法
html部分:
<div id="mount-point"></div>
js部分:
1. 通过extend预设组件,得到组件类
var Profile = Vue.extend({
template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
data: function () {
return {
firstName: 'Walter',
lastName: 'White',
alias: 'Heisenberg'
}
}
})
2. 创建 Profile 实例,并挂载到一个元素上。
new Profile().$mount('#mount-point')
3. 或者进行全局注册或局部注册
3.1 局部注册
new vue({
el:'#app',
components:{
Hello:Profile
}
})
3.2 全局注册
Vue.component('Hello',Profile)
结果:
<p>Walter White aka Heisenberg</p>
Vue 2.0简写
Vue.component('Hello', {
这里相当于vue.extend({})
})
钩子
钩子函数顺序:beforeCreate
>created
>beforeMounte
>bind
>inserted
>mounted
更新时:directive
的updated
> instance
的updated
实例中的updated hook
没有el参数
自定义指令中的updated hook
有el,binding
参数
Instance hook
beforeCreate
:组件创建前的准备工作, 初始化事件和生命周期, 为事件的发布和订阅做准备。拿不到data,拿不到dom。
created
:组件创建结束, 数据可以拿到 , DOM 拿不到。可以进行数据请求和默认数据修改, 一般进行数据的请求和数据的修改
beforeMounted
:真实DOM存在,模版未渲染。此时DOM中的数据,如{{ msg }
}还没有被正确渲染,但是DOM结构已存在。一般用来数据请求
mounted
:组件实现挂载, DOM渲染正确。一般用来操作第三方库的实例化,但是在使用swiper
中存在轮播图划不动的情况,需要把实例化代码放在setTimeout()中,进行实例化
beforeUpdate
:虚拟DOM
更新,真实DOM
未被render
函数完全渲染,此时model
最新的,view
未更新
updated
:此时model
和view
同步,render
函数执行完毕
Vue-directive(自定义指令)
自定义vue指令,通过v-name调用。这个函数接受两个参数,第一个为自定义指令的名称,第二个为一个配置项
简写:在很多时候,你可能想在 bind 和 update 时触发相同行为,而不关心其它的钩子。比如这样写
Vue.directive('name',(el, binding)=>{
el.style.backgroundColor = binding.value
console.log('bind & update hook')
});
Vue.directive('name',{
bind(el,binding){}, // 样式写这里
inserted(){}, // 行为写这里
update(){},
unbind(){},
...
});
bind
钩子函数内,el.focus()
并未生效,这是因为在 bind 钩子函数被调用时,虽然能够通过 bind
的第一个参数 el
拿到对应的 DOM 元素,但是此刻该 DOM 元素还未被插入进 DOM 树中,因此在这个时候执行 el.focus()
是无效的
- 问题:对比这里生成的 inptu 是直接在 dom 中的吗?对比在 vue bind钩子内的 el.focus() 有何区别
- 思考:document.createElement 可能掉用的时候就在 dom 中了,后边的 appendChild 只是插入在哪的问题
var box = document.querySelector('#app');
var input = document.createElement('input');
input.autofocus = 'autofocus';
box.appendChild(input)
指令
指令能在=后双引号内传递参数而不是用{{}}是因为指令钩子内有binding对象,binding对象的value属性和express属性可以拿到""内的值
基于此我们可以通过自定义指令完全模拟vue的自带指令
DOM
<input v-focus=" 'red' ">
js
new Vue({
directives:{
focus:{
inserted(el,binding){
binding.value--red 字符串类型
binding.express--'red'
el.style.color=binding.value
}
}
当只在bind和update钩子上操作时候,可以简写
focus(el,binding){xxx}
}
})
自定义指令
在vue中输入框如果想自动获取焦点,传统的autofocus不管用
directives:{
focus:{
// 指令钩子,当被绑定的元素插入到 DOM 中时
inserted(el){
el.focus()
}
}
}
v-bind
模版内标签属性默认是不会解析变量的,如果需要将属性与数据绑定,需要用到 v-bind 指令
<input title=" msg " > 默认不解析
< input :title=" msg "> 绑定使用
data:{
msg:' myInput '
}
v-text
和 {{}} 的区别:
共同:
- 都不会解析数据内的字符串标签,对比 v-html
区别: - v-text 默认没有闪烁问题,{{}}有闪烁问题要配合 v-clock
- v-text中间不能插入其它文本,默认会被覆盖掉,而插值表达式可以配合使用
<p v-clock> {{msg}} </p>
<p v-text="msg" />
事件修饰符
.stop:阻止冒泡
.prevent:阻止冒泡
.capture:添加事件监听时使用事件捕获
.once:只有一次
表单元素双向绑定
input(radio text address email …) select checkbox textare
<select v-model="opt">
<options value="+">+</options>
<options value="-">-</options>
<options value="*">*</options>
<options value="/">/</options>
</select>
v-for
注意:v-for 需要绑定 :key 来添加唯一标识符( number/string )
v-for=" i in 9 " 如果迭代数组,i 从1开始
v-for=" ( item, index ) in lists "
v-for=" ( val, key, index ) in obj"
样式绑定
- 数组
:class = "[ '类名', 条件 ? 'class_name':'class_name' ]" 三元表达式
- 对象
:class="{ 类名: boolean }"
- 数组对象
:class="[ '类名', { '类名': flag }]" 代替三元表达式,简化代码逻辑提高可读性
内联样式
- 直接给一个对象
:style="{ color: 'red', 'font-size':'14px' }" 这里用字符串 ' font-size' 或者 fontSize
否则js会将 - 解析为减
也可将对象写在data内
:style=" styleObj "
- 数组
:style="[ obj1, obj2 ]"
条件渲染
v-if=" flag " @click=" flag=!flag " 删除DOM,用于极少切换
v-show=" flag " @click=" flag=!flag " 改变display,用于频繁切换
过滤器
过滤器只能用在 v-bind 和 {{}}中,其他地方不可以使用。用管道符表示
{{ str | myFilter() }}
生命周期
- 初始化阶段()
beforeCreate(){}
:在beforeCeated中,vue实例存在,但未创建完成。所以data等数据拿不到
created(){}
:实例创建完毕,所以是最早的可以改变data的钩子,是我们常用来获取数据的地方
ceated(){}
后做了什么事情?
1.是否有 el 的 options,没有等待被挂载
2.是否有 template,有渲染没有将挂载 DOM 编译成模版渲染
beforeMount(){}
:模版已在内存中,尚未挂载到页面
以上为vue实例化,模版编译阶段,最终在内存中生成一个编译好的字符串模版,然后把这个模版渲染为内存中的DOM。此时只存在与内从中,渲染好了并没有挂载到真正的页面中去
mounted(){}
:内存中的模版已经挂载到真实页面中,页面已经渲染好了。实例创建期间的最后一个周期函数,当执行完毕后,这个实例就被完全创建好了,如果没有其他操作的话,这个实例就静静的躺在我们的内存中一动不动。
-
运行期
beforeUpdate(){}
:当data数据改变时触发,M改变,VM做事情,将要改变V。当触发这个钩子时,V层旧数据,M层更新。虚拟DOM根据数据更新,然后re-render and patch(挂载)
,完成从M成到V层的更新
updated(){}
:表示V层更新完成 -
销毁期
beforeDestory(){}
:销毁前
Destory(){}
:组件完全销毁,所有数据方法过滤器等消失。
过渡
组件切换过度动画,Vue提供了transition
标签实现过渡动画。注意首次加载时无过渡动画,可以通过给 transition
标签设置 appear
属性实现首次加载动画
transition
标签只能在单标签实现过渡动画, can only be used on a single element
。像列表循环那样需要使用transition-group
v-enter,v-leave
v-enter-to,v-leave-to
.v-leave-to,.v-enter{
opacity:0;
transform:translateX(150px);
}
.v-enter-active,.v-leave-active{
transition: all 0.4s ease;
}
在同一个组件内,所有transform标签内共用该类,如果想要个性化需要transform设置name属性
<transform name="my">
css:
.my-enter,.my-leave-to{
opacity:0;
transform:translateY(150px);
}
.my-enter-active,.my-leave-active{
transition: all 0.4s ease;
}
v-enter是进入前,v-leave-to是离开后
使用 animate.css:
<transition enter-active-class="animated enter_class" leave-acitve-class="animated leave_class">
<h3 v-if="flag"> 过渡测试 </h3>
<transition >
可以简写为:
<transition enter-active-class="enter_class" leave-acitve-class="leave_class">
<h3 v-if="flag" class="animated"> 过渡测试 </h3>
<transition >
<transition :duration="300"> 为入场和离场时间
:duration="{enter:300,leave:400}" 指定时常
半场动画:只有进入没有离开。前两种都无法实现半场动画(购物车添加动画)
动画生命周期钩子:
template:
<transition
@before-enter="beforeEnter"
@enter="enter"
@after-enter="afterEnter"
>
<div class="boll" v-if="bollFlag"></div>
</transition>
js:
methods:{
beforeEnter(el){
el.style.transform="translate(0,0)";
},
enter(el, done ){
el.offsetWidth 注意这句话虽然没什么实际意义,但是不加没动画 offsetTop Left Height都可以
el.style.transform="translate(150px,450px)";
el.style.transition="all 1s ease"
done() 注意如果为纯js实现过度这里的done必须要写
}
}
列表过渡:如果需要动态添加的循环上存在过度动画,transition标签无效,使用transition-group
,该标签默认渲染为span
标签不复合w3c
规范,可以通过tag属性
直接改为ul标签
添加动画:
css:
.v-enter,.v-leave-to{
opacity: 0;
transform: translateX(150px)
}
.v-enter-active,.v-leave-active{
transition: all 0.4s ease
}
li:hover{background:red}
template:
<transition-group appear tag="ul">
<li v-for="item in lists" :key="item.id">
name : {{item.name}}------age : {{item.age}}
</li>
</transition-group >
删除动画:
列表删除过渡时候会出现当前元素过渡,其他元素位置不变等待过渡结束才位移到正确位置的问题。解决方案:
css:增加以下类名
实现列表平滑过渡核心代码
.v-move{
transition: all 0.4s ease
}
.v-leave-active{
position: absolute;
}
li{
width: 100% 如果不加的话删除时li标签宽度会塌陷,因为position:absolute的原因
}
实现列表添加删除,购物车小球等完整过度动画代码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>Document</title>
<style>
.v-enter,.v-leave-to{
opacity: 0;
transform: translateX(150px)
}
.v-enter-active,.v-leave-active{
transition: all 0.4s ease
}
.v-move{
transition: all 0.4s ease
}
.v-leave-active{
position: absolute;
}
.boll{
width: 30px;
height: 30px;
border-radius: 50%;
background: red;
}
.lists{
list-style: none;
border: 1px dotted green;
padding: 0 10px;
margin: 10px;
line-height: 35px;
}
li:hover{
background: deeppink;
}
li{
width: 100%
}
</style>
</head>
<body>
<div id="app">
<div>
<input type="button" value="targg" @click="flag=!flag">
<transition appear>
<h1 v-if="flag">{{msg}}</h1>
</transition>
<input type="button" value="快到碗里来" @click="bollFlag=!bollFlag">
<transition
@before-enter="beforeEnter"
@enter="enter"
@after-enter="afterEnter"
>
<div class="boll" v-if="bollFlag" :css="false"></div>
</transition>
<br>
<label for="">
<input type="text" v-model="name">
<input type="text" v-model="age">
<input type="button" value="添加" @click="add">
</label>
<ul>
<transition-group appear>
<li v-for="(item,i) in lists" :key="item.id" class="lists" @click="del(i)">
name : {{item.name}}------age : {{item.age}}
</li>
</transition-group>
</ul>
</div>
</div>
</body>
</html>
<script>
new Vue({
el: '#app',
data: {
name:'',
age:'',
msg: 'hello',
flag: true,
bollFlag:false,
lists:[
{id:1,name:'张三',age:23},
{id:2,name:'李四',age:33},
{id:3,name:'王五',age:13},
{id:4,name:'赵六',age:26}
]
},
methods:{
add(){
this.lists.push({
id:this.lists.length+1,
name:this.name,
age:this.age
})
this.name=this.age=''
},
del(i){
this.lists.splice(i,1)
},
beforeEnter(el){
el.style.transform="translate(0,0)";
},
enter(el,done){
el.offsetWidth
el.style.transform="translate(150px,450px)";
el.style.transition="all 1s ease"
done()
},
afterEnter(){
this.bollFlag=!this.bollFlag;
}
}
})
</script>
组件
Vue.extend({})
返回组件的类构造器,。参数是一个包含组件选项的对象
必须通过Vue.component(‘name’, class )或局部注册后使用
创建组件的三种方式
第一种:
var x = Vue.extend({
template:'<div>自定义模版</div>',
data(){
msg:'msg'
}
})
Vue.component( 'myvue', x )
第二种:
Vue.component( 'myvue', {
template:'<div>自定义模版</div>'
})
第三种:在app根实例外
<template id="tmp">
<div>
<h1>第三种组件创建方式</h1>
</div>
</template>
Vue.component('mycomponent', {
template:'#tmp'
})
组件的data必须是一个函数返回一个对象
<template id="comp">
<div>自定义组件</div>
<template>
在组件内注册
new Vue({
el:'#app',
components:{
这里一个对象就是一个组件
mycomp:{
template:'#comp', 指定 ID 类似el挂载html结构
data(){
return {
msg:'newmsg'
}
}
}
}
})
渲染组件的两种方式
在vue脚手架中 main.js就是通过render函数渲染(在vue结合webpack使用必须通过render渲染组件,通过components然后标签的方式不成功)
render
渲染:render渲染只能覆盖指定的容器,虽然router-view也是写在#app的盒子内,但是他独立于这之外,路由组件通过路由监听渲染出来
import App from './App.vue'
new Vue({
render: h => h(App),
}).$mount('#app')
第一种常规方式:通过组件内components中注册或者通过Vue.component()全局注册,然后以标签的形式使用
template:
<div id="app">
<Login></Login> 通过标签
</div>
js:
var Login = {
template:'<h3>注册模版</h3>'
}
new Vue({
components:{Login} 局部注册
})
第二种:使用render函数
template:
<div id="app"></div>
js:
var Login = {
template: '<h3>登录组件</h3>'
}
new Vue({
el: '#app', 使用render函数会覆盖div#app(消失)
render(createElement){ createElement方法可以把指定的组件渲染为html结构
return createElement(Login) 这里返回的结果会替换页面中el挂载的容器(覆盖div#app)
}
简写:
render: h => h(App) return省略{} 箭头省略()
})
区别:render会清空内容,只能放一个组件。传动方式渲染不会覆盖,可放多个组件。
组件切换
第一种方式:通过条件渲染配合开关思想
<button @click="flag=!flag"></button>
<login v-if="flag" />
<register v-else="flag">
Vue.component( 'login', {
template:'<h1>登录组件</h1>'
})
Vue.component( 'register', {
template:'<h1>注册组件</h1>'
})
data:{
flag:true
}
第二种方式:
vue提供的component标签,通过绑定is属性来展示对应的组件
<a href="" @click.prevent="componentName='login'">登录</a>
<a href="" @click.prevent="componentName='register'">注册</a>
<component :is=" componentName " />
data:{
componentName:'login'
}
props
data是组件内私有的状态,props并不是私有的而是父组件传递过来的
data可读写,props只读
在Vue中,父组件通过v-bind形式给子组件传递数据,子组件需props属性接受。props是建议更改的,如果需要,在data中定义
父组件方法通过v-on事件绑定机制传递,当自定义一个事件类型之后,子组件通过 自己的事件处理函数通过this.$emit触发该方法
组件通讯
父子通讯
1.父组件v-bind绑定属性,在子组件身上,子组件内通过props属性接受一个数组,数组内用字符串填充属性名
2.父组件通过this.$children
访问子组件数据
父:
<Son :msg="msg"></Son>
子:
props:['msg']
<input v-model="$parent.data">
子父通讯:
1.父组件通过事件绑定一个自定义事件类型,值为父组件内的方法。子组件通过绑定事件函数通过this.$emit('父自定义事件名',参数)
触发父组件事件
2.子组件内 this.$parent
访问当父组件内的数据
3.this.$root
作为所有组件共享的全局 store
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>Document</title>
</head>
<body>
<div id="app">
父:{{msg}}
<Son @change_father_name="change"></Son>
</div>
<template id="son">
<div>
<input type="text" @input="change">
</div>
</template>
</body>
</html>
<script>
var Son = {
template: '#son',
methods: {
change(e) {
this.$emit('change_father_name', e.target.value)
}
},
}
new Vue({
el: '#app',
data: { msg: 'fathers msg' },
methods: {
change(data) {
this.msg = data
}
},
components: { Son }
})
</script>
跨组件通信
https://cn.vuejs.org/v2/guide/components-edge-cases.html
1.ref
1.在普通 dom 标签上绑定 ref 获取的是 dom 元素
<div ref="box"></>
console.log(this.$refs.box)
-> <div></>
2.在子组件标签绑定 ref 获取组件实例属性和方法
<Title ref = titleComponent>
console.log(this.$refs.titleComponent)
-> VueComponent{}
2.bus总线
计算属性
computed依赖缓存,用于为了在模版上不呈现过于复杂的逻辑代码,我们把需要处理复杂逻辑的数据写在computed内并且return出来。
不会根据时间改变
computed:{
now(){ 这里的now当作变量使用
return Date.now()
}
}
watch
watch可以看作是computed和methods的结合体,可以监听虚拟的东西,比如路由的变化。(只要是实例内的数据都可以被监听到,所以可以用来监听路由)
webpack结合vue
安装vue
在普通网页中使用vue开发:通过script标签引入完整的vue 文件
在webpack内通过 import Vue from ’ vue '不是完整的vue文件,只是node-modules下的vue文件夹下的package.json中的main属性指定的地址,是vue-runtime-only形式,不完整。我们可以直接给完整文件的路径import Vue from '../node_modules/vue/dist/vue.js'
1.安装vue: cnpm i vue -S
2.安装vue的loader:cnpm i vue-loeader vue-template-complier -D
3.在main.js中导入vue模块:import Vue from 'vue'
4.定义vue文件:三部分
5.导入组件:import App from './App.vue'
6.创建实例:var vm = new Vue({ el:'#app',render:h=>h(App) })
7.创建div#app做挂载容器
安装vue-router
import VueRouter from 'vue-router'
Vue.ues(VueRouter)
var routes=[
{},{}
]
var router = new VueRouter({
routes,
mode:'history'
})
在一个模块中,export default 和 export 可以同时存在,区别:
1.exprot default只能暴露一次,直接接收不需要{},变量名不需要对应
const text = {name:'zhangsan'}
export default text
import txt from './text.js'
2.exprot可以导出多次,引入任意。引入时只能使用{}且必须对应属性名。这种形式叫做按需导出。
如果改变默认属性名,需要使用as重命名
export var init ={name:'init'}
export var show ={name:'show'}
import { show as s } from './text.js'
饿了么 MintUI
MintUI不同于MUI,是真正的组件库,是使用Vue技术封装出来的成套的组件,可以无缝的和Vue项目进行集成开发。而MUI和Bootscrap类似一套好用的代码片段。
MintUI分为js 组件 css组件和表单组件三类
当import Mint from 'mint-ui'
时css组件都可以用,js组件如果需要使用则必须单独引入js
全部引入
当完全引入的时候,相当于把所有的组件都注册到全局下,所以我们可以直接在模版内使用组件标签
import Vue from 'vue'
import Mint from 'mint-ui'
Vue.use(Mint)
<mt-button></mt-button>
按需引入
import { Button } from 'mint-ui'
Vue.component(name, obj) 组件注册