Vue2【尚硅谷--天禹老师】:Vue组件化编程

目录

1.对组件的理解

 1. 模块

2. 组件

3. 模块化

4. 组件化

2. 非单文件组件:一个文件中包含有n个组件

1.组件定义时,一定必要写el配置选项

 2.组件中的data一定要写成函数式【new Vue中的data可以写成对象式】

 3.在Vue开发者工具中会解析成School(将标签的首字母大写化)

4.注册局部组件(components)

5.注册全局组件 (component)

 6.总结

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

如何定义一个组件?

如何注册组件?

7.组件注意点

(1)当标签名中出现多个字母时

(2)标签的名字注册

 (3)标签的使用

(4)简写方式

​编辑

8.组件的嵌套

 9.VueComponent【实际上组件简称”vc“】

(1)每次调用Vue.extend,返回的都是一个全新的VueComponent!

 (2)new Vue中的this是指vm,但是创建组件时Vue.extent(option)中的option中的this是指VueComponent实例对象

 (3)总结

10.Vue实例(vm)与组件实例(vc)

 11.重要的内置关系

3.单文件组件:一个文件中只包有1个组件

1.命名常用

 2.三个标签

3.三种向外暴露

(1)export const school=Vue.extend({})

  (2)  export {school}

  (3) export default school(默认暴露)

4.代码

school.vue

Student.vue

App.vue

main.js

index.html


1.对组件的理解

image-20210723115936262

 image-20210723120028543

 1. 模块

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

2. 组件

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

3. 模块化

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

4. 组件化

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

【组件就是一块砖,哪里需要哪里搬】

2. 非单文件组件:一个文件中包含有n个组件

1.组件定义时,一定必要写el配置选项

组件定义时,一定不要写el配置,因为最终所有的组件的要被一个vm管理

 2.组件中的data一定要写成函数式【new Vue中的data可以写成对象式】

 3.在Vue开发者工具中会解析成School(将标签的首字母大写化)

4.注册局部组件(components)

    components: {
      school: school,
      student: student
    }

5.注册全局组件 (component)

  // 注册全局组件
  Vue.component('hello',hello)

 6.总结

<!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>Document</title>
  <script type="text/javascript" src="./js/vue.js"></script>
</head>

<body>
  <div id="root">
    <h1>{{msg}}</h1>
    <!-- 3.编写组件标签 -->
    <!-- <xuexiao></xuexiao> -->
    <!-- 在Vue开发者工具中会解析成School(首字母大写) -->
    <school></school>
    <hr>
    <!-- <xuesheng></xuesheng> -->
    <student></student>
  </div>
</body>
<!-- 

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

      1.定义组件(创建组件)
      2.注册组件
      3.使用组件(写组件标签)
      一、如何定义一个组件?

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

      1.el不要写,为什么?------最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器

      2.data必须写成函数,为什么?------避免组件被复用时,数据存在引用关系

    二、如何注册组件?

          1.局部注册:new Vue的时候传入components选项
          2.全局注册:Vue.component('组件名',组件)
    三、编写组件标签:
          <school></school> -->

<script type="text/javascript">

  // 1.创建school组件
  // Vue.extend--继承
  const school = Vue.extend({
    // 记得加上div
    template:`
				<div>
					<h2>学校名称:{{schoolName}}</h2>
					<h2>学校地址:{{address}}</h2>	
				</div>
			`,
    // 组件定义时,一定不要写el配置,因为最终所有的组件的要被一个vm管理
    // el:'#root',
    data(){
				return {
					schoolName:'尚硅谷',
					address:'北京昌平'
				}
			}
  })


  // 1.创建student组件
  // Vue.extend--继承
  const student = Vue.extend({
    // 记得加上div
    template:
      `
    <div>
    <h2>学生姓名:{{studentName}}</h2>
    <h2>学生年龄:{{age}}</h2>
      </div>
    `,
    // 组件定义时,一定不要写el配置,因为最终所有的组件的要被一个vm管理
    // el:'#root',
    data() {
      return {
        studentName: '小林',
        age: 19
      }
    },
  })

    // 1.创建hello组件
  // Vue.extend--继承
  const hello = Vue.extend({
    // 记得加上div
    template:
      `
    <div>
    <h2>学生姓名:{{studentName}}</h2>
    <h2>学生年龄:{{age}}</h2>
      </div>
    `,
    // 组件定义时,一定不要写el配置,因为最终所有的组件的要被一个vm管理
    // el:'#root',
    data() {
      return {
        studentName: '小林',
        age: 19
      }
    },
  })


  // 注册全局组件
  Vue.component('hello',hello)

  // 创建vm
  new Vue({
    el: '#root',
    data(){
      return {
        
      msg:'hello'
      }
    },
    //2.注册组件:key:value组合(局部注册)
    components: {
      school: school,
      student: student
      // xuexiao:school,
      // xuesheng:student
    }
  })
</script>

</html>

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

  1. 定义组件(创建组件)
  2. 注册组件
  3. 使用组件(写组件标签)

如何定义一个组件?

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

               1.el不要写,为什么?

                        最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器

                2.data必须写成函数,为什么?

                        避免组件被复用时,数据存在引用关系

如何注册组件?

    1. 局部注册:new Vue的时候传入components选项
    2. 全局注册:Vue.component('组件名',组件)
  • 编写组件标签:<school></school>

7.组件注意点

(1)当标签名中出现多个字母时

(2)标签的名字注册

      一个单词组成:

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

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

      多个单词组成:

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

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

          备注:

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

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

 (3)标签的使用

    关于组件标签:

        第一种写法:<school></school>

        第二种写法:<school/>

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

(4)简写方式

 const school = Vue.extend(options)可简写为:const school = options

8.组件的嵌套

 

<!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: `
				<div>
					<h2>学生名称:{{name}}</h2>	
					<h2>学生年龄:{{age}}</h2>	
				</div>
			`,
    data() {
      return {
        name: '小林',
        age: 20
      }
    }
  })

  //定义school组件
  const school = Vue.extend({

    // 调用student组件
    template: `
				<div>
					<h2>学校名称:{{name}}</h2>	
					<h2>学校地址:{{address}}</h2>	
					<student></student>
				</div>
			`,
    // 注册子级组件(student),记得student写在school前面
    components: {
      student
    },
    data() {
      return {
        name: '尚硅谷',
        address: '北京'
      }
    }
  })

  //定义hello组件
  const hello = Vue.extend({
    template: `
				<h1>{{msg}}</h1>
			`,
    data() {
      return {
        msg: "欢迎学习尚硅谷Vue教程!"
      }
    }
  })

  // app是一人之下(vm),万人(组件)之上
  //定义app组件
  const app = Vue.extend({
    template: `
				<div>
					<hello></hello>
					<school></school>
				</div>
			`,
    components: {
      school,
      hello
    }
  })

  //创建vm
  new Vue({
    template: `
				<app></app>
			`,
    el: '#root',
    components: {
      app
    }
  })
</script>

</html>

 9.VueComponent【实际上组件简称”vc“】

(1)每次调用Vue.extend,返回的都是一个全新的VueComponent!

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

 (2)new Vue中的this是指vm,但是创建组件时Vue.extent(option)中的option中的this是指VueComponent实例对象

          (1)组件配置中(options):

              data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是VueComponent实例对象

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

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

 (3)总结

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

      2.我们只需要写<school/>或<school></school>,Vue解析时会帮我们创建school组件的实例对象,

            即Vue帮我们执行的:new VueComponent(options)

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

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8" />
  <title>VueComponent</title>
  <script type="text/javascript" src="./js/vue.js"></script>
</head>

<body>
  <div id="root">
    <school></school>
    <hello></hello>
    <button @click="showVm">点我创建vm实例对象</button>
  </div>
</body>

<!-- 关于VueComponent:

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

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

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

      4.关于this指向:

          (1)组件配置中(options):
              data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是VueComponent实例对象
          (2)new Vue(options)配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是Vue实例对象
              VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)

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


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

    // 调用student组件
    template: `
				<div>
					<h2>学校名称:{{name}}</h2>	
					<h2>学校地址:{{address}}</h2>	
				</div>
			`,
    data() {
      return {
        name: '尚硅谷',
        address: '北京'
      }
    }
  })

  //定义hello组件
  const hello = Vue.extend({
    // 调用student组件
    template: `
    <div>
      {{msg}}	
      <hr>
      <button @click="showVc">点我创建VueComponent实例对象</button>
    </div>
  `,
    data() {
      return {
        msg: '您好'
      }
    },
    methods: {
      showVc(){
        console.log('option中的this',this);
      }
    },
  })

  
  // 是一个构造函数:VueComponent
  console.log('@', school);

  //创建vm
  new Vue({
    el: '#root',
    methods:{
      showVm(){
        console.log('new vm中的this',this);
      }
    },
    components: {
      school,
      hello
    }
  })
</script>

</html>

10.Vue实例(vm)与组件实例(vc)

 11.重要的内置关系

实例的隐式原型属性,永远指向自己缔造者的原型对象(Object所有类的父类)

 VueComponent.prototype.__proto__ === Vue.prototype

为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue 原型上的属性、方法

3.单文件组件:一个文件中包有1个组件

1.命名常用

 2.三个标签

3.三种向外暴露

(1)export const school=Vue.extend({})

  (2)  export {school}

  (3) export default school(默认暴露)

4.代码

school.vue

<template>
  <!-- 组件的结构 -->
  <div id='Demo'>
        <h2>学校名称:{{name}}</h2>
        <h2>学校地址:{{address}}</h2>
        <button @click="showName">点我提示学校名</button>
    </div>
</template>

<script>
  // 组件交互相关的代码(数据、方法)
  export default {
    // 与外部文件名保持一致
        name:'School',
        data() {
            return {
                name:'尚硅谷',
                address:'北京'
            }
        },
        methods: {
            showName(){
                alert(this.name)
            }
        },
    }
</script>

<style>
/* 组件的样式 */
#Demo{
        background: orange;
    }
</style>

Student.vue

<template>
  <div>
      <h2>学生姓名:{{name}}</h2>
      <h2>学生年龄:{{age}}</h2>
  </div>
</template>

<script>
  export default {
      name:'Student',
      data() {
          return {
              name:'小林',
              age:20
          }
      },
  }
</script>

App.vue

<template>
  <div>
      <School></School>
      <Student></Student>
  </div>
</template>

<script>
  import School from './School.vue'
  import Student from './Student.vue'

  export default {
      name:'App',
      components:{
          School,
          Student
      }
  }
</script>

main.js

import App from './App.vue'

new Vue({
    template:`<App></App>`,
    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>

4.Vue脚手架(Vue CLI)

3.1. 初始化脚手架

3.1.1. 说明

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

3.1.2. 具体步骤

  • 如果下载缓慢请配置 npm 淘宝镜像:npm config set registry http://registry.npm.taobao.org
  • 全局安装@vue/cli:npm install -g @vue/cli
  • 切换到你要创建项目的目录,然后使用命令创建项目:vue create xxxx
  • 选择使用vue的版本
  • 启动项目:npm run serve
  • 暂停项目:Ctrl+C

Vue 脚手架隐藏了所有 webpack 相关的配置,若想查看具体的 webpakc 配置,请执行:

vue inspect > output.js

3.1.3. 分析脚手架结构

.文件目录
├── node_modules 
├── public
│   ├── favicon.ico: 页签图标
│   └── index.html: 主页面
├── src
│   ├── assets: 存放静态资源
│   │   └── logo.png
│   │── component: 存放组件
│   │   └── HelloWorld.vue
│   │── App.vue: 汇总所有组件【所有组件的父组件】
│   └── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 应用包配置文件 
├── README.md: 应用描述文件
└── package-lock.json: 包版本控制文件

 文件执行顺序:

   执行index.html文件

   执行main.js文件

   main.js挂载了app.vue文件,用app.vue的templete替换index.html中的<div id="app"></div>

   main.js中注入了路由文件,将对应的组件渲染到router-view中

   router-view中加载Layout文件

   Layout 加载Navbar, Sidebar, AppMain

src/components/School.vue:

<template>
    <div id='Demo'>
        <h2>学校名称:{{name}}</h2>
        <h2>学校地址:{{address}}</h2>
        <button @click="showName">点我提示学校名</button>
    </div>
</template>

<script>
    export default {
        name:'School',
        data() {
            return {
                name:'尚硅谷',
                address:'北京'
            }
        },
        methods: {
            showName() {
                alert(this.name)
            }
        },
    }
</script>

<style>
    #Demo{
        background: orange;
    }
</style>

src/components/Student.vue:

<template>
    <div>
        <h2>学生姓名:{{name}}</h2>
        <h2>学生年龄:{{age}}</h2>
    </div>
</template>

<script>
    export default {
        name:'Student',
        data() {
            return {
                name:'JOJO',
                age:20
            }
        },
    }
</script>

src/App.vue:

<template>
    <div>
        <School></School>
        <Student></Student>
    </div>
</template>

<script>
    import School from './components/School.vue'
    import Student from './components/Student.vue'

    export default {
        name:'App',
        components:{
            School,
            Student
        }
    }
</script>

src/main.js:

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
    el:'#app',
    render: h => h(App),
})

public/index.html:

<!DOCTYPE html>
<html lang="">
    <head>
        <meta charset="UTF-8">
        <!-- 针对IE浏览器的特殊配置,含义是让IE浏览器以最高渲染级别渲染页面 -->
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <!-- 开启移动端的理想端口 -->
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <!-- 配置页签图标   <%= BASE_URL %>:相当于“./" -->
        <link rel="icon" href="<%= BASE_URL %>favicon.ico">
        <!-- 配置网页标题 -->
        <title><%= htmlWebpackPlugin.options.title %></title>
    </head>
    <body>
        <!-- 容器 -->
        <div id="app"></div>
    </body>
</html>

 3.1.4. render函数

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
    el:'#app',
    // 简写形式
    //render:q=>q('h1','你好啊')
	render: h => h(App),
    // 完整形式
	// render(createElement){
	//     return createElement(App)
	// }
})
关于不同版本的函数:vue.js 与 vue.runtime.xxx.js的区别:
    1. vue.js 是完整版的 Vue,包含:核心功能+模板解析器
    2. vue.runtime.xxx.js 是运行版的 Vue,只包含核心功能,没有模板解析器
  1. 因为 vue.runtime.xxx.js 没有模板解析器,所以不能使用 template 配置项,需要使用 render函数接收到的createElement 函数去指定具体内容

3.1.5. 修改默认配置

在终端输入:vue inspect ?> output.js【生成一个output.js文件】

module.exports = {
    pages: {
        index: {
            // 入口
            entry: 'src/index/main.js'
        }
    },
  // 关闭语法检查
  lineOnSave:false
}

  • vue.config.js 是一个可选的配置文件,如果项目的(和 package.json 同级的)根目录中存在这个文件,那么它会被 @vue/cli-service 自动加载
  • 使用 vue.config.js 可以对脚手架进行个性化定制,详见配置参考 | Vue CLI

 

 3.2. ref属性【id的替代者】

<template>
    <div>
        <h1 ref="title">{{msg}}</h1>
        <School ref="sch"/>
        <button @click="show" ref="btn">点我输出ref</button>
    </div>
</template>

<script>
    import School from './components/School.vue'
    export default {
        name:'App',
        components: { School },
        data() {
            return {
                msg:'欢迎学习Vue!'
            }
        },
        methods:{
            show(){
                console.log(this.$refs.title)//真实dom元素
                console.log(this.$refs.sch)//拿到School组件的vc对象【实例对象】
                console.log(this.$refs.btn)//真实dom元素
            }
        }
    }
</script>

  1.  被用来给元素或子组件注册引用信息(id的替代者)
  2. 应用在html标签上获取的是真实DOM元素,应用在组件标签上获取的是组件实例对象(vc)
  3. 使用方式:
    1. 打标识:<h1 ref="xxx"></h1> 或 <School ref="xxx"></School>
    2. 获取:this.$refs.xxx

3.3. props配置项

src/App.vue:

<template>
    <div>
        <Student name="李四" sex="男" :age="20" />
    </div>
</template>

<script>
    import Student from './components/Student.vue'
    export default {
        name:'App',
        components: { Student },
    }
</script>

src/components/Student.vue:

<template>
    <div>
        <h1>{{msg}}</h1>
        <h2>学生姓名:{{name}}</h2>
        <h2>学生性别:{{sex}}</h2>
        <h2>学生年龄:{{age}}</h2>     
    </div>
</template>

<script>
    export default {
        name:'Student',
        data() {
            return {
                msg:"我是一名来自汕头",
            }
        },
        // 简单声明接收
		// props:['name','age','sex']

        // 接收的同时对数据进行类型限制
		/* props:{
			name:String,
			age:Number,
			sex:String
		} */

        // 接收的同时对数据进行类型限制 + 指定默认值 + 限制必要性
		props:{
			name:{
				type:String,
				required:true,
			},
			age:{
				type:Number,
				default:99
			},
			sex:{
				type:String,
				required:true
			}
		}
    }
</script>

props配置项:

  1. 功能:让组件接收外部传过来的数据
  2. 传递数据:<Demo name="xxx"/>
  3. 接收数据:
    1. 第一种方式(只接收):props:['name']
注意点:

如果要接收的是一个数值类型的并对其进行加减操作,则应该在赋值之前加上(v-model),表示只计算双引号里面的数值,而且不是字符串。

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

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

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

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

接收的同时对数据进行类型限制 + 指定默认值 + 限制必要性

注意点:

let a=1;a=2;【此时a是被修改了】

obj={a:1,b:2} obj.a=2;【此时a不算被修改了】

obj={x=1;y=2}【此时obj算是被修改了】

3.4. mixin混入

基本使用:

 

 

也可以在minin.js文件中配置data,monted等数据。 

局部混入:

src/mixin.js:

export const mixin = {
    methods: {
        showName() {
            alert(this.name)
        }
    },
    mounted() {
        console.log("你好呀~")
    }
}

src/components/School.vue

<template>
    <div>
        <h2 @click="showName">学校姓名:{{name}}</h2>
        <h2>学校地址:{{address}}</h2>   
    </div>
</template>

<script>
    //引入混入
    import {mixin} from '../mixin'
    
    export default {
        name:'School',
        data() {
            return {
                name:'尚硅谷',
				address:'北京'
            }
        },
        mixins:[mixin]
    }
</script>

src/components/Student.vue:

<template>
    <div>
        <h2 @click="showName">学生姓名:{{name}}</h2>
        <h2>学生性别:{{sex}}</h2>   
    </div>
</template>

<script>
    //引入混入
    import {mixin} from '../mixin'
    
    export default {
        name:'Student',
        data() {
            return {
                name:'JOJO',
				sex:'男'
            }
        },
		mixins:[mixin]
    }
</script>

src/App.vue:

<template>
    <div>
        <School/>
        <hr/>
        <Student/>
    </div>
</template>

<script>
    import Student from './components/Student.vue'
    import School from './components/School.vue'

    export default {
        name:'App',
        components: { Student,School },
    }
