Vue基础
(6)computed:计算属性。computed属性根据数据计算出另一个数据。每当其依赖的数据改变,computed属性也会改变,从而触发视图的更新。
const MyComponent = {
data() {
return {
counter: 1
};
},
computed: {
even() {
return this.counter % 2 === 0;
}
},
template: `
<div>
{{ counter }}
<button v-on:click="increase">加1</button>
<div v-if="even">偶数</div>
<div v-else>奇数</div>
</div>
`,
methods: {
increase() {
this.counter++;
}
}
}
(7)watch:侦听器。如果数据变化的时候我们希望做一些操作,比如弹出提示,远程请求数据等等,这时候可以使用watch属性,watch指定了数据变化时候的回调。
const MyComponent = {
data() {
return {
counter: 1
};
},
watch: {
counter(value) {
if (value % 2 === 0) {
alert('偶数');
}
}
},
template: `
<div>
{{ counter }}
<button v-on:click="increase">加1</button>
</div>
`,
methods: {
increase() {
this.counter++;
}
}
};
watch有两个写法:
一为函数式写法:侦听对象(){};
二为对象声明式写法:侦听对象:{}。当为对象声明式写法的时候,watch有两个选项满足部分场景的需求:deep和immediate,deep决定是否深度监听,即是否监听多层对象数据;immediate决定是否立即执行,如果为true,会在绑定时候(初始时候)立即执行,如果为false,只在监听的值变更时候执行。默认为false。
const MyComponent = {
name: 'myComponent',
data() {
return {
message: {
info: 'hello'
}
};
},
watch: {
message: {
handler: function(value) {
console.log('message change', value.info);
},
deep: true,
immediate: true
}
}
}
考点:computed计算属性和watch侦听器的区别
应用场景:
computed
选项主要用于同步对数据的处理,而watch
选项主要用于事件的派发,可异步自身特性:
computed
拥有缓存属性,只有当依赖的数据发生变化时,关联的数据才会变化,适用于计算或者格式化数据的场景
watch
监听数据,有关联可是没有依赖,只要某个数据发生变化,就能够处理一些数据或者派发事件并同步/异步执行.实际应用场景:
computed:电商领域的购物车统计,一个数据依赖于一个或者多个数据.
watch:主要适用于与事件和交互有关的场景,数据变化为条件,适用于一个数据同时触发多个事物.
总的来说:弹框提示等事件交互的,适用于
watch
,数据计算和字符处理的适用于computed
组件
1、组件注册
组件注册有全局注册和局部注册
在之前的组件示例中,展示了引入子组件,其中注册组件是局部注册,即只有在父组件的"components"属性中声明了子组件的引用,才能使用子组件,除了局部注册,还可以对组件进行全局注册,全局注册的组件不需要父组件声明子组件即可使用。
全局注册组件:
const MyText = {
data() {
return {
text: 'hello, vue'
};
},
template: '<div>{{text}}</div>'
};
const Counter = {
template: '<div> Counter: {{ counter }} <my-text></my-text></div>',
data() {
return {
counter: 1
};
},
mounted() {
setInterval(() => {
this.counter++;
}, 1000);
}
}
const app = Vue.createApp(Counter);
app.component('my-text', MyText);
app.mount('#counter')
上面代码展示了全局注册组件。
全局注册组件的步骤:
-
使用Vue.createApp(<根组件>) 创建应用。
-
调用app.component('<child component name>', ChildComponet);注册组件。
-
在父组件模板中就可以正常使用子组件了。
2、组件生命周期钩子
组件的生命周期:一个组件在整个应用运行过程中有不同的阶段(强调的是一个时间段),称为生命周期,Vue在组件的生命周期过程中的不同阶段会执行我们声明的生命周期钩子。其中mounted钩子方法是在组件挂载之后(即dom渲染完成)执行的。
在组件被加载后,在应用运行过程中,组件可能会经历挂载、数据更新、销毁等各个阶段,称为组件的生命周期。每个阶段会执行相应的生命周期钩子(强调的是一个时间点),用户可以在生命周期钩子中处理业务逻辑。
<!DOCTYPE html>
<html>
<head>
<title>vue-demo</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="counter"></div>
<script>
const MyText = {
data() {
return {
text: '子组件'
};
},
template: '<div>{{text}}</div>',
mounted() {
console.log('子组件挂载');
},
unmounted() {
console.log('子组件销毁');
}
};
const MyComponent = {
template: `
<div>
父组件
<my-text
v-if="isShowChildComponent"
></my-text>
<button @click="onButtonClick">{{isShowChildComponent ? '销毁子组件' : '展示子组件'}}</button>
</div>
`,
data() {
return {
isShowChildComponent: false
};
},
methods: {
onButtonClick() {
this.isShowChildComponent = !this.isShowChildComponent;
}
}
}
const app = Vue.createApp(MyComponent);
app.component('my-text', MyText);
app.mount('#counter');
</script>
</body>
</html>
示例中父组件通过点击按钮控制变量"isShowChildComponent",通过变量控制是否展示子组件。
可以在控制台上看到当子组件挂载和销毁时候都会执行相应的生命周期钩子。
Vue的生命周期钩子
-
创建:beforeCreate、created
-
挂载:beforeMount、mounted
-
更新:beforeUpdate、updated
-
销毁:beforeDestroy、destroyed(Vue3中被更改为 beforeUnmount、mounted)
父子组件的生命周期钩子执行顺序
加载渲染过程
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted。
更新过程 父beforeUpdate->子beforeUpdate->子updated->父updated。
销毁过程 父beforeDestroy->子beforeDestroy->子destroyed->父destroyed。
生命周期钩子的使用
通常我们会在初始化(created/mounted)中绑定事件、启动定时器,相应地,在beforeDestroyed中解绑事件、停止定时器。
在updated中执行一些依赖新状态,或者依赖新的DOM的操作,例如一个聊天面板组件中,收到消息后更新数据,在update中需要判断,如果当前面板的列表是向上滚动的状态,即用户正在会看之前的消息,就给一个提示“有新消息”,如果列表处在底部,就自动向上滚动,展示出最新消息。
组件通信
通常父组件引用了子组件后,都会需要和子组件进行通信,比如父组件需要控制子组件的展示内容、父组件需要监听子组件的变化等等。下面说明父子组件通信的用法。
父子组件通信主要有3种方式:
-
父组件通过props给子组件传递属性
-
子组件通过$emit方法给父组件抛出事件
-
父组件通过ref获得子组件引用,从而调用子组件的方法
父子组件通信
-
props:
先看下父组件给子组件传递属性的示例
<!DOCTYPE html>
<html>
<head>
<title>vue-demo</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="counter"></div>
<script>
const MyText = {
props: ['digit', 'className'],
template: '<div :class="className">{{text}}</div>',
computed: {
text() {
return this.digit % 2 === 0 ? '偶数' : '奇数';
}
}
};
const MyComponent = {
template: `
<div>
{{counter}}
<my-text v-bind:digit="counter" className="my-child"></my-text>
<button @click="increase">加1</button>
</div>
`,
data() {
return {
counter: 1
};
},
methods: {
increase() {
this.counter++;
}
}
}
const app = Vue.createApp(MyComponent);
app.component('my-text', MyText);
app.mount('#counter');
</script>
</body>
</html>
上面示例中,父组件给子组件传递了两个属性,'className'和'digit',注意className是个字符串字常量,因此给子组件传递时候直接 className="my-child"即可,而digit是变量,因此要用v-bind指令来进行传递。
子组件拿到className属性后绑定到自己元素的class上面,拿到digit属性后经过computed计算奇偶展示出来。
当父组件的counter变化时候,子组件的props属性也跟着变化,可以通过界面观察到其变化。
父组件给子组件传递属性的步骤
-
子组件声明props属性,声明需要的属性
-
父组件在模板中将属性传递给子组件
-
子组件使用属性
子组件声明属性时候,除了使用数组形式,还可以通过对象形式,指定属性的类型
const MyText = {
props: {
digit: Number,
className: String
},
template: '<div :class="className">{{text}}</div>',
computed: {
text() {
return this.digit % 2 === 0 ? '偶数' : '奇数';
}
}
};
上面示例子组件声明了属性digit为数值类型,className为字符串类型。
声明了props类型后,如果父组件传入的属性的类型不匹配,则会在控制台warning提示,这样可以避免一些预期之外的错误,
-
事件
除了父组件可以给子组件传递属性,子组件还可以给父组件抛出事件,这样父组件就会知道子组件的变化,进而做出一些反应。
子组件触发事件,父组件监听事件 的示例:
<!DOCTYPE html>
<html>
<head>
<title>vue-demo</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="counter"></div>
<script>
const MyText = {
props: ['digit', 'className'],
template: '<div :class="className">{{text}}</div>',
computed: {
text() {
return this.digit % 2 === 0 ? '偶数' : '奇数';
}
},
watch: {
digit(value) {
if (value % 10 === 0) {
this.$emit('digitMultiple', value / 10);
}
}
}
};
const MyComponent = {
template: `
<div>
{{counter}}
<my-text
v-bind:digit="counter"
className="my-child"
@digit-multiple="onDigitMultiple"
></my-text>
<button @click="increase">加1</button>
</div>
`,
data() {
return {
counter: 1
};
},
methods: {
increase() {
this.counter++;
},
onDigitMultiple(value) {
console.log(`10的${value}倍!`);
}
},
}
const app = Vue.createApp(MyComponent);
app.component('my-text', MyText);
app.mount('#counter');
</script>
</body>
</html>
上面示例的效果是,点击按钮增加计数,当计数达到10的倍数时候,会在控制台打印提示。
父组件监听子组件的步骤是
(1)子组件通过this.$emit()触发事件,第一个参数是事件名,第二个是事件的参数
(2)父组件通过v-on:(或简写为@)绑定事件,指定回调函数,注意绑定的事件名会把驼峰转换成横杠格式
(3)在methods指定的回调中处理事件
-
ref
有些场景父组件需要直接调用子组件的方法,这时候需要通过ref获取子组件引用
见下面示例
<!DOCTYPE html>
<html>
<head>
<title>vue-demo</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="counter"></div>
<script>
const MyText = {
data() {
return {
isShow: false,
text: '子组件'
};
},
template: `
<div v-if="isShow">
<span>{{text}}</span>
<button @click="hide">关闭</button>
</div>
`,
methods: {
hide() {
this.isShow = false;
},
show() {
this.isShow = true;
}
}
};
const MyComponent = {
template: `
<div>
<my-text
ref="myTextNode"
></my-text>
<button @click="showChild">展示子组件</button>
</div>
`,
methods: {
showChild() {
this.$refs.myTextNode.show();
}
},
}
const app = Vue.createApp(MyComponent);
app.component('my-text', MyText);
app.mount('#counter');
</script>
</body>
</html>
上面示例展示了父组件通过ref获取子组件引用并调用子组件show方法的效果父组件有一个按钮,点击可以展示子组件,而子组件有个按钮,点击可以隐藏子组件。
实现这个效果需要3步
-
父组件在模板中子组件标签上加上ref属性,指定为一个自定义字符串:"myTextNode"
-
父组件通过this.$refs.myTextNode就获取到了子组件的示例的引用
-
子组件在methods里面实现相应的方法,这里是"show",父组件就可以调用了:this.$refs.myTextNode.show()
兄弟组件通信
-
eventBus 事件总线
eventBus是一个典型的发布-订阅模式,当状态改变时候,改变方通过eventBus发布状态改变事件,关心这个状态的组件可以通过订阅该事件来获知最新的状态,这样就实现组件通信,即组件间的状态共享。
Vue项目中可以通过简单地实例化一个Vue对象来实现一个eventBus:
const eventBus = new Vue();
然后使用实例提供的$on()
**方法订阅,使用$emit()
方法实现发布**。
另外可以通过$off()
**解绑事件**。
-
Vuex 状态管理
该知识点会在之后专门归纳总结
-
父子组件挂载顺序
加载渲染过程
父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created ->子 beforeMount -> 子 mounted -> 父 mounted
更新过程
父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated 销毁过程
父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed