> For the complete documentation index, see [llms.txt](https://gb.akanote.cn/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://gb.akanote.cn/vue/day06.md).

# Part6-开发常用知识点

## 一、Devtools与Vscode开发配置（了解）

Vue-Devtools是vue在做项目开发时常用的浏览器插件。

### 1、安装方式

* 升级浏览器至最新版

[![](https://tva1.sinaimg.cn/large/008eGmZEgy1gpq8atz03bj31d10u010b.jpg)](https://tva1.sinaimg.cn/large/008eGmZEgy1gpq8atz03bj31d10u010b.jpg)

* 点击浏览器右上角的 `三个点` ，在 `更多工具` 中，选择 `扩展程序`
* 打开 `扩展程序` 的 `开发者模式`

[![image-20200718142634459](https://tva1.sinaimg.cn/large/008eGmZEgy1gpq8aw9wqnj31d30u0k8k.jpg)](https://tva1.sinaimg.cn/large/008eGmZEgy1gpq8aw9wqnj31d30u0k8k.jpg)

* 将 `Vue.js Devtools_5.3.3.crx` 这个插件直接拽入当前界面，即可完成安装

### 2、打开控制台测试devtools

随意写一个点击事件，并查看点击效果

[![https://tva1.sinaimg.cn/large/008eGmZEgy1gpq8avnnyej30go0a6glw.jpg](https://tva1.sinaimg.cn/large/008eGmZEgy1gpq8avnnyej30go0a6glw.jpg)](https://tva1.sinaimg.cn/large/008eGmZEgy1gpq8avnnyej30go0a6glw.jpg)

### 3、VsCode用户片段

```json
{
  "demo": {
    "prefix": "vue",
    "body": [
      "<template>",
      " <div class=\"$1\">",
      " ",
      " </div>",
      "</template>",
      " ",
      "<script>",
      "export default {",
      " data () {",
      " return {\n",
      " }",
      " }",
      "}",
      "</script>",
      " ",
      "<style lang = \"less\" scoped>",
      " ",
      "</style>"
    ],
    "description": "自定义的一个vue代码段"
  }
}
```

### 4、@路径指向

* 安装插件：Path Intellisense
* 配置：

```json
"path-intellisense.mappings": {
  "@": "${workspaceRoot}/src"
}
```

* 在项目package.json所在同级目录下创建文件jsconfig.json：

```json
{
    "compilerOptions": {
        "target": "ES6",
        "module": "commonjs",
        "allowSyntheticDefaultImports": true,
        "baseUrl": "./",
        "paths": {
          "@/*": ["src/*"]
        }
    },
    "exclude": [
        "node_modules"
    ]
}
```

## 二、Watch（熟悉）

watch的作用可以监控一个值的变换，并调用因为变化需要执行的方法。可以通过watch动态改变关联的状态。

简单点说，就是实时监听某个数据的变化。

### 1、普通监听

```
<template>
  <div class="home">
    <input type="text" v-model="msg">
    <h3>{{msg}}</h3>
  </div>
</template>

<script>
export default {
  name: 'Home',
  data(){
    return {
      msg: "你好，世界"
    }
  },
  watch: {
    msg(val, oldVal){
      console.log(val, oldVal)
    }
  }
}
</script>
```

### 2、立即监听

如果我们需要在最初绑定值的时候也执行函数，则就需要用到immediate属性。

```
<template>
  <div class="home">
    <input type="text" v-model="msg">
  </div>
</template>

<script>
export default {
  name: "Home",
  data() {
    return {
      msg: "你好"
    };
  },
  watch: {
    msg: {
      handler(val, oldVal) {
        console.log(123)	// 在最初绑定时直接打印123
        // console.log(val, oldVal);
      },
      immediate: true
    }
  }
};
</script>
```

> immediate需要搭配handler一起使用，其在最初绑定时，调用的函数也就是这个handler函数。

### 3、深度监听

当需要监听一个对象的改变时，普通的watch方法无法监听到对象内部属性的改变，只有data中的数据才能够监听到变化，此时就需要deep属性对对象进行深度监听。

```
<template>
  <div class="home">
    <h3>{{obj.age}}</h3>
    <button @click="btnClick">按钮</button>
  </div>
</template>

<script>
export default {
  name: 'Home',
  data(){
    return {
      obj: {
        name: "Lucy",
        age: 13
      }
    }
  },
  methods: {
    btnClick(){
      this.obj.age = 33;
    }
  },
  watch: {
    obj: {
      handler(val, oldVal){
        console.log(val.age, oldVal.age)		//  33   33
      },
      deep: true
    }
  }
}
</script>
```

> 注意：
>
> 1、如果监听的数据是一个对象，那么 `immediate: true` 失效；
>
> 2、一般使用于对引用类型的监听，深度监听，如监听一个Object，只要Object里面的任何一个字段发生变化都会被监听，但是比较消耗性能，根据需求使用，能不用则不用。

### 4、deep优化

```
<template>
  <div class="home">
    <h3>{{obj.age}}</h3>
    <button @click="btnClick">按钮</button>
  </div>
</template>

<script>
export default {
  name: "Home",
  data() {
    return {
      obj: {
        name: "Lucy",
        age: 13
      }
    };
  },
  methods: {
    btnClick() {
      this.obj.age = 33;
    }
  },
  watch: {
    // 通过点语法获取对象中的属性，然后转为字符串，即是对深度监听的优化
    "obj.age": {
      handler(val, oldVal) {
        console.log(val, oldVal);
      },
      deep: true,
      immediate: true,		// 此时监听的数据不是一个对象，可以使用immediate
    }
  }
};
</script>
```

### 5、Watch与Computed的区别

* watch中的函数是不需要调用的，computed内部的函数调用的时候不需要加()
* watch(属性监听)，监听的是属性的变化，而computed(计算属性)，是通过计算而得来的数据
* watch需要在数据变化时执行异步或开销较大的操作时使用，而对于任何复杂逻辑或一个数据属性，在它所依赖的属性发生变化时，也要发生变化，这种情况下，我们最好使用计算属性computed。
* computed 属性的结果会被缓存，且computed中的函数必须用return返回最终的结果
* watch 一个对象，键是需要观察的表达式，值是对应回调函数。主要用来监听某些特定数据的变化，从而进行某些具体的业务逻辑操作；

### 6、Watch与Computed的使用场景

* computed 　　　
  * 当一个属性受多个属性影响的时候就需要用到computed
  * 最典型的例子： 购物车商品结算的时候
* watch
  * 当一条数据影响多条数据的时候就需要用watch
  * 搜索数据
* 总结：
  * 一个值受其他值的影响，用computed
  * 一个值将时刻影响其他值，用watch

## 三、Mixins混入（熟悉）

mixins就是定义一部分公共的方法或者计算属性,然后混入到各个组件中使用,方便管理与统一修改。同一个生命周期，混入对象会比组件的先执行。

### 1、导出mixins

在src下创建 `mixins/index.js`，写入：

```js
export const MixinsFn = {
    created() { 
        console.log("这是mixins触发的created")
    }
}
```

### 2、引用mixins

```
<template>
  <div class="about">
    <h1>This is an about page</h1>
  </div>
</template>
<script>
import { MixinsFn } from '@/mixins/index.js'
export default {
  created(){
    console.log("这是about触发的created")
  },
  mixins: [MixinsFn]
}
</script>
```

我们会发现，`mixins中的created` 比 `about中的created` 优先执行。

## 四、ref与$refs（掌握）

vue中获取页面里的某个元素（标签或组件），可以给它绑定ref属性，有点类似于给它添加id名。

### 1、简单使用

```
<template>
 <div class="">
     <h3 ref="title">{{msg}}</h3>
     <button @click="btnclick">按钮</button>
 </div>
</template>
 
<script>
export default {
  data() {
    return {
      msg: "你好"
    };
  },
  methods: {
    btnclick() {
      console.log(this.$refs.title);		// 得到h3标签
      console.log(this.$refs.title.innerHTML);		// 得到h3标签的文本
    }
  }
};
</script>
 
<style lang = "less" scoped>
</style>
```

### 2、子组件中的数据获取及方法调用

子组件：

```
<template>
 <div class="">
     <h4>{{num}}</h4>
 </div>
</template>
 
<script>
export default {
  data() {
    return {
      num: 100
    };
  },
  methods: {
      subFn(){
          console.log('子组件中的方法')
      }
  }
};
</script>
 
<style lang = "less" scoped>
</style>
```

父组件：

```
<template>
 <div class="">
     <Sub ref="sub" />
     <button @click="btnclick">按钮</button>
 </div>
</template>
 
<script>
import Sub from '../components/Sub'
export default {
  methods: {
    btnclick() {
      console.log(this.$refs.sub.num);	// 100
      this.$refs.sub.subFn();						// '子组件中的方法'
    }
  },
  components: {
      Sub
  }
};
</script>
 
<style lang = "less" scoped>
</style>
```

## 五、动态组件

类似于Tab栏这种效果，有时我们往往不会去使用v-show或v-if来动态切换，而会使用动态组件搭配 `:is` 属性来完成，效果如下：

[![](https://tva1.sinaimg.cn/large/008eGmZEgy1gpq8ax278hj30ai04g74a.jpg)](https://tva1.sinaimg.cn/large/008eGmZEgy1gpq8ax278hj30ai04g74a.jpg)

在学习动态组件之前，需要记住这点：

* :is属性中的值，必须是已注册组件的名字，或[一个组件的选项对象](https://github.com/Macharf/aka-note/blob/main/note/vue/组件的选项对象.md)

具体操作：

### 1、写好三个子组件的内容

```
<template>
 <div>Home子组件</div>
</template>

<template>
 <div>List子组件</div>
</template>

<template>
 <div>User子组件</div>
</template>
```

### 2、在父组件中引入并注册

```
<template>
 <div class="">
     <button @click="btnClick(1)" :style="{background: num==1 ? 'pink' : ''}">Home</button>
     <button @click="btnClick(2)" :style="{background: num==2 ? 'pink' : ''}">List</button>
     <button @click="btnClick(3)" :style="{background: num==3 ? 'pink' : ''}">User</button>
     <component :is="componentId"></component>
 </div>
</template>
 
<script>
import Home from "@/components/Home";
import List from "@/components/List";
import User from "@/components/User";
export default {
  data() {
    return {
      componentId: "Home",
      num: 1
    };
  },
  components: {
    Home,
    List,
    User
  },
  methods: {
    btnClick(arg) {
      this.num = arg;
      switch (arg) {
        case 1:
          this.componentId = "Home";
          break;
        case 2:
          this.componentId = "List";
          break;
        default:
          this.componentId = "User";
          break;
      }
    }
  }
};
</script>
```

### 3、动态组件其他应用场景

例如table、select这种外层HTML标签，他们内部的标签（如：tr、option等），是不能被单独拿出来作为组件放在他们内部的，否则会被作为无效的内容提升到外部，并导致最终渲染结果出错。而使用动态组件的 `:is` 属性，就可以解决这个问题。

需要注意的是**如果我们从以下来源使用模板的话，这条限制是\*不存在\*的**：

* 字符串 (例如：`template: '...'`)
* 单文件组件 (.vue)
* `<script type="text/x-template">`

## 六、KeepAlive

`<keep-alive>`是Vue的内置组件，能在组件切换过程中将状态保留在内存中，防止重复渲染DOM。

KeepAlive用于处理组件缓存。有什么用呢？想象一个业务场景：

> 在Login页面填写完手机号，发现未注册，需要跳去Register页面完成注册，再返回Login页面填写密码，进行登录，此时如果在Login页面没有保存好手机号，那么跳回来时，用户又得重新输入手机号。为了解决这样的需求，我们需要借助keep-alive。

### 1、keep-alive结合component

首先，创建 `a.vue`、`b.vue` 和 `c.vue` 三个组件，内容大致如下：

```
<template>
  <li>a组件 <input type="text"></li>
</template>
```

在某个页面（如：about.vue）中引入：

```
<template>
  <div class="about">
    <button @click="mycom='acom'">a组件</button>
    <button @click="mycom='bcom'">b组件</button>
    <button @click="mycom='ccom'">c组件</button>
    <ul>
      <component :is="mycom"></component>
    </ul>
  </div>
</template>

<script>
import acom from '../components/a'
import bcom from '../components/b'
import ccom from '../components/c'

export default {
  data(){
    return {
      mycom: "acom"
    }
  },
  components: {
    acom, bcom, ccom
  }
}
</script>
```

到目前为止，我们可以切换组件，但是在切换时却无法缓存组件内容，因此我们给 component 组件套一个 `keep-alive`：

```
<keep-alive>
   <component :is="mycom"></component>
</keep-alive>
```

这样我们在三个组件的 input 中输入的东西就会被缓存。

### 2、include与exclude

如果我们想缓存指定的组件，可以使用include：

```
<keep-alive :include="['acom', 'bcom']">
   <component :is="mycom"></component>
</keep-alive>
```

如果我们想排出部分组件的缓存，可以使用exclude：

```
<keep-alive :exclude="['ccom']">
   <component :is="mycom"></component>
</keep-alive>
```

### 3、keep-alive生命周期

Keep-alive 生命周期包含：`actived` 和 `deactivated`

找到 `a.vue`，写入：

```
<script>
export default {
    created(){
        console.log('a组件的created')
    },
    mounted(){
        console.log('a组件的mounted')
    },
    activated(){
        console.log('actived: 进入当前组件时触发')
    },
    deactivated(){
        console.log('deactivated：离开当前组件时触发')
    }
}
</script>
```

我们可以观察到两个现象：

* actived在created和mounted之后调用
* deactivated在离开组件时会触发，无论第几次进入组件，actived都会触发，但created和mounted只会触发一次

## 七、NextTick

### 1、数据更新延迟的问题

我们先来思考一个数据更新问题：

当我修改一个数据时，这个数据会立刻渲染到页面中吗？

```
<template>
 <div class="">
     <h2 ref="title">{{num}}</h2>
     <button @click="btnClick">按钮</button>
 </div>
</template>
<script>
export default {
  data() {
    return {
      num: 1
    };
  },
  methods: {
    btnClick() {
      this.num++;
      console.log(this.$refs.title.innerHTML);		// 1
    }
  }
};
</script>
```

很显然，这里打印出来的是视图更新前的数据，所以我们得出结论：

> 当侦听到你的数据发生变化时， Vue将开启一个队列（该队列被Vue官方称为异步更新队列）。视图需要等队列中所有数据变化完成之后，再统一进行更新。

### 2、NextTick使用方法

那么，如果我们想提前获取更新后的数据，就需要借助NextTick。来看看官方对NextTick的定义：

> 在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法，获取更新后的 DOM。

NextTick有两种基本使用方法：

```js
// 1、CallBack形式
this.$nextTick(()=>{
  console.log(this.$refs.title.innerHTML);
})

// 2、Promise形式
this.$nextTick().then(()=>{
  console.log(this.$refs.title.innerHTML);
})
```

我们来尝试修改刚刚的案例：

```
<template>
 <div class="">
     <h2 ref="title">{{num}}</h2>
     <button @click="btnClick">按钮</button>
 </div>
</template>
 
<script>
export default {
  data() {
    return {
      num: 1
    };
  },
  methods: {
    btnClick() {
      this.num++;
      console.log(this.$refs.title.innerHTML);		// 1
      // this.$nextTick(()=>{
      //   console.log(this.$refs.title.innerHTML);	// 2
      // })
      this.$nextTick().then(()=>{
        console.log(this.$refs.title.innerHTML);	// 2
      })
    }
  }
};
</script>
```

### 3、应用场景

1、如果要在`created()`钩子函数中进行的`DOM`操作，由于`created()`钩子函数中还未对DOM进行任何渲染，所以无法直接操作dom，需要通过`$nextTick()`来完成。

```js
created () {
    this.$nextTick(()=>{
        this.$refs.ptag.innerText = "一个p标签"
    })
}
```

注：在`created()`钩子函数中进行的`DOM`操作，不使用`$nextTick()`会报错：

```js
//  Error in created hook: "TypeError: Cannot set property 'innerText' of undefined"
created(){
    this.$refs.ptag.innerText = "一个p标签"
}
```

## 八、Transition

Css中有个transition属性，我们称为过渡属性。Vue中也有个transition，我们将它做为标签。

`<transition>` 元素作为**单个**元素/组件的过渡效果。`<transition>` 只会把过渡效果应用到其包裹的内容上，而不会额外渲染 DOM 元素，也不会出现在可被检查的组件层级中。

我们来看看怎么写：

```
<button @click="flag = !flag">切换h3显示</button>
<transition name="fade">
  <h3 v-show="flag">你好，标题</h3>
</transition>

<style lang="less" scoped>
.fade-enter, .fade-leave-to{
  opacity: 0;
}
.fade-enter-active, .fade-leave-active{
  transition: opacity 1s;
}
</style>
```

我们来看看这个图：

[![](https://tva1.sinaimg.cn/large/008eGmZEgy1gpq8ausqjjj30fe07pjrp.jpg)](https://tva1.sinaimg.cn/large/008eGmZEgy1gpq8ausqjjj30fe07pjrp.jpg)

一个完整的**transiton组件的class类**，有6个，通过指定不同的class来添加样式来达到过渡的CSS效果。

1. v-enter：定义进入过渡的开始状态。
2. v-enter-active：定义进入过渡生效时的状态。这个类可以被用来定义进入过渡的过程时间，延迟和曲线函数。
3. v-enter-to: 定义进入过渡的结束状态。
4. v-leave: 定义离开过渡的开始状态。
5. v-leave-active：定义离开过渡生效时的状态。这个类可以被用来定义离开过渡的过程时间，延迟和曲线函数。
6. v-leave-to: 定义离开过渡的结束状态。

默认类名是以上6个，如果在transition标签中指定了name=’xxx’,那么6个类名将会变成xxx开头的，例如xxx-enter-active。