</script>
 全局混入:

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(混入):

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

  2. 使用方式:

                定义混入:

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

        使用混入:

  1. 全局混入【在mian.js文件中】:Vue.mixin(xxx)
  2. 局部混入:mixins:['xxx']

备注:

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

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" }
  	}
})

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

var mixin = {
  	created () {
    	console.log('混入对象的钩子被调用')
  	}
}

new Vue({
  	mixins: [mixin],
  	created () {
    	console.log('组件钩子被调用')
  	}
})

// => "混入对象的钩子被调用"
// => "组件钩子被调用"
//以上代码都输出

3.5. plugin插件

注意点:

1.先注册插件【在src文件夹下】

2.使用插件【在main.js文件下,先注册插件,在声明vc实例对象】

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('你好啊')}
	}
}

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:

<template>
    <div>
        <h2>学校姓名:{{name | mySlice}}</h2>
        <h2>学校地址:{{address}}</h2>   
    </div>
</template>

<script>
    export default {
        name:'School',
        data() {
            return {
                name:'尚硅谷atguigu',
				address:'北京'
            }
        }
    }
</script>

src/components/Student.vue:

<template>
    <div>
        <h2>学生姓名:{{name}}</h2>
        <h2>学生性别:{{sex}}</h2> 
        <button @click="test">点我测试hello方法</button>  
    </div>
</template>

<script>
    export default {
        name:'Student',
        data() {
            return {
                name:'JOJO',
				sex:'男'
            }
        },
        methods:{
            test() {
                this.hello()
            }
        }
    }
</script>

插件:

  1. 功能:用于增强Vue

  2. 本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据

  3. 定义插件:

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

3.6. scoped样式

src/components/School.vue:

<template>
    <div class="demo">
        <h2>学校姓名:{{name}}</h2>
        <h2>学校地址:{{address}}</h2>   
    </div>
</template>

<script>
    export default {
        name:'School',
        data() {
            return {
                name:'尚硅谷',
				address:'北京'
            }
        }
    }
</script>

<style scoped>
    .demo{
        background-color: blueviolet;
    }
</style>

src/components/Student.vue

<template>
    <div class="demo">
        <h2>学生姓名:{{name}}</h2>
        <h2>学生性别:{{sex}}</h2> 
    </div>
</template>

<script>
    export default {
        name:'Student',
        data() {
            return {
                name:'JOJO',
				sex:'男'
            }
        }
    }
</script>

<style scoped>
    .demo{
        background-color: chartreuse;
    }
</style>

src/App.vue:

<template>
    <div>
        <School/>
        <Student/>
    </div>
</template>

<script>
    import Student from './components/Student.vue'
    import School from './components/School.vue'

    export default {
        name:'App',
        components: { Student,School },
    }
</script>

scoped样式:

  1. 作用:让样式在局部生效,防止冲突
  2. 写法:<style scoped>

 scoped样式一般不会在App.vue中使用

TodoList案例

1.基本构建

2.编码基本流程

 3.动态展示数据

4.添加

4.1  注意点:对象的id,重复问题

方法一:

自动生成id:uuid【全球唯一字符串】

方法二:nanoid【是一个函数可以直接调用】

 

4.2 组件之间的数据传输

 4.2.1 将todos数据放在App组件中【父传子】

4.2.2 在MyList组件中接收todos数据【父传子】

 4.2.3 将在MyHeader获得的数据传递给App组件中的MyList组件中【子传父】

 1.要父亲先给儿子定义一个函数

2.在合适的时候,儿子调用这个函数

5.勾选

拿到要修改勾选的id

 

 6.删除

拿到要进行删除的id

 7.底部统计

reduce函数

 

 8.底部交互

1.底部显示框是否展示

 2.底部“全选”按钮的效果

 

3.”清除已完成的任务"

 9.总结

src/components/MyHeader.vue:

<template>
    <div class="todo-header">
        <input type="text" placeholder="请输入你的任务名称,按回车键确认" @keydown.enter="add" v-model="title"/>
    </div>
</template>

<script>
    import {nanoid} from 'nanoid'
    export default {
        name:'MyHeader',
        data() {
            return {
                title:''
            }
        },
        methods:{
            add(){
                if(!this.title.trim()) return
                const todoObj = {id:nanoid(),title:this.title,done:false}
                this.addTodo(todoObj)
                this.title = ''
            }
        },
        props:['addTodo']
    }
</script>

<style scoped>
    .todo-header input {
        width: 560px;
        height: 28px;
        font-size: 14px;
        border: 1px solid #ccc;
        border-radius: 4px;
        padding: 4px 7px;
    }

    .todo-header input:focus {
        outline: none;
        border-color: rgba(82, 168, 236, 0.8);
        box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
    }
</style>

src/components/MyItem.vue:

<template>
    <li>
        <label>
            <input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/>
            <span>{{todo.title}}</span>
        </label>
        <button class="btn btn-danger" @click="handleDelete(todo.id,todo.title)">删除</button>
    </li>
</template>

<script>
    export default {
        name:'MyItem',
        props:['todo','checkTodo','deleteTodo'],
        methods:{
            handleCheck(id){
                this.checkTodo(id)
            },
            handleDelete(id,title){
                if(confirm("确定删除任务:"+title+"吗?")){
                    this.deleteTodo(id)
                }
            }
        }
    }
</script>

<style scoped>
    li {
        list-style: none;
        height: 36px;
        line-height: 36px;
        padding: 0 5px;
        border-bottom: 1px solid #ddd;
    }

    li label {
        float: left;
        cursor: pointer;
    }

    li label li input {
        vertical-align: middle;
        margin-right: 6px;
        position: relative;
        top: -1px;
    }

    li button {
        float: right;
        display: none;
        margin-top: 3px;
    }

    li:before {
        content: initial;
    }

    li:last-child {
        border-bottom: none;
    }

    li:hover {
        background-color: #eee;
    }

    li:hover button{
        display: block;
    }
</style>

src/components/MyList.vue:

<template>
    <ul class="todo-main">
        <MyItem 
            v-for="todo in todos" 
            :key="todo.id" 
            :todo="todo" 
            :checkTodo="checkTodo"
            :deleteTodo="deleteTodo"
        />
    </ul>
</template>

<script>
    import MyItem from './MyItem.vue'

    export default {
        name:'MyList',
        components:{MyItem},
        props:['todos','checkTodo','deleteTodo']
    }
