【尚硅谷】chapter3 Vue脚手架_尚硅谷Vue2.0+Vue3.0全套教程丨vuejs从入门到精通

视频地址:【尚硅谷Vue2.0+Vue3.0全套教程丨vuejs从入门到精通】 https://www.bilibili.com/video/BV1Zy4y1K7SH/?p=13&share_source=copy_web&vd_source=b1cb921b73fe3808550eaf2224d1c155

目录

3 Vue脚手架

3.1 初始化脚手架

3.1.1 说明

3.1.2 具体步骤

3.1.3 模板项目的结构

3.1.3.1 main.js

3.1.3.2 App.vue

3.1.3.3 index.html

3.1.3.4 render配置项

3.1.3.5 修改默认配置项

1 查看项目的默认配置

2 修改项目的默认配置

 2.1 修改入口文件名

2.2 关闭代码语法检查

3.2 ref 与 props

3.2.1 ref属性

3.2.1.1 使用id获取元素或子组件

3.2.1.2 ref属性的使用方式

1 标识元素或子组件

2 获取标识的元素或子组件

3.2.1.3 使用ref标记html标签元素

3.2.1.4 使用 ref 属性标记子组件

3.2.2 props配置项

3.2.2.1 props配置项的使用

3.2.2.2 对age进行运算

1 简单接收 最常用

2 声明接收

3 复杂接收

3.2.2.3 总结

3.3 mixin混入

3.3.1 mixin混入

3.3.2 未使用mixin

3.3.3 mixin的定义与使用

3.3.4 mixin中的配置项与组件的配置项冲突

3.3.4.1 普通配置项

3.3.4.2 生命周期钩子

3.3.5 总结

3.4 插件 

3.4.1 插件

3.4.2 插件的定义与使用

3.4.3 总结

 3.5 scoped样式

3.5.1 样式冲突问题

3.5.2 样式冲突解决

3.5.3 解决的原理

3.5.4 scoped的注意事项

3.5.5 总结

3.6 Todo-list案例 

3.6.1 组件化编码流程(通用)

3.6.2 页面组件划分

3.6.3 静态组件

3.6.4 展示动态数据

3.6.4.1 初始化列表

3.6.4.2 添加

3.6.4.3 勾选

3.6.4.4 删除

3.6.4.5 底部统计

3.6.4.5.1 全部的数量

3.6.4.5.2 已完成的数量

3.6.4.6 底部交互

3.6.4.6.1 多选框状态

3.6.4.6.2 清空数据

3.6.5 总结

3.6.6 todo-List案例-本地存储版

3.7 浏览器的本地存储

3.7.1 本地存储的保存

3.7.2 本地存储的读取

3.7.3 本地存储的删除

3.7.4 本地存储的清空

3.7.5 sessionStorage

3.8 Vue中的自定义事件

3.8.1 自定义属性实现子组件向父组件传递数据

3.8.2 自定义事件实现子组件向父组件传递数据

3.8.3 使用ref绑定自定义事件

3.8.4 解绑自定义事件

1 解绑一个自定义事件

2 解绑多个自定义事件

3 解绑所有自定义事件

3.8.5 父组件显示子组件传来的数据

1 使用v-on绑定自定义事件

2 使用ref绑定自定义事件

3.8.6 总结

3.9 全局事件总线

3.10 消息订阅与发布

3.11 过渡与动画


3 Vue脚手架

3.1 初始化脚手架

3.1.1 说明

目前脚手架是4版本,脚手架平台用最新版本。目前Vue2还是主流。

Vue CLI,即Vue脚手架,command line interface,即命令行接口工具。

3.1.2 具体步骤

  • 第一步:全局安装@vue/cli
    • npm install -g @vue/cli
  • 第二步:切换到要创建项目的目录,使用命令创建项目
    • vue create xxx(项目名称)
    • 之后出现配置
    • 选择default就行,然后回车,等着就行啦
  • 第三步:启动项目
    • npm run serve

 打开网址http://localhost:8080/,下面就是vue准备的hello world组件。

 关闭项目,ctrl+C

1. 如出现下载缓慢请配置 npm 淘宝镜像:npm config set registry https://registry.npm.taobao.org

3.1.3 模板项目的结构

(在块引用里没有别的意思,就想让他们紧凑些)

├── node_modules

├── public

│ ├── favicon.ico: 页签图标

│ └── index.html: 主页面

├── src

│ ├── assets: 存放静态资源(比如图片、视频等文件)

│ │ └── logo.png

│ │── components: 存放组件(所有程序员写出的组件都存放在这里,除了App.vue之外)

│ │ └── HelloWorld.vue

│ │── App.vue: 汇总所有组件

│ │── main.js: 入口文件

├── .gitignore: git 版本管制忽略的配置(记录不想用git管理的文件)

├── babel.config.js: babel 的配置文件,es6转es5

├── package-lock.json:包版本控制文件

├── package.json: 应用包配置文件

├── README.md: 应用描述文件

当执行完npm run serve后(在vscode里新建终端,选择当前项目的文件夹,然后输入命令npm run serve),马上执行main.js.

备注

脚手架的高级功能:修改代码后ctrl+s,程序会重新编译,页面会自动刷新

3.1.3.1 main.js

分析main.js,仔细看看下面代码里的注释。

*/
// 引入vue 
// 之前是在html文件里通过script标签引入外部js
// 现在es6引入语法引入
// 脚手架已经安装完了vue,在node_modules里

import Vue from 'vue'
// 引入APP组件,是所有组件的父组件。
import App from './App.vue'
// 关闭vue的生产提示
Vue.config.productionTip = false
// 创建vue实例对象--vm
new Vue({
  el: '#app',
  // 稍后分解
  // 功能:将APP组件放入容器中,
  render: h => h(App),
})

3.1.3.2 App.vue

然后分析与main.js同级的APP.vue。

在单文件组件里已经写过App.vue,以及School.vue和student.vue了,因此App.vue和components没有什么好讲的。

3.1.3.3 index.html

到此为止,main.js里的id为app的容器还没有看到,在public的index.html里。

├── public

│ ├── favicon.ico: 页签图标

│ └── index.html: 主页面

3.1.3.4 render配置项

main.js中的render配置项。

在main.js中使用render()函数将App组件放入容器中,是由于在脚手架中默认引入的vue不是完整的vue,而是缺少模板解析器的vue。(当引入残缺版Vue还想配置内容,那么就得借助render())

vue.js与vue.runtime.xxx.js的区别:
(1).vue.js是完整版的Vue,包含:核心功能+模板解析器。
(2).vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。

模板解析器是用于解析vue配置项中的template配置项。将vue中的模板解析器去除后,可以节省项目的占用空间(模版解析器占了Vue.js1/3的体积),同时也更加符合逻辑,因为项目构建后已经是浏览器可以解析的html、css、js等文件,不再需要模板解析器.

render()的完整代码

//创建Vue实例对象---vm
new Vue({
  // 将App组件放入容器中
  // 简写,由于此函数不需要使用this,可以写成箭头函数
  // 然后再对箭头函数进行简写
  // render: h => h(App),
  
  // 完整写法
  render(createElement) {
    // render函数接收一个创建页面元素的createElement函数,
    // 用于创建页面元素
    // render()函数将创建的元素返回,再将其放入容器中
    return createElement(App)
  }
// 指定vue控制的容器
}).$mount('#app')

其他组件

  render(createElement) {
    // 创建返回 <h1>hello world</h1>
    return createElement('h1', 'hello world')
  }

缩写形式

render: h => h(App)

App组件外的其他组件使用模板<template>标签能够被放入页面,是由于vue脚手架为其配置了模板编译的第三方包。vm里(main.js)的template就不能被第三方包解析。

  • 总结
    •  vue.js与vue.runtime.xxx.js的区别:
      (1).vue.js是完整版的Vue,包含:核心功能+模板解析器。
      (2).vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。
    • 因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElement函数去指定具体内容。
3.1.3.5 修改默认配置项
1 查看项目的默认配置

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

vue inspect > output.js

在当前项目文件夹下运行此命令。

将vue脚手架默认配置全都整理成一个js文件。

  

  

可以看到整个app应用的入口就是在src下的main.js。

output.js只是整理所有的配置项给你展示出来,不能用于修改配置项。

2 修改项目的默认配置

可以修改的配置项以及如何修改可以参考官网:
Vue CLI 官网 配置参考

p64跳过

  

 2.1 修改入口文件名
2.2 关闭代码语法检查

3.2 ref 与 props

从头开始写一个Vue项目

main.js

// 引入Vue
import Vue from 'vue'
// 引入App
import App from './App.vue'
// 关闭Vue生产提示
Vue.config.productionTip = false
// 创建vm
new Vue({
    el: '#app',
    render: h => h(App)
})

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>
.demo {
    font-size: 10px
}

</style>

App.vue

<template>
    <div>
        <!-- 自闭合,脚手架可用 -->
        <School/>  
        <School/>  
        <School/> 
    </div>
</template>

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

<style>

</style>

启动,命令 npm run serve

运行结果

  

3.2.1 ref属性

(之前学过标签属性key。ref也是一种标签属性。)

3.2.1.1 使用id获取元素或子组件

使用id获取标签或子组件的DOM元素。通过给标签加id属性,然后通过document.getElementById('id名')获取元素。

<template>
  <div>
    <h1 v-text="msg" id="title"></h1>
    <button @click="showDOM">点我输出上方的DOM元素</button>
  </div>
</template>

<script>
import School from "./components/School.vue";
export default {
  name: "App",
  components: { School },
  data() {
    return {
      msg: "欢迎学习Vue",
    };
  },
  methods: {
    showDOM() {
      console.log(document.getElementById("title"));
    },
  },
};
</script>

<style>
</style>

运行结果

3.2.1.2 ref属性的使用方式

ref属性,可以理解为id的替代者。

1 标识元素或子组件
  • 标记 html 标签元素
    • <h1 ref="xxx">.....</h1>
  • 标记子组件
    • <School ref="xxx"></School>

2 获取标识的元素或子组件
this.$refs.xxx

其中,this为被标记的元素或子组件所在的组件实例对象。

3.2.1.3 使用ref标记html标签元素

ref 属性应用在 html 标签元素上,获取的是对应的真实 DOM 元素。

<template>
  <div>
    <h1 v-text="msg" ref="title"></h1>
    <button @click="showDOM">点我输出上方的DOM元素</button>
  </div>
</template>

<script>
import School from "./components/School.vue";
export default {
  name: "App",
  components: { School },
  data() {
    return {
      msg: "欢迎学习Vue",
    };
  },
  methods: {
    showDOM() {
      console.log(this.$refs.title);
    },
  },
};
</script>

<style>
</style>

运行结果

3.2.1.4 使用 ref 属性标记子组件

ref 属性应用在组件标签上,获取的是对应组件实例对象

School组件

<template>
  <div>
    <h1 v-text="msg" ref="title"></h1>
    <button @click="showDOM">点我输出上方的DOM元素</button>
    <School ref="myschool"> </School>
  </div>
</template>

<script>
import School from "./components/School.vue";
export default {
  name: "App",
  components: { School },
  data() {
    return {
      msg: "欢迎学习Vue",
    };
  },
  methods: {
    showDOM() {
      console.log(this.$refs.title);
      console.log(this.$refs.myschool);
    },
  },
};
</script>

<style>
</style>

运行结果

使用 ref 属性和使用 id 进行对比,使用 ref 属性不用自己操作 DOM 元素,且使用 ref 属性获取子组件时,获取的为整个组件实例对象而不是子组件编译解析后的模板,有利于后期对子组件进行操作。

3.2.2 props配置项

关于配置项,之前就有el、data、methods、watch、computed...

案例

Student.vue

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

<script>
export default {
  name: "Student",
  data() {
    return {
      msg: "我是尚硅谷的学生",
      name: "张三",
      gender: "男",
      age: 18,
    };
  },
};
</script>

App.vue

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

<script>
import Student from "./components/Student.vue";
export default {
  name: "App",
  components: { Student },
  methods: {
    showDOM() {},
  },
};
</script>

<style>
</style>

main.js不用改。

运行结果

3.2.2.1 props配置项的使用
  • 李四也想通过这个形式展示自己的学生信息。
    • 可以复用吗
    • 可以,通过props配置项完成。
  • prop,属性,一堆属性就是props。

完整代码实现

Student.vue

前面跟上面都一样,只是原来data里的name gender 和 age都删掉,props里加上name gender 和 age。

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

<script>
export default {
  name: "Student",
  data() {
    return {
      msg: "我是尚硅谷的学生",
    };
  },
  props: ['name', 'gender', 'age']
};
</script>

App.vue

然后在App.vue里,使用Student组件的标签里加上三个属性的具体取值

<template>
  <div>
    <Student name="李四" gender="女" age="18"> age </Student>
    <hr />
  </div>
</template>

<script>
import Student from "./components/Student.vue";
export default {
  name: "App",
  components: { Student },
  methods: {
    showDOM() {},
  },
};
</script>

<style>
</style>

运行结果

3.2.2.2 对age进行运算
1 简单接收 最常用

将传入的age属性改为 :age="18"(App.vue 付款方),然后使用的时候是{{ age + 1}} (Student.vue 收款方)

因为单纯的 age=“18”,模板拿到会将其作为字符串去处理。不会作为表达式运算。但是加上 冒号:就可以了,其实就是v-bind:age="18",也就是数据绑定了。

Student.vue

<template>
  <div>
    <h1>{{ msg }}</h1>
    <h2>学生名称:{{ name }}</h2>
    <h2>学生性别:{{ gender }}</h2>
    <h2>学生年龄:{{ age + 1 }}</h2>
  </div>
</template>

<script>
export default {
  name: "Student",
  data() {
    return {
      msg: "我是尚硅谷的学生",
    };
  },
  props: ['name', 'gender', 'age']
};
</script>

App.vue

<template>
  <div>
    <Student name="李四" gender="女" :age="18"> age </Student>
    <hr />
  </div>
</template>

<script>
import Student from "./components/Student.vue";
export default {
  name: "App",
  components: { Student },
  methods: {
    showDOM() {},
  },
};
</script>

<style>
</style>

2 声明接收

声明接收——接收的同时对数据进行类型限制。

  props: {
    name:String,
    gender:String,
    age:Number
  }

完整Student.vue

<template>
  <div>
    <h1>{{ msg }}</h1>
    <h2>学生名称:{{ name }}</h2>
    <h2>学生性别:{{ gender }}</h2>
    <h2>学生年龄:{{ age+1 }}</h2>
  </div>
</template>

<script>
export default {
  name: "Student",
  data() {
    return {
      msg: "我是尚硅谷的学生",
    };
  },
  // props: ['name', 'gender', 'age']
  props: {
    name:String,
    gender:String,
    age:Number
  }
};
</script>

3 复杂接收

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

不按要求来就会报错,页面不显示。

default和required不会同时出现

  props: {

    name : {

      type:String,  //name的类型是字符串

      required: true //名字是必要的

    },

    age : {

      type:Number,  //name的类型是字符串

      default: 99   //不传的话age就是99,默认值是99

    },

    gender: {

      type: Stirng,

      required: false

    }

  }

完整代码

Student.vue

<template>
  <div>
    <h1>{{ msg }}</h1>
    <h2>学生名称:{{ name }}</h2>
    <h2>学生性别:{{ gender }}</h2>
    <h2>学生年龄:{{ age+1 }}</h2>
  </div>
</template>

<script>
export default {
  name: "Student",
  data() {
    return {
      msg: "我是尚硅谷的学生",
    };
  },
  // props: ['name', 'gender', 'age']
  // props: {
  //   name:String,
  //   gender:String,
  //   age:Number
  // }
  props: {
    name : {
      type:String,  //name的类型是字符串
      required: true //名字是必要的
    },
    age : {
      type:Number,  //name的类型是字符串
      default: 99   //不传的话age就是99,默认值是99
    },
    gender: {
      type: Stirng,
      required: false
    }
  }
};
</script>

总结,简单接收最常用。

  • 细节
    • 1 声明的时候不要瞎声明
      • 没有传的属性值不要在props里出现。
    • 2 接收到的props不允许修改
      • 外部传入的数据不允许改,内部数据可以改
      • 能有效果,但是不建议改,不然vue会出现问题
      • 如果业务需求要改
        • 这里props是优先被接收的,优先被放到vc上,然后给myName赋值
        • 这里漏写了实现的方法,不难,methods里写个函数就可以了
        • 实现
    • 3 props里的prop名字不能与vue、js等的关键词重名
      • 不能是key,ref等
3.2.2.3 总结

3.3 mixin混入

混入就是复用配置

3.3.1 mixin混入

  • mixin配置项,叫混入,一般也叫混合。
  • mixin (混入)可以把多个组件共用的配置提取成一个混入对象,实现对组件配置项的复用。

3.3.2 未使用mixin

需求,当点击学生姓名的时候,弹窗展示这个人的名字

School.vue

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

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

  }
  
};
</script>

Student.vue

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

<script>
export default {
  name: "Student",
  data() {
    return {
      name: "张三",
      gender: '男',
    };
  },
  methods: {
    showName() {
      alert(this.name)
    }

  }
  
};
</script>

App.vue

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

    <hr />
  </div>
</template>

<script>
import Student from "./components/Student.vue";
import School from "./components/School.vue";

export default {
  name: "App",
  components: { Student, School },
  methods: {
    showDOM() {},
  },
};
</script>

<style>
</style>

结果

问题发现:School和Student组件有同样的method等一样的东西。

3.3.3 mixin的定义与使用

在src文件夹下新建一个mixin.js文件(命名没有要求),

步骤1:将组件里相同的配置项删除,复制在mixin.js里。

mixin.js完整代码

export const mixin = {  //分别暴露
    methods: {
        showName() {
            alert(this.name)
        }
    }
}

步骤2:School组件引入混合mixin

// 引入一个混合
import {mixin} from '../mixin'

之前用了分别暴露,所以这里import后面有{}

步骤3:使用mixin,在vc中使用mixin,与data,name的位置一样。混合必须写在数组里。

mixin: [mixin]

步骤2和3的完整代码

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: "北京",
    };
  },
  mixin: [mixin]
  
};
</script>

Student.vue

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

<script>
import {mixin} from '../mixin'
export default {
  name: "Student",
  data() {
    return {
      name: "张三",
      gender: '男',
    };
  },
  mixin: [mixin]
};
</script>

运行结果

在mixin混入中也可以编写组件的其他配置项。mounted()例子,还有data例子

mixin.js

export const mixin = {
  methods: {
    showName() {
      alert(this.name)
    }
  }
}

export const mixin2 = {
  data() {
    return {
      x: 100,
      y: 200
    }
  },
}

School.vue

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

<script>
// 导入mixin
import {mixin, mixin2} from '../mixin'

export default {
  name: 'School',
  data() {
    return {
      name: 'SGG',
      address: 'Beijing'
    }
  },
  mixins: [mixin, mixin2]
}
</script>

<style>
</style>

3.3.4 mixin中的配置项与组件的配置项冲突

3.3.4.1 普通配置项

当mixin中的普通配置项与组件的普通配置项发生冲突时,优先使用组件中自己的配置项。

mixin配置见上节。

School.vue

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

<script>
// 导入mixin
import {mixin, mixin2} from '../mixin'

export default {
  name: 'School',
  data() {
    return {
      name: 'SGG',
      address: 'Beijing',
      x: 666
    }
  },
  mixins: [mixin, mixin2]
}
</script>

<style>
</style>

3.3.4.2 生命周期钩子

当mixin中的生命周期函数与组件中的周期函数发生冲突时,会先执行mixin中的生命周期函数,后执行组件自己的生命周期函数。

mixin.js

export const mixin = {
  methods: {
    showName() {
      alert(this.name)
    }
  }
}

export const mixin2 = {
  data() {
    return {
      x: 100,
      y: 200
    }
  },
}

export const mixin3 = {
  mounted() {
    console.log('mixin3 mounted')
  }

School.vue

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

<script>
// 导入mixin
import {mixin, mixin2, mixin3} from '../mixin'

export default {
  name: 'School',
  data() {
    return {
      name: 'SGG',
      address: 'Beijing',
      x: 666
    }
  },
  mixins: [mixin, mixin2, mixin3],
  mounted() {
    console.log('School mounted')
  }
}
</script>

<style>
</style>

运行结果

3.3.5 总结

  • 功能:可以把多个组件共用的配置提取成一个混入对象
  • 使用方式:
    • 第一步定义混合:
      {
          data(){....},
          methods:{....}
          ....
      }

    • 第二步使用混入:

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

  •  局部混入:mixins:['xxx']

3.4 插件 

插件,可以增强vue。

3.4.1 插件

Vue中自定义的插件,插件就是包含install方法的一个对象,install的第一个参数是Vue(),第二个以后的参数是插件使用者传递的数据,插件对象中的install方法会被vue自动调用。

使用插件能够增强vue的功能。

3.4.2 插件的定义与使用

步骤1:在src下创建一个plugin.js。往install方法里放入 过滤器、自定义全局指令、混入、往原型上添加方法。

plugin.js

export default {
	// 使用插件时,vue会自动将Vue()[vue实例对象的构造函数]传入
	// x,y,z 为其他自己传入的参数
	install(Vue,x,y,z){
		console.log(x,y,z)
		//全局过滤器
		Vue.filter('mySlice',function(value){
			return value.slice(0,4)
		})

		//定义全局指令
		Vue.directive('fbind',{
			//指令与元素成功绑定时(一上来)
			bind(element,binding){
				element.value = binding.value
			},
			//指令所在元素被插入页面时
			inserted(element,binding){
				element.focus()
			},
			//指令所在的模板被重新解析时
			update(element,binding){
				element.value = binding.value
			}
		})

		//定义混入
		Vue.mixin({
			data() {
				return {
					x:100,
					y:200
				}
			},
		})

		//给Vue原型上添加一个方法(vm和vc就都能用了)
		Vue.prototype.demo= ()=>{alert('你好啊')}
	}
}

步骤2:引入插件

使用插件时,先导入对应的插件,使用Vue.use()方法使用对应的插件。

这里只需要在main.js里导入和引入插件,不需要在后面的组件里引入混入等等。

main.js

//引入Vue
import Vue from 'vue'
//引入App组件,它是所有组件的父组件
import App from './App.vue'
// 导入插件
import plugins from './plugins.js'

//关闭vue的生产提示
Vue.config.productionTip = false

// 使用插件
Vue.use(plugins)

//创建Vue实例对象---vm
new Vue({
  // 将App组件放入容器中
  render: h => h(App),
// 指定vue控制的容器
}).$mount('#app')

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>

<style>
</style>

Student.vue (没有变化)

<template>
	<div>
		<h2>学生姓名:{{name}}</h2>
		<h2>学生性别:{{sex}}</h2>
		<input type="text" v-fbind:value="name">
	</div>
</template>

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

运行结果

3.4.3 总结

 3.5 scoped样式

最开始的案例,跟前面每节的代码其实差不多。

3.5.1 样式冲突问题

给School组件和Student写样式,class类名一样,都是demo,背景色一个是skyblue,一个是orange,会出现样式冲突,结果是什么呢。

School.vue

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

<script>
export default {
  name: "School",
  data() {
    return {
      name: "尚硅谷",
      address: "北京",
    };
  }
};
</script>
<style>
.demo {
  background-color: skyblue;
}
</style>

Student.vue

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

<script>
export default {
  name: "Student",
  data() {
    return {
      name: "张三",
      gender: "男",
    };
  }
};
</script>
<style>
.demo {
  background-color: orange;
}
</style>

结果

结果以student组件的样式为准。为什么呢。

原因:

APP.vue

import Student from "./components/School.vue";
import School from "./components/Student.vue";

因为App这里的引入的顺序的问题,Student将School里的样式覆盖掉了。

3.5.2 样式冲突解决

给style标签后加一个scoped(scope,作用域,范围,scoped,局部的),意味着style里写的全部样式只负责同一个vue组件template标签里的结构。

School.vue

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

<script>
export default {
  name: "School",
  data() {
    return {
      name: "尚硅谷",
      address: "北京",
    };
  }
};
</script>
<style scoped>
.demo {
  background-color: skyblue;
}
</style>

Student.vue

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

<script>
export default {
  name: "Student",
  data() {
    return {
      name: "张三",
      gender: "男",
    };
  }
};
</script>
<style scoped>
.demo {
  background-color: orange;
}
</style>

结果

3.5.3 解决的原理

scoped给最外侧的div加一个特殊的标签属性,而且data-v-xxx的xxx是随机生成的。然后通过demo配合标签属性选择器就完成了控制指定的div。

3.5.4 scoped的注意事项

  • App组件不适合用scoped
    • 1 如果只给App的style里添加一个样式控制,可以用来控制School和Student的样式(前提是School和Student里有相应的类名)
    • 2 但是在1的基础上,给App的style加上scoped,那么App的style就不能控制App子组件的样式了。
    • 3 一般而言,App里的样式基本上是很多子组件都在用的样式。
  • <style lang=" "> 
    • lang:language的简称。不写lang默认就是css。
    • 在 vue中,不仅可以用css写样式,还可以用less,即lang="css" /  lang="less"
    • 但是vue脚手架不能处理less,可以安装less-loader,指令npm i less-loader。但是要降版本,npm i less-loader@7.
    • less可以嵌套写样式。
      .demo {
        background-color: skyblue;
        .atguigu {
          font-size: 40px;
        }
      }

3.5.5 总结

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

3.6 Todo-list案例 

todo-list案例,类似手机里的功能待办事项。

3.6.1 组件化编码流程(通用)

  1. 实现静态组件:抽取组件,使用组件实现静态页面效果
  2. 展示动态数据:
    • 数据的类型、名称是什么?
    • 数据保存在哪个组件?
  3. 交互——从绑定事件监听开始。

3.6.2 页面组件划分

组件的划分:按照功能点划分。

对于每个要做的事,也作为一个组件。

如果很难给组件起名字,说明可能组件拆分不合理。

3.6.3 静态组件

组件化编码流程步骤1:实现静态组件:抽取组件,使用组件实现静态页面效果。

静态组件:只考虑header、Item、todoList和footer四个组件的结构和样式,不包含交互和动态数据。

Item是List的子组件。

完成静态组件。

App.vue

<template>
  <div id="root">
    <div class="todo-container">
      <div class="todo-wrap">
        <MyHeader/>
        <MyList/>
        <MyFooter/>
      </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 },
  methods: {},
};
</script>

<style>
/*base*/
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>

MyHeader.vue

<template>
  <div class="todo-header">
    <input type="text" placeholder="请输入你的任务名称,按回车键确认" />
  </div>
</template>

<script>
export default {
  name: "MyHeader",
};
</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>

MyList.vue

<template>
  <ul class="todo-main">
      <MyItem/>
  </ul>
</template>

<script>
import MyItem from "./MyItem.vue";

export default {
  name: "MyList",
  components: { MyItem },
};
</script>

<style scoped>
/*main*/
.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>

MyItem.vue

<template>
  <li>
    <label>
      <input type="checkbox" />
      <span>yyyy</span>
    </label>
    <button class="btn btn-danger" style="display: none">删除</button>
  </li>
</template>

<script>
export default {
  name: "MyItem",
};
</script>

<style scoped>
/*item*/
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;
}
</style>

MyFooter.vue

<template>
  <div class="todo-footer">
    <label>
      <input type="checkbox" />
    </label>
    <span> <span>已完成0</span> / 全部2 </span>
    <button class="btn btn-danger">清除已完成任务</button>
  </div>
</template>

<script>
export default {
  name: "MyFooter",
};
</script>

<style scoped>
/*footer*/
.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>

以上就是静态组件。

运行结果。

3.6.4 展示动态数据

组件化编码流程步骤2:展示动态数据。

3.6.4.1 初始化列表
  • 数据的类型、名称是什么?
    • todoList:用数组存,里面存每个要做的事——对象(一堆数据用数组,每个数据的属性太多了用对象。)
  • 数组保存在哪个组件?
    • 谁用在谁里面写

步骤1:准备数据,数据在MyList里。

data() {
    return {
      todos: [
        {id:'001',title:'吃饭',done: true},//id是字符串,数字是有尽头的,一般是哈希值
        {id:'002',title:'学习',done: false},
        {id:'003',title:'工作',done: false},
      ]
    }
  }

步骤2:展示,先在MyList里展示。这里将数据传给MyItem展示。

      <MyItem v-for="todo in todos" :key="todo.id" :todo="todo"/>

分析一下,v-for将每一项传给MyItem。

