# Vue(4)

# 13. vue-cli

# 13.1 vue项目的安装

  • 安装vue-cli: npm install -g @vue-cli
  • 在指定的目录下创建项目: 项目名称(英文的,最好别带空格)
  • 方向键控制选中的文字,建议选第三个(Manually select features),确认敲回车
  • 选择需要装的功能,按空格进行选择或取消,括号里有*表示安装此项
选项名 作用
Choose Vue version 询问安装那个版本的Vue
Babel 解决JS兼容性**(一定要装)**
TypeScript TS,比JS更强大的脚本语言
Progressive Web App (PWA) Support 渐进式的Web框架
Router 路由
Vuex 状态管理
CSS Pre-processors CSS预处理器
Linter 1 Formatter 代码规范检验
Unit Testing 单元测试
E2E Testing 端到端测试

选项配置

  1. Where do you prefer placing config for Babel, ESLint, etc. ?
  • In dedicated config files(✔️)
  • In package. json
  1. Save this as a preset for future projects? (y/N)
  • 一般选y表示保存选择过的选项,下一次如果用相同的配置的话选择起来会快一点
  • 最后进行安装,得到如下结果:
added 1241 packages in 40s

13 packages are looking for funding
  run `npm fund` for details
🚀  Invoking generators...
📦  Installing additional dependencies...


added 15 packages in 3s

13 packages are looking for funding
  run `npm fund` for details
⚓  Running completion hooks...

📄  Generating README.md...

🎉  Successfully created project demo1.
👉  Get started with the following commands:

 $ cd demo1
 $ npm run serve
  • 打开目录的终端界面并运行指令cd 项目名称npm run serve

文件结构目录

  • node_modules: 项目依赖,用npm安装的包都在这里放着
  • src: 源码
    • assets : 存放项目中用到的静态资源文件,例如: css 样式表、图片资源
    • components: 程序员封装的、可复用的组件,都要放到components 目录下
    • main.js 是项目的入口文件。整个项目的运行,要先执行main.js
    • App.vue: 项目的根组件

# 13.2 vue项目的运行与使用

  • 在工程化的项目中,vue 要做的事情很单纯——通过main.jsApp.vue渲染到index.html的指定区域中。
    • App.vue用来编写待渲染的模板结构
    • index.html中需要预留一个el区域
    • main.js把App.vue渲染到了index.html所预留的区域中

main.js:

//导入vue这个包,得到Vue构造函数
import Vue from 'vue'
//导入App.vue根组件,将来要把App.vue中的模板结构,渲染到HTML页面中
import App from './App.vue'
Vue.config.productionTip = false

//创建Vue的实例对象
new Vue({
	el: '#app',
	//把render函数指定的组件渲染到HTML页面中
	render: h => h(App)
})
// Vue实例的$mount() 方法,作用和el属性完全一样!

# 14. Vue组件

返回顶部

例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。

为了能在模板中使用,这些组件必须先注册以便 Vue 能够识别。这里有两种组件的注

册类型:全局注册局部注册

组件

  • vue是一个支持组件化开发的前端框架。
  • vue中规定组件的后缀名是.vue。之前接触到的App.vue文件本质上就是一个vue的组件
  1. vue组件的三个组成部分
  • template -> 组件的模板结构
  • script -> 组件的JavaScript行为
  • style -> 组件的样式
  1. 在components目录下新建自定义组件
// test.vue
<template>
	<!-- template中只能包含一个根节点 -->
  <div class="test">
    <div class="a">
      {{ str }}
      <p>111111</p>
    </div>
    <button @click="changeText">改变文字</button>
  </div>
</template>

<script>
// 默认导出,这是固定写法
// 注意: .vue组件中的data不能像之前一样,不能指向对象
// 注意: 组件中的data必须是一个函数
export default {
  data() {
    // 这个return出去的{ }中,可以定义数据
    return {
      str: "hello vue.js",
    };
  },
  methods: {
    changeText() {
      this.str = "hello world";
    },
  },
};
</script>

<style lang="less">
/* 用less的话得加上lang="less" */
.test {
  background-color: orange;
  .a {
    color: red;
    p {
      color: purple;
    }
  }
}
</style>
  1. 在App.vue文件中引用并使用
<template>
  <div id="app">
    <h1>这是根组件</h1>
    <!-- 步骤3: 以标签的形式使用刚才注册的组件 -->
    <Test></Test>
  </div>
</template>

<script>
// 步骤1: 使用import语法导入需要的组件
import Test from '@/components/Test.vue'

