第二章Vue组件化编程

文章目录

  • 模块与组件、模块化与组件化
      • 模块
      • 组件
      • 模块化
      • 组件化
    • Vue中的组件含义
  • 非单文件组件
    • 基本使用
    • 组件注意事项
        • 使用 kebab-case
        • 使用 PascalCase
    • 组件的嵌套
    • 模板template
    • VueComponent
    • 一个重要的内置功能
  • 单文件组件
  • Vue脚手架
    • 使用Vue CLI脚手架
      • 先配置环境
      • 初始化脚手架
      • 什么是Webpack
      • 模块化的演进
    • 分析脚手架结构
      • 实例
      • render函数——解决无模板解析
      • 修改默认配置
  • ref属性——定位元素
  • props配置项——传递参数
    • :age="20"和 age="20的区别"
  • mixin混入
  • plugin插件
  • scoped样式
  • ToList——基础版本
  • WebStorage——浏览器本地存储
    • localStorage
    • sessionStorage
    • 使用本地存储优化Todo-List
  • 自定义事件——实现组件子传夫数据
    • 绑定
    • 解绑
    • 使用自定义事件优化Todo-List
  • 全局事件总线——处理其他组件关系传递数据
    • 使用全局总线事件优化Todo-List
  • 消息的订阅与发布
  • $nextTick
    • 使用$nextTick优化Todo-List
  • 过度和动画
    • 过度和动画改造TodoList
  • 插槽Slot
    • 默认插槽
    • 具名插槽
    • 作用域插槽
    • 总结

模块与组件、模块化与组件化

第二章Vue组件化编程_第1张图片

第二章Vue组件化编程_第2张图片

模块

  • 理解:向外提供特定功能的 js 程序,一般就是一个 js 文件
  • 为什么:js 文件很多很复杂
  • 作用:复用 js,简化 js 的编写,提高 js 运行效率

组件

  • 定义:用来实现局部功能的代码和资源的集合(html/css/js/image…)
  • 为什么:一个界面的功能很复杂
  • 作用:复用编码,简化项目编码,提高运行效率

模块化

当应用中的 js 都以模块来编写的,那这个应用就是一个模块化的应用

组件化

当应用中的功能都是多组件的方式来编写的,那这个应用就是一个组件化的应用

Vue中的组件含义

组件是可复用的Vue实例, 说白了就是一组可以重复使用的模板, 跟JSTL的自定义标签、Thymeleal的th:fragment等框架有着异曲同工之妙,通常一个应用会以一棵嵌套的组件树的形式来组织:

  • 组件系统是 Vue 的另一个重要概念,因为它是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用。仔细想想,几乎任意类型的应用界面都可以抽象为一个组件树

第二章Vue组件化编程_第3张图片

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

非单文件组件

基本使用

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script type="text/javascript" src="../js/vue.js">script>
    <title>Documenttitle>
head>
<body>
    <div id="root">
        
        <school>school>
        <student>student>
    div>
    <script type="text/javascript">
        //1创建组件
        const school=Vue.extend({
            template: `
            

{{schoolName}}

{{address}}

`
, data() { return { schoolName: '一中', address: '柳岸' } }, }) //1创建组件的快捷方式 const student={ template: `

{{studentName}}

{{age}}