todo前面加冒号后,后面的todo才是一个表达式,才会去读v-for里的todo。否则就只是给todo属性赋值为“todo”。

步骤3:在MyItem里声明接收todo对象。

 props:['todo'],

然后,在MyItem里展示todos,

<span>{{todo.title}}</span>

,且在checkbox的input显示是否done。(是否完成要看todo的done属性,checkbox是否勾选看checked属性,因此用下面的方式动态展示多选框的状态)

<input type="checkbox" :checked="todo.done"/>

本小节结束,完整代码

MyList.vue

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

<script>
import MyItem from "./MyItem.vue";

export default {
  name: "MyList",
  components: { MyItem },
  data() {
    return {
      todos: [
        {id:'001',title:'吃饭',done: true},//id是字符串,数字是有尽头的,一般是哈希值
        {id:'002',title:'学习',done: false},
        {id:'003',title:'工作',done: false},
      ]
    }
  }
};
</script>

<style scoped>
/*main*/
.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>

MyItem.vue

<template>
  <li>
    <label>
      <input type="checkbox" :checked="todo.done"/>
      <span>{{todo.title}}</span>
    </label>
    <button class="btn btn-danger" style="display: none">删除</button>
  </li>
</template>

<script>
export default {
  name: "MyItem",
  props:['todo'],
};
</script>

<style scoped>
/*item*/
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;
}
</style>

运行结果

可以看到吃饭已经被打勾。

3.6.4.2 添加

步骤1:按下回车添加,因此在MyHeader里的input框中,添加事件,keyup绑定键盘事件,enter修饰符——按下回车走逻辑。

  <input type="text" placeholder="请输入你的任务名称,按回车键确认" @keyup.enter="add"/>

步骤2:还是在MyHeader里,配置methods,写add方法。首先要收集input框里的输入,因此用v-model收集。

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

步骤3:然后初始化title

  data() {
    return {
      title: ''
    }
  },

步骤4:然后编写add方法。

        1 将用户的输入包装成一个todo对象

uuid:用于生成全球唯一的字符串编码。地理位置+电脑网卡Mac地址+等等。变种:nanoid,uuid一定程度上的精简。安装:npm i nanoid。

                1.1 引入nanoid  

import {nanoid} from 'nanoid'

                1.2 包装为一个对象

const todoObj = { id: nanoid(), title: this.title, done: false };

        2 将对象添加到todoList中。这里出现问题了。

问题分析:todoList在MyList.vue中,todoObj对象在MyHeader.vue中。想从组件外部往组件内部携带一些数据,可以通过全局事件总线/消息订阅与发布/vuex实现。

怎么处理呢?将todoList数据放到App.vue中。然后MyList.vue要用就从App.vue拿,MyHeader.vue要添加就将数据传给App.vue。示意图如下。

首先将MyList的todos给App.vue。

export default {
  name: "App",
  components: { MyHeader, MyList, MyFooter },
  data() {
    return {
      todos: [
        {id:'001',title:'吃饭',done: true},//id是字符串,数字是有尽头的,一般是哈希值
        {id:'002',title:'学习',done: false},
        {id:'003',title:'工作',done: false},
      ]
    }
  },
  methods: {},
};

todos是MyList要用,因此传给MyList

        <MyList :todos="todos"/>

MyList接收。props接收的数据出现在了MyList组件的实例对象vc上。只要是vc身上的东西,模板里可以随意使用,因此模板里的todos这里不需要修改。

  props:['todos']

然后是MyHeader里的todoObj给App.vue。

(父组件给子组件传一个函数,子组件在合适的时候去调用这个函数,但是函数是在父组件的,因此父组件就能收到参数了)

        首先是App.vue传给MyHeader一个receive函数。分为1,先定义函数,

  methods: {
    receive(x) {
      console.log(x)
    }
  },

                2,传给MyHeader这个函数。这里还是通过props传,props可以传一个函数。

<MyHeader :receive="receive"/>

        其次是MyHeader收到这个函数,

  props:['receive'],

然后在add方法(合适的时候)里调用receive函数。

  methods: {
    add() {
      const todoObj = { id: nanoid(), title: this.title, done: false };
      this.receive(todoObj);
      // console.log(todoObj);
    },
  },

本来receive函数的定义是在App.vue中,因此App.vue就能收到函数的参数了。

然后App里将收到的参数添加到todoList中。

  methods: {
    addTodo(todoObj) {
      // console.log(x)
      this.todos.unshift(todoObj)
    }

这里将receive函数改名为addTodo了(之前的receive函数包括App使用MyHeader标签,以及MyHeader中的receive都改名了)

最好添加完,input里的输入也清空。在MyHeader的add方法里清空,

  methods: {
    add() {
      const todoObj = { id: nanoid(), title: this.title, done: false };
      this.addTodo(todoObj);
      this.title = ''
      // console.log(todoObj);
    },
  },

如果输入为空(空格也认为是空),不会添加,且会提示。

methods: {
    add() {
      if (this.title.trim() !== "") {
        const todoObj = { id: nanoid(), title: this.title, done: false };
        this.addTodo(todoObj);
        this.title = "";
        // console.log(todoObj);
      } else {
        alert("输入不能为空");
      }
    },
  },

效果

本小节结束,完整代码

MyList

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

<script>
import MyItem from "./MyItem.vue";

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

<style scoped>
/*main*/
.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>

MyHeader

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

<script>
import { nanoid } from "nanoid";
export default {
  name: "MyHeader",
  data() {
    return {
      title: "",
    };
  },
  props: ["addTodo"],
  methods: {
    add() {
      if (this.title.trim() !== "") {
        const todoObj = { id: nanoid(), title: this.title, done: false };
        this.addTodo(todoObj);
        this.title = "";
        // console.log(todoObj);
      } else {
        alert("输入不能为空");
      }
    },
  },
};
</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>

App

<template>
  <div id="root">
    <div class="todo-container">
      <div class="todo-wrap">
        <MyHeader :addTodo="addTodo"/>
        <MyList :todos="todos"/>
        <MyFooter />
      </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: true},//id是字符串,数字是有尽头的,一般是哈希值
        {id:'002',title:'学习',done: false},
        {id:'003',title:'工作',done: false},
      ]
    }
  },
  methods: {
    addTodo(todoObj) {
      // console.log(x)
      this.todos.unshift(todoObj)
    }
  },
};
</script>

<style>
/*base*/
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>

运行结果

添加功能实现。

3.6.4.3 勾选

勾选要可以引起数据的变化。

步骤1:绑定事件。在MyItem里绑定事件

做法1:绑定点击事件。@click

      <input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/>

做法2:绑定change事件。

      <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>

然后编写handleCheck方法

  methods: {
    handleCheck(id) {

    }
  }

步骤2:通知App组件将对应的todo对象的done值取反。

数据在哪里,操作数据的方法就在哪里。

todos在App组件里,那么所有对数据的增删改查操作都应该配置在App里。

    //勾选或者取消勾选一个todo
    checkTodo(id) {
      this.todos.forEach((todo)=>{
        if(todo.id === id) {
          todo.done = !todo.done;
        }
      })
    }

然后真正要用这个方法的是MyItem,因此要将这个checkTodo方法传给MyList,然后传给MyItem。

先传给MyList

<MyList :todos="todos" :checkTodo="checkTodo"/>

