生命周期
new Vue()
初始化一些事件和生命周期 例如:vm.$on, vm.$once, vm.$emit
等
beforeCreate
初始化之前,会先把一些数据方法放在实例中,还没初始化完,不能操作数据,一般用不到
created
数据,方法初始化完成,可以操作数据,实现响应式绑定,此处一般ajax
获取数据
必须有el
属性(有编译的元素),才能进行挂载,只能有一个根元素,如果有template
属性(内部html),app
(外部html)中的内容就会被覆盖掉,就没有意义了
beforeMount
挂载之前,没有什么实际意义,会用vm.$el
替换el
mounted
挂载完成,数据和模板挂载好了,真实DOM
渲染完了,可以操作DOM
beforeUpdate
更新之前,页面依赖的数据有变化就会开始更新
updated
更新之后,一般用watch
方法替代这两个方法
beforeDestory
销毁之前,开始移除一些定时器和事件绑定等操作,方法还没销毁,
destoryed
销毁完成
this.$data
:vm上的数据
this.$watch
:监控
this.$el
:当前el元素
this.$set
:后添加的属性实现响应式变化
this.$options
:实例上的属性,包括内置的还有自定义的
this.$refs
:所有ref的集合,带ref属性的标签,如果不是通过v-for
循环出来的DOM
元素,只能获取一个
this.$nextTick()
:异步方法,等待渲染完成后获取vm
,数据变化后想获取真实dom,需要等待页面获取完成后再去获取,因此所有dom操作最好都放在this.$nextTick()
中
mounted(){
// console.log(document.getElementsByTagName('p')[0].innerHTML);
console.log(this.$refs.message);
console.log(this.$refs.wrap);
this.arr = [1,2,3,4,5]; // dom的渲染是异步的
this.$nextTick(function () {
// 数据变化后想获取真实dom,需要等待页面获取完成后再去获取
console.log(this.$refs.wrap.children.length); // 5
});
console.log(this.$refs.wrap.children.length); // 3
}
组件化开发
组件化开发可以提高开发效率,方便重复利用,便与协同开发,更容易被管理和维护。一般根据功能可分为两种:
1、页面级组件
,一个页面是一个组件
2、基础组件
,将可复用的部分抽离出来
vue中,一个自定义标签就会被看成一个组件
根据用法划分:
全局组件
:声明一次可以在任何地方使用,写插件的时候用的多
局部组件
:必须要声明这个组件属于哪一部分
声明组件的时候,标签名不要大写,多个单词用- 组件名和定义的名字相同是可以的(首字母可以大写)
html中用-,JS中转驼峰也是可以的
组件中的data
必须是函数类型
的,返回一个实例作为组件中的数据
<body>
<!--分类 页面级组件 一个页面是一个组件-->
<!--将可复用的部分抽离出来 基础组件-->
<div id="app">
<my-vue></my-vue>
</div>
<script src="../node_modules/vue/dist/vue.js"></script>
<script>
// 一个对象可以看成一个组件
Vue.component('my-vue', {
template: '<div>{{msg}}</div>',
data(){
return {msg: 'vue学习'}
}
});
</script>
</body>
局部组件
局部组件使用的三部曲
1、创建组件
2、注册组件
3、引用组件
组件是相互独立的,不能直接跨作用域,vm这个实例也是一个组件,组件中拥有生命周期函数,如果组件共用了数据会导致同时更新,因此要求data
必须是函数类型
的。
子组件不能直接使用父组件的数据(组件间数据传递),组件理论上可以无限嵌套
<body>
<div id="app">
<component1></component1>
<component2></component2>
</div>
<script src="../node_modules/vue/dist/vue.js"></script>
<script>
// 局部组件使用的三部曲
// 1、创建组件
// 2、注册组件
// 3、引用组件
let component1 = {
template: '<div>{{msg}}</div>',
data(){
return {msg: '组件2'}
}
};
let component2 = {
template: '<div>组件2</div>',
};
let vm = new Vue({
el: '#app',
components:{
component1,
component2
},
data: {}
});
</script>
组件间的嵌套:
1、被调用的子组件必须先定义,否则就拿不到。
2、哪里要用当前组件,就在哪里通过components注册,
3、需要在调用的组件中通过标签的形式引入
理论上是无限嵌套的,单位了好维护,一般最多嵌套3层
<div id="app">
<!--<div>parent-->
<!--<div>child-->
<!--<div>grandson</div>-->
<!--</div>-->
<!--</div>-->
<parent>
<child>
<grandson></grandson>
</child>
</parent>
</div>
<script src="../node_modules/vue/dist/vue.js"></script>
<script>
let grandson = {
template: `<div>grandson</div>`
},
child = {
template: `<div>child<grandson></grandson></div>`,
components:{
grandson
}
},
parent = {
template: `<div>parent<child></child></div>`,
components: {
child
}
};
let vm = new Vue({
el: '#app',
components:{
parent,
},
data: {}
});
</script>
</body>
父组件给子组件传值:属性传递,
:money=""
是传值的,传了一个空值,所以不会用到default
,不在子组件定义这个属性,这才算不传值
required: true
:表示该值必须传递,不穿就发警告,不能与默认值default
同时出现。
校验时不会阻断代码的执行,只会出现警告
还可以自己定义校验信息,用validator
方法,里面的参数就是当前传递的值,返回true表示通过反之不通过。(用的8多)
<body>
<div id="app">
parent: {{money}}
<!--m属于子,属性值属于父-->
<!--当前组件的属性=父级的值-->
<child :money="money"></child>
</div>
<script src="../node_modules/vue/dist/vue.js"></script>
<script>
// 父传子
let vm = new Vue({
el: '#app',
data: {
money: 100,
a: 400
},
components: {
child: {
// 会在当前子组件上声明一个m属性,值是父组件的
// 数组的形式可以直接取值,但无法校验
// props: ['money'], // this.m = money变量 this->child
// 对象的形式可以校验
props: {
// 子父组件中的属性名不能重复,控制台也会报错
// 父组件的会覆盖子组件的值
// 可以加个default值,不传值的时候就用默认值
money: {
// 判断传递值的类
// 如果不带冒号:,得到的肯定是字符串类型
// 类型不对页面上依旧会显示,但控制台会报类型错误
type: [Number],
// default: 0
required: true,
validator(val){ // 参数是当前传递的值,返回true表示通过
return val > 300;
}
}
},
template: '<div>child: {{money}}</div>'
}
}
});
</script>
</body>
子组件给父组件传值:通过发布订阅的模式,父亲绑定一些事件,儿子触发这个事件,将参数传递过去,单向数据流 父组件数据刷新,子组件就刷新,不能子组件直接改父组件的值,要想这样,就需要子组件先通知父组件要修改值,父组件再去修改
在本例子中,子组件通过点击事件触发($emit)自己的child-msg
方法,而该方法又触发了父组件的moreMoney
方法执行,这样一来就实现了子组件向父组件传值
<body>
<div id="app">
parent: {{money}} <button @click="lessMoney">少要点</button>
<!--m属于子,属性值属于父-->
<!--当前组件的属性=父级的值-->
<!--刚刚那个事件是父级的,订阅需要在子级做-->
<child :money="money" @child-msg="moreMoney"></child>
</div>
<script src="../node_modules/vue/dist/vue.js"></script>
<script>
let vm = new Vue({
el: '#app',
data: {
money: 400,
},
methods: {
moreMoney(val){
this.money = val;
},
lessMoney(){
this.money = 200;
}
},
components: {
child: {
props: ['money'],
template: '<div>child: {{money}}
<button @click="getMoney">再来点</button></div>',
methods: {
getMoney(){ // 触发自己的自定义事件让父组件的方法执行
this.$emit('child-msg', 800);
}
}
}
}
});
</script>
</body>
sync语法糖的用法
<child :money="money" @update:money="val=>this.money=val"></child>
same as
<child :money.sync="money"></child>
子父组件声明周期
父组件需要等到子组件挂载完成(mounted)之后才会触发挂载
mounted(){
console.log(this.$refs.child.$el.innerHTML);
// 1 2 3
// 因为存在DOM映射,所以页面上的数据实时变化,
// 但是DOM渲染是个异步的过程,这里还新的数据还没有渲染完
this.$nextTick(() => {
console.log(this.$refs.child.$el.innerHTML);
// 4 5 6
});
},
兄弟组件间相互通信
eventBus
一般不用,了解,发布订阅模式失败的原因是因为在不同组件中的this是不一样的,组件2触发,组件1监听,显然是行不通的。发布订阅的执行者应该是同一个才能成功,因此就需要有一个第三方实例eventBus
来实现交互。
let brother1 = {
template: '<div>{{color}} <button>变绿</button></div>',
data() {
return {color: '绿色', old: '绿色'}
},
created() {
// 组件1监听
this.$on('changeRed', (val) => { // 页面一加载,组件1长一个耳朵来监听
this.color = val;
})
}
};
let brother2 = {
template: '<div>{{color}} <button @click="change">变红</button></div>',
data() {
return {color: '红色', old: '红色'}
},
methods: {
change() {
// 组件2发布
this.$emit('changeRed', this.old);
}
}
};
eventBus
使用,创建一个Vue实例,在兄弟组件中发布订阅都用这个实例来操作即可,但是触发的方法名不能重复,否则就乱套了
let eventBus = new Vue();
eventBus.$on('changeRed', (val) => {
this.color = val;
});
change() {
eventBus.$emit('changeRed', this.old);
}