赞
踩
比如我们现在想要实现了一个功能:
这个案例我们可以通过两种不同的实现思路来实现:
我们可以先通过v-if来判断显示不同的组件,这个可以使用我们之前讲过的知识来实现:
<template> <div> <button v-for="tab in tabs" :key="tab" :class="{active: currentTab === tab}" @click="tabClick(tab)"> {{tab}} </button> <template v-if="currentTab === 'home'"> <home></home> </template> <template v-else-if="currentTab === 'about'"> <about></about> </template> <template v-else> <category></category> </template> </div> </template> <script> import Home from "./pages/Home.vue"; import About from "./pages/About.vue"; import Category from "./pages/Category.vue"; export default { components: { Home, About, Category }, data() { return { tabs: ["home", "about", "category"], currentTab: "home" } }, methods: { tabClick(tab) { this.currentTab = tab; } } } </script> <style scoped> .active { color: red; } </style>
动态组件是使用 component 组件,通过一个特殊的attribute is 来实现:
<template>
<div>
<button v-for="tab in tabs"
:key="tab"
:class="{active: currentTab === tab}"
@click="tabClick(tab)">
{{tab}}
</button>
<component :is="currentTab"></component>
</div>
</template>
这个currentTab的值需要是什么内容呢?
如果是动态组件我们可以给它们传值和监听事件吗?
App.vue的代码如下:
<template> <div> <button v-for="tab in tabs" :key="tab" :class="{active: currentTab === tab}" @click="tabClick(tab)"> {{tab}} </button> <component name="why" :age="18" @pageClick="pageClick" :is="currentTab"/> </div> </template> <script> import Home from "./pages/Home.vue"; import About from "./pages/About.vue"; import Category from "./pages/Category.vue"; export default { components: { Home, About, Category }, data() { return { tabs: ["home", "about", "category"], currentTab: "home" } }, methods: { tabClick(tab) { this.currentTab = tab; }, pageClick(payload) { console.log("pageClick", payload); } } } </script> <style scoped> .active { color: red; } </style>
Home.vue中的代码如下:
<template> <div @click="pageClick"> Home组件: {{name}}-{{age}} </div> </template> <script> export default { props: { name: String, age: Number }, emits: ["pageClick"], methods: { pageClick() { this.$emit("pageClick", "Home组件"); } } } </script>
我们先对之前的案例中About组件进行改造:
<template> <div> About组件 <button @click="counter++">{{counter}}</button> </div> </template> <script> export default { data() { return { counter: 0 } } } </script>
比如我们将counter点到10,那么在切换到home再切换回来about时,状态是否可以保持呢?
但是,在开发中某些情况我们希望继续保持组件的状态,而不是销毁掉,这个时候我们就可以使用一个内置组件:keep-alive。
<keep-alive>
<component name="why"
:age="18"
@pageClick="pageClick"
:is="currentTab"/>
</keep-alive>
keep-alive有一些属性
include 和 exclude prop 允许组件有条件地缓存:
<!-- 逗号分隔字符串 -->
<keep-alive include="a,b">
<component :is="view"></component>
</keep-alive>
<!-- regex (使用 `v-bind`) -->
<keep-alive :include="/a|b/">
<component :is="view"></component>
</keep-alive>
<!-- Array (使用 `v-bind`) -->
<keep-alive :include="['a', 'b']">
<component :is="view"></component>
</keep-alive>
1.4.3. 缓存的生命周期
对于缓存的组件来说,再次进入时,我们是不会执行created或者mounted等生命周期函数的:
但是有时候我们确实希望监听到何时重新进入到了组件,何时离开了组件;
这个时候我们可以使用activated 和 deactivated 这两个生命周期钩子函数来监听;
<template> <div> About组件 <button @click="counter++">{{counter}}</button> </div> </template> <script> export default { name: "about", data() { return { counter: 0 } }, // 当重新进入活跃状态时会回调 activated() { console.log("about activated") }, // 当离开活跃状态时会回调 deactivated() { console.log("about deactivated") } } </script>
默认的打包过程:
打包时,代码的分包:
那么webpack中如何可以对代码进行分包呢?
默认情况下,我们直接使用import来依赖一个模块时,是不会进行分包的:
import {sum} from './utils/math';
console.log(sum(20, 30));
如果我们希望进行分包,那么可以使用import函数:(返回一个promise)
import("./utils/math").then(({ sum }) => {
console.log(sum(20, 30));
});
如果我们的项目过大了,对于某些组件我们希望通过异步的方式来进行加载(目的是可以对其进行分包处理),那么Vue中给我们提供了一个函数:defineAsyncComponent。
defineAsyncComponent接受两种类型的参数:
工厂函数类型一的写法:
<script>
import { defineAsyncComponent } from 'vue';
const AsyncHome = defineAsyncComponent(() => import("./AsyncHome.vue"));
export default {
components: {
AsyncHome
}
}
</script>
对象类型类型二的写法:
<script> import { defineAsyncComponent } from "vue"; // const AsyncHome = defineAsyncComponent(() => import("./AsyncHome.vue")); import Loading from "./Loading.vue"; import Error from "./Error.vue"; const AsyncHome = defineAsyncComponent({ // 工厂函数 loader: () => import("./AsyncHome.vue"), // 加载过程中显示的组件 loadingComponent: Loading, // 加载失败时显示的组件 errorComponent: Error, // 在显示 loadingComponent 之前的延迟 | 默认值:200(单位 ms) delay: 200, // 如果提供了 timeout ,并且加载组件的时间超过了设定值,将显示错误组件 // 默认值:Infinity(即永不超时,单位 ms) timeout: 3000, // 定义组件是否可挂起 | 默认值:true suspensible: false, /** * * @param {*} error 错误信息对象 * @param {*} retry 一个函数,用于指示当 promise 加载器 reject 时,加载器是否应该重试 * @param {*} fail 一个函数,指示加载程序结束退出 * @param {*} attempts 允许的最大重试次数 */ onError(error, retry, fail, attempts) { if (error.message.match(/fetch/) && attempts <= 3) { // 请求发生错误时重试,最多可尝试 3 次 retry(); } else { // 注意,retry/fail 就像 promise 的 resolve/reject 一样: // 必须调用其中一个才能继续错误处理。 fail(); } }, }); export default { components: { AsyncHome, }, }; </script>
注意,(2021-06-08)Suspense显示的是一个实验性的特性,API随时可能会修改。
Suspense是一个内置的全局组件,该组件有两个插槽:
<template>
<div>
<suspense>
<template #default>
<async-home></async-home>
</template>
<template #fallback>
<loading/>
</template>
</suspense>
</div>
</template>
某些情况下,我们在组件中想要直接获取到元素对象或者子组件实例:
组件实例有一个$refs属性:
App.vue的实现:
<template> <div> <h2 ref="title">哈哈哈</h2> <hello-world ref="helloCpn"></hello-world> <button @click="visitElement">访问元素或者组件</button> </div> </template> <script> import HelloWorld from './HelloWorld.vue'; export default { components: { HelloWorld }, methods: { visitElement() { // 访问元素 console.log(this.$refs.title); // 访问组件实例 this.$refs.helloCpn.showMessage(); // 访问子组件里的根元素 console.log(this.$refs.helloCpn.$el); } } } </script>
HelloWorld.vue实现:
<template>
<div>
<h2>HelloWorld</h2>
</div>
</template>
<script>
export default {
methods: {
showMessage() {
console.log("我是HelloWorld组件的showMessage方法");
}
}
}
</script>
我们可以通过$parent来访问父元素。
HelloWorld.vue的实现:
<template> <div> <button @click="visitParent">访问父组件</button> </div> </template> <script> export default { methods: { showMessage() { console.log("我是HelloWorld组件的showMessage方法"); }, visitParent() { console.log(this.$parent.message); } } } </script>
注意:在Vue3中已经移除了$children的属性,所以不可以使用了。
什么是生命周期呢?
我们通过一个App和Home来演练所有的生命周期函数。
App.vue组件对象:
<template> <div> <button @click="toggle">切换</button> <div v-if="isShow"> <home></home> </div> </div> </template> <script> import Home from './Home.vue'; export default { components: { Home }, data() { return { isShow: true } }, methods: { toggle() { this.isShow = !this.isShow; console.log(this.isShow); } } } </script>
Home.vue组件对象:
<template> <div> <button @click="changeMessage">修改message</button> <h2 ref="titleRef">{{message}}</h2> </div> </template> <script> export default { data() { return { message: "Hello World" } }, methods: { changeMessage() { this.message = "你好啊,李银河"; } }, beforeUpdate() { console.log("beforeUpdate"); console.log(this.$refs.titleRef.innerHTML); }, updated() { console.log("updated"); console.log(this.$refs.titleRef.innerHTML); }, beforeCreate() { console.log("beforeCreate"); }, created() { console.log("created"); }, beforeMount() { console.log("beforeMount"); }, mounted() { console.log("mounted"); }, beforeUnmount() { console.log("beforeUnmount"); }, unmounted() { console.log("unmounted"); } } </script>
前面我们在input中可以使用v-model来完成双向绑定:
如果我们现在封装了一个组件,其他地方在使用这个组件时,是否也可以使用v-model来同时完成这两个功能呢?
当我们在组件上使用的时候,等价于如下的操作:
那么,为了我们的MyInput组件可以正常的工作,这个组件内的 必须:
MyInput.vue的组件代码如下:
<template> <div> <input :value="modelValue" @input="inputChange"> </div> </template> <script> export default { props: ["modelValue"], emits: ["update:modelValue"], methods: { inputChange(event) { this.$emit("update:modelValue", event.target.value); } } } </script>
在App.vue中,我们在使用MyInput可以直接使用v-model:
<template> <div> <my-input v-model="message"/> <button @click="changeMessage">修改message</button> </div> </template> <script> import MyInput from './MyInput.vue'; export default { components: { MyInput }, data() { return { message: "" } }, methods: { changeMessage() { this.message = "Hello World" } } } </script>
在上面的案例中,我们可能会想到一种实现方法:直接将Props中的属性双向绑定到input上
<template>
<div>
<input v-model="modelValue">
</div>
</template>
<script>
export default {
props: ["modelValue"]
}
</script>
上面这种方式可以实现组件的双向绑定吗?答案是不可以
那么,我们依然希望在组件内部按照双向绑定的做法去完成,应该如何操作呢?我们可以使用计算属性的setter和getter来完成。
<template> <div> <input v-model="value"> </div> </template> <script> export default { props: ["modelValue"], emits: ["update:modelValue"], computed: { value: { get() { return this.modelValue; }, set(value) { this.$emit("update:modelValue", value) } } } } </script>
我们现在通过v-model是直接绑定了一个属性,如果我们希望绑定多个属性呢?也就是我们希望在一个组件上使用多个v-model是否可以实现呢?
我们先看一下在App.vue中我是如何使用的:
<template> <div> <my-input v-model="message" v-model:title="title"/> <h2>{{message}}</h2> <button @click="changeMessage">修改message</button> <hr> <h2>{{title}}</h2> <button @click="changeTitle">修改title</button> </div> </template> <script> import MyInput from './MyInput.vue'; export default { components: { MyInput }, data() { return { message: "", title: "" } }, methods: { changeMessage() { this.message = "Hello World" }, changeTitle() { this.title = "Hello Title" } } } </script>
注意:这里我是绑定了两个属性的
<my-input v-model="message" v-model:title="title"/>
v-model:title相当于做了两件事:
所以,我们MyInput中的实现如下:
<template> <div> <input :value="modelValue" @input="input1Change"> <input :value="title" @input="input2Change"> </div> </template> <script> export default { props: ["modelValue", "title"], emits: ["update:modelValue", "update:title"], methods: { input1Change(event) { this.$emit("update:modelValue", event.target.value); }, input2Change(event) { this.$emit("update:title", event.target.value); } } } </script>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。