MyList接收

  props: ["todos", "checkTodo"],

MyList传给MyItem(这里可以注意一下标签的缩进)

    <MyItem
      v-for="todo in todos"
      :key="todo.id"
      :todo="todo"
      :checkTodo="checkTodo"
    />

MyItem接收到checkTodo方法,并且使用该方法

  props:['todo', 'checkTodo'],
  methods: {
    handleCheck(id) {
      // 通知App组件将对应的todo对象的done值取反
      this.checkTodo(id);
    }
  }

功能完成,完整代码如下。

App.vue

<template>
  <div id="root">
    <div class="todo-container">
      <div class="todo-wrap">
        <MyHeader :addTodo="addTodo"/>
        <MyList :todos="todos" :checkTodo="checkTodo"/>
        <MyFooter />
      </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: true},//id是字符串,数字是有尽头的,一般是哈希值
        {id:'002',title:'学习',done: false},
        {id:'003',title:'工作',done: false},
      ]
    }
  },
  methods: {
    addTodo(todoObj) {
      // console.log(x)
      this.todos.unshift(todoObj)
    },
    //勾选或者取消勾选一个todo
    checkTodo(id) {
      this.todos.forEach((todo)=>{
        if(todo.id === id) {
          todo.done = !todo.done;
        }
      })
    }
  },
};
</script>

<style>
/*base*/
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>

MyList.vue

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

<script>
import MyItem from "./MyItem.vue";

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

<style scoped>
/*main*/
.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>

MyItem.vue

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

<script>
export default {
  name: "MyItem",
  props:['todo', 'checkTodo'],
  methods: {
    handleCheck(id) {
      // 通知App组件将对应的todo对象的done值取反
      this.checkTodo(id);
    }
  }
};
</script>

<style scoped>
/*item*/
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;
}
</style>

做法2

将前面的操作都删掉。

<input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>

如果input是checkbox,且v-model绑定的是一个boolean值,那么这个boolean值就可以觉得checkbox勾还是不勾。

      <input type="checkbox" v-model="todo.done">

为什么呢?首先初始化列表,来维护勾还是不勾。然后v-model是一个双向数据绑定,勾不勾都会引起todo.done的变化,而todo对象来自App.vue,那么todo.done就会变化。

但是不建议这么写,稍微有点违反原则(修改了props)。因为todo是通过props接收的,但是props只读不能修改数据值。但是呢,对于对象属性的修改,vue监听不到,因此todo这里没有改变。所以再改回去吧。

3.6.4.4 删除

每项都有删除按钮,鼠标悬浮时会有高亮效果(整个li)

步骤1:加高亮效果

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

步骤2:鼠标悬浮的时候,对应todo显示删除按钮

li:hover button{
  display: block;
}

步骤3:点击删除按钮,拿到这件事的id,然后从todos根据id删除。3.1 找到点击按钮,绑定点击事件,

    <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>

3.2 写事件处理函数

删除todo的函数在App里定义,用filter过滤,且将过滤后的数组作为新的todos

    // 删除一个todo
    deleteTodo(id) {
      this.todos = this.todos.filter((todo) => {
        return todo.id !== id;
      });
    },

与checkTodo的操作一样,App将deleteTodo传给MyList,MyList接收后传给MyItem,MyItem使用deleteTodo。

删除todo的操作在MyItem里完成。

    handleDelete(id) {
      if(confirm('确定删除吗')) {
        this.deleteTodo(id);
      }
    }

以上操作完成,完整代码如下。

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 />
      </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: true }, //id是字符串,数字是有尽头的,一般是哈希值
        { id: "002", title: "学习", done: false },
        { id: "003", title: "工作", done: false },
      ],
    };
  },
  methods: {
    addTodo(todoObj) {
      // console.log(x)
      this.todos.unshift(todoObj);
    },
    //勾选或者取消勾选一个todo
    checkTodo(id) {
      this.todos.forEach((todo) => {
        if (todo.id === id) {
          todo.done = !todo.done;
        }
      });
    },
    // 删除一个todo
    deleteTodo(id) {
      this.todos = this.todos.filter((todo) => {
        return todo.id !== id;
      });
    },
  },
};
</script>

<style>
/*base*/
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>

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>
/*main*/
.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>

MyItem.vue

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

<script>
export default {
  name: "MyItem",
  props: ["todo", "checkTodo", "deleteTodo"],
  methods: {
    handleCheck(id) {
      // 通知App组件将对应的todo对象的done值取反
      this.checkTodo(id);
    },
    handleDelete(id) {
      if (confirm("确定删除吗?")) {
        this.deleteTodo(id);
      }
    },
  },
};
</script>

<style scoped>
/*item*/
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: #ddd;
}
li:hover button {
  display: block;
}
</style>

3.6.4.5 底部统计

todos是全部的数量,todos里的todo的done值为true的个数是已完成的数量。

3.6.4.5.1 全部的数量

步骤1:App将todos传给MyFooter,

        <MyFooter 
          :todos="todos"/>

且MyFooter声明接收

props: ['todos']

步骤2:全部就是todos.length

    <span> <span>已完成0</span> / 全部{{todos.length}} </span>

3.6.4.5.2 已完成的数量

要算的东西,考虑使用计算属性。

vue的原则:模板语法的代码,要求尽可能简单,不要太长。

步骤1:给已完成起个名字

<span>已完成{{doneTotal}}</span>

步骤2:定义计算属性doneTotal

写法1:简单写法

  computed: {
    doneTotal() {
      let i = 0;
      this.todos.forEach((todo)=>{
        if(todo.done === true) {
          i++;
        }
        return i;
      })
    }
  }

写法2:高端写法,使用reduce(专门用于做条件统计)

MDN->搜索reduce

// 箭头函数return可以不写
      return this.todos.reduce((pre,todo)=> pre + (todo.done? 1:0 ),0)

本小节结束,全部代码如下

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"/>
      </div>
    </div>
  </div>
</template>

MyFooter.vue

<template>
  <div class="todo-footer">
    <label>
      <input type="checkbox" />
    </label>
    <span> <span>已完成{{doneTotal}}</span> / 全部{{todos.length}} </span>
    <button class="btn btn-danger">清除已完成任务</button>
  </div>
</template>

<script>
export default {
  name: "MyFooter",
  props: ['todos'],
  computed: {
    doneTotal() {
      // 箭头函数return可以不写
      return this.todos.reduce((pre,current)=> pre + (current.done? 1:0 ),0)
      // let i = 0;
      // this.todos.forEach((todo)=>{
      //   if(todo.done === true) {
      //     i++;
      //   }
      //   return i;
      // })
    }
  }
};
</script>

<style scoped>
/*footer*/
.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>

3.6.4.6 底部交互

完成底部的多选框和清除功能

3.6.4.6.1 多选框状态

如果tod都被勾选,那么底部的多选框要被勾选。如果已完成!==全部,那么多选框就不能被勾选。

步骤1:将全部用计算属性计算出来

    <span> <span>已完成{{doneTotal}}</span> / 全部{{total}} </span>

total定义

    total() {
      return this.todo.length;
    },

步骤2:将多选框的状态通过计算属性得出

      <input type="checkbox" :checked="isAll"/>

isAll定义为

    isAll() {
      return this.doneTotal === this.total && this.total > 0;
    }

