Vue 学习笔记-2022/12/09
Vue 组件深入
一、响应性组件 props 和 组件刷新
父组件
<template>
<main>
<div class="container">
<PaginationComponent
:totalPage="totalPage"
:defaultCurrentPage="currentPage"
/>
<!-- totalPage 发生变化 同时 data也发生变化 也会传给PaginationComponent 子组件 子组件也随着刷新 -->
<button @click="totalPage += 1">增加页数</button>
</div>
</main>
</template>
<script>
import PaginationComponent from "./components/PaginationComponent.vue";
export default {
components: {
PaginationComponent,
},
data() {
return {
totalPage: 6,
currentPage: 4,
};
},
};
</script>
子组件
<template>
<div>
<p>当前页: {{ currentPage }}</p>
<!--接收父组件的 n 然后传给子组件-->
<button v-for="n in totalPage" @click="changePage(n)">
{{ n }}
</button>
</div>
</template>
<script>
export default {
props: ["totalPage", "defaultCurrentPage"],
data() {
return {
currentPage: this.defaultCurrentPage,
};
},
methods: {
changePage(n) {
this.currentPage = n;
},
},
};
</script>
二、 组件的数据流向
在子组件里最好使用 emit 去触发事件
其他属性则可以用 props 进行传递
三、组件的生命周期
和 Vue 的生命周期保持一致
四、Provide / Inject 给深度组件传值
provide 在我的个人理解就是出值
Inject 则是接收值
provide 有两种用法
// 可以使用 this 但是必须为函数 内容是响应式
provide() {
return {
title: this.movie.title,
};
},
// 直接返回 内容是静态的
provide: {
title: "测试电影",
},
Inject 用法
export default {
// 在数组里定义 provide 的属性名
inject: ["title"],
};
五、在传递slot模板中访问子组件的属性
子组件
<template>
<ul>
<li v-for="contact in contacts" :key="contact.id">
<slot />
</li>
</ul>
</template>
父组件
<template>
<main>
<div>
<!-- 这里使用了解构语法 因为子组件只是声明了一个默认的 solt 模板 -->
<ContactList v-slot="{ contact }">
<!-- // 在多个模板的情况下 还是需要去定义对象名的 {props为自定义变量名} -->
<!-- <ContactList v-slot="props"> -->
<!-- <ContactList v-slot:default="props"> -->
<!-- <template v-slot:default="props"> -->
<!-- <p>{{ props.contact.name }}</p>
<!-- <p>{{ props.contact.email }}</p> -->
<p>{{ contact.name }}</p>
<p>{{ contact.email }}</p>
<!-- </template> -->
</ContactList>
</div>
</main>
</template>
<script>
import ContactList from "./components/ContactList.vue";
export default {
components: {
ContactList,
},
};
</script>
六、组件支持 v-model
1.单个 v-model 绑定
父组件
<template>
<main>
<div>
<SearchInput v-model="searchTerm" />
<p>{{ searchTerm }}</p>
</div>
</main>
</template>
<script>
import SearchInput from "./components/SearchInput.vue";
export default {
components: {
SearchInput,
},
data() {
return {
searchTerm: "",
};
},
};
</script>
子组件
<template>
<label
><span>搜索:</span
><input
type="text"
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</label>
</template>
<script>
export default {
// props 和 emits 都为固定接收内容
props: ["modelValue"],
emits: ["update:modelValue"],
};
</script>
2.多个 v-model 绑定
子组件
<template>
<label
><span>搜索:</span
><input
type="text"
:value="searchTerm"
@input="$emit('update:searchTerm', $event.target.value)"
/>
</label>
<label>
<span>类别:</span>
<select
:value="category"
@change="$emit('update:category', $event.target.value)"
>
<option value="default">默认</option>
<option value="fontend">前端</option>
<option value="backend">后端</option>
<option value="fullstack">全栈</option>
</select>
</label>
</template>
<script>
父组件
<template>
<main>
<div>
<!-- v-model 后面的参数必须和子组件接收的属性名相同,例如 searchTerm -->
<SearchInput
v-model:searchTerm="searchTerm"
v-model:category="category"
/>
<div class="splitLine"></div>
<p>搜索词:{{ searchTerm }}</p>
<p>类别:{{ category }}</p>
</div>
</main>
</template>
<script>
import SearchInput from "./components/SearchInput.vue";
export default {
components: {
SearchInput,
},
data() {
return {
// 名字无需和 SearchInput 中的属性名相同
// 例如这里可以叫 searchQuery,
searchTerm: "",
category: "default",
};
},
};
</script>
七、Ref实例
万不得已使用,这个会破坏组件数据流向
1.获取Dom实例
<template>
<input type="text" ref="inputControl" />
</template>
<script>
export default {
//获取所有的refs拿到设置的ref 也就是inputControl 然后使用获取焦点的事件
mounted() {
this.$refs.inputControl.focus();
},
};
</script>
2.获取组件实例
子组件
<template>
<!-- 绑定inputText -->
<input type="text" v-model="inputText" ref="inputControl" />
</template>
<script>
export default {
data() {
return {
inputText: "",
};
},
mounted() {
this.$refs.inputControl.focus();
},
methods: {
blur() {
this.$refs.inputControl.blur();
},
},
};
</script>
父组件
<template>
<main>
<div>
<AutoFocus ref="autofocus" />
</div>
</main>
</template>
<script>
import AutoFocus from "./components/AutoFocus.vue";
export default {
components: {
AutoFocus,
},
mounted() {
// 拿到 ref 实例就能操作 子组件里面的方法
setTimeout(() => {
console.log(this.$refs.autofocus.inputText);
this.$refs.autofocus.blur();
}, 5000);
},
};
</script>
八、自定义指令
自定义组件实际上是就是一系列的生命周期的钩子
在不同生命周期进行操作数据
app.directive("fsize", {
mounted(el, binding) {
el.style.fontSize = binding.value + "px";
},
// 这样在 data 更新时,才会触发指令更新
updated(el, binding) {
el.style.fontSize = binding.value + "px";
},
});
// 如果 mounted 和 updated 的代码相同,可以合并为一个:
app.directive("fsize", (el, binding) => {
el.style.fontSize = binding.value + "px";
});
// 带有 args:
app.directive("fsize", (el, binding) => {
el.style.fontSize = binding.value + (binding.arg || "px");
});
// 进行使用
<p v-fsize:[unit]="fontSize">这是一个段落</p>
// [unit] 为响应式数据
<script>
export default {
data() {
return {
fontSize: 18,
unit: "em",
};
},
};
</script>
九、动态组件
1.设置动态的 h 标签
<!-- TextHeading.vue -->
<template>
<Component :is="heading"><slot></slot></Component>
</template>
<script>
export default {
props: ["level"],
computed: {
heading() {
return `h${this.level}`;
},
},
};
</script>
父组件
<template>
<main>
<div>
<!-- 动态 HTML 元素 -->
<TextHeading level="1">一级标题</TextHeading>
<TextHeading level="2">二级标题</TextHeading>
<TextHeading level="3">三级标题</TextHeading>
<TextHeading level="4">四级标题</TextHeading>
<TextHeading level="5">五级标题</TextHeading>
<TextHeading level="6">六级标题</TextHeading>
</div>
</main>
</template>
<script>
import TextHeading from "./components/TextHeading.vue";
export default {
components: {
TextHeading,
},
};
</script>
2.动态切换组件
- ProfileForm.vue
<template>
<form @submit.prevent>
<label>昵称:<input type="text" /></label>
<label>生日:<input type="date" /></label>
<label>地址:<input type="text" /></label>
</form>
</template>
<script>
export default {};
</script>
<style scoped>
::-webkit-calendar-picker-indicator {
filter: invert(1);
}
</style>
- RegisterForm.vue
<template>
<form @submit.prevent>
<label>手机号:<input type="number" /></label>
<label
>验证码:<input type="number" /><button class="sendSMSCodeBtn">
发送验证码
</button></label
>
</form>
</template>
<script>
export default {};
</script>
<style scoped>
.sendSMSCodeBtn {
margin-left: 24px;
}
</style>
- App.vue
<!-- 弊端 无法保存数据 每次切换都会生成新的组件实例 -->
<template>
<main>
<div>
<!-- keepAlive 可以缓存数据 -->
<KeepAlive>
<Component :is="currentForm" />
</KeepAlive>
<div class="buttons">
<button
v-if="currentForm === 'RegisterForm'"
@click="currentForm = 'ProfileForm'"
>
下一步
</button>
<template v-else-if="currentForm === 'ProfileForm'">
<button @click="currentForm = 'RegisterForm'">
上一步
</button>
<button>完成</button>
</template>
</div>
</div>
</main>
</template>
<script>
import RegisterForm from "./components/RegisterForm.vue";
import ProfileForm from "./components/ProfileForm.vue";
export default {
components: {
RegisterForm,
ProfileForm,
},
data() {
return {
currentForm: "RegisterForm",
};
},
};
</script>
十、组件传送
1. teleport 传送
<!-- 给 to 属性对应的选择器就可以进行传送 -->
<Teleport to="body">
<div v-if="show" class="alertBox">
<div class="closeIcon" @click="show = false">X</div>
<div class="content">
<slot>消息提示框组件</slot>
</div>
</div>
</Teleport>
2.teleport 多次传送
根据自定义的
Css
进行追加
十一、编程式的模板
1.使用渲染函数
使用它可以直接在模板里使用 JavaScript 语法
难搞 感觉这个东西麻烦的很
<script>
import { h } from "vue";
export default {
props: ["title"],
render() {
return h("div", { class: "card" }, [
h("div", { class: "title" }, this.title),
h("div", { class: "content" }, this.$slots.default()),
]);
},
};
</script>
2.渲染函数中使用指令
<script>
import { h } from "vue";
export default {
props: ["title"],
render() {
// map 代替 v-for,if/else 代替 v-if,children 可以是数组
return h("div", { class: "card" }, [
h("div", { class: "title" }, this.title),
[
h("div", { class: "content" }, this.$slots.default()),
[1, 2, 3, 4].map((item) => h("h" + item, {}, item)),
],
]);
},
};
</script>
十二、Mxins 组件配置
现在不推荐使用了,有更好的进行替代了
其实在我的个人理解 mixin 配置就和个普通的对象一样 只是别的组件可以进行复用而已 只需要在
mixins: [xxx]
导入即可,当然是我的个人理解,可能有错
1. 普通使用
// 导出
const PaginationMixin = {
props: ["totalPage", "defaultCurrentPage"],
data() {
return {
currentPage: this.defaultCurrentPage,
};
},
methods: {
changePage(page) {
this.currentPage = page;
},
},
};
export default PaginationMixin;
// 导入
import PaginationMixin from "../mixins/PaginationMixin";
export default {
mixins: [PaginationMixin],
};
如果 mixin 的属性和组件的属性发生的冲突 则组件的属性会覆盖 mixin 的相同属性,不同的属性则会进行合并
如果 mixin 的生命周期钩子和 组件的生命周期相同,这个时候二者都会执行,不过 mixin的钩子优先级更高会先执行
2. 全局 mixin 配置
全局 mixin 配置 适合给组件做通用的配置,或者是自定义的配置
// 需要用 this 访问
app.mixin({
siteTitle: "我的 Vue 应用",
computed: {
siteTitle() {
return this.$options.siteTitle;
},
},
});
十三、异步组件
1.组件异步加载
import { defineAsyncComponent } from "vue";
// es6 异步导入 + defineAsyncComponent 实现第一次挂载时导入
const ProductPage = defineAsyncComponent(() =>
import("./components/ProductPage.vue")
);
十四、组件错误处理
1.全局处理
app.config.errorHandler = (err, vm, info) => {
console.log(err);
console.log(vm);
console.log(info);
};
2.局部组件处理(错误边界)
// 错误捕获生命周期钩子
errorCaptured(err, instance, info) {
},