export default {
  // 步骤2: 使用components节点注册组件,一般组件名首字母大写来与原生组件区别
  components: {
    Test
  }
}
</script>

<style lang="less">
#app {
  font-family: Avenir,Helvetica,Arial,sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
  background-color: #ccc;
}
</style>
  1. 路径提示插件安装
  • VScode插件名:Path Autocomplete,作者名:Mihai Vilcu

    • 在settings.json文件中加入如下代码:
    // import导入文件时是否携带文件的扩展名
    "path-autocomplete.extensionOnImport": true,
    // 配置@的路径提示 
    "path-autocomplete.pathMappings": {
      "@": "${folder}/src"
    }
    
  • 还有三个插件【推荐】:VeturAuto Close TagVue 3 Snippets

# 14.1 vue 全局组件注册

在vue项目的main.js入口文件中,通过Vue.component(...)方法注册全局组件

// 导入需要全局注册的组件
import Count from '@/components/Count.vue'
// 参数1: 字符串格式,表示组件的注册名称
// 参数2: 需要被全局注册的那个文件
Vue.component('MyCount',Count)

# 14.2 组件的复用性

  • props是组件的自定义属性,在封装通用组件的时候,合理地使用props可以极大的提高组件的复用性!
<!-- 封装的组件的vue文件 -->
<script>  
	export default {
    // props是”自定义属性”,允许使用者通过自定义属性,为当前组件指定初始值
    // 注意: props是只读的,不要直接修改props的值,否则终端会报错!
    props: {
      init: {
        // 设置自定义属性的默认值
        default: 0,
        // 指定值的类型必须是Number数字
        type: Number,
        // 必填属性
        required: true
      }
    },
    data() {
      return {
        // 要想修改props的值,可以把props的值转存到data中,因为data中的数据都是可读可写的!
        count: this.init
      }
    }
  }
</script>

<!-- 使用自定义组件的vue文件 -->
<MyCount :init="6"></MyCount>

# 14.3 组件样式的冲突

  • 默认情况下,写在**.vue组件中的样式会全局生效**,因此很容易造成多个组件之间的样式冲突问题

  • 导致组件之间样式冲突的根本原因是:

    1. 单页面应用程序中,所有组件的DOM结构,都是基于唯一的index.html页面进行呈现的
    2. 每个组件中的样式,都会影响整个index.html页面中的DOM元素
  • 使用属性选择器,在组件内的每个标签都加上一个固定的属性或者在style加scoped属性:

    • <style lang="less" scoped>...</style>

# 14.4修改子组件样式

/deep/

# 14.5 生命周期和生命周期函数

  • 生命周期(Life Cycle)是指一个组件从创建->运行->销毁的整个阶段,强调的是一一个时间段。
  • 生命周期函数:是由vue框架提供的内置函数,会伴随着组件的生命周期,自动按次序执行。

生命周期

  • 组件创建阶段: beforeCreate、created、beforeMount、mounted
  • 组件运行阶段: beforeUpdate、updated
  • 组件销毁阶段: beforeDestroy、destroyed

  1. beforeCreate:组件的props/data/methods尚未被创建,都处于不可用状态。

初始化事件和生命周期函数

  1. created:组件的porps/data/methods已创建好,都处于可用的状态。但是组件的模板结构尚未生成!

初始化props、 data、 methods。created生命周期函数,非常常用。经常在它里面,调用methods 中的方法,请求服务器的数据。并且,把请求到的数据,转存到data中,供template模板渲染的时候使用!

  1. 基于数据模板在内存中编译生成HTML结构

  2. beforeMount将要把内存中编译好的HTML结构渲染到浏览器中。此时浏览器中还没有当前组件的DOM结构。

  3. 内存中编译生成的HTML结构,替换掉el属性指定的DOM元素。

  4. mounted:已经把内存中的HTML结构,成功的渲染到了浏览器之中。此时浏览器中已然包含了当前组件的DOM结构

如果想操作DOM,最早只能在mounted进行

  1. beforeUpdate:将要根据变化过后、最新的数据重新渲染组件的模板结构。

  2. 根据最新的数据,重新渲染组件的DOM结构

  3. updated:已经根据最新的数据,完成了组件DOM结构的重新渲染

第7,8,9步是循环的,如果有改变数据的话。因此当数据变化之后,为了能够操作到最新的DOM结构,必须把代码写到updated生命周期函数中。

  1. beforeDestroy:将要销毁此组件,此时尚未销毁,组件还处于正常工作的状态。
  2. 销毁当前组件的数据侦听器、子组件、事件监听
  3. destroyed:组件已经被销毁,此组件在浏览器中对应的DOM结构已被完全移除!