且如果没有任务,那么底部不要显示

  <div class="todo-footer" v-show="total">

当total为0,那么v-show为0,则footer不会显示。

步骤2:点击多选框,所有todo都被勾选。

绑定一个change事件。

      <input type="checkbox" :checked="isAll" @change="changeAll"/>

编写changeAll函数,告诉存储todo的App,全选/全不选

App里改变todo状态的函数

    // 全选/取消全选todo
    changeAll(done) {
      this.todo.forEach((todo) => {
        todo.done = done;
      })
    }

且传给MyFooter

<MyFooter :todos="todos" :changeAll="changeAll"/>

然后MyFooter接收下

  props: ['todos','changeAll'],

且使用changeAll函数

  methods: {
    changeAll(e) {
      this.changeAll(e.target.checked);
    }
  }

改进方式

将多选框的状态用v-model实现。

      <input type="checkbox" v-model="isAll"/>

v-model绑定isAll计算属性,isAll通过get获取到值,通过set修改值

    isAll: {
      get() {
        return this.doneTotal === this.total && this.total > 0;
      },
      set(value) {
        this.changeAll(value);
      }
    }
3.6.4.6.2 清空数据

步骤1:给清空按钮绑定点击事件

<button class="btn btn-danger"  @click="clearAll">清除已完成任务</button>

步骤2:编写清除所有已完成todos的函数,App里完成

    // 清除所有已经完成的tood
    clearAll() {
      this.todos = this.todos.filter((todo) => {
        return todo.done !== true;
      });
    }

步骤3:App将函数传给MyFooter,MyFooter接收clearAll

        <MyFooter 
          :todos="todos" :changeAll="changeAll" :clear="clearAll"/>
  props: ['todos','changeAll','clearAll'],

步骤4:Myfooter去调用

    clear() {
      this.clearAll();
    }

完整代码如下

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" :changeAll="changeAll" :clearAll="clearAll"/>
      </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: true }, //id是字符串,数字是有尽头的,一般是哈希值
        { id: "002", title: "学习", done: false },
        { id: "003", title: "工作", done: false },
      ],
    };
  },
  methods: {
    addTodo(todoObj) {
      // console.log(x)
      this.todos.unshift(todoObj);
    },
    //勾选或者取消勾选一个todo
    checkTodo(id) {
      this.todos.forEach((todo) => {
        if (todo.id === id) {
          todo.done = !todo.done;
        }
      });
    },
    // 删除一个todo
    deleteTodo(id) {
      this.todos = this.todos.filter((todo) => {
        return todo.id !== id;
      });
    },
    // 全选/取消全选todo
    changeAll(done) {
      this.todo.forEach((todo) => {
        todo.done = done;
      })
    },
    // 清除所有已经完成的tood
    clearAll() {
      this.todos = this.todos.filter((todo) => {
        return todo.done !== true;
      });
    }
  },
};
</script>

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="clear">清除已完成任务</button>
  </div>
</template>

<script>
export default {
  name: "MyFooter",
  props: ["todos", "changeAll", "clearAll"],
  computed: {
    total() {
      return this.todos.length;
    },
    doneTotal() {
      // 箭头函数return可以不写
      return this.todos.reduce(
        (pre, current) => pre + (current.done ? 1 : 0),
        0
      );
    },
    isAll: {
      get() {
        return this.doneTotal === this.total && this.total > 0;
      },
      set(value) {
        this.changeAll(value);
      },
    },
  },
  methods: {
    clear() {
      this.clearAll();
    },
  },
};
</script>

3.6.5 总结

  • 1 组件化编码流程
    • 1 拆分静态组件:组件按照功能点拆分,命名不要与html元素冲突
    • 2 实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用。
      • 1 一个组件在用,放在组件自身即可
      • 2 一些组件在用,放在他们共同的父组件上
    • 3 实现交互,从绑定事件开始
  • 2 props适用于
    • 父组件==>子组件 通信
    • 子组件==>父组件通信(要求父先给子一个函数)
  • 3 使用v-model要切记,v-model绑定的值不能是props传过来的值,因为props是不可以被修改的
  • 4 props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这么做

3.6.6 todo-List案例-本地存储版

用监视watch实现todos的变化,

App.vue

当todos改变的时候,通过watch添加到浏览器的本地存储。这里启用深度监视,这样todo里的done属性改变,本地存储的数据也会更新。

  watch: {
    todos: {
      deep:true,//深度监视
      handler(value) {
        localStorage.setItem('todos',JSON.stringify(value));
      }
    }
  }

初始化的时候也要从本地存储里读数据。

data() {
    return {
      todos: JSON.parse(localStorage.getItem('todos')) || []
    }
}

3.7 浏览器的本地存储

浏览器的本地存储,是JS里的功能,不是Vue的,不需要脚手架。

localStorage

浏览器的本地存储。

举例,唯品会网站,点击搜索皮鞋,然后浏览器整个都关掉。再打开网站和搜索框,结果搜索历史里有皮鞋。

皮鞋 借助本地存储,存到了硬盘上。

查看:浏览器的开发者工具-> Application -> Storage

以上就是浏览器本地存储的应用,就是往用户本地存储点东西。

唯品会可以放,我们能不能放呢?

3.7.1 本地存储的保存

本地存储实现

<body>
    <h2>localStorage</h2>
    <!-- click事件,原生html要加() -->
    <button onclick="saveData()">点我保存一个数据</button>

    <script type="text/javascript">
        function saveData() {
            // window.localStorage.setItem('msg','hello!');
            // window上的东西,那么可以省略window不写
            localStorage.setItem('msg','hello!');
        }
    </script>
</body>

1 key和value都是string类型,如果不是就会转换为string类型

2 保存一个对象(对象是直接调对象.toString()存到本地存储的value里)。

let p = { name: '张三', age: 18 };
        function saveData() {
            // window.localStorage.setItem('msg','hello!');
            // window上的东西,那么可以省略window不写
            localStorage.setItem('msg', 'hello!');
            localStorage.setItem('msg1', p);
        }

怎么处理呢,使用JSON.stringify()

localStorage.setItem('msg1', JSON.stringify(p));

这会就对了

这里本质是个字符串,但是浏览器能猜到这是个对象,就解析成对象的形式了。

3.7.2 本地存储的读取

读取对象

        function readData() {
            const result = localStorage.getItem('msg1');
            console.log(JSON.parse(result));
        }

以对象的形式显示(普通字符串就直接显示result,对象的话要对result进行JSON.parse())

3.7.3 本地存储的删除

直接调用removeItem()

localStorage.removeItem('msg');

3.7.4 本地存储的清空

清空本地存储。

localStorage.clear();

注意:读取一个没有的结果,是null,JSON.parse(null) 还是null。

3.7.5 sessionStorage

session,会话。sessionStorage对象和localStorage对象中的方法一致,两者不同的是:只要浏览器关闭,那么sessionStorage就会被全部清除掉;

localStorage和sessionStorage统称为WebStorage。

4备注,2 手动清楚,有2种方式,1是调用API,2清空缓存(用户主动对浏览器清空缓存)。

3.8 Vue中的自定义事件

自定义事件,区别于js里的内置事件(比如click、keyup等内置事件)

内置事件给html元素使用,自定义事件给组件使用。