</script>

<style scoped>
    .todo-main {
        margin-left: 0px;
        border: 1px solid #ddd;
        border-radius: 2px;
        padding: 0px;
    }

    .todo-empty {
        height: 40px;
        line-height: 40px;
        border: 1px solid #ddd;
        border-radius: 2px;
        padding-left: 5px;
        margin-top: 10px;
    }
</style>

src/components/MyFooter.vue:

<template>
    <div class="todo-footer" v-show="total">
        <label>
            <input type="checkbox" v-model="isAll"/>
        </label>
        <span>
            <span>已完成{{doneTotal}}</span> / 全部{{total}}
        </span>
        <button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
    </div>
</template>

<script>
    export default {
        name:'MyFooter',
        props:['todos','checkAllTodo','clearAllTodo'],
        computed:{
            doneTotal(){
                return this.todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0) ,0)
            },
            total(){
                return this.todos.length
            },
            isAll:{
                get(){
                    return this.total === this.doneTotal && this.total > 0
                },
                set(value){
                    this.checkAllTodo(value)
                }
            }
        },
        methods:{
            clearAll(){
                this.clearAllTodo()
            }
        }
    }
</script>

<style scoped>
    .todo-footer {
        height: 40px;
        line-height: 40px;
        padding-left: 6px;
        margin-top: 5px;
        }

    .todo-footer label {
        display: inline-block;
        margin-right: 20px;
        cursor: pointer;
    }

    .todo-footer label input {
        position: relative;
        top: -1px;
        vertical-align: middle;
        margin-right: 5px;
    }

    .todo-footer button {
        float: right;
        margin-top: 5px;
    }
</style>

src/App.vue:

<template>
    <div id="root">
        <div class="todo-container">
            <div class="todo-wrap">
            <MyHeader :addTodo="addTodo"/>
            <MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
            <MyFooter :todos="todos" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"/>
            </div>
        </div>
    </div>
</template>

<script>
    import MyHeader from './components/MyHeader.vue'
    import MyList from './components/MyList.vue'
    import MyFooter from './components/MyFooter.vue'

    export default {
        name:'App',
        components: { MyHeader,MyList,MyFooter },
        data() {
            return {
                todos:[
                    {id:'001',title:'抽烟',done:false},
                    {id:'002',title:'喝酒',done:false},
                    {id:'003',title:'烫头',done:false},
                ]
            }
        },
        methods:{
            //添加一个todo
            addTodo(todoObj){
                this.todos.unshift(todoObj)
            },
            //勾选or取消勾选一个todo
            checkTodo(id){
                this.todos.forEach((todo)=>{
                    if(todo.id === id) todo.done = !todo.done
                })
            },
            //删除一个todo
            deleteTodo(id){
                this.todos = this.todos.filter(todo => todo.id !== id)
            },
            //全选or取消勾选
            checkAllTodo(done){
                this.todos.forEach(todo => todo.done = done)
            },
            //删除已完成的todo
            clearAllTodo(){
                this.todos = this.todos.filter(todo => !todo.done)
            }
        }
    }
</script>

<style>
    body {
    	background: #fff;
    }

    .btn {
        display: inline-block;
        padding: 4px 12px;
        margin-bottom: 0;
        font-size: 14px;
        line-height: 20px;
        text-align: center;
        vertical-align: middle;
        cursor: pointer;
        box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
        border-radius: 4px;
    }

    .btn-danger {
        color: #fff;
        background-color: #da4f49;
        border: 1px solid #bd362f;
    }

    .btn-danger:hover {
        color: #fff;
        background-color: #bd362f;
    }

    .btn:focus {
    	outline: none;
    }

    .todo-container {
        width: 600px;
        margin: 0 auto;
    }
    .todo-container .todo-wrap {
        padding: 10px;
        border: 1px solid #ddd;
        border-radius: 5px;
    }
</style>

10.使用本地存储优化Todo-List:

使用监视(watch)

<template>
    <div id="root">
        <div class="todo-container">
            <div class="todo-wrap">
            <MyHeader :addTodo="addTodo"/>
            <MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
            <MyFooter :todos="todos" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"/>
            </div>
        </div>
    </div>
</template>

<script>
    import MyHeader from './components/MyHeader.vue'
    import MyList from './components/MyList.vue'
    import MyFooter from './components/MyFooter.vue'

    export default {
        name:'App',
        components: { MyHeader,MyList,MyFooter },
        data() {
            return {
                //若localStorage中存有'todos'则从localStorage中取出,否则初始为空数组
                //注意点:这里要记得“|| []” 要不然Footer会出错
                todos:JSON.parse(localStorage.getItem('todos')) || []
            }
        },
        methods:{
            //添加一个todo
            addTodo(todoObj){
                this.todos.unshift(todoObj)
            },
            //勾选or取消勾选一个todo
            checkTodo(id){
                this.todos.forEach((todo)=>{
                    if(todo.id === id) todo.done = !todo.done
                })
            },
            //删除一个todo
            deleteTodo(id){
                this.todos = this.todos.filter(todo => todo.id !== id)
            },
            //全选or取消勾选
            checkAllTodo(done){
                this.todos.forEach(todo => todo.done = done)
            },
            //删除已完成的todo
            clearAllTodo(){
                this.todos = this.todos.filter(todo => !todo.done)
            }
        },
        watch:{
            todos:{
                //由于todos是对象数组,所以必须开启深度监视才能发现数组中对象的变化
                deep:true,
                handler(value){
                    localStorage.setItem('todos',JSON.stringify(value))
                }
            }
        }
    }
</script>

<style>
    body {
    	background: #fff;
    }

    .btn {
        display: inline-block;
        padding: 4px 12px;
        margin-bottom: 0;
        font-size: 14px;
        line-height: 20px;
        text-align: center;
        vertical-align: middle;
        cursor: pointer;
        box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
        border-radius: 4px;
    }

    .btn-danger {
        color: #fff;
        background-color: #da4f49;
        border: 1px solid #bd362f;
    }

    .btn-danger:hover {
        color: #fff;
        background-color: #bd362f;
    }

    .btn:focus {
    	outline: none;
    }

    .todo-container {
        width: 600px;
        margin: 0 auto;
    }
    .todo-container .todo-wrap {
        padding: 10px;
        border: 1px solid #ddd;
        border-radius: 5px;
    }
</style>

11.使用自定义事件优化TodoList

 

 src/App.vue:

 

