Vue组件开发--ref引用、动态组件、组件缓存、异步组件、组件混入 以你之姓@ 2024-04-03 09:13 26阅读 0赞 ### 一、$refs的使用 ### 某些情况下,我们在组件中想要直接获取到`DOM`元素对象或者子组件实例。 `Vue`开发中并不推荐进行向原生`js`一样操作`DOM`元素,此时可以给`DOM`元素或者组件绑定一个`ref`属性。 当前组件实例有一个`$refs`属性: 它是一个对象,对象中有注册过`ref`属性的`DOM`元素和组件实例。 #### 1.1 代码示例 #### <template> <div class="app"> <!-- 给h2、button中两个DOM元素绑定ref属性 --> <h2 ref="title" class="title" :style="{ color: titleColor }">{ { message }}</h2> <button ref="btn" @click="changeTitle">修改title</button> <!-- 给banner组件绑定ref属性 --> <banner ref="banner"/> </div> </template> <script> import Banner from "./Banner.vue" export default { components: { Banner }, data() { return { message: "Hello ref", } }, methods: { changeTitle() { // 1.获取h2/button 这种DOM元素 console.log(this.$refs.title) console.log(this.$refs.btn) // 2.获取banner组件: 组件实例 console.log(this.$refs.banner) // 2.1.在父组件中可以主动的调用子组件的对象方法 this.$refs.banner.bannerClick() // 2.2.获取banner组件实例, 获取banner中的DOM元素 console.log(this.$refs.banner.$el) // 3.3.如果banner template是多个根节点, 拿到的是第一个node节点 // 这种情况很少遇到,一般不会去给组件设置多个根节点 console.log(this.$refs.banner.$el.nextElementSibling) // 4.组件实例还有两个属性: // 在Vue3中已经移除了$children的属性,不可以再使用了,这两个其实也不是很常用 console.log(this.$parent) // 获取父组件 console.log(this.$root) // 获取根组件 } } } </script> #### 1.2 小结 #### * 在`template`中给想要获取的元素添加`ref`属性。 * 在`Vue`当前组件下,使用`this.$refs`去获取添加了`ref`属性的元素。 ### 二、动态组件 ### 假设有两个按钮,点击一下就切换一个组件。 一般来讲会有两种方案去实现这个需求: * 使用`v-if`或者`v-show`来显示不同的组件 * 使用动态组件的方式 动态组件使用 `component` 组件,通过一个属性`is` 来实现组件切换 #### 2.1 代码示例 #### <template> <div class="app"> <div class="tabs"> <template v-for="(item, index) in tabs" :key="item"> <button :class="{ active: currentTab === item }" @click="itemClick(item)"> { { item }} </button> </template> </div> <div class="view"> <!-- 动态组件 component --> <!-- is中的组件需要来自两个地方: 1.全局注册的组件 2.局部注册的组件 --> <component name="张三" :age="18" @homeClick="homeClick" :is="currentTab"> </component> </div> </div> </template> <script> import Home from './views/Home.vue' import About from './views/About.vue' import Category from './views/Category.vue' export default { components: { Home, About, Category }, data() { return { tabs: ["home", "about", "category"], currentTab: "home" } }, methods: { itemClick(tab) { this.currentTab = tab }, homeClick(payload) { console.log("homeClick:", payload) } } } </script> <style scoped> .active { color: red; } </style> #### 2.2 小结 #### * 动态组件使用 `component` 组件,通过一个属性`is` 来实现组件切换 * `is`中只需要传递组件的字符串名称。 * 组件需要来自两个地方: 全局注册的组件或者局部注册的组件 * 动态组件也可以给它们传值和监听事件,只要将属性和监听事件放到`component`上来使用 * 其它没有这些属性数据或者事件的不用管就可以了 ### 三、组件缓存 ### `<KeepAlive>` 是一个内置组件,它的功能是在多个组件间动态切换时缓存被移除的组件实例。 简单的说,就是让一个组件保持存活状态。 默认情况下,一个组件实例在被替换掉后会被销毁,这会导致它丢失其中所有已变化的状态。 当这个组件再一次被显示时,会创建一个只带有初始状态的新实例。 而使用`<KeepAlive>`这个内置组件包裹需要保存状态的动态组件就可以解决上面的问题。 #### 3.1 KeepAlive基本使用 #### <KeepAlive> <component :is="view" /> </KeepAlive> #### 3.2 KeepAlive的属性 #### * **include:** 只有名称匹配的组件会被缓存 * 数据类型支持:字符串、正则表达式、数组 * 匹配首先检查组件自身的`name`选项 * **exclude:** 任何名称匹配的组件都不会被缓存 * 数据类型支持:字符串、正则表达式、数组 * 匹配首先检查组件自身的`name`选项 * **max:** 最大缓存实例数,达到最大值是缓存组件中最久没有被访问的缓存实例将被销毁 * 数据类型支持:字符串、数字 <!-- 以英文逗号分隔的字符串 --> <KeepAlive include="a,b"> <component :is="view" /> </KeepAlive> <!-- 正则表达式 (需使用 `v-bind`) --> <KeepAlive :include="/a|b/"> <component :is="view" /> </KeepAlive> <!-- 数组 (需使用 `v-bind`) --> <KeepAlive :include="['a', 'b']"> <component :is="view" /> </KeepAlive> <KeepAlive :max="10"> <component :is="activeComponent" /> </KeepAlive> #### 3.3 缓存实例的生命周期 #### 对于缓存的组件来说,再次进入时,不会执行`created`或者`mounted`等生命周期函数的。 但是有时候我们确实希望监听到何时重新进入到了组件,何时离开了组件。 这个时候可以使用`activated` 和 `deactivated` 这两个生命周期钩子函数来监听上述变化。 <script> export default { activated() { // 在首次挂载、 // 以及每次从缓存中被重新插入的时候调用 }, deactivated() { // 在从 DOM 上移除、进入缓存 // 以及组件卸载时调用 } } </script> **注意:** * `activated` 在组件挂载时也会调用,并且 `deactivated` 在组件卸载时也会调用。 * 这两个钩子不仅适用于 `<KeepAlive>` 缓存的根组件,也适用于缓存树中的后代组件。 ### 四、异步组件 ### 异步组件和打包是息息相关的。了解它对项目的优化也是很有必要的。 #### 4.1 webpack分包 #### ![在这里插入图片描述][be5b660dadeb449db1a6f24ecb116490.png] ##### 1)js文件目录解释 ##### 可以看到,之前的所有自己编写的`Vue`组件模块代码都被打包到了`app.js`中。 `chunk-vendors`字面意思是代码块供应商,其实就是项目中依赖得的各种第三包,也包括`Vue`的源代码。 `.map`文件,是为了方便调试用的。 因为打包后的代码已经无法直观的通过肉眼去看了。调试时,是不清楚到底哪行代码出现的问题。 而`.map`文件就是一个把打包后的代码和源代码一一对应的映射文件,从而解决了上述问题。 ##### 2)这种代码方式带来的问题 ##### 随着项目的不断庞大,`app.js`文件的内容过大,会造成首屏的渲染速度变慢。 ##### 3)代码分包 ##### 打包时,对于一些不需要立即使用的组件,我们可以单独对它们进行拆分,拆分成一些小的代码块`chunk.js`。 这些`chunk.js`会在需要时从服务器加载下来,并且运行代码,显示对应的内容。 这样就解决了`app.js`文件过大的问题。 **具体实现:** import("./utils/math").then(res => { res.sum(20, 30) }) `import`函数可以让`webpack`对导入文件进行分包处理。 这种方式是一种异步导入,返回的是一个`promise`对象。 ![在这里插入图片描述][2de11a6e662d4074b41c1bfff9e8e5e5.png] 可以看到多了两个以`749`开头的文件。这就是`import`函数导入的异步组件,`webpack`会对它进行分包处理。 #### 4.2 Vue中异步组件 #### 在大型项目中,我们可能需要拆分应用为更小的块,并仅在需要时再从服务器加载相关组件。 目的是可以对其进行分包处理。`Vue` 提供了`defineAsyncComponent`方法来实现此功能。 `defineAsyncComponent`接受两种类型的参数: * 工厂函数:该工厂函数需要返回一个`Promise`对象 * 对象类型:对异步函数进行配置 ##### 1)工厂函数 ##### `ES`模块动态导入会返回一个`Promise`,所以多数情况下我们会将它和 `defineAsyncComponent` 搭配使用。 类似 `Vite` 和 `Webpack` 这样的构建工具也支持此语法 ,并且会将它们作为打包时的代码分割点。 <script> import { defineAsyncComponent } from 'vue' const AsyncComp = defineAsyncComponent(() => import('./components/MyComponent.vue') ) export default { components: { AsyncComp }, } </script> ##### 2)对象类型 ##### <script> const AsyncComp = defineAsyncComponent({ // 加载函数 loader: () => import('./Foo.vue'), // 加载异步组件时使用的组件 loadingComponent: LoadingComponent, // 展示加载组件前的延迟时间,默认为 200ms delay: 200, // 加载失败后展示的组件 errorComponent: ErrorComponent, // 如果提供了一个 timeout 时间限制,并超时了 // 也会显示这里配置的报错组件,默认值是:Infinity timeout: 3000 }) export default { components: { AsyncComp }, } </script> > 这种写法实际用的比较少,了解即可。一般都会用路由懒加载的方式。 ##### 3)注意点 ##### <script> const Category = import("./views/Category.vue") export default { components: { Category: AsyncCategory } } </script> 可能有人会有疑问,为什么`.vue`文件中不用`import`函数进行导入。 因为`import`函数进行导入返回的是一个`Promise`对象。`Promise`对象在`components`中是无法使用的。 ### 五、组件混入 ### 使用组件化的方式在开发整个`Vue`的应用程序,但是组件和组件之间有时候会存在相同的代码逻辑。 如果想要对相同的代码逻辑进行抽取,在`Vue2`和`Vue3`中都支持的一种方式就是使用`Mixin`的方式来完成。 #### 5.1 Mixin的作用如下: #### * `Mixin`提供了一种非常灵活的方式,来分发`Vue`组件中的可复用功能 * 一个`Mixin`对象可以包含任何组件选项 * 当组件使用`Mixin`对象时,所有`Mixin`对象的选项将被 混合 进入该组件本身的选项中 #### 5.2 局部混入代码示例 #### ##### 1)新建mixin.js存放想要抽取的代码逻辑 ##### export default { data() { return { message: "Hello World" } }, created() { console.log("message:", this.message) } } ##### 2)组件中混入mixin.js中的代码逻辑 ##### <template> <h2>组件A</h2> </template> <script> // 引入抽取出来的代码逻辑 import messageMixin from '../mixins/mixin' export default { // 对messageMixin中的代码逻辑进行混入 mixins: [messageMixin] } </script> <style scoped> </style> `A`组件混入`mixin.js`中的代码后,也就获得了`mixin.js`里面封装的功能。 #### 5.3 全局混入Mixin代码示例 #### 在`main.js`中使用`app`对象的`mixin`方法对所有组件进行代码混入。 const app = createApp(App) // 这里面就是需要混入所有组件的内容 app.mixin({ created() { console.log("mixin created") } }) app.mount('#app') > 全局混入用的并不多,了解即可 #### 5.4 Mixin对象中的选项和组件对象中的选项发生冲突的解决方式 #### * `data`函数的返回值对象冲突 * 返回值对象默认情况下会进行合并 * `data`返回值对象的属性发生了冲突,那么会保留组件自身的数据 * 如何生命周期钩子函数冲突 * 生命周期的钩子函数会被合并到数组中,都会被调用 * 其它值为对象的选项,如 `methods`、`components` 和 `directives`,将被合并为同一个对象。 * 比如都有`methods`选项,并且都定义了方法,那么它们都会生效 * 但是如果对象的`key`相同,那么会取组件对象本身的键值对 [be5b660dadeb449db1a6f24ecb116490.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/03/107b2387483a4c8cb164165623ae0326.png [2de11a6e662d4074b41c1bfff9e8e5e5.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/03/ab10f2f829ae4625a3166b24db0240b4.png
还没有评论,来说两句吧...