自定义事件是一种组件间通信的方式,适用于:子组件 ===> 父组件

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

案例

要求:在school组件有个按钮,点下按钮,就将学校名传递给app组件。即子组件向父组件传递数据。

3.8.1 自定义属性实现子组件向父组件传递数据

这样的操作以前做过,即通过props传递数据,但是前提是父组件要给子组件提供一个函数。

App.vue

<template>
  <div class="app">
    <h1>{{msg}}</h1>
    <School :getSchoolName="getSchoolName"></School>
    <Student></Student>
  </div>
</template>

<script>
//导入子组件
import Student from './components/Student'
import School from './components/School'

export default {
  name: 'App',
  components: { School, Student },
  data() {
    return {
      msg: '你好啊!'
    }
  },
  methods: {
    getSchoolName(name) {
      console.log('App收到了学校名:', name)
    }
  }
}
</script>

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

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:'SGG',
				address:'Beijing',
			}
		},
		methods: {
			sendSchoolName(){
				// 调用父组件传递过来的方法
				// 将数据传递给父组件
				this.getSchoolName(this.name)
			}
		},
	}
</script>

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

这样就实现了school组件向app组件传递数据的操作。

3.8.2 自定义事件实现子组件向父组件传递数据

在父组件里给子组件绑定自定义事件

App.vue

    <Student v-on:atgugui="getStudentName"> </Student>
    <!-- v-on给student组件的实例对象vc上绑定了一个atguigu事件,
                   如果触发,就会调用getStudentName函数 -->
    <!-- 给student组件的实例对象绑定了事件,就要去student那里去触发事件 -->

触发事件的demo函数

  methods: {
    demo() {
      console.log('getStudentName被调用了');
    }
  },

在子组件里触发自定义事件

通过按钮点击来触发事件

    <button @click="sendStudentName">把学生名给学校</button>

在sendStudentName方法内部触发事件。

注意:这里给student组件的实例对象vc绑定了自定义事件atguigu,那么要触发这个事件,就去找student的vc,在student中也就是个this。然后调用this的$emit(emit:发射;爆发)方法,函数的参数是要触发的事件——'atguigu'。

并且将学生的name传给App.vue

  methods: {
    sendStudentName() {
      // 触发student身上的atguigu
      this.$emit('atguigu', this.name);
    }
  }

App.vue里的getStudentName接受student传来的name

  methods: {
    getStudentName(name) {
      console.log('demo被调用了',name);
    }
  },

示意图

v-on可以简写为@

3.8.3 使用ref绑定自定义事件

使用ref绑定student实例对象

    <Student ref="student"/>

触发事件

  mounted() {
    //app挂载完毕
    this.$refs.student.$on('atguigu',this.getStudentName);
  }

使用ref绑定自定义事件的优点:灵活性强。

案例:要求App组件挂载完成3秒钟后,给student子组件实例绑定自定义事件。这样就只能用ref实现,而v-on就不能完成。

  mounted() {
    //app挂载完毕
    setTimeout(() => {
      this.$refs.student.$on('atguigu',this.getStudentName);
    }, 3000);
  }

案例2:让自定义事件只能被触发一次。

使用ref,$on改为$once

  mounted() {
    //app挂载完毕
    setTimeout(() => {
      this.$refs.student.$once('atguigu',this.getStudentName);
    }, 3000);
  }

使用v-on

在自定义事件后加入事件修饰符once

    <Student v-on:atgugui.once="getStudentName"> </Student>

案例3:接收多个参数。

    getStudentName(name,...params) {
      console.log('demo被调用了',name);
    }

这里第一个参数就是name,剩下的所有参数都会放到数组params中。

...是扩展运算符。

以上两小节没有给出完整代码,可以参考【精选】[Vue]组件自定义事件_vue组件自定义事件_萤火虫的小尾巴的博客-CSDN博客

3.8.4 解绑自定义事件

不用的自定义事件,最好解绑一下。

给谁绑定的自定义事件,就去找谁去解绑。

1 解绑一个自定义事件

在student组件里,给解绑按钮绑定一个点击事件。

<button @click="unbind">解绑atguigu事件</button>

定义unbind事件

  methods: {
    unbind() {
      this.$off('atguigu');
    }
  }

以上适用于解绑一个自定义事件。

完整代码

App.vue

<template>
  <div class="app">
    <h1>{{ msg }}</h1>
    <School />
    <hr />
    <Student v-on:atguigu="getStudentName"> </Student>
    <hr />
  </div>
</template>

<script>
import School from "./components/School.vue";
import Student from "./components/Student.vue";

export default {
  name: "App",
  components: { School, Student },
  data() {
    return {
      msg: "你好啊",
    };
  },
  methods: {
    getStudentName(name) {
      console.log("demo被调用了", name);
    }
  }
};
</script>

<style>
.app {
  background-color: gray;
}
</style>

Student.vue

<template>
  <div class="student">
    <h2>学生名称:{{ name }}</h2>
    <h2>学生性别:{{ gender }}</h2>
    <button @click="sendStudentName">把学生名给学校</button>
    <button @click="unbind">解绑atguigu事件</button>
  </div>
</template>

<script>
export default {
  name: "Student",
  data() {
    return {
      name: "张三",
      gender: "男",
    };
  },
  methods: {
    sendStudentName() {
      // 触发student身上的atguigu
      this.$emit("atguigu", this.name);
    },
    unbind() {
      this.$off("atguigu");
    },
  },
};
</script>
<style scoped>
.student {
  background-color: orange;
}
</style>
2 解绑多个自定义事件

在$off方法里,传入一个数组,里面有多个事件名。

  methods: {
    unbind() {
      this.$off(['atguigu','atguiguguigu']);
    }
  }
3 解绑所有自定义事件
  methods: {
    unbind() {
      this.$off();
    }
  }

销毁后,自定义事件就失效了,但是原生的DOM事件依然可以调用,只是没有响应。(对应生命周期的destroy)

3.8.5 父组件显示子组件传来的数据

1 使用v-on绑定自定义事件

在App.vue中定义变量studentName,用于接收从子组件传来的值

  data() {
    return {
      msg: "你好啊",
      studentName: ''
    };
  },

给变量studentName赋值。

  methods: {
    getStudentName(name) {
      console.log("demo被调用了", name);
      this.studentName = name;
    }
  }
2 使用ref绑定自定义事件

也可以实现。

但是如果将自定义事件触发后的回调函数写在mounted()中,此时的this是触发事件的子组件的实例对象,

但是写成箭头函数就可以了。为什么?如果是箭头函数,但是箭头函数没有自己的this。那就向外找,也就是mounted(),mounted()里写普通函数,this就是当前组件的实例对象。

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

此外,为子组件绑定内置事件,且不让内置事件被认为是自定义事件,需要使用事件修饰符native,即可为子组件绑定内置事件。(这里有点)

3.8.6 总结

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

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

绑定自定义事件:

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

<Demo @atguigu="test"/>或 <Demo v-on:atguigu="test"/>

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

<Demo ref="demo"/>
......
mounted(){
   this.$refs.xxx.$on('atguigu',this.test)
}

若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法。

触发自定义事件:this.$emit('atguigu',数据)

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

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

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

3.9 全局事件总线

可以实现任意组件间的通信。

p84

3.10 消息订阅与发布

3.11 过渡与动画

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值