<template>
    <div id="root">
        <div class="todo-container">
            <div class="todo-wrap">
            <MyHeader @addTodo="addTodo"/>
            <MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
            <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/>
            </div>
        </div>
    </div>
</template>

<script>
    import MyHeader from './components/MyHeader.vue'
    import MyList from './components/MyList.vue'
    import MyFooter from './components/MyFooter.vue'

    export default {
        name:'App',
        components: { MyHeader,MyList,MyFooter },
        data() {
            return {
                todos:JSON.parse(localStorage.getItem('todos')) || []
            }
        },
        methods:{
            //添加一个todo
            addTodo(todoObj){
                this.todos.unshift(todoObj)
            },
            //勾选or取消勾选一个todo
            checkTodo(id){
                this.todos.forEach((todo)=>{
                    if(todo.id === id) todo.done = !todo.done
                })
            },
            //删除一个todo
            deleteTodo(id){
                this.todos = this.todos.filter(todo => todo.id !== id)
            },
            //全选or取消勾选
            checkAllTodo(done){
                this.todos.forEach(todo => todo.done = done)
            },
            //删除已完成的todo
            clearAllTodo(){
                this.todos = this.todos.filter(todo => !todo.done)
            }
        },
        watch:{
            todos:{
                deep:true,
                handler(value){
                    localStorage.setItem('todos',JSON.stringify(value))
                }
            }
        }
    }
</script>

<style>
    body {
    	background: #fff;
    }

    .btn {
        display: inline-block;
        padding: 4px 12px;
        margin-bottom: 0;
        font-size: 14px;
        line-height: 20px;
        text-align: center;
        vertical-align: middle;
        cursor: pointer;
        box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
        border-radius: 4px;
    }

    .btn-danger {
        color: #fff;
        background-color: #da4f49;
        border: 1px solid #bd362f;
    }

    .btn-danger:hover {
        color: #fff;
        background-color: #bd362f;
    }

    .btn:focus {
   		outline: none;
    }

    .todo-container {
    	width: 600px;
    	margin: 0 auto;
    }
    .todo-container .todo-wrap {
        padding: 10px;
        border: 1px solid #ddd;
        border-radius: 5px;
    }
</style>

src/components/MyHeader.vue:

<template>
    <div class="todo-header">
        <input type="text" placeholder="请输入你的任务名称,按回车键确认" @keydown.enter="add" v-model="title"/>
    </div>
</template>

<script>
    import {nanoid} from 'nanoid'
    export default {
        name:'MyHeader',
        data() {
            return {
                title:''
            }
        },
        methods:{
            add(){
                if(!this.title.trim()) return
                const todoObj = {id:nanoid(),title:this.title,done:false}
                this.$emit('addTodo',todoObj)
                this.title = ''
            }
        }
    }
</script>

<style scoped>
    /*header*/
    .todo-header input {
        width: 560px;
        height: 28px;
        font-size: 14px;
        border: 1px solid #ccc;
        border-radius: 4px;
        padding: 4px 7px;
    }

    .todo-header input:focus {
        outline: none;
        border-color: rgba(82, 168, 236, 0.8);
        box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
    }
</style>

src/components/MyFooter:

<template>
    <div class="todo-footer" v-show="total">
        <label>
            <input type="checkbox" v-model="isAll"/>
        </label>
        <span>
            <span>已完成{{doneTotal}}</span> / 全部{{total}}
        </span>
        <button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
    </div>
</template>

<script>
    export default {
        name:'MyFooter',
        props:['todos'],
        computed:{
            doneTotal(){
                return this.todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0) ,0)
            },
            total(){
                return this.todos.length
            },
            isAll:{
                get(){
                    return this.total === this.doneTotal && this.total > 0
                },
                set(value){
                    this.$emit('checkAllTodo',value)
                }
            }
        },
        methods:{
            clearAll(){
                this.$emit('clearAllTodo')
            }
        }
    }
</script>

<style scoped>
    .todo-footer {
        height: 40px;
        line-height: 40px;
        padding-left: 6px;
        margin-top: 5px;
        }

    .todo-footer label {
        display: inline-block;
        margin-right: 20px;
        cursor: pointer;
    }

    .todo-footer label input {
        position: relative;
        top: -1px;
        vertical-align: middle;
        margin-right: 5px;
    }

    .todo-footer button {
        float: right;
        margin-top: 5px;
    }
</style>

12.使用全局事件总线优化

原本:在App中要接收Item传递的数据,所以要先App-->List--->Item

使用数据总线:App--->Item

src/mian.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
    }
})

 src/components/App.vue

<template>
    <div id="root">
        <div class="todo-container">
            <div class="todo-wrap">
            <MyHeader @addTodo="addTodo"/>
            <MyList :todos="todos"/>
            <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/>
            </div>
        </div>
    </div>
</template>

<script>
    import MyHeader from './components/MyHeader.vue'
    import MyList from './components/MyList.vue'
    import MyFooter from './components/MyFooter.vue'

    export default {
        name:'App',
        components: { MyHeader,MyList,MyFooter },
        data() {
            return {
                todos:JSON.parse(localStorage.getItem('todos')) || []
            }
        },
        methods:{
            //添加一个todo
            addTodo(todoObj){
                this.todos.unshift(todoObj)
            },
            //勾选or取消勾选一个todo
            checkTodo(id){
                this.todos.forEach((todo)=>{
                    if(todo.id === id) todo.done = !todo.done
                })
            },
            //删除一个todo
            deleteTodo(id){
                this.todos = this.todos.filter(todo => todo.id !== id)
            },
            //全选or取消勾选
            checkAllTodo(done){
                this.todos.forEach(todo => todo.done = done)
            },
            //删除已完成的todo
            clearAllTodo(){
                this.todos = this.todos.filter(todo => !todo.done)
            }
        },
        watch:{
            todos:{
                deep:true,
                handler(value){
                    localStorage.setItem('todos',JSON.stringify(value))
                }
            }
        },
        mounted(){
            this.$bus.$on('checkTodo',this.checkTodo)
            this.$bus.$on('deleteTodo',this.deleteTodo)
        },
        beforeDestroy(){
            this.$bus.$off(['checkTodo','deleteTodo'])
        }
    }
</script>

<style>
</style>

src/components/MyItem.vue:

<template>
    <li>
        <label>
            <input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/>
            <span>{{todo.title}}</span>
        </label>
        <button class="btn btn-danger" @click="handleDelete(todo.id,todo.title)">删除</button>
    </li>
