一、销毁前后
<body>
<div id='app'></div>
</body>
- destroyed => 组件销毁后的善后处理,例如停止定时器
- 销毁发送了什么事情?组件实例变null了吗?
- 组件销毁只是让数据不驱动视图更新了而已.组件实例还是存在的.
<script src="js/vue.js"></script>
<script>
const box = {
template: `<h3>box组件--{{count}}</h3>`,
data() {
return { count: 0 }
},
// 销毁前
beforeDestroy() {
console.log('销毁前')
},
// 销毁后
destroyed() {
console.log('销毁后');
// 销毁后关闭定时器
clearInterval(this.id);
},
created() {
console.log('创建后');
// 记录当前的定时器id.
this.id = setInterval(() => {
// console.log(this.count);
this.count++
},1000);
},
mounted() {
console.log('挂载后')
}
}
const App = {
template: `
<div>
<button @click='flag = !flag'>切换</button>
<box v-if='flag' />
</div>
`,
components: { box },
data() {
return {
flag: true
}
}
}
const vm = new Vue({
el: '#app',
components: { App },
template: '<App />'
})
</script>
二、静态数据
<body>
<div id='app'></div>
</body>
哪些vue数据需要放在data内,哪些不方法?
不写在视图上的数据,是不应该劫持的,不应该劫持就不应该写在data中.
<script src="js/vue.js"></script>
<script>
const box = {
template: `<h3>box组件--{{count}}</h3>`,
data() {
return { count: 0 }
},
// 销毁后
destroyed() {
console.log('销毁后');
// 销毁后关闭定时器
clearInterval(this.id);
},
created() {
// 这个id不应该放在data中.这是一个静态数据,不驱动视图更新,直接给当前组件实例添加即可.
this.id = setInterval(() => {
this.count++
},1000);
}
}
const App = {
template: `
<div>
<button @click='flag = !flag'>切换</button>
<box v-if='flag' />
</div>
`,
components: { box },
data() {
return {
flag: true
}
}
}
const vm = new Vue({
el: '#app',
components: { App },
template: '<App />'
})
</script>
三、nextTick
<body>
<div id='app'></div>
</body>
update => 只要视图更新都触发.
如何针对某个数据变化导致的视图更新写单独的逻辑?
vue的视图更新是异步的,数据变化不会马上导致视图更新.
<script src="js/vue.js"></script>
<script>
const App = {
template: `
<div>
<button @click='msg=Math.random()'>修改msg</button>
<button @click='str=Math.random()'>修改str</button>
<div>msg:{{msg}}</div>
<div>str:{{str}}</div>
</div>
`,
data() {
return {
msg: 0,
str: 0
}
},
watch: {
msg() {
// console.log('msg导致视图更新了');
// nextTick的回调函数,会在视图更新后自动触发.只触发一次
// 一定是用来处理跟视图相关的逻辑.
this.$nextTick(() => {
console.log('msg导致视图更新了');
});
},
str() {
// nextTick的回调函数,会在视图更新后自动触发.只触发一次
this.$nextTick(() => {
console.log('str导致视图更新了');
});
}
}
// updated没办法在视图变化时知道是因为哪个数据变化导致的.
// updated() {
// console.log('视图更新了')
// }
}
const vm = new Vue({
el: '#app',
components: { App },
template: '<App />'
})
</script>
四、触底
<style>
ul{
height: 200px;
overflow-y: auto;
width: 300px;
}
</style>
</head>
<body>
<div id='app'></div>
</body>
- update => 只要视图更新都触发.
- 如何针对某个数据变化导致的视图更新写单独的逻辑?
- vue的视图更新是异步的,数据变化不会马上导致视图更新.
<script src="js/vue.js"></script>
<script>
const App = {
template: `
<div>
<button @click='fn'>修改str</button>
<ul ref='ul'>
<li v-for='d in count'>{{d}}</li>
</ul>
</div>
`,
data() {
return {
count: 0
}
},
methods: {
fn() {
// 数据变化会导致视图变化.视图变化完成之后,就会触发nextTick
this.count++;
// 这里只会在count变化导致的视图更新后触发.
this.$nextTick(() => {
let oUl = this.$refs.ul;
oUl.scrollTop = oUl.scrollHeight - oUl.clientHeight;
});
}
}
// 每次视图更新后,都设置触底.
// updated() {
// let oUl = this.$refs.ul;
// oUl.scrollTop = oUl.scrollHeight - oUl.clientHeight;
// }
}
const vm = new Vue({
el: '#app',
components: { App },
template: '<App />'
})
</script>
五、render选项
<body>
<div id='app'></div>
</body>
render选项 => 代替template选项
render选项也是为了确定组件视图.template => 字符串视图 => 优点是直观.缺点是需要编译.
render => 跳过编译字符串template,直接生成虚拟节点 => 优点是性能好,不用编译,确定是模板不直观
<script src="js/vue.js"></script>
<script>
// tag,属性列表,子子节点
const App = {
// template: `
// <div id='app'>
// <h3>App组件</h3>
// </div>
// `,
// 直接通过render函数生成虚拟节点.
// createElement(标签名, 属性列表, 子节点列表)
// createElement(组件配置项)
render(createElement) {
return createElement('div', { attrs: { id: 'app' } }, [
createElement('h3', null, 'App组件')
]);
}
}
new Vue({
// el: '#app',
// template: '<App />',
// render(h) {
// return h(App)
// },
render: h => h(App),
}).$mount('#app');
</script>
六、bus组件通信
<body>
<div id='app'></div>
</body>
任意组件的组件通信.
A组件传B组件
1:B函数使用了一个方法接收A组件数据.
2:A组件利用自定义事件调用B组件的这个方法,并传入A组件数据.自定义事件一定需要给A组件添加吗?
不是,给任何组件添加自定义事件都可以.(只要方便获取这个组件)因此我们在比较复杂的布局情况下,可以通过一个空的vue实例来充当一个中间组件.
给这个中间组件绑定自定义事件,通过这个中间组件触发自定义事件.
这个中间组件就充当了任意两个组件的数据运输工具.bus.vm.$on => 添加一个自定义事件
vm.$emit => 触发一个自定义事件
vm.$off => 解绑自定义事件.
<script src="js/vue.js"></script>
<script>
// 空的vue实例。
const bus = new Vue();
const box = {
template: `
<div>
<h3>box组件---{{count}}</h3>
<button @click='fn'>传递count给item</button>
</div>
`,
data() {
return { count: 666 }
},
methods: {
fn() {
bus.$emit('getcount', this.count);
}
}
}
const item = {
template: `
<div>
<h3>item组件---{{num}}</h3>
</div>
`,
data() {
return { num: 0 }
},
methods: {
getCount(count) {
this.num = count
}
},
created() {
bus.$on('getcount', this.getCount);
}
}
const App = {
template: `
<div id='app'>
<h3>App组件</h3>
<box />
<item />
</div>
`,
components: { box, item }
}
new Vue({
render: h => h(App),
}).$mount('#app');
</script>
七、子传父
<body>
<div id='app'></div>
</body>
<script src="js/vue.js"></script>
<script>
// 空的vue实例。
const bus = new Vue();
// <box @getcount='getcount' /> => 给box组件绑定自定义事件的一个简写
const box = {
template: `
<div>
<h3>box组件---{{count}}</h3>
<button @click='fn'>传递count给item</button>
</div>
`,
data() {
return { count: 666 }
},
methods: {
fn() {
this.$emit('getcount', this.count);
}
},
created() {
// 给当前的子组件绑定一个getcount事件,这个事件触发父组件的getcount方法
// this.$on('getcount', this.$parent.getcount);
}
}
const App = {
template: `
<div id='app'>
<h3>App组件---{{num}}</h3>
<box @getcount='getcount' />
</div>
`,
data() {
return {
num: 0
}
},
methods: {
getcount(count) {
this.num = count
}
},
components: { box }
}
new Vue({
render: h => h(App),
}).$mount('#app');
</script>
八、bus实现子传父
<body>
<div id='app'></div>
</body>
空的vue实例。
const bus = new Vue();bus通信方式可以实现任意两个组件间的通信问题,包裹父子组件.
bus实现的通信,无法实现状态管理.
<script src="js/vue.js"></script>
<script>
// 把控的vue实例bus对象,放在Vue的原型上。
// 这样所有组件实例都可以通过原型链访问到这个bus对象.
Vue.prototype.$bus = new Vue();
const box = {
template: `
<div>
<h3>box组件---{{count}}</h3>
<button @click='$bus.$emit("getcount", count)'>传递count给item</button>
</div>
`,
data() {
return { count: 666 }
},
// methods: {
// fn() {
// this.$bus.$emit('getcount', this.count);
// }
// }
}
const App = {
template: `
<div id='app'>
<h3>App组件---{{num}}</h3>
<box />
</div>
`,
data() {
return {
num: 0
}
},
components: { box },
created() {
// 在父组件created中给bus实例添加自定义事件
this.$bus.$on('getcount', (count) => {
this.num = count
});
}
}
new Vue({
render: h => h(App),
}).$mount('#app');
</script>
九、父子组件生命周期顺序
<body>
<div id='app'></div>
</body
父子钩子顺序
- 父beforeCreate
- 父created
- 父beforeMount
- 子beforeCreate
- 子created
- 子beforeMount
- 子mounted
- 父mounted
<script src="js/vue.js"></script>
<script>
const item = {
template: `
<div>
<h3>box组件</h3>
<button>传递count给item</button>
</div>
`,
created() {
console.log('item子组件created')
},
beforeCreate() {
console.log('item子组件beforeCreate')
},
beforeMount() {
console.log('item子组件beforeMount')
},
mounted() {
console.log('item子组件mounted')
}
}
const box = {
template: `
<div>
<h3>box组件</h3>
<button>传递count给item</button>
</div>
`,
created() {
console.log('box子组件created')
},
beforeCreate() {
console.log('box子组件beforeCreate')
},
beforeMount() {
console.log('box子组件beforeMount')
},
mounted() {
console.log('box子组件mounted')
}
}
// <div id='app'>
// <h3>App组件</h3>
// <box />
// </div>
// function App () {
// 编译div
// 编译h3
// 创建box,挂载box
// 完成编译,挂载App组件
// }
const App = {
template: `
<div id='app'>
<h3>App组件</h3>
<box v-if='flag' />
</div>
`,
components: { box, item },
created() {
console.log('父组件created')
},
beforeCreate() {
console.log('父组件beforeCreate')
},
beforeMount() {
console.log('父组件beforeMount')
},
mounted() {
console.log('父组件mounted');
// 父组件挂载完成之后再编译子组件.
this.flag = true;
},
data() {
return {
flag: false
}
}
}
new Vue({
render: h => h(App),
}).$mount('#app');
</script>
十、bus通信
<body>
<div id='app'></div>
</body>
任意组件的组件通信.
A组件传B组件
1:B函数使用了一个方法接收A组件数据.
2:A组件利用自定义事件调用B组件的这个方法,并传入A组件数据.自定义事件一定需要给A组件添加吗?
不是,给任何组件添加自定义事件都可以.(只要方便获取这个组件)因此我们在比较复杂的布局情况下,可以通过一个空的vue实例来充当一个中间组件.
给这个中间组件绑定自定义事件,通过这个中间组件触发自定义事件.
这个中间组件就充当了任意两个组件的数据运输工具.bus.vm.$on => 添加一个自定义事件
vm.$emit => 触发一个自定义事件
vm.$off => 解绑自定义事件.
<script src="js/vue.js"></script>
<script>
// 空的vue实例。
const bus = new Vue();
const box = {
template: `
<div>
<h3>box组件---{{count}}</h3>
</div>
`,
data() {
return { count: 666 }
},
// box组件的mounted在item组件的creatd之后触发
// 因此bus的事件触发应该写在这里.
mounted() {
bus.$emit('getcount', this.count);
}
}
const item = {
template: `
<div>
<h3>item组件---{{num}}</h3>
</div>
`,
data() {
return { num: 0 }
},
methods: {
getCount(count) {
this.num = count
}
},
created() {
bus.$on('getcount', this.getCount);
}
}
const App = {
template: `
<div id='app'>
<h3>App组件</h3>
<box />
<item />
</div>
`,
components: { box, item }
}
new Vue({
render: h => h(App),
}).$mount('#app');
</script>
十一、插槽
1.0
<body>
<div id='app'></div>
</body>
如何在同一个组件复用多次时,渲染不同的标签?
可以使用插槽来实现.要使用插槽,组件挂载必须使用双标签.
子组件中,通过内置组件slot来引入插槽.
组件插槽最终会替换slot组件.以下的插槽是默认插槽.
默认插槽可以是多个标签.
<script src="js/vue.js"></script>
<script>
const box = {
template: `
<div>
<h3>{{title}}</h3>
<p>大家都好</p>
<slot />
</div>
`,
props: ['title']
}
const App = {
template: `
<div>
<box title='你好'>
<button>按钮111</button>
<button>按钮222</button>
</box>
<box title='他好'>
<span>span</span>
</box>
<box title='我好'>
<a href='#'>百度一下</a>
</box>
</div>
`,
components: { box }
}
new Vue({
el: '#app',
render: h => h(App)
})
</script>
1.1
插槽:
1:默认插槽.
2:具名插槽.
3:作用域插槽.具名插槽 => 具有name属性命名的插槽。插槽内容需要通过slot来对应name属性.
具名插槽可以一对多.
如果要一对多,可以用template抽象组件包裹所有的插槽内容.然后给template组件设置slot.template内置的抽象组件 => 最终不会渲染到页面上. => 只能在插槽中使用.
<script src="js/vue.js"></script>
<script>
const box = {
template: `
<div>
<slot name='a' />
<h3>{{title}}</h3>
<slot name='span' />
<slot name='btn2' />
<p>大家都好</p>
<slot name='btn1' />
</div>
`,
props: ['title']
}
const App = {
template: `
<div>
<box title='你好'>
<button slot='btn1'>按钮1111</button>
<button slot='btn2'>按钮2222</button>
</box>
<box title='他好'>
<template slot='span'>
<span>span</span>
<span>span</span>
</template>
</box>
<box title='我好'>
<a href='#' slot='a'>百度一下</a>
</box>
</div>
`,
components: { box }
}
new Vue({
el: '#app',
render: h => h(App)
})
</script>
1.2
<script src="js/vue.js"></script>
<script>
// 如果多个组件的模板多数类似,少数不一致,就可以使用插槽来复用组件.
const box1 = {
template: `
<div>
<table border='1' cellspacing='10' cellpadding='10'>
<tr v-for='({name,age,sex}) in arr'>
<td>{{name}}</td>
<td>{{age}}</td>
<td>{{sex}}</td>
<slot />
</tr>
</table>
</div>
`,
data() {
return {
arr: [
{
name: '小崔',
age: 32,
sex: '女'
}, {
name: '小陈',
age: 23,
sex: '女'
}, {
name: '小邓',
age: 65,
sex: '男'
}
]
}
}
}
const App = {
template: `
<div>
<box1></box1>
<box1>
<td>汉</td>
</box1>
</div>
`,
components: { box1 }
}
new Vue({
el: '#app',
render: h => h(App)
})
</script>
十二、作用域插槽
<body>
<div id='app'>
</div>
</body>
什么情况下用作用域插槽?
插槽内容中包含子组件数据时,就得用作用域插槽.作用域插槽内有子组件数据,但是默认却在父组件上找数据,找不到会报错.
可以通过给组件添加一个v-slot指令,这样组件内部就构成了一个作用域。
插槽内部的数据,会优先在作用域内查找.
v-slot的值,默认是一个对象,这个对象的名字可以任意书写
这个对象就能获取到slot组件上的所有绑定数据.
<script src="js/vue.js"></script>
<script>
// v-slot支持解构赋值的写法.
const box1 = {
template: `
<div>
<table border='1' cellspacing='10' cellpadding='10'>
<tr v-for='({name,age,sex,nation}) in arr'>
<td>{{name}}</td>
<td>{{age}}</td>
<td>{{sex}}</td>
<slot :nation='nation' a='100' c='200' />
</tr>
</table>
</div>
`,
data() {
return {
arr: [
{
name: '小陈',
age: 32,
sex: '女',
nation: '汉'
}, {
name: '小崔',
age: 23,
sex: '女',
nation: '苗'
}, {
name: '小邓',
age: 65,
sex: '男',
nation: '蒙古'
}
]
}
}
}
const App = {
template: `
<div>
<box1></box1>
<!--
<box1 v-slot='slotProps'>
<td>{{slotProps.nation}}--{{slotProps}}</td>
</box1>
-->
<box1 v-slot='{nation}'>
<td>{{nation}}</td>
</box1>
</div>
`,
components: { box1 }
}
new Vue({
el: '#app',
render: h => h(App)
})
</script>
十三、作用域插槽
<body>
<div id='app'></div>
</body>
如果在组件上使用v-slot,则默认只能获取到默认插槽的绑定数据。
如果是具名插槽,就不能再组件上使用v-slot指令.需要在template组件上使用 => v-slot:插槽名
<script src="js/vue.js"></script>
<script>
const box1 = {
template: `
<div>
<table border='1' cellspacing='10' cellpadding='10'>
<thead>
<tr>
<th>姓名</th>
<th>年龄</th>
<th>性别</th>
<slot name='c'></slot>
</tr>
</thead>
<tr v-for='({name,age,sex,nation,marryed}) in arr'>
<td>{{name}}</td>
<td>{{age}}</td>
<td>{{sex}}</td>
<slot name='a' :marryed='marryed' />
<slot name='b' :nation='nation'/>
</tr>
</table>
</div>
`,
data() {
return {
arr: [
{
name: '小陈',
age: 32,
sex: '女',
nation: '汉',
marryed: '已离异'
}, {
name: '小崔',
age: 23,
sex: '女',
nation: '苗',
marryed: '未婚'
}, {
name: '小邓',
age: 65,
sex: '男',
nation: '蒙古',
marryed: '已婚'
}
]
}
}
}
const App = {
template: `
<div>
<box1>
<th slot='c'>婚姻状况</th>
<template v-slot:a='{marryed}' slot='a'>
<td>{{marryed}}</td>
</template>
</box1>
<box1>
<th slot='c'>民族</th>
<template v-slot:b='{nation}' slot='b'>
<td>{{nation}}</td>
</template>
</box1>
</div>
`,
components: { box1 }
}
new Vue({
el: '#app',
render: h => h(App)
})
</script>
十四、视图更新为什么是异步的
<body>
<div id='app'></div>
</body>
视图更新是异步的,为了性能考虑。
需要等待所有数据都修改完毕之后,再进行视图更新,只更新最新的数据状态.
<script src="js/vue.js"></script>
<script>
const App = {
template: `
<div>
<button @click='fn'>修改msg</button>
<div>msg:{{msg}}</div>
<div>str:{{str}}</div>
</div>
`,
data() {
return {
msg: 0,
str: 0
}
},
methods: {
fn() {
// this.msg = Math.random();
// this.str = Math.random();
for (let i = 0; i < 1000; i++) {
this.msg = Math.random();
}
}
},
updated() {
console.log('视图更新了')
}
}
const vm = new Vue({
el: '#app',
components: { App },
template: '<App />'
})
</script>