`
, data() { return { studentName: 'lsc', age: 22 } }, } //2全局注册组件 Vue.component('student',student) new Vue({ el: '#root', data: { msg: 'hello compentments' }, //2局部注册组件 components: { school:school //这种可以简写为 school } })
script> body> html>

第二章Vue组件化编程_第4张图片

总结:

Vue中使用组件的三大步骤:

  • 定义组件(创建组件)——Vue.extend

  • 注册组件——局部和全局

  • 使用组件(写组件标签)

如何定义一个组件?

  • 使用Vue.extend(options)创建,其中options和new Vue(options)时传入的options几乎一样,但也有点区别:

  • el不要写,为什么?

    • 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器
  • data必须写成函数,为什么?

    • 避免组件被复用时,数据存在引用关系,防止同一个组件的实例对象之间数据相互影响

如何注册组件?

  • 局部注册:new Vue的时候传入components选项
  • 全局注册:Vue.component(‘组件名’,组件)

全局注册

Vue.component('my-component-name', {
// ... 选项 ...
})

这些组件是全局注册的。也就是说它们在注册之后可以用在任何新创建的 Vue 根实例 (new Vue) 的模板中

Vue.component('component-a', { /* ... */ })
Vue.component('component-b', { /* ... */ })
Vue.component('component-c', { /* ... */ })

new Vue({ el: '#app' })
<div id="app">
<component-a></component-a>
<component-b></component-b>
<component-c></component-c>
</div>

在所有子组件中也是如此,也就是说这三个组件在各自内部也都可以相互使用。

局部注册

  • 全局注册往往是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。

在这些情况下,你可以通过一个普通的 JavaScript 对象来定义组件:

var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
var ComponentC = { /* ... */ }

然后在 components 选项中定义你想要使用的组件:

new Vue({
  el: '#app',
  components: {
    'component-a': ComponentA,
    'component-b': ComponentB
  }
})

对于 components 对象中的每个 property 来说,其 property 名就是自定义元素的名字,其 property 值就是这个组件的选项对象。

注意局部注册的组件在其子组件中不可用。例如,如果你希望 ComponentAComponentB 中可用,则你需要这样写:

var ComponentA = { /* ... */ }

var ComponentB = {
  components: {
    'component-a': ComponentA
  },
  // ...
}

如果你通过 Babel 和 webpack 使用 ES2015 模块

import ComponentA from './ComponentA.vue'

export default {
  components: {
    ComponentA
  },
  // ...
}

注意在 ES2015+ 中,在对象中放一个类似 ComponentA 的变量名其实是 ComponentA: ComponentA 的缩写,即这个变量名同时是用在模板中的自定义元素的名称

  • 包含了这个组件选项的变量名

如何使用

编写组件标签:

组件注意事项



	
		
		组件注意事项
		
	
	
		

{{msg}}

总结:

关于组件名:

一个单词组成:

  • 第一种写法(首字母小写):school

  • 第二种写法(首字母大写):School

多个单词组成:

  • 第一种写法(kebab-case命名):my-school

  • 第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)

组件名大小写

定义组件名的方式有两种:

使用 kebab-case

Vue.component('my-component-name', { /* ... */ })

当使用 kebab-case (短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用 kebab-case,例如

使用 PascalCase

Vue.component('MyComponentName', { /* ... */ })

当使用 PascalCase (首字母大写命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也就是说 都是可接受的。注意,尽管如此,直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的。

备注:

  • 组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行

  • 可以使用name配置项指定组件在开发者工具中呈现的名字

关于组件标签:

  • 第一种写法:

  • 第二种写法:

  • 备注:不使用脚手架时,会导致后续组件不能渲染

一个简写方式:const school = Vue.extend({options})可简写为:const school = {options}

组件的嵌套

DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>组件的嵌套title>
		<script type="text/javascript" src="../js/vue.js">script>
	head>
	<body>
		<div id="root">
		div>
	body>

	<script type="text/javascript">
		Vue.config.productionTip = false
		
		//定义student组件
		const student = Vue.extend({
			template:`
				

学生名称:{{name}}

学生年龄:{{age}}

`
, data(){ return { name:'JOJO', age:20 } } }) //定义school组件 const school = Vue.extend({ template:`

学校名称:{{name}}

学校地址:{{address}}

`
, components:{ student }, data(){ return { name:'尚硅谷', address:'北京' } } }) //定义hello组件 const hello = Vue.extend({ template:`

{{msg}}

`
, data(){ return { msg:"欢迎学习尚硅谷Vue教程!" } } }) //定义app组件 const app = Vue.extend({ template:`
`
, components:{ school, hello } }) //创建vm new Vue({ template:` `, el:'#root', components:{ app } })
script> html>

效果:

第二章Vue组件化编程_第5张图片

  • 对于一个组件想嵌套在哪个组件中,就要将对于的字组件嵌套在对应的父组件中

模板template

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
    <script language="JavaScript" src="static/script/vue.js">script>
head>
<body>

<div id="app">
   <myfirstcomponent>myfirstcomponent>
div>

<script language="JavaScript">
    // 定义一个Vue组件
    Vue.component("myfirstcomponent", {
        template: '
hello,我是一个组件
'
}); var vm=new Vue({ el: "#app", data: { } });
script> body> html>
  • 组件的第一个参数就是组件名
    • 当直接在 DOM 中使用一个组件 (而不是在字符串模板或单文件组件) 的时候,我们强烈推荐遵循 W3C 规范中的自定义组件名 (字母全小写且必须包含一个连字符)。这会帮助你避免和当前以及未来的 HTML 元素相冲突。
  • 组件失效的原因
    • 没有实例化某个Vue对象
    • 组件必须挂载在某个Vue实例之下,否则不会生效
    • 标签名称不能有大写字母
    • 创建组件构造器和注册组件的代码必须在Vue实例之前

VueComponent

const school = Vue.extend({
			name:'atguigu',
			template:`
				

学校名称:{{name}}

学校地址:{{address}}

