动态组件
切换组件的案例
现在我们想要实现一个导航栏的小案例,点击导航栏,切换不同组件的展示。
有三种方式可以实现:
- v-if
- 动态组件
- 路由
```html
———————-nav-bar———————-
<a name="ITgSZ"></a>
## 动态组件的实现
**动态组件是使用 **`**component**`** 组件实现,通过一个特殊的attribute **`**is**`** 来指定渲染哪个组件,通过注册组件名来指定。**这个组件可以是全局组件也可以是局部组件,并且该组件的传值属性等,都是写在 component 标签上。
```html
<template>
<div>
<nav-bar @setFlag="setFlag"></nav-bar>
<!-- 动态组件实现,为什么小写的 home 也行,因为驼峰转短横线写法-->
<component :is="currentTarget"></component>
</div>
</template>
<script>
import About from './components/about.vue'
import Home from './components/home.vue'
import navBar from './components/nav-bar.vue'
export default {
components: { navBar, Home, About },
data() {
return {
currentTarget: 'home'
}
},
methods: {
setFlag(currentTarget) {
this.currentTarget = currentTarget
}
}
}
</script>
组件缓存
认识 keep-alive
组件切换后,组件会被销毁掉,再次回来时会重新创建组件,所以在离开组件之前所有的数据都会丢失。
我们希望继续保持组件的状态,而不是销毁掉,这个时候我们就可以使用一个内置组件:**keep-alive**
。
keep-alive有一些属性:
include
- string | RegExp | Array。只有名称匹配的组件会被缓存;exclude
- string | RegExp | Array。任何名称匹配的组件都不会被缓存;max
- number | string。最多可以缓存多少组件实例,一旦达到这个数字,那么缓存组件中最近没有被访问的实例会被销毁;include 和 exclude prop 可以筛选组件地缓存,匹配首先检查组件自身的 name 选项。 ```html
一般和路由一起连用,router-view,并且还可以添加过渡动画。<br />[13. VueRouter](https://www.yuque.com/jinwanchisha-beh08/vmcrnr/ag4hkt?view=doc_embed) router-view 的作用域插槽。
```html
<router-view v-slot="hhh">
<!-- <router-view v-slot ='{ Component }'> -->
<transition name="ddd">
<keep-alive>
<component :is="hhh.Component"></component>
</keep-alive>
</transition>
</router-view>
缓存组件的生命周期
对于缓存的组件来说,再次进入时,我们是不会执行 created 或者 mounted 等生命周期函数的:但是有时候我们确实希望监听到何时重新进入到了组件,何时离开了组件;
这个时候我们可以使用activated()
(活跃)和 deactivated()
(不活跃) 这两个生命周期钩子函数来监听;
异步组件
Webpack 的代码分包
默认的打包过程:
- 默认情况下,在构建整个组件树的过程中,因为组件和组件之间是通过模块化直接依赖的,那么webpack在打包时就会将组件模块打包到一起(比如一个app.js文件中);
- 这个时候随着项目的不断庞大,app.js文件的内容过大,会造成首屏的渲染速度变慢;
打包时,代码的分包:
- 所以,对于一些不需要立即使用的组件,我们可以单独对它们进行拆分,拆分成一些小的代码块 chunk.js;
- 这些chunk.js会在需要时从服务器加载下来,并且运行代码,显示对应的内容;
**import()**
函数能实现异步加载模块,从而实现代码分包。import()
函数返回一个 promise 对象,当异步加载模块成功,调用 then 方法,res 为异步请求成功的结果,这里就为导入的模块对象,里面就有导入的内容。
// test.js
export function sum() { console.log(123) }
// main.js
import('./test.js').then(res => {
console.log(res)
res.sum() // 123
})
Vue 中实现异步组件
vue 中对于某些组件我们希望通过异步的方式来进行加载,实现分包。为此提供了一个函数:defineAsyncComponent()
实际就是对impport()
函数进行了一个封装。
defineAsyncComponent
接受两种类型的参数:
- 类型一:接收一个工厂函数,工厂函数中使用
import()
异步导入组件 - 类型二:接受一个对象类型,有很多属性可以对异步加载进行配置; ```javascript // 导入定义异步组件函数 import { defineAsyncComponent } from ‘vue’
// 导入异步组件 const home = defineAsyncComponent( () => import(‘./components/home.vue’))
```javascript
const AsyncCategory = defineAsyncComponent({
// 工厂函数
loader: () => import("./AsyncCategory.vue"),
loadingComponent: Loading, // 等待加载时展示的组件
// errorComponent: 异步加载组件失败时,展示的组件
// 加载失败后,延迟显示loadingComponent组件的时间
delay: 2000,
// 正确加载组件的超时时间,超过这个时间意味着加载失败
// 默认值:infinity,永不超时,一直请求
timeout: 0,
/**
* err: 错误信息,
* retry: 函数, 调用retry尝试重新加载
* fail: 函数,调用则终止继续尝试加载
* attempts: 记录尝试的次数
*/
onError: function(err, retry, fail, attempts) { // 加载失败时响应的函数
},
// 定义组件是否可挂起,默认值 true
suspensible: false
})
内置组件 Suspense
对象配置异步组件的方式太麻烦了,我们可以使用全局的内置组件suspense
(悬而未决的)来对异步组件进行简单的配置。
但是 suspense 处于试验阶段,API 随时可能会修改,谨慎使用。
suspense 组件有两个插槽:
- default:相当于 loadingComponent,等待异步加载成功时显示。
fallback:如果 default 无法显示,那么会显示 fallback (应急的) 插槽的内容 ```html
<template #fallback> <loading></loading> </template>
<a name="QQYQ0"></a>
# 组件 dom
<a name="j9WrT"></a>
## $refs 的使用
某些情况下,我们在组件中想要直接获取到元素对象或者子组件实例:这个时候,我们可以给元素或者组件绑定一个 `ref` 的 attribute 属性。<br />组件实例上有一个 `$refs` 属性:它是一个代理对象 Proxy,对象中持有注册过 ref 属性的所有 DOM 元素或组件实例。所以我们能通过`$refs`获取 dom 元素或者组件实例。<br />另外,在Vue开发中并不推荐进行DOM操作的;如:querySelect 等
```html
<template>
<div>
<!-- 绑定 ref attribute -->
<h2 ref="hhh">app.vue</h2>
<button @click="getElement">获取上面的 dom 元素</button>
<home ref="home"></home>
<button @click="getComponentInstance">获取组件的实例</button>
</div>
</template>
<script>
import home from './components/home.vue';
export default {
components: { home },
methods: {
// 获取 dom 元素
getElement() {
console.log(this.$refs.hhh); // <h2>app.vue</h2>
},
// 获取组件实例
getComponentInstance() {
console.log(this.$refs); // Proxy {hhh: h2, home: Proxy}
// 虽然获取的也是个代理对象,但是看上面的输出 {hhh: h2, home: Proxy}
// 完全可以将这个代理对象看成是组件实例
console.log(this.$refs.home); // Proxy {w: ƒ, …}
}
},
}
</script>
$parent 和 $root
$parent
可以访问父组件;$root
可以访问根组件。
注意:在Vue3中已经移除了$children的属性,所以不可以使用了
<template>
<div>
<h2>app.vue</h2>
<home></home>
</div>
</template>
<script>
import home from './components/home.vue';
export default {
components: { home },
data() {
return {
message: 123
}
},
}
</script>
----------home-------------
<template>
<div>
<button @click="getParentComponent">获取父组件的 message</button>
<button @click="getRootComponent">获取根组件的 message</button>
</div>
</template>
<script>
export default {
methods: {
getParentComponent() {
console.log(this.$parent.message); // 123
},
getRootComponent() {
console.log(this.$root.message); // 123
}
},
}
</script>
$el
$el
获取顶层 dom 元素。
<template>
<div>
<h2>app.vue</h2>
<button @click="getTopElement">获取组件顶层 dom 元素</button>
</div>
</template>
<script>
import home from './components/home.vue';
export default {
components: { home },
data() {
return {
message: 123
}
},
methods: {
getTopElement() {
console.log(this.$el);
// <div>
// <h2>app.vue</h2>
// <button>获取组件顶层 dom 元素</button>
// </div>
}
},
}
</script>
$set
$set 是 vue2 中将一般变量转成响应式变量的方法,实际就是劫持它的属性描述符 setter getter。
- this.$set(你要改变的数组/对象,你要改变的位置/key,你要改成什么value)
Vue2 中给 data 中的对象属性添加一个新的属性时会发生什么?如何解决? ```htmlthis.$set(this.arr, 0, "OBKoro1"); // 改变数组this.$set(this.obj, "c", "OBKoro1"); // 改变对象
- {{value}}
点击 button 会发现,obj.b 已经成功添加,但是视图并未刷新。这是因为在Vue实例创建时,obj.b并未声明,因此就没有被Vue转换为响应式的属性,自然就不会触发视图的更新,这时就需要使用Vue的全局 api `$set()`
```javascript
addObjB () (
this.$set(this.obj, 'b', 'obj.b')
console.log(this.obj)
}
$set()方法相当于手动的去把obj.b处理成一个响应式的属性,此时视图也会跟着改变了。
vue2 怎么处理数组的响应式
Object.defineProperty() 具有的局限性无法监听数据增减,尤其是数组多了一个元素少一个元素压根监听不了。
- 那 vue2 是怎么实现监听数组的?vue2 重写了数组的操作方法,主要利用
splice()
方法,该方法可以很好的操作数组元素的增减。