[TOC]

动态组件

切换组件的案例

现在我们想要实现一个导航栏的小案例,点击导航栏,切换不同组件的展示。
有三种方式可以实现:

  1. v-if
  2. 动态组件
  3. 路由 ```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

<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)
    this.$set(this.arr, 0, "OBKoro1"); // 改变数组this.$set(this.obj, "c", "OBKoro1"); // 改变对象
    
    Vue2 中给 data 中的对象属性添加一个新的属性时会发生什么?如何解决? ```html

点击 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()方法,该方法可以很好的操作数组元素的增减。