</template>

<script>
    export default {
        name:'MyItem',
        props:['todo'],
        methods:{
            handleCheck(id){
                this.$bus.$emit('checkTodo',id)
            },
            handleDelete(id,title){
                if(confirm("确定删除任务:"+title+"吗?")){
                    this.$bus.$emit('deleteTodo',id)
                }
            }
        }
    }
</script>

<style scoped>
  
</style>

13.使用消息的订阅与发布优化

src/App.vue:

<template>
    <div id="root">
        <div class="todo-container">
            <div class="todo-wrap">
            <MyHeader @addTodo="addTodo"/>
            <MyList :todos="todos"/>
            <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/>
            </div>
        </div>
    </div>
</template>

<script>
    import pubsub from 'pubsub-js'
    import MyHeader from './components/MyHeader.vue'
    import MyList from './components/MyList.vue'
    import MyFooter from './components/MyFooter.vue'


    export default {
        name:'App',
        components: { MyHeader,MyList,MyFooter },
        data() {
            return {
                todos:JSON.parse(localStorage.getItem('todos')) || []
            }
        },
        methods:{
            //添加一个todo
            addTodo(todoObj){
                this.todos.unshift(todoObj)
            },
            //勾选or取消勾选一个todo
            checkTodo(_,id){
                this.todos.forEach((todo)=>{
                    if(todo.id === id) todo.done = !todo.done
                })
            },
            //删除一个todo
            deleteTodo(id){
                this.todos = this.todos.filter(todo => todo.id !== id)
            },
            //全选or取消勾选
            checkAllTodo(done){
                this.todos.forEach(todo => todo.done = done)
            },
            //删除已完成的todo
            clearAllTodo(){
                this.todos = this.todos.filter(todo => !todo.done)
            }
        },
        watch:{
            todos:{
                deep:true,
                handler(value){
                    localStorage.setItem('todos',JSON.stringify(value))
                }
            }
        },
        mounted(){
            this.pubId = pubsub.subscribe('checkTodo',this.checkTodo)
            this.$bus.$on('deleteTodo',this.deleteTodo)
        },
        beforeDestroy(){
            pubsub.unsubscribe(this.pubId)
            this.$bus.$off('deleteTodo')
        }
    }
</script>

<style>
    body {
        background: #fff;
    }

    .btn {
        display: inline-block;
        padding: 4px 12px;
        margin-bottom: 0;
        font-size: 14px;
        line-height: 20px;
        text-align: center;
        vertical-align: middle;
        cursor: pointer;
        box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
        border-radius: 4px;
    }

    .btn-danger {
        color: #fff;
        background-color: #da4f49;
        border: 1px solid #bd362f;
    }

    .btn-danger:hover {
        color: #fff;
        background-color: #bd362f;
    }

    .btn:focus {
        outline: none;
    }

    .todo-container {
        width: 600px;
        margin: 0 auto;
    }
    .todo-container .todo-wrap {
        padding: 10px;
        border: 1px solid #ddd;
        border-radius: 5px;
    }
</style>

src/components/myItem.vue:

<template>
    <li>
        <label>
            <input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/>
            <span>{{todo.title}}</span>
        </label>
        <button class="btn btn-danger" @click="handleDelete(todo.id,todo.title)">删除</button>
    </li>
</template>

<script>
    import pubsub from 'pubsub-js'
    export default {
        name:'MyItem',
        props:['todo'],
        methods:{
            handleCheck(id){                    
                pubsub.publish('checkTodo',id)
            },
            handleDelete(id,title){
                if(confirm("确定删除任务:"+title+"吗?")){
                    this.$bus.$emit('deleteTodo',id)
                }
            }
        }
    }
</script>

<style scoped>
    li {
        list-style: none;
        height: 36px;
        line-height: 36px;
        padding: 0 5px;
        border-bottom: 1px solid #ddd;
    }

    li label {
        float: left;
        cursor: pointer;
    }

    li label li input {
        vertical-align: middle;
        margin-right: 6px;
        position: relative;
        top: -1px;
    }

    li button {
        float: right;
        display: none;
        margin-top: 3px;
    }

    li:before {
        content: initial;
    }

    li:last-child {
        border-bottom: none;
    }

    li:hover {
        background-color: #eee;
    }

    li:hover button{
        display: block;
    }
</style>

WebStorage

1.本地存储:localStorage【浏览器关闭不会消失】

设置:localStorage.setItem('key‘,'value’)

读取:localStorage.getItem('key','value')

删除:localStorage.removeItem('key','value')

清空:localStroage.clear()

<!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>localStorage</title>
</head>
<body>
    <h2>localStorage</h2>
    <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:是window上的
            localStorage.setItem('msg','localStorage')
            //JSON.stringify:将对象转换为字符串
            localStorage.setItem('person',JSON.stringify(person))
        }
        function readDate(){
            console.log(localStorage.getItem('msg'))
            //JSON.parse() 方法将数据转换为 JavaScript 对象。
            const person = localStorage.getItem('person')
            console.log(JSON.parse(person))
            console.log(localStorage.getItem('msg3')//读取没有的返回的是null
        }
        function deleteDate(){
            localStorage.removeItem('msg')
            localStorage.removeItem('person')
        }
        function deleteAllDate(){
            localStorage.clear()
        }
    </script>
</body>
</html>

2.会话存储:sessionStorage【浏览器关闭,数据消失】

设置:sessionStorage.setItem('key‘,'value’)

读取:sessionStorage.getItem('key','value')

删除:sessionStorage.removeItem('key','value')

清空:sessionStorage.clear()

<!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>sessionStorage</title>
</head>
<body>
    <h2>sessionStorage</h2>
    <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>
  1. 存储内容大小一般支持5MB左右(不同浏览器可能还不一样)
  2. 浏览器端通过Window.sessionStorage和Window.localStorage属性来实现本地存储机制
  3. 相关API:
    1. xxxStorage.setItem('key', 'value'):该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值
    2. xxxStorage.getItem('key'):该方法接受一个键名作为参数,返回键名对应的值
    3. xxxStorage.removeItem('key'):该方法接受一个键名作为参数,并把该键名从存储中删除
    4. xxxStorage.clear():该方法会清空存储中的所有数据
  4. 备注:
    1. SessionStorage存储的内容会随着浏览器窗口关闭而消失
    2. LocalStorage存储的内容,需要手动清除才会消失
    3. xxxStorage.getItem(xxx)如果 xxx 对应的 value 获取不到,那么getItem()的返回值是null
    4. JSON.parse(null)的结果依然是null

自定义事件

1.绑定

 

 方法一:通过父组件给子组件传递函数类型的props实现:子给父传递数据

App.vue

		<!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
		<School :getSchoolName="getSchoolName"/>

		methods: {
			getSchoolName(name){
				console.log('App收到了学校名:',name)
			},
        }


School.vue
<template>
	<div class="school">
		<h2>学校名称:{{name}}</h2>
		<h2>学校地址:{{address}}</h2>
		<button @click="sendSchoolName">把学校名给App</button>
	</div>
</template>

<script>
	export default {
		name:'School',
		props:['getSchoolName'],
		data() {
			return {
				name:'尚硅谷',
				address:'北京',
			}
		},
		methods: {
			sendSchoolName(){
				this.getSchoolName(this.name)
			}
		},
	}
</script>

方法二:过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用@或v-on)

