一、组件化思想:(老知识:函数–> 新知识:组件化)
把一个大的东西,按照一个逻辑或者业务场景,划分成一个一个小的东西,然后拼接起来。
类似函数思想,这样的好处是职责单一
<body>
<div id="app">div>
<script>
// 根组件
const app = new Vue({
el: "#app",
// template 描述当前组件的信息,无template 情况会走el当成组件;有template 情况会覆盖el;如下图
// vue2 的时候 必须要有一个 根节点
template: `
{{msg}}
`,
data() {
return {
msg: "hello",
};
},
});
script>
body>
注册组件
vue2 的时候 必须要有一个 根节点 div
注意:后续自己注册组件时不需要el了
<body>
<div id="app">
<foo>foo>
div>
<script>
// 后期学习 .vue 单文件组件,会给我们编译成对应的template,无命名规范问题所在了。
// 为什么data一定写成函数形式?(用对象会发生引用类型的问题)
// 组件 -》 只是个object来进行描述
// 定义的话 object.data ={name:123,count:123}
// 引用类型的问题,用函数的话每次都会返回一个新的对象,这样就能保证对应一个组件对应一个独立的data,这样一个组件的更改不会影响其他的
// 注册组件 Bar (局部注册)
const Bar = {
template: `
bar
`,
};
// 注册组件 Foo(全局注册)
Vue.component("Foo", {
// 注册组件 Bar (局部注册) components 用于注册局部的组件, template 是描述当前这个组件的元素
components: {
Bar,
},
data() {
return {
key: value,
};
},
// 模板 局部组件Bar只能在局部调用,只能有一个根元素div(vue3就解决了这个限制了)
template: `
foo
`,
});
// 使用组件 Foo ,而局部组件Bar会报错,只能在局部调用(全局注册和局部注册的区别)
// vue2 的时候 必须要有一个 根节点
// 根组件
const app = new Vue({
el: "#app",
// template 描述当前组件的信息
template: `
{{msg}}
用大写
这里调用局部组件会报错
`,
data() {
return {
msg: "hello",
};
},
});
script>
body>
二、生命周期(从生命开始到结束的过程)
钩子:在特定的时间内执行这个函数
beforeCreate() // 创建之前调用
created() // 创建完成后调用
beforeMount() // 挂载之前调用
mounted() // 挂载完成后调用
beforeUpdate() // 更新之前调用
updated() // 更新完成后调用
beforeDestroy() // 组件销毁之前调用
destroyed() // 组件销毁之后调用
<body>
<div id="app">div>
<script>
// 子组件 Bar
Vue.component("Bar", {
template: "bar",
beforeCreate() {
// 创建之前调用
console.log("Bar - before - create");
},
created() {
// 创建完成后调用
console.log("Bar - created");
},
beforeMount() {
// 挂载之前调用
console.log("Bar - before mount");
},
mounted() {
// 挂载完成后调用;
console.log("Bar - mounted");
},
// 页面发生变化时调用
beforeUpdate() {
// 更新之前调用
console.log("Bar - before update");
},
updated() {
// 更新完成后调用;
console.log("Bar - updated");
},
// v-if 可决定一个组件的销毁
beforeDestroy() {
// 组件销毁之前调用;
console.log("Bar - before -destroy");
// 这里面可以做一些事情:比如清空事件监听
},
destroyed() {
// 组件销毁之后调用;
console.log("Bar - destroy");
},
});
// 全局组件 Foo
Vue.component("Foo", {
components: {},
beforeCreate() {
// 创建之前调用
console.log("foo - before - create");
},
created() {
// 创建完成后调用
console.log("foo - created");
},
beforeMount() {
// 挂载前调用
console.log("foo - before mount");
},
mounted() {
// 挂载完成调用;
// 当调用此钩子函数时,所有子组件都已经初始化完成
console.log("foo - mounted");
},
// 页面发生变化时调用
beforeUpdate() {
// 更新前调用
console.log("foo - before update");
},
updated() {
// 更新完成后调用;
console.log("foo - updated");
},
// v-if 可决定一个组件的销毁
beforeDestroy() {
// 组件销毁之前调用;
console.log("foo - before -destroy");
},
destroyed() {
// 组件销毁之后调用;
console.log("foo - destroy");
},
data() {
return {
count: 0,
};
},
template: `
foo
{{count}}
`,
});
// 根组件
app = new Vue({
el: "#app",
template: `
{{msg}}
`,
data() {
return {
msg: "hello",
showFoo: false,
};
},
});
script>
body>
重点:多个组件调用顺序
三、props(组件接收参数)
props 定义当前组件可接收什么参数,和data一样,但props不能被修改;
props基于对象的语法使用以下选项:
1-type:可以是下列原生构造函数中的一种:String、Number、Boolean、Array、Object、Date、Function、Symbol、任何自定义构造函数、或上述内容组成的数组。会检查一个 prop 是否是给定的类型,否则抛出警告。Prop 类型的更多信息在此。
2-default:any
为该 prop 指定一个默认值。如果该 prop 没有被传入,则换做用这个值。对象或数组的默认值必须从一个工厂函数返回。
3-required:Boolean
定义该 prop 是否是必填项。在非生产环境中,如果这个值为 truthy 且该 prop 没有被传入的,则一个控制台警告将会被抛出。
4-validator:Function
自定义验证函数会将该 prop 的值作为唯一的参数代入。在非生产环境下,如果该函数返回一个 false的值 (也就是验证失败),一个控制台警告将会被抛出。
<body>
<div id="app">div>
<script>
// ts -> String
// function setName(name) {
// // name 函数的参数
// }
Vue.component("Foo", {
components: {},
props: {
fooName: {
type: String,// 定义当前组件传参的类型为String
default: "foo --- name",// 定义默认值,不传参是显示默认值
// 校验 , value为用户传过来的值,必须返回一个值查看校验是否成功或失败
validator: (value) => {
console.log(value);
return value === "heihei";
},
required: true,// 定义当前fooName是否是必传的
},
},
data() {
return {};
},
methods: {
changeProp() {
console.log("changeProp");
// this.props.fooName = "123";// this.props内的值不能被修改,会报错
},
},
template: `
foo
{{fooName}}
`,
});
// 根组件
const app = new Vue({
el: "#app",
template: `
{{msg}}
// 变量需要绑定:,常量不需要
响应式数据对象需要绑定一下 “:” 警告错误:定义的是string,传的是number
`,
data() {
return {
msg: "hello",
count: 0,
};
},
});
script>
body>
四、emit(自定义事件类型)
子组件和父组件如何进行交互?通过emit向上发出一个消息
<body>
<div id="app">div>
<script>
// emit
Vue.component("Foo", {
components: {},
data() {
return {};
},
methods: {
handleClick() {
console.log("click");
// 发起一个事件,子向父级发起事件
//(自定义事件类型,第一个参数是事件名,第二个参数是事件传参)
this.$emit("heihei", 1);
// 烤肉串 命名法 ---> aa-bb-cc
// 驼峰 命名法 ---> aaBbCc
this.$emit("change-page", 2);// 官方推荐 ---> 烤肉串 命名法
},
},
template: `
foo
`,
});
// 根组件
const app = new Vue({
el: "#app",
methods: {
handleHeihei(v) {
console.log(v);//1
console.log("heihei");
},
handleChangePage(v) {
console.log(v);//2
},
},
template: `
{{msg}}
`,
data() {
return {
msg: "hello",
};
},
});
script>
body>
五、v-model
应用场景:子组件发出来,父组件去更新的(显示)需求
<body>
<div id="app">div>
<script>
Vue.component("Foo", {
components: {
},
data() {
return {
};
},
methods: {
handleClose(){
this.$emit("close")
}
},
template: `
foo
`,
});
// 根组件
const app = new Vue({
el: "#app",
template: `
{{msg}}
`,
data() {
return {
msg: "hello",
showFoo: false,
};
},
methods: {
handleShowFoo(){
this.showFoo = true;
},
handleCloseFoo(){
this.showFoo = false;
}
}
});
script>
body>
<body>
<div id="app">div>
<script>
// 第一, 在对应的组件 Foo内声明 props对应的value
Vue.component("Foo", {
components: {},
props: ["visible"],
data() {
return {};
},
model: {
// 自定义
prop: "visible", // 默认prop -> value
event: "close", // 默认event -> input
},
methods: {
handleClose() {
// this.$emit("close");
// 第二
this.$emit("input", false);// 写法一 必须写成input
this.$emit("close", false);// 写法二 model内定义(自定义名称)
},
},
template: `
foo
{{visible}}
`,
});
// sync
// 根组件
const app = new Vue({
el: "#app",
template: `
{{msg}}
//
// 第三,写上v-model
// 此处去掉v-if="showFoo" 可在 子组件内根节点写上 v-if="visible"
// 有点局限性,就是别的组件也想通过v-model来用,不行,只能有一个,vue3解决了允许设置多个v-model
// vue2想用多个的话可通过sync
`,
// value -> input
data() {
return {
msg: "hello",
showFoo: false,
};
},
methods: {
handleShowFoo() {
this.showFoo = true;
},
handleClose() {
this.showFoo = false;
},
},
});
script>
body>
六、sync
需求(应用场景):当有多个需求(子组件发出来,父组件去更新的需求)
不再限制props接收的是什么东西 了,随意去定义;
无model;
sync与 v-model对比的优势:sync可以执行多个,支持多个,而v-model只支持一个
<body>
<div id="app">div>
<script>
Vue.component("Foo", {
components: {},
props: ["visible", "number"],//第一
data() {
return {};
},
methods: {
handleClose() {
this.$emit("update:visible", false);// 第三,update:,把对应的visible改为false
},
handleNumber() {
this.$emit("update:number", Math.random());
},
},
template: `
foo
{{visible}}
`,
});
// sync
// 根组件
const app = new Vue({
el: "#app",
template: `
{{msg}}
{{count}}
// 第二,:visible.sync
`,
// value -> input
data() {
return {
msg: "hello",
showFoo: false,
count: 0,
};
},
methods: {
handleShowFoo() {
this.showFoo = true;
},
handleClose() {
this.showFoo = false;
},
},
});
script>
body>
七、插槽 slot(一个占用口,到时替换)
插槽slot,用于父组件内容插入子组件(内容分发)
功能:
1.默认插槽,可设置默认值
2.允许写多个插槽
3.具名插槽
4.具名插槽作用域,可定义多个(可传值)
5.缩写 # (v-slot:header 或者 #header)
应用场景:利用slot作用域,做一个模板
<body>
<div id="app">div>
<script>
Vue.component("Foo", {
components: {},
data() {
return {};
},
template: `
foo
// 4.具名插槽作用域,可定义多个(可传值) ---> count="123" test="234"
headers
//1.默认插槽,默认值headers
main // 3.具名插槽 name="main"
footer
`,
});
// 根组件
const app = new Vue({
el: "#app",
template: `
{{msg}}
// 通过vue提供的空组件template去指定对应的插槽
// 写法 --> v-slot:header 或者 #header
//解构count
{{count}}
red
yellow
blue
`,
data() {
return {
msg: "hello",
};
},
});
script>
body>
案例:slot-template
<body>
<div id="app">div>
<script>
// TodoList 帮助我们循环数据,遍历出来,可作为一个模板
// 需求:TodoList 不改变循环,作为模板使用(利用slot做复用,可重复使用)模板的复用
Vue.component("TodoList", {
template: `
-
{{item.name}} // 利用slot复用
`,
data() {
return {
items: [{ name: "heihei" }, { name: "haha" }],
};
},
});
// Vue.component("TodoList", {
// template: `
//
//
//
// {{item.name}} + "----heihei"
//
//
//
// `,
// data() {
// return {
// items: [{ name: "heihei" }, { name: "haha" }],
// };
// },
// });
// 根组件
const app = new Vue({
el: "#app",
template: `
{{item.name}} + "----heihei"
`,
data() {
return {
msg: "hello",
};
},
});
script>
body>
复用性
1.mixin -> 解决组件的复用性
<body>
<div id="app">div>
<script>
// mixin -> 组件的复用性 -> 抽离出来公用
// A组件可用 -> featureA
// B组件可用 -> featureA
// function setName(name) {
// // 函数的参数
// }
// 鼠标移动的功能 --> 抽离对象(普通的对象)
const mousemoveMixin = {
methods: {
handleMousemove(e) {
this.x = e.pageX;
this.y = e.pageY;
},
},
mounted() {//组件创建完成 写个 事件监听addEventListener
window.addEventListener("mousemove", this.handleMousemove);
},
destroyed() {// 组件销毁完成 写个 移除事件监听removeEventListener
window.removeEventListener("mousemove", this.handleMousemove);
},
data() {
return {
x: 0,
y: 0,
};
},
};
// Foo组件
Vue.component("Foo", {
mixins: [mousemoveMixin],
template: `
Foo- {{x}} -- {{y}}
`,
});
// 根组件
const app = new Vue({
el: "#app",
mixins: [mousemoveMixin],
template: `
`,
});
script>
body>
2.slots -> 解决组件的复用性
<body>
<div id="app">div>
<script>
// 鼠标移动的功能 Mousemove 对应的组件
const Mousemove = {
template: `
// 通过slot把x、y传出去
`,
methods: {
handleMousemove(e) {
this.x = e.pageX;
this.y = e.pageY;
},
},
mounted() {
window.addEventListener("mousemove", this.handleMousemove);
},
destroyed() {
window.removeEventListener("mousemove", this.handleMousemove);
},
data() {
return {
x: 0,
y: 0,
};
},
};
// slots
// 根组件
// ->问题(多个minxin的话就不清楚x,y是哪个minxin内的了,有命名冲突的问题) vue3 里面解决 -> composition
const app = new Vue({
el: "#app",
components: {// 注册组件Mousemove
Mousemove,
},
template: `
// 用注册好的组件
{{x}} - {{y}}
`,
});
script>
body>
八、自定义指令
之前的指令如v-if,v-show都是官方的指令,现在可自定义指令
以下生命周期在vue3会改变,和vue2组件生命周期一样的名称
Vue.directive
生命周期(这里的生命周期与vue的生命周期不一样,vue2不一样,vue3对齐生命周期了)
bind
只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置
inserted
被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
update
所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
componentUpdated
指令所在组件的 VNode 及其子 VNode 全部更新后调用。
unbind
只调用一次,指令与元素解绑时调用。
案例:自定义v-focus指令
<body>
<div id="app">div>
<script>
//需求:打开input 之后自动聚焦
// 根组件
//focus
// input -> input
// v-if v-show v-focus
// 聚焦的自定义指令
Vue.directive("focus", {
bind(el, binding) {
// el.focus();不起作用,需要插入父节点后才进行调用
// console.log(binding)
},
inserted(el, binding) {
console.log(el);
console.log(binding);
el.focus();//此处有作用,父节点
},
});
const app = new Vue({
el: "#app",
template: `
`,
data() {
return {
msg: "hello",
};
},
});
script>
body>
小作业
- (自定义组件)实现组件 PhotoItem 封装展示图片的逻辑
- 可通过 props 传入图片路径 imgUrl
- 可通过 props 传入图片名称 imgName
解释题目:
// PhotoItem
// imgUrl -> 显示一张图片 -> img
// imgName -> {{imgName}}
//
答案:
在这里插入代码片