# 14.6 父组件到子组件的传值

父组件向子组件共享数据需要使用自定义属性。示例代码如下:

<template>
	<div>
    <MyCount :init="myCountNum"></MyCount>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        myCountNum: 999
      };
    }
  };
</script>

# 14.7 子组件到父组件到传值

子组件向父组件共享数据使用自定义事件,示例代码如下:

<!-- 子组件MyCount -->
<script>
  export default {
    data() {
      return {
        count: 0
      }
    },
    methods: {
    	add(){
      	this.count++;
      	this.$emit('numchange'this.count);
     	}
  	}
	}
</script>


<!-- 父组件 -->
<template>
	<div>
    {{ nowCnt }}
    <MyCount :init="myCountNum" @numchange="getCount"></MyCount>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        nowCnt: 0
      }
    },
    methods: {
      getCount(val){
        this.nowCnt = val;
      }
    },
  };
</script>

# 14.8 兄弟组件之间的传值

在vue2.x中,兄弟组件之间数据共享的方案是EventBus

# 14.8.1 EventBus的使用步骤

  1. components文件下创建eventBus.js模块,并向外共享一个 Vue的实例对象
  2. 在数据发送方,调用bus. $emit('事件名称',要发送的数据)方法触发自定义事件
  3. 在数据接收方,调用bus.$on('事件名称',事件处理函数)方法注册一个自定义事件

# 14.8.2 EventBus使用示例

  • 兄弟组件A(发送方)
<script>
  // 1. 导入eventBus.js模块
  import bus from './eventBus.js'

  export default {
    data() {
      return {
        msg: "hello vue.js"
      }
    },
    mathods: {
      sendMsg() {
        // 2. 通过eventBus来发送数据
        bus.$emit("share", this.msg)
      }
    }
  }
</script>
  • 兄弟组件B(接受方)
<script>
  // 1. 导入eventBus.js模块
	import bus from './eventBus.js'
  
  export default {
    data() {
      return {
        msgfromLeft: ""
      }
    },
    created() {
      // 为bus绑定自定义事件
      bus.$on("share", val => {
        this.msgfromLeft = val
      })
    }
  }
</script>
  • eventBue.js
import Vue from 'vue'
export default new Vue()

# 14.9 ref引用

# 14.9.1 什么是ref引用

ref用来辅助开发者在不依赖于jQuery的情况下,获取DOM元素或组件的引用。 每个vue的组件实例上,都包含一个$refs对象,里面存储着对应的DOM元素或组件的引用。

# 14.9.2 ref的使用

  1. 在想要操作的DOM上设置ref属性
<p ref="count1">当前值为: {{ count }}</p>

<!-- <hr ref="count1" /> -->

注意事项

默认情况下,组件的$refs指向一个空对象。

  • 但是要注意ref属性名的值别冲突,如果冲突了,后面的元素会覆盖之前的元素,如上述代码所示,如果取消第二行注释,那么将获取到
    元素
  • 也可以在Vue组件上加上ref属性,那么就可以获得组件元素
  1. 获取DOM
console.log(this.$refs.count1)

# 14.9.3 $nextTick

组件的$nextTick(cb)会把cb回调推迟到下一个DOM更新周期之后执行。通俗的理解是: 等组件的DOM更新完成之后,再执行cb回调函数。从而能保证cb回调函数可以操作到最新的DOM元素。

# 14.10 动态展示组件

实现此功能需要用到component组件

# 14.10.1 代码示例

<!-- 通过is属性来动态绑定想要展示的组件的名字,切换后之前的组件会被销毁 -->
<template>
  <div id="app">
    <!-- 1. component标签是vue内置的,作用: 组件的占位符 -->
		<!-- 2. is属性的值表示要渲染的组件的名字-->
    <component :is="TagName"></component>
		<button @click="changeTag">改变组件</button>
  </div>
</template>

<script>
import Test from '@/components/test.vue'
import MyCount from '@/components/myCount.vue'

export default {
  data(){
    return {
      TagName: 'Test'
    }
  },
  components: {
    Test,
    MyCount
  },
  methods: {
    changeTag(){
      this.TagName = this.TagName === 'Test' ? 'MyCount' : 'Test';
    }
  }
}
</script>

更改is属性的值会导致原来的组件会被销毁,新的组件会被创建,可以在组件中加入如下代码查看:

<script>
  export default {
    name: "MyCount",
    data() {
      return {
        count: 0
      }
    },
    created() {
      console.log("MyCount组件被创建了");
    },
    destroyed() {
      console.log("MyCount组件被销毁了");
    }
  }
</script>

# 14.10.2 keep-alive的使用

为了解决上面提到的问题,可以使用keep-alive组件

<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png"><br>
    <button @click="name='Left'">显示Left组件</button>
    <button @click="name='Right'">显示Right组件</button>
    <hr>
    <div class="componentsBox">
      <!-- keep-alive可以把内部的组件进行缓存,而不是销毁组件 -->
      <keep-alive>
        <component :is="name"></component>
      </keep-alive>
    </div>
  </div>
</template>

打开Vue DevTools工具查看,未显示的组件后会有inactive标记

keep-alive对应的生命周期函数

  • 当组件被缓存时,会自动触发组件的deactivated生命周期函数。
  • 当组件被激活时,会自动触发组件的activated生命周期函数。

代码如下:

<script>
  export default {
    ...,
    // 先于activated执行
    created() {
      console.log("组件被创建了");
    },
    destroyed() {
      console.log("组件被销毁了");
    },
    activated() {
      console.log("组件被激活了");
    },
    deactivated() {
      console.log("组件被缓存了");
    },
    ...
  }
</script>

include属性指定要缓存的组件include="MyCount,Test"

exclude属性同理,但是include和exclude不能同时使用

引号里写的是每个组件的name属性名称,示例如下:

<script>
  export default {
    //当提供了name 属性之后,组件的名称,就是name 属性的值
    //对比:
    // 1.组件的“注册名称”的主要应用场景是以标签的形式,把注册好的组件,渲染和使用到页面结构之中
    // 2.组件声明时候的“name” 名称的主要应用场景: 结合<keep-alive>标签实现组件缓存功能;以及在调试工具中看到组件的name名称
    name: "MyCount",
    ...
  }
</script>

现在vue3里需要按如下方式进行:

<router-view v-slot="{ Component }">
  <keep-alive include="Axios">
    <component :is="Component" />
  </keep-alive>
</router-view>

# 14.11 插槽(slot)的使用

<template>
  <div>
    <p>这是Count组件</p>
    <p ref="count1">当前值为: {{ count }}</p>
    <button @click="add">+1</button>
    <!-- 声明一个插槽区域-->
		<!-- vue 官方规定:每一个slot插槽,都要有一个name名称-->
		<!-- 如果省略了slot 的name属性,则有一个默认名称叫做default -->
    <slot></slot>
  </div>
</template>

<MyCount :init="myCountNum" @numchange="getCount">
  <!-- 默认情况下,在使用组件的时候,提供的内容都会被填充到名字为default的插槽之中 -->
  <p>这是插槽</p>
</MyCount>

注意

每一个插槽slot必须有一个name名称,默认name的值为default

# 14.11.1 具名插槽

<template>
  <div>
    <p>这是Count组件</p>
    <p ref="count1">当前值为: {{ count }}</p>
    <button @click="add">+1</button>
    <!-- 给插槽定义一个name名称 -->
    <slot name="footer">默认内容</slot>
  </div>
</template>

<MyCount :init="myCountNum" @numchange="getCount">
  <!-- 1.如果要把内容填充到指定名称的插槽中,需要使用v-slot这个指令 -->
	<!-- 2. v-slot: 后面要跟上插槽的名字 -->
	<!-- 3. v-slot: 指令不能直接用在元素身上,必须用在template标签上 -->
	<!-- 4. template这个标签,它是一个虚拟的标签,只起到包裹性质的作用,但是,不会被渲染为任何实质性的html元素 -->
  <template v-slot:footer><!-- <template #footer> -->
    <p>这是插槽</p>
  </template>
</MyCount>

TIP

v-slot的语法糖形式是#,即#footer

# 14.11.2 作用域插槽

<slot name="footer" >默认内容</slot>

<MyCount :init="myCountNum" @numchange="getCount">
  <!-- 接受传过来的值msg并重命名为scope -->
  <!-- <template #footer="{ msg }"> -->
  <template #footer="scope">
    <p>这是插槽</p>
		<!-- 使用值 -->
		<!-- <p>{{ msg }}</p> -->
    <p>{{ scope.msg }}</p>
  </template>
</MyCount>

<template>
  <div>
    <p>这是Count组件</p>
    <p ref="count1">当前值为: {{ count }}</p>
    <button @click="add">+1</button>
    <!-- msg是预留的,可能用得到的值 -->
    <slot name="footer" msg="这是尾部"></slot>
  </div>
</template>