在父组件中自定义一个事件,然后在子组件中使用$emit设置这个自定义事件及其值

App.vue
		<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用@或v-on) -->
		<Student @atguigu="getStudentName" @demo="m1"/>

		methods: {
			getStudentName(name,...params){//...params:相当一个数组
				console.log('App收到了学生名:',name,params)
				this.studentName = name
			},
        }


Student.vue


		<button @click="sendStudentlName">把学生名给App</button>
			sendStudentlName(){
				//触发Student组件实例身上的atguigu事件
				this.$emit('atguigu',this.name,666,888,900)
				// this.$emit('demo')
				// this.$emit('click')
			},

方法三:通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref)

在父组件中使用ref和在钩子函数(mounted函数)中使用$on设置。在子组件中使用$emit

App.vue
        <!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref) -->
		<Student ref="student"/>

		mounted() {
			//this.$refs.student:Student的实例对象vc
			this.$refs.student.$on('atguigu',this.getStudentName) //绑定自定义事件
			// this.$refs.student.$once('atguigu',this.getStudentName) //绑定自定义事件(一次性)
		},

Student.vue
			sendStudentlName(){
				//触发Student组件实例身上的atguigu事件
				this.$emit('atguigu',this.name,666,888,900)
				// this.$emit('demo')
				// this.$emit('click')
			},

2.解绑

$off:解绑事件

        this.$off('atguigu') //解绑一个自定义事件

        this.$off(['atguigu','demo']) //解绑多个自定义事件

        this.$off() //解绑所有的自定义事件

Student.vue

		<button @click="unbind">解绑atguigu事件</button>
			unbind(){
				this.$off('atguigu') //解绑一个自定义事件
				this.$off(['atguigu','demo']) //解绑多个自定义事件
				this.$off() //解绑所有的自定义事件
			},

$destory:解绑组件


		<button @click="death">销毁当前Student组件的实例(vc)</button>

			death(){
				this.$destroy() //销毁了当前Student组件的实例,销毁后所有Student实例的自定义事件全都不奏效。
			}
  1. 一种组件间通信的方式,适用于:==子组件 > 父组件
  2. 使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中的methods)
  3. 绑定自定义事件:
    1. 第一种方式,在父组件中:<Demo @atguigu="test"/> 或 <Demo v-on:atguigu="test"/>
    2. 第二种方式,在父组件中:
    3. <Demo ref="demo"/>
      ...
      mounted(){
          this.$refs.demo.$on('atguigu',data)
      }
      
    4. 若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法
  4. 触发自定义事件:this.$emit('atguigu',数据)
  5. 解绑自定义事件:this.$off('atguigu')
  6. 组件上也可以绑定原生DOM事件,需要使用native修饰符

注意:通过this.$refs.xxx.$on('atguigu',回调)绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!

全局事件总线【Vue.prototype.$bus=this】

全局事件总线是一种可以在任意组件间通信的方式,本质上就是一个对象。它必须满足以下条件:1. 所有的组件对象都必须能看见他 2. 这个对象必须能够使用$on$emit$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 //安装全局事件总线
	}
})

src/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>

 src/components/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>

src/components/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>
  1. 一种组件间通信的方式,适用于任意组件间通信

  2. 安装全局事件总线:

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

使用事件总线:

  1. 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身

export default {
    methods(){
        demo(data){...}
    }
    ...
    mounted() {
        this.$bus.$on('xxx',this.demo)
    }
}

         2.提供数据:this.$bus.$emit('xxx',data)

最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件

消息的订阅与发布【pubsub.js库】

 1.安装

npm i pubsub-js

2.引入库

哪一个组件是消息的订阅者,就在哪一个组件中引入

 ​​​​​

src/components/School.vue:

<template>
	<div class="school">
		<h2>学校名称:{{name}}</h2>
		<h2>学校地址:{{address}}</h2>
	</div>
</template>

<script>
	import pubsub from 'pubsub-js'
	export default {
		name:'School',
		data() {
			return {
				name:'尚硅谷',
				address:'北京',
			}
		},
		mounted() {
			// console.log('School',this)
			/* this.$bus.$on('hello',(data)=>{
				console.log('我是School组件,收到了数据',data)
			}) */
			// 订阅消息
			this.pubId = pubsub.subscribe('hello',(msgName,data)=>{//如果写成function,则this===undefined
				console.log(this)//vc
				// console.log('有人发布了hello消息,hello消息的回调执行了',msgName,data)
			})
		},
		beforeDestroy() {
			// this.$bus.$off('hello')
			//取消订阅:必须要通过id进行取消
			pubsub.unsubscribe(this.pubId)
		},
	}
</script>

<style scoped>
	.school{
		background-color: skyblue;
		padding: 5px;
	}
</style>

 src/components/Student.vue:

<template>
	<div class="student">
		<h2>学生姓名:{{name}}</h2>
		<h2>学生性别:{{sex}}</h2>
		<button @click="sendStudentName">把学生名给School组件</button>
	</div>
</template>

<script>
	import pubsub from 'pubsub-js'
	export default {
		name:'Student',
		data() {
			return {
				name:'张三',
				sex:'男',
			}
		},
		mounted() {
			// console.log('Student',this.x)
		},
		methods: {
			sendStudentName(){
				// this.$bus.$emit('hello',this.name)
				// 发布消息
				pubsub.publish('hello',666)
			}
		},
	}
</script>

<style lang="less" scoped>
	.student{
		background-color: pink;
		padding: 5px;
		margin-top: 30px;
	}
</style>

 $nextTick

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值