`
, data(){ return { name:'尚硅谷', address:'北京' } } })

关于VueComponent:

  • school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的

  • 我们只需要写,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的:new VueComponent(options)

  • 特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!

  • 关于this指向:

    • 组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是VueComponent实例对象

    • new Vue(options)配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是Vue实例对象

VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)

  • Vue的实例对象,以后简称vm

  • 只有在本笔记中VueComponent的实例对象才简称为vc

    • vc在创建的时候不能写el,只能跟着对应的vm的时候,来决定

一个重要的内置功能



	
		
		一个重要的内置关系
		
	
	
		

第二章Vue组件化编程_第6张图片

第二章Vue组件化编程_第7张图片

  1. 一个重要的内置关系:VueComponent.prototype.__proto__ === Vue.prototype

    • VueComponent.prototype指的是我们的VueComponent的原型对象 Vue.prototype指的是Vue的原型对象
  2. 为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue 原型上的属性、方法

    • 通过显示原型链可以给原型对象添加属性,实例对象通过隐式原型链获取原型对象的属性,从自身沿着原型链一直找到window的原型对象为空

单文件组件

School.vue







  • export default 默认暴露,将我们的组件School暴露出去
    • 可以使用name配置项指定组件在开发者工具中呈现的名字
  • 其实export default {}是 简写 全称是 export default Vue.expend({optitons})

Student.vue





App.vue





main.js

import App from './App.vue'

new Vue({
    template:``,
    el:'#root',
    components:{App}
})

index.html

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>单文件组件练习title>
head>
<body>
    <div id="root">div>
    <script src="../../js/vue.js">script>
    <script src="./main.js">script>
body>
html>

  • 直接这样写,并不能成功。需要我们的Vue 脚手架的帮助,才能实现

Vue脚手架

使用Vue CLI脚手架

先配置环境

Node.js:http://nodejs.cn/download/安装就是无脑的下一步就好,安装在自己的环境目录下

  • 确认nodejs安装成功
    • cmd下输入node -v,查看是否能够正确打印出版本号即可!
    • cmd下输入npm -v,查看是否能够正确打印出版本号即可!
      • 这个npm,就是一个软件包管理工具,就和linux下的apt软件安装差不多!

Git:https://git-scm.com/doenloads

初始化脚手架

  • Vue 脚手架是 Vue 官方提供的标准化开发工具(开发平台)
  • 最新的版本是 4.x
  • 文档:Vue CLI

具体步骤

  • 如果下载缓慢请配置 npm 淘宝镜像:npm config set registry http://registry.npm.taobao.org

-g 就是全局安装

npm install cnpm -g

或使用如下语句解决npm速度慢的问题

npm install --registry=https://registry.npm.taobao.org

  • 但是能不用cnpm就不用,npm比较好,因为cnpm可能打包的时候会出现问题
  • 全局安装@vue/cli——npm install -g @vue/cli
  • 切换到你要创建项目的目录,然后使用命令创建项目:vue create xxx(项目名)
    • 选择使用vue的版本,我们学习的是Vue2,所以选择2
  • 启动项目:npm run serve
  • 暂停项目:Ctrl+C
  • Vue 脚手架隐藏了所有 webpack 相关的配置,若想查看具体的 webpakc 配置,请执行:vue inspect > output.js

呈现效果——访问http://localhost:8080/

第二章Vue组件化编程_第8张图片

  • 这就是 Node.js的服务,跟tomcat 差不多。
  • Node.js它是一个服务器,它可以运行一些东西。

webpack 的主要编程语言是 js,想要在你的机器上跑 js 脚本,就需要一个解释执行 js 的环境,nodejs 出现之前我们只能在浏览器环境下解释执行 js,而 nodejs 基于 V8 引擎进行了一系列封装,使得我们可以在非浏览器环境下解释执行 js。

nodejs 可以支持本地文件的操作,也就是文件读写,webpack 基于此提供了编译前端代码的功能,使得我们可以在前端代码开发的过程中选择我们喜欢的框架和预编译语言。

nodejs 也可以支持搭建网络服务,webpack 基于此提供了开发环境的搭建,使得我们可以轻松的在本地构建服务调试我们的前端代码。

说 webpack 依赖 nodejs 其实并不太准确,应该说 webpack 是用 nodejs 执行的,js 不是机器语言,解释执行 js 必须要一个特定的环境,nodejs 提供了这个环境。

什么是Webpack

  • 本质上, webpack是一个现代JavaScript应用程序的静态模块打包器(module bundler) 。当webpack处理应用程序时, 它会递归地构建一个依赖关系图(dependency graph) , 其中包含应用程序需要的每个模块, 然后将所有这些模块打包成一个或多个bundle.
  • Webpack是当下最热门的前端资源模块化管理和打包工具, 它可以将许多松散耦合的模块按照依赖和规则打包成符合生产环境部署的前端资源。还可以将按需加载的模块进行代码分离,等到实际需要时再异步加载。通过loader转换, 任何形式的资源都可以当做模块, 比如Commons JS、AMD、ES 6、CSS、JSON、Coffee Script、LESS等;

伴随着移动互联网的大潮, 当今越来越多的网站已经从网页模式进化到了WebApp模式。它们运行在现代浏览器里, 使用HTML 5、CSS 3、ES 6等新的技术来开发丰富的功能, 网页已经不仅仅是完成浏览器的基本需求; WebApp通常是一个SPA(单页面应用) , 每一个视图通过异步的方式加载,这导致页面初始化和使用过程中会加载越来越多的JS代码,这给前端的开发流程和资源组织带来了巨大挑战。

前端开发和其他开发工作的主要区别,首先是前端基于多语言、多层次的编码和组织工作,其次前端产品的交付是基于浏览器的,这些资源是通过增量加载的方式运行到浏览器端,如何在开发环境组织好这些碎片化的代码和资源,并且保证他们在浏览器端快速、优雅的加载和更新,就需要一个模块化系统,这个理想中的模块化系统是前端工程师多年来一直探索的难题。

模块化的演进

Script标签

<script src = "module1.js"></script>
<script src = "module2.js"></script>
<script src = "module3.js"></script>

这是最原始的JavaScript文件加载方式,如果把每一个文件看做是一个模块,那么他们的接口通常是暴露在全局作用域下,也就是定义在window对象中,不同模块的调用都是一个作用域。

这种原始的加载方式暴露了一些显而易见的弊端:

  • 全局作用域下容易造成变量冲突
  • 文件只能按照

    src/components/Student.vue

    
    
    
    

    src/App.vue

    
    
    
    

    src/main.js

    import Vue from 'vue'
    import App from './App.vue'
    
    Vue.config.productionTip = false
    
    new Vue({
       //将App组件放入容器中
      render: h => h(App),
    }).$mount('#app')
    
    
    • 我们发现在main.js中,我们没有注册App组件,这些其实都是在render函数中,等会解释

    public/index.html

    DOCTYPE html>
    <html lang="">
        <head>
            <meta charset="UTF-8">
            
            <meta http-equiv="X-UA-Compatible" content="IE=edge">
            
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            
            <link rel="icon" href="<%= BASE_URL %>favicon.ico">
            
            <title><%= htmlWebpackPlugin.options.title %>title>
        head>
        <body>
            
            <div id="app">div>
        body>
    html>
    
    
    • 虽然在表面上我们没有使用来引入,但是我们可以进行使用
      ,是因为脚手架帮我们做好了

    render函数——解决无模板解析

    import Vue from 'vue'
    import App from './App.vue'
    
    Vue.config.productionTip = false
    
    new Vue({
        el:'#app',
        // 简写形式
    	render: h => h(App),
        // 完整形式
    	// render(createElement){
    	//     return createElement(App)
    	// }
       //  因为只有一个参数所以可以把括号去掉 和一条语句只是return,写成render createElement =>return createElement(App)
    })
    
    

    总结:

    关于不同版本的函数:

    • vue.js 与 vue.runtime.xxx.js的区别:

      • vue.js 是完整版的 Vue,包含:核心功能+模板解析器
      • vue.runtime.xxx.js 是运行版的 Vue,只包含核心功能,没有模板解析器,我们的import Vue from 'vue’引入的就是import Vue from ‘vue’
    • 因为 vue.runtime.xxx.js 没有模板解析器,所以不能使用 template 配置项,需要使用 render函数接收到的createElement 函数去指定具体内容

    修改默认配置

    • vue.config.js 是一个可选的配置文件,如果项目的(和 package.json 同级的)根目录中存在这个文件,那么它会被 @vue/cli-service 自动加载
    • 使用 vue.config.js 可以对脚手架进行个性化定制,详见配置参考 | Vue CLI
    const { defineConfig } = require('@vue/cli-service')
    module.exports = defineConfig({
      transpileDependencies: true,
      lintOnSave:false /*关闭语法检查*/
    })
    

    webpack将配置文件放在了webpack.config.js中,这个文件被隐藏,就是防止我们乱改,对于我们想改Vue的配置,我们可以通过新建一个vue.config.js 文件,将这个文件和我们webpack.config.js进行合并,达到我们修改配置的功能

    ref属性——定位元素

    
    
    
    
    
    

    第二章Vue组件化编程_第9张图片

    总结:

    ref属性:

    • 被用来给元素子组件注册引用信息(id的替代者)

      • 应用在html标签上获取的是真实DOM元素,应用在组件标签上获取的是组件实例对象(vc)
    • 使用方式:

      • 打标识:

    • 获取:this.$refs.xxx

    props配置项——传递参数

    像上面那样用组件没有任何意义,所以我们是需要传递参数到组件的,此时就需要使用props属性了!注意:默认规则下props属性里的值不能为大写

    HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名:

    src/App.vue

    
    
    
    

    src/components/Student.vue

    
    
    
    

    第二章Vue组件化编程_第10张图片

    总结:

    props配置项:

    • 功能:让组件接收外部传过来的数据

    • 传递数据:,在使用组件标签的时候,进行传递数据

    • 接收数据:

      • 第一种方式(只接收):props:[‘name’]

      • 第二种方式(限制数据类型):props:{name:String}

      • 第三种方式(限制类型、限制必要性、指定默认值):

    props:{
        name:{
        	type:String, //类型
            required:true, //必要性
            default:'JOJO' //默认值
        }
    }
    

    props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据

    • 经过测试,我们发现对于组件来说,prop的属性的优先级是大于我们的data里面的属性,所以我们复制可以data() return{ mydata: this.prop}

    :age="20"和 age=“20的区别”

    • :age=“20” 的全称是v-bind:age=“20” ,也就是数据单向绑定了,而且我们知道这样绑定,""中的内容是表示js运算结果,所以传给age的值是数字20
    • age=“20”,就表示传给age的值是一个字符串

    mixin混入

    src/mixin.js

    //注册组件并暴露出去
    export const mixin = {
        methods: {
            showName() {
                alert(this.name)
            }
        },
        mounted() {
            console.log("你好呀~")
        }
    }
    
    
    • 独立写一个混入的js文件

    src/components/School.vue

    
    
    
    
    

    src/components/Student.vue

    
    
    
    
    

    src/App.vue

    
    
    
    
    

    第二章Vue组件化编程_第11张图片

    全局混入:

    src/main.js:

    import Vue from 'vue'
    import App from './App.vue'
    import {mixin} from './mixin'
    
    Vue.config.productionTip = false
    Vue.mixin(mixin)
    
    new Vue({
        el:"#app",
        render: h => h(App)
    })
    

    总结:

    mixin(混入):

    • 功能:可以把多个组件共用的配置提取成一个混入对象

    使用方式:

    第一步定义混入:

    const mixin = {
        data(){....},
        methods:{....}
        ....
    }
    

    第二步使用混入:

    • 全局混入:Vue.mixin(xxx)

    • 局部混入:mixins:[‘xxx’]

    备注:

    组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”,在发生冲突时以组件优先。

    var mixin = {
    	data: function () {
    		return {
        		message: 'hello',
                foo: 'abc'
        	}
      	}
    }
    
    new Vue({
      	mixins: [mixin],
      	data () {
        	return {
          		message: 'goodbye',
                bar: 'def'
        	}
        },
      	created () {
        	console.log(this.$data)
        	// => { message: "goodbye", foo: "abc", bar: "def" }
      	}
    })
    

    同名生命周期钩子将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。

    var mixin = {
      	created () {
        	console.log('混入对象的钩子被调用')
      	}
    }
    
    new Vue({
      	mixins: [mixin],
      	created () {
        	console.log('组件钩子被调用')
      	}
    })
    
    // => "混入对象的钩子被调用"
    // => "组件钩子被调用"
    

    plugin插件

    src/plugin.js:

    export default {
    	install(Vue,x,y,z){
    		console.log(x,y,z)
    		//全局过滤器
    		Vue.filter('mySlice',function(value){
    			return value.slice(0,4)
    		})
    
    		//定义混入
    		Vue.mixin({
    			data() {
    				return {
    					x:100,
    					y:200
    				}
    			},
    		})
    
    		//给Vue原型上添加一个方法(vm和vc就都能用了)
    		Vue.prototype.hello = ()=>{alert('你好啊')}
    	}
    }
    
    
    • 独立写一个插件的js文件

    src/main.js:

    import Vue from 'vue'
    import App from './App.vue'
    import plugin from './plugin'
    
    Vue.config.productionTip = false
    Vue.use(plugin,1,2,3)
    
    new Vue({
        el:"#app",
        render: h => h(App)
    })
    

    src/components/School.vue:

    
    
    

    src/components/Student.vue:

    
    
    

    第二章Vue组件化编程_第12张图片

    总结:

    插件:

    • 功能:用于增强Vue

    • 本质:包含install方法的一个对象,install的第一个参数是Vue,也就vm实例对象的构造函数,第二个以后的参数是插件使用者传递的数据

    定义插件:

    plugin.install = function (Vue, options) {
            // 1.添加全局过滤器
            	Vue.filter(....)
            // 2.添加全局指令
        		 Vue.directive(....)
        	// 3. 配置全局混入
       			Vue.mixin(....)
    		// 4. 添加实例方法
       		    Vue.prototype.$myMethod = function () {...}
        		Vue.prototype.$myProperty = xxxx
    }
    

    使用插件:Vue.use(plugin)

    scoped样式

    src/components/School.vue

    
    
    
    
    
    
    

    src/components/Student.vue

    
    
    
    
    
    
    

    src/App.vue

    
    
    
    
    

    第二章Vue组件化编程_第13张图片

    总结:

    scoped样式:

    1. 作用:让样式在局部生效,防止冲突
      • 我们在给组件写对应的样式时,可能会有类名相同而导致样式冲突,所以对于组件的样式,我们最好能让其只在组件内局部生效,而不影响其他组件的样式
    2. 写法:
      • MyHeader想实现一个输入内容,对应添加一个MyItem选项
      • 因为添加数据是要改变MyList内容,所以将动态数据放入到其公共的父组件,也就是我们的App.vue
        • 在App.vue实现对应改变我们数据的方法,然后将方法传递给我们的MyHeader组件,让我们的MyHeader调用方法,进行改变数据
        • 想要不是父子组件来进行数据交互传递,现在还不能实现,后面会继续学习进行实现

      MyList.vue

      
      
      
      
      
      
      
      • props:[‘todos’,‘checkTodo’,‘deleteTodo’] ,因为数据是从App.vue传递给MyItem,现在学的知识只能从父传子,或者子传父,不能直接从爷传子,所以需要MyList进行中转过度

      MyItem.vue

      
      
      
      
      
      
      

      MyFooter.vue

      
      
      
      
      
      
      

      src/App.vue

      
      
      
      
      
      
      

      实现效果

      第二章Vue组件化编程_第14张图片

      总结:

      组件化编码流程:

      • 拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突

      第二章Vue组件化编程_第15张图片

      • 实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:

        • 一个组件在用:放在组件自身即可
        • 一些组件在用:放在他们共同的父组件上(状态提升)
      • 实现交互:从绑定事件开始

      • props适用于:

        • 父组件 ==> 子组件 通信

        • 子组件 ==> 父组件 通信(要求父组件先给子组件一个函数)

      • 使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的

        • props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做

      WebStorage——浏览器本地存储

      localStorage

      DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <meta http-equiv="X-UA-Compatible" content="IE=edge">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>localStoragetitle>
      head>
      <body>
          <h2>localStorageh2>
          <button onclick="saveDate()">点我保存数据button><br/>
          <button onclick="readDate()">点我读数据button><br/>
          <button onclick="deleteDate()">点我删除数据button><br/>
          <button onclick="deleteAllDate()">点我清空数据button><br/>
      
          <script>
              let person = {name:"JOJO",age:20}
      
              function saveDate(){
                  localStorage.setItem('msg','localStorage')
                  localStorage.setItem('person',JSON.stringify(person))
              }
              function readDate(){
                  console.log(localStorage.getItem('msg'))
                  const person = localStorage.getItem('person')
                  console.log(JSON.parse(person))
              }
              function deleteDate(){
                  localStorage.removeItem('msg')
                  localStorage.removeItem('person')
              }
              function deleteAllDate(){
                  localStorage.clear()
              }
          script>
      body>
      html>
      
      

      sessionStorage

      DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <meta http-equiv="X-UA-Compatible" content="IE=edge">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>sessionStoragetitle>
      head>
      <body>
          <h2>sessionStorageh2>
          <button onclick="saveDate()">点我保存数据button><br/>
          <button onclick="readDate()">点我读数据button><br/>
          <button onclick="deleteDate()">点我删除数据button><br/>
          <button onclick="deleteAllDate()">点我清空数据button><br/>
      
          <script>
              let person = {name:"JOJO",age:20}
      
              function saveDate(){
                  sessionStorage.setItem('msg','sessionStorage')
                  sessionStorage.setItem('person',JSON.stringify(person))
              }
              function readDate(){
                  console.log(sessionStorage.getItem('msg'))
                  const person = sessionStorage.getItem('person')
                  console.log(JSON.parse(person))
              }
              function deleteDate(){
                  sessionStorage.removeItem('msg')
                  sessionStorage.removeItem('person')
              }
              function deleteAllDate(){
                  sessionStorage.clear()
              }
          script>
      body>
      html>
      
      

      总结:

      • 存储内容大小一般支持5MB左右(不同浏览器可能还不一样)

      • 浏览器端通过Window.sessionStorage和Window.localStorage属性来实现本地存储机制

      相关API:

      • xxxStorage.setItem(‘key’, ‘value’):该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值

      • xxxStorage.getItem(‘key’):该方法接受一个键名作为参数,返回键名对应的值

      • xxxStorage.removeItem(‘key’):该方法接受一个键名作为参数,并把该键名从存储中删除

      • xxxStorage.clear():该方法会清空存储中的所有数据

      备注:

      • SessionStorage存储的内容会随着浏览器窗口关闭而消失

      • LocalStorage存储的内容,需要手动清除才会消失

      • xxxStorage.getItem(xxx)如果 xxx 对应的 value 获取不到,那么getItem()的返回值是null

      • JSON.parse(null)的结果依然是null

      使用本地存储优化Todo-List

      
      
      
      
      
      
      

      自定义事件——实现组件子传夫数据

      之前我们实现子元素给父元素传递数据,是将父元素的方法传递给我们的子元素,然后子元素通过父元素传递的方法来进行传递数据给父元素

      
      
      
      
      
      
      

      绑定

      student.vue

      
      
      
      
      
      
      

      App.vue——也是student,school的父组件

      
      
      
      
      
      
      
      
      • this. r e f s . s t u d e n t . refs.student. refs.student.on(‘myevent’,this.getStudentName)这种方式更加灵活,比如我们可以在mounted钩子函数中调用定时器任务

      解绑

      student.vue

      
      
      
      
      
      
      

      App.vue

      
      
      
      
      
      
      

      总结:

      组件的自定义事件:

      • 一种组件间通信的方式,适用于:==子组件 > 父组件

      • 使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)

      • 绑定自定义事件:

        • 第一种方式,在父组件中:

        • 第二种方式,在父组件中

          • 若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法
       mounted(){
                  this.$refs.student.$on('myevent',this.getStudentName)
                 // this.$refs.student.$once('myevent',this.getStudentName)//只调用一次
       }
      
      • 触发自定义事件:this.$emit(‘atguigu’,数据)——在对应触发事件的组件中,也就是子组件

      • 解绑自定义事件:this.$off(‘atguigu’)——在对应绑定的组件中,也就是子组件

      • 组件上也可以绑定原生DOM事件,需要使用native修饰符

      <Student @click.native="getStudentName"/>
      
      • 注意:通过this. r e f s . x x x . refs.xxx. refs.xxx.on(‘atguigu’,回调)绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!

      使用自定义事件优化Todo-List

      src/App.vue

      
      
      
      
      
      
      

      MyHeader.vue

      
      
      
      
      
      
      

      MyFooter.vue

      
      
      
      
      
      
      

      全局事件总线——处理其他组件关系传递数据

      在前面通过props可以解决父组件传递数据给子组件,也可以通过自定义事件将子组件数据传递给父组件,但是我们没法完成

      全局事件总线是一种可以在任意组件间通信的方式,本质上就是一个对象。它必须满足以下条件:

      1. 所有的组件对象都必须能看见他
      2. 这个对象必须能够使用 o n 、 on、 onemit和$off方法去绑定、触发和解绑事件

      src/main.js

      import Vue from 'vue'
      import App from './App.vue'
      
      Vue.config.productionTip = false
      
      new Vue({
      	el:'#app',
      	render: h => h(App),
      	beforeCreate() {
      		Vue.prototype.$bus = this //安装全局事件总线
      	}
      })
      
      

      App.vue

      <template>
      	<div class="app">
      		<School/>
      		<Student/>
      	</div>
      </template>
      
      <script>
      	import Student from './components/Student'
      	import School from './components/School'
      
      	export default {
      		name:'App',
      		components:{School,Student}
      	}
      </script>
      
      <style scoped>
      	.app{
      		background-color: gray;
      		padding: 5px;
      	}
      </style>
      
      

      Student.vue

      <template>
      	<div class="student">
      		<h2>学生姓名:{{name}}</h2>
      		<h2>学生性别:{{sex}}</h2>
      		<button @click="sendStudentName">把学生名给School组件</button>
      	</div>
      </template>
      
      <script>
      	export default {
      		name:'Student',
      		data() {
      			return {
      				name:'张三',
      				sex:'男'
      			}
      		},
      		methods: {
      			sendStudentName(){
      				this.$bus.$emit('demo',this.name)
      			}
      		}
      	}
      </script>
      
      <style scoped>
      	.student{
      		background-color: pink;
      		padding: 5px;
      		margin-top: 30px;
      	}
      </style>
      
      

      School.vue

      <template>
      	<div class="school">
      		<h2>学校名称:{{name}}</h2>
      		<h2>学校地址:{{address}}</h2>
      	</div>
      </template>
      
      <script>
      	export default {
      		name:'School',
      		data() {
      			return {
      				name:'尚硅谷',
      				address:'北京',
      			}
      		},
      		methods:{
      			demo(data) {
      				console.log('我是School组件,收到了数据:',data)
      			}
      		},
      		mounted() {
      			this.$bus.$on('demo',this.demo)
      		},
      		beforeDestroy() {
      			this.$bus.$off('demo')
      		},
      	}
      </script>
      
      <style scoped>
      	.school{
      		background-color: skyblue;
      		padding: 5px;
      	}
      </style>
      
      

      总结:

      全局事件总线(GlobalEventBus):

      • 一种组件间通信的方式,适用于任意组件间通信

      安装全局事件总线:

      new Vue({
         	...
         	beforeCreate() {
         		Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
         	},
          ...
      }) 
      

      第二章Vue组件化编程_第16张图片

      • 为什么我们选用Vue的原型对象,我们既然是全局的,肯定能让所有的组件实例对象和Vue的实例对象都能找到,一看就是需要我们的Vue的原型对象
        • 补充一下,我们的Vue.extends每次返回的VueComponent这个构造函数都是新建的,所以对应的VueComponent的实例对象访问的VueComponent的原型对象也不同

      使用事件总线:

      • 接收数据:因为要被操作的组件是A,A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身
      export default {
          methods(){
              demo(data){...}
          }
          ...
          mounted() {
              this.$bus.$on('xxx',this.demo)
          }
      }
      
      • 提供数据:this. b u s . bus. bus.emit(‘xxx’,data),因为谁想去操作A组件,那就使用emit去触发他的对应自定义事件
      methods: {
      	sendStudentName(){
      		this.$bus.$emit('demo',this.name)
      	}
      }
      
      • 最好在A的beforeDestroy钩子中,用$off去解绑当前组件所用到的事件
      export default {
      	beforeDestroy() {
      		this.$bus.$off('demo')
      	},
      }
      

      使用全局总线事件优化Todo-List

      先分析以下改改造那些

      • 父传子 用props最方便 不需要改造
      • 子传父 用自定义也很方便,也不需要
      • 其他的组件关系之间传递就用全局总线——所以改造MyItem传给App这种孙传爷的关系

      src/main.js

      import Vue from 'vue'
      import App from './App.vue'
      
      Vue.config.productionTip = false
      
      new Vue({
          el:"#app",
          render: h => h(App),
          beforeCreate() {
              Vue.prototype.$bus = this
          }
      })
      
      

      App.vue

      
      
      
      
      
      
      

      MyItem.vue

      
      
      
      
      
      
      

      消息的订阅与发布

      需要我们额外去安装对应的包

      npm i pubsub-js
      

      其实用法跟全局总线差不多,我们最好还是用全局总线,因为是vue自带的,但是还是可以了解一下

      src/components/School.vue

      
      
      
      
      
      
      
      
      
      
      
      
      
      

      总结:

      消息订阅与发布(pubsub):

      • 消息订阅与发布是一种组件间通信的方式,适用于任意组件间通信

      • 使用步骤:

        • 安装pubsub:npm i pubsub-js

        • 引入:import pubsub from ‘pubsub-js’

      • 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身

      export default {
          methods(){
              demo(data){...}
          }
          ...
          mounted() {
      		this.pid = pubsub.subscribe('xxx',this.demo)
          }
      }
      
      • 提供数据:pubsub.publish(‘xxx’,data)
      • 最好在beforeDestroy钩子中,使用pubsub.unsubscribe(pid)取消订阅

      $nextTick

      $nextTick(回调函数)可以将回调延迟到下次 DOM 更新循环之后执行

      使用$nextTick优化Todo-List

      App.vue

      
      
      
      
      
      
      

      MyItem.vue

      
      
      
      
      
      
      

      总结:

      $nextTick:

      1. 语法:this.$nextTick(回调函数)
      2. 作用:在下一次 DOM 更新结束后执行其指定的回调
      3. 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行
        • 比如我们的改变todo.isEdit的属性,如果isEdit值改变了,我们的Vue不会马上更新,会获取焦点后再更新,那么我们的获取焦点也就没有效果,所以加上this.$nextTick

      过度和动画

      • 用来修饰我们的元素在出现和消失的时候对应播放的样式

      transition需要触发一个事件才会随着时间改变其CSS属性;
      animation在不需要触发任何事件的情况下,也可以显式的随时间变化来改变元素CSS属性,达到一种动画的效果
      1)动画不需要事件触发,过渡需要。
      2)过渡只有一组(两个:开始-结束)关键帧,动画可以设置多个

      App.vue

      
      
      
      
      

      MyAnimation.vue

      
      
      
      
      
      
      
      • vue想实现动画效果。将对应元素用transition标签包裹,如果给该标签加上name属性,去找对应的.name属性-enter/leave-active样式,如果不写name属性,则是.v-enter/leave-active样式
      • appear表示进入页面时,就播放一次动画

      MyTransition.vue

      
      
      
      
      
      
      

      MyTransitionGroup.vue

      
      
      
      ·
      
      
      

      ThirdPartAnimation.vue

      
      
      
      
      
      
      

      总结:

      Vue封装的过度与动画:

      • 作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名

      第二章Vue组件化编程_第17张图片

      写法:

      • 准备好样式

        • 元素进入的样式

          • v-enter:进入的起点

          • v-enter-active:进入过程中

          • v-enter-to:进入的终点

        • 元素离开的样式:

          • v-leave:离开的起点

          • v-leave-active:离开过程中

          • v-leave-to:离开的终点

      • 使用包裹要过度的元素,并配置name属性:

      
      	

      你好啊!

      • 备注:若有多个元素需要过度,则需要使用:,且每个元素都要指定key值

      过度和动画改造TodoList

      我们的TodoList中会被删除,添加的是我们的MyItem组件

      
      
      
      
      
      
      

      插槽Slot

      为什么我们需要插槽呢?——说直接点,就是想定制化组件,什么叫定制化组件?

      第二章Vue组件化编程_第18张图片

      • 我们很明显能看出来,这三个蓝色的框都是相同的组件,只是根据我们传入的数据不同,从而可以显示不同的数据

      第二章Vue组件化编程_第19张图片

      • 这个图就可以体现出来什么叫定制化?我们的第一个组件实例对象显示的是图片,第二是列表,第三个是一个视频,这就不是光光靠我们传入的数据不同,就可以做到的,因为照片和视频和列表的展示是需要不同的标签实现,这时候就体现出我们的插槽的作用

      默认插槽

      Category.vue

      
      
      
      
      
      
      
      • 用slot表示这是一个插槽,这里可以进行定制化内容
      
      
      
      
      
      
      
      • 对slot进行传入对应格式,进行定制化

      具名插槽

      对于一个组件,可能会出现多个插槽的未知,所以我们在使用的时候需要指定用哪个插槽

      
      
      
      
      
      

      App.vue

      
      
      
      
      
      
      
      • 一个插槽可以被使用多次
      • v-slot:这种用法只能在