2022_黑马vue笔记

vue2

vue插件

1.Vue Snippets
2.Vetur

vue 的两个特性

  1. 数据驱动视图:

    • 数据的变化会驱动视图自动更新
    • 好处:程序员只管把数据维护好,那么页面结构会被 vue 自动渲染出来!
  2. 双向数据绑定:

    在网页中,form 表单负责采集数据,Ajax 负责提交数据

    • js 数据的变化,会被自动渲染到页面上
    • 页面上表单采集的数据发生变化的时候,会被 vue 自动获取到,并更新到 js 数据中

MVVM

数据驱动视图和双向数据绑定的底层原理是 MVVM(Mode 数据源、View 视图、ViewModel 就是 vue 的实例

2022_黑马vue笔记_第1张图片

VivwModel作为MVVM的核心


基本使用

1.导入vue.js的script脚本

<script src="./lib/vue-2.6.12.js"></script>

2.在页面声明一个将要被vue所控制的DOM区域

<div id="app">{{username}}div>

3.创建VM实例对象

const vm = new Vue({
    el:'#app', 
    data:{
        username:'zhangsan'
    }
})

vue 指令

指令是vue为开发者提供的模板语法
1.内容渲染
2.属性绑定
3.事件绑定
4.双向绑定
5.条件渲染
6.列表渲染

1. 内容渲染指令

  1. v-text 指令的缺点:会覆盖元素内部原有的内容!
  2. {{ }} 插值表达式:在实际开发中用的最多,只是内容的占位符,不会覆盖原有的内容!
    • 插值表达式中可以运算js表达式
     <div :title="'box'+index">这是一个divdiv>
    
  3. v-html 指令的作用:可以把带有标签的字符串,渲染成真正的 HTML 内容!

2. 属性绑定指令

注意:插值表达式只能用在元素的内容节点中 ,不能用在元素的属性节点中!

  • 在 vue 中,可以使用 v-bind: 指令,为元素的属性动态绑定值;

  • 简写是英文的 :

  • 在使用 v-bind 属性绑定期间,如果绑定内容需要进行动态拼接,则字符串的外面应该包裹单引号,例如:

<input type="text" :placeholder="tips">
data:{
        tips:'请输入用户名'
    }

3. 事件绑定

  1. v-on: 简写是 @

  2. 语法格式为:

 <p>count的值是:{{count}}p>
 <button @click="add">点击加1button>
   methods: {
      add() {
        // 如果在方法中要修改 data 中的数据,可以通过 this 访问到
        this.count += 1
      }
   }

绑定传参

<button @click="add(2)">点击加2button>
add(index){
    this.count += index;
 }

  1. $event 是vue内置变量,表示原生的DOM事件对象
<button @click="add(1,$event)">如果count是偶数,则按钮背景变成红色,否则不变色button>
add(n,e){
    this.count += n;
    if(this.count % 2 == 0){
        e.target.style.backgroundColor = 'red'
    }else{
        e.target.style.backgroundColor = ''
    }
}

事件修饰符:
事件修饰符 说明
.prevent 阻止默认行为(a链接跳转、表单提交)
.stop 阻止事件冒泡
.capture 以捕获模式触发当前的事件处理函数
.once 绑定的事件只触发一次
.self 只有在event.target是当前元素自身触发事件处理函数
  • .prevent 阻止默认行为
    阻止a链接跳转行为

    <a href="http://www.baidu.com" @click.prevent="show">跳转到百度首页a>
    
  • .stop 阻止事件冒泡

<div style="height:150px;backgroundColor:orange;padding-left:100px;line-height:150px"  @click="divHandler"> 
   <button @click.stop="btnHandler">按钮button>
div>
按键修饰符

@keyup

 <input type="text" @keyup.esc="clearInput($event)" @keyup.enter="commitAjax">
clearInput(e){
    e.target.value=""
},
commitAjax(){
    console.log('触发了提交');
}

4. v-model 双向数据绑定

帮助用户在不操作DOM的前提下,快速获取表单的数据

  1. input 输入框
  <p>用户的名字是:{{username}}p>
  <input type="test" v-model="username">
  1. textarea
<p style="white-space: pre-line;">{{ message }}p>
<br>
<textarea v-model="message" placeholder="add multiple lines">textarea>
  1. select
v-model修饰符
修饰符 作用
.number 自动将用户的输入值转为数值类型
.trim 自动过滤用户输入的首尾空白字符
.lazy 在“change”时而非“input”时更新
<input type="text" v-model.number="n1"> + <input type="text" v-model.number="n2"> = <span>{{n1+n2}}span>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-445ECG0H-1648046654845)(D:\images\92518294628c3a565cb92ed70a861c1a0b5970378989976dc7630cd3d646fd2e.png)]

<input type="text" v-model.trim="username">
<button @click="show">获取用户名button>

5. 条件渲染指令

  1. v-show 的原理是:动态为元素添加或移除 display: none 样式,来实现元素的显示和隐藏
    • 如果要频繁的切换元素的显示状态,用 v-show 性能会更好
  2. v-if 的原理是:每次动态创建或移除元素,实现元素的显示和隐藏
    • 如果刚进入页面的时候,某些元素默认不需要被展示,而且后期这个元素很可能也不需要被展示出来,此时 v-if 性能更好

在实际开发中,绝大多数情况,不用考虑性能问题,直接使用 v-if 就好了!!!

v-if 指令在使用的时候,有两种方式:

  1. 直接给定一个布尔值 true 或 false

    <p v-if="true">被 v-if 控制的元素p>
    
  2. 给 v-if 提供一个判断条件,根据判断的结果是 true 或 false,来控制元素的显示和隐藏

    <p v-if="type === 'A'">良好p>
    
key值管理可复用的元素
<template v-if="loginType === 'username'">
  <label>Usernamelabel>
  <input placeholder="Enter your username" key="username-input">
template>
<template v-else>
  <label>Emaillabel>
  <input placeholder="Enter your email address" key="email-input">

v-for列表渲染

我们可以用 v-for 指令基于一个数组来渲染一个列表。v-for 指令需要使用 item in items 形式的特殊语法,其中 items 是源数据数组,而 item 则是被迭代的数组元素的别名。

 <ul id="example-2">
     <li v-for="(item, index) in items">
         {{ parentMessage }} - {{ index }} - {{ item.message }}
     li>
 ul>

建议给每个v-for提供一个唯一的key属性,既提升性能,又防止列表状态絮乱
1.key值只能是字符串或数字
2.具有唯一性
3.使用index做key没有任何意义

过滤器

过滤器的注意点

  1. 要定义到 filters 节点下,本质是一个函数
  2. 在过滤器函数中,一定要有 return 值
  3. 在过滤器的形参中,可以获取到“管道符”前面待处理的那个值
  4. 如果全局过滤器和私有过滤器名字一致,此时按照“就近原则”,调用的是”私有过滤器“

watch 侦听器

所有的侦听器,都应该被定义到 watch 节点下
侦听器本质上是一个函数,要监视哪个数据的变化,就把数据名作为方法名即可
新值在前,旧值在后

 watch: {
   username(newVal,oldVal){
     console.log("变换前"+oldVal);
     console.log("变换后"+newVal);
   }
 }

判断用户名是否被占用
默认情况下,组件在初次加载完毕后不会调用 watch 侦听器。如果想让 watch 侦听器立即被调用,则需要使
用 immediate 选项。示例代码如下:

 watch: {
        // 定义对象格式的侦听器
        username: {
          // 侦听器的处理函数
          handler(newVal, oldVal) {
            console.log(newVal, oldVal)
          },
          // immediate 选项的默认值是 false
          // immediate 的作用是:控制侦听器是否自动触发一次!
          immediate: true
        }
      }

handler是固定写法

侦听器的格式

  1. 方法格式的侦听器
    • 缺点1:无法在刚进入页面的时候,自动触发!!!
    • 缺点2:如果侦听的是一个对象,如果对象中的属性发生了变化,不会触发侦听器!!!
  2. 对象格式的侦听器
    • 好处1:可以通过 immediate 选项,让侦听器自动触发!!!
    • 好处2:可以通过 deep 选项,让侦听器深度监听对象中每个属性的变化!!!
deep选项 深度监听

如果 watch 侦听的是一个对象,如果对象中的属性值发生了变化,则无法被监听到。此时需要使用 deep 选项
两种写法
直接获取要监听的属性名

watch: {
  'info.username'(newVal) {
    console.log(newVal)
  }

通过deep选项监听

 info:{
   handler(newVal){
     console.log(newVal);
   },
   deep:true
 }

计算属性

特点:

  1. 定义的时候,要被定义为“方法”
  2. 在使用计算属性的时候,当普通的属性使用即可

好处:

  1. 实现了代码的复用
  2. 只要计算属性中依赖的数据源变化了,则计算属性会自动重新求值!
 data: {
    message: 'Hello'
  },
  computed: {
    // 计算属性的 getter
    reversedMessage () {
      return this.message.split('').reverse().join('')
    }
  }

我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要 message 还没有发生改变,多次访问 reversedMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数

axios

axios 是一个专注于网络请求的库!

axios 的基本使用

  1. 发起 GET 请求:

    axios({
      // 请求方式
      method: 'GET',
      // 请求的地址
      url: 'http://www.liulongbin.top:3006/api/getbooks',
      // URL 中的查询参数
      params: {
        id: 1
      }
      // result 是返回的结果
    }).then(function (result) {
      console.log(result)
    })
    
  2. 发起 POST 请求:

    document.querySelector('#btnPost').addEventListener('click', async function () {
      // 如果调用某个方法的返回值是 Promise 实例,则前面可以添加 await!
      // await 只能用在被 async “修饰”的方法中
      const { data: res } = await axios({
        method: 'POST', 
        url: 'http://www.liulongbin.top:3006/api/post',
        data: {
          name: 'zs',
          age: 20
        }
      })
    
      console.log(res)
    })
    

使用async简化

如果调用某个方法的返回值是 Promise 实例,则前面可以添加 await
await 只能用在被 async “修饰”的方法中


 document.querySelector('#btnPost').addEventListener('click', async function () {
   const { data } = await axios({
     method: 'POST',
     url: 'http://www.liulongbin.top:3006/api/post',
     data: {
       name: 'zs',
       age: 20
     }
   })
   console.log(data)
 })

解构赋值的时候,使用 : 进行重命名

  1. 调用 axios 之后,使用 async/await 进行简化
  2. 使用解构赋值,从 axios 封装的大对象中,把 data 属性解构出来
  3. 把解构出来的 data 属性,使用 冒号 进行重命名,一般都重命名为 { data: res }
 document.querySelector('#btnGet').addEventListener('click', async function () {
   const { data: res } = await axios({
     method: 'GET',
     url: 'http://www.liulongbin.top:3006/api/getbooks'
   })
   console.log(res.data)
 })

直接发送请求

 const { data: res } = await axios.post('http://www.liulongbin.top:3006/api/post', { name: 'zs', gender: '女' })

vue-cli 使用axios

基本使用

<template>
    <button @click="postInfo">发起 POST 请求button>
template>

<script>
// 1. 导入 axios 
import axios from 'axios' 
export default {
 //2. 在 methods 定义 axios请求方法
  methods: {
    async postInfo() {
      const { data: res } = await axios.post('http://www.liaoyia.top:3306/api/post', { name: 'zs', age: 20 })
      console.log(res)
    }
  }
}
script>

缺点: 在每次使用时候都要导入 axios 文件,写请求地址 (对后期维护不友好)

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

Vue.config.productionTip = false

// 全局配置 axios 的请求根路径 (官方提供配置项)
axios.defaults.baseURL = 'http://www.liaoyia.top:3006'

// 把 axios 挂载到 Vue.prototype 上,供每个 .vue 组件的实例直接使用
Vue.prototype.$http = axios

// 今后,在每个 .vue 组件中要发起请求,直接调用 this.$http.xxx
// 但是,把 axios 挂载到 Vue 原型上,有一个缺点:不利于 API 接口的复用!!!

new Vue({
  render: h => h(App)
}).$mount('#app')

<template>
     <button @click="btnGetBooks">获取图书列表的数据</button>
</template>


<script>
export default {
  methods: {
    // 点击按钮,获取图书列表的数据
    async btnGetBooks() {
      const { data: res } = await this.$http.get('/api/getbooks')
      console.log(res)
    }
  }
}
</script>

缺点:无法实现API接口的复用 : 在多个组件想使用同一个请求方法(API)的时候,只能在每个组件重新定义一次。

axios 封装

1.如果项目中有多个请求地址,我们可以根据多个地址使用工厂模式封装 js 模块,创建多个 axios 实例对象,并设置请求根路径 (baseURL) :

步骤如下: 在项目的 src目录下创建utils文件夹并新建一个 request.js文件:

import axiox from 'axios'

const request =axiox.create({
    //baseURL会在发送请求的时候拼接在url参数的前面
    baseURL:'http://jsonplaceholder.typicode.com/',
    
    timeout:5000
})

// 向外导出
export default  request

使用这种方法时: 一般我们只会在一个 js 模块创建一个axios 实例对象,并向外导出。
如果有多个服务器地址,那就创建多个 js模块,并在里面创建axios实例对象。
2.为了实现复用性,我们还可以把所有请求,都封装在API模块里,在API模块中,按需导出一个方法,这个方法调用 request.get 或 request.post 来请求一个接口,最后return 一个Promise 对象。

比如想调用接口获取用户相关信息:

在根目录新建 utils 文件夹并在里面新建 userAPI.js 文件

//导入 utils 文件夹下的 request.js

import request from '@/utils/request.js'

export const getArticleListAPI = function(_page, _limit) {
  return request.get('/articles', {
    params: {
      _page,
      _limit
    }
  })
}

  • 使用这种方法时: 一般我们只会在一个 js 模块创建一个axios 实例对象,并向外导出。
  • 如果有多个服务器地址,那就创建多个 js模块,并在里面创建axios实例对象。

2.为了实现复用性,我们还可以把所有请求,都封装在API模块里,在API模块中,按需导出一个方法,这个方法调用 request.get 或 request.post 来请求一个接口,最后return 一个Promise 对象。

比如想调用接口获取用户相关信息:

在根目录新建 utils 文件夹并在里面新建 userAPI.js 文件

//导入 utils 文件夹下的 request.js

import request from '@/utils/request.js'

export const getArticleListAPI = function(_page, _limit) {
  return request.get('/articles', {
    params: {
      _page,
      _limit
    }
  })
}

2022_黑马vue笔记_第2张图片

vue-cli 的使用

https://cli.vuejs.org/zh/guide/

安装

npm install -g @vue/cli
  1. 在终端下运行如下的命令,创建指定名称的项目:

    vue cerate 项目的名称
    

    2022_黑马vue笔记_第3张图片

    2022_黑马vue笔记_第4张图片

    图 3

vue 项目中 src 目录的构成:

assets 文件夹:存放项目中用到的静态资源文件,例如:css 样式表、图片资源
components 文件夹:程序员封装的、可复用的组件,都要放到 components 目录下
main.js 是项目的入口文件。整个项目的运行,要先执行 main.js
App.vue 是项目的根组件。

  1. main.js
import Vue from 'vue'    // 导入Vue这个包   得到Vue构造函数
import App from './App.vue'   // 导入App组件,将App.vue模板结构渲染到html页面上
import Test from './Test.vue'

Vue.config.productionTip = false

new Vue({
  render: h => h(Test), // reader函数指定的组件,渲染到HTML页面上
}).$mount('#app')  // 替代el 写法

vue组件的三个组成部分

<template>
  <div class="text-box">
    <h3>这是用户自定义的test.vueh3>
  div>
template>


<script>
// 默认导出
export default{
  // data数据源   data必须是一个函数
  data(){
    return {
      username:'zs'
    }
  }

}

script>

<style lang="less">
.text-box{
  background-color: pink;
}
style>

4.组件之间父子关系

####4.1使用组件的步骤
1.使用import语法导入需要的组件
2.使用components节点注册组件
3.以标签形式使用注册好的组件

2022_黑马vue笔记_第5张图片

4.2 通过components 注册的是私有子组件

4.3 注册全局组件

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

import Count from '@/components/Count'  //  导入全局需要注册的组件
Vue.component('MyCount',Count)   

5.组件自定义属性props

允许使用者通过自定义属性,为当前组件指定初始值

Count.vue

<p>count的值是:{{init}}p>

<script>
export default {
    props:['init']
}

Left.vue

<MyCount init="9">MyCount>
5.1 props 的值是只读
// 不加: 传的值是字符串
<MyCount :init="6">MyCount>

想修改props值,可以将数值转存到data中

 data(){
     return{
         count:this.init
     }
 }
5.2 props的default默认值

将props作为一个对象存储

  // props:['init'],
  props:{
      // 自定义属性A:{配置选项}
      init:{
          default:0
      }
 },

5.3 tyoe值类型
 props:{
     // 自定义属性A:{配置选项}
     init:{
         default:0,
         type:Number
     }
 },
5.4 proprs 的required必填项

如果设定了以后,就必须传某个属性值,否则会报错

required:true

6.组件之间样式冲突问题

默认情况,写在.vue组件中的样式会全局生效
添加scoped

<style lang="less" scoped>

图 6

使用depp修改子组件的样式

当使用到第三方组件库的时候,如果有修改第三方组件默认样式的需求,需要用到/deep/

/deep/ h5{
    color:red
}

图 5

生命周期

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

2022_黑马vue笔记_第6张图片

2022_黑马vue笔记_第7张图片

组件之间传值

1.父子传值

父 -> 子 自定义属性
2022_黑马vue笔记_第8张图片

子 -> 父 自定义事件

2022_黑马vue笔记_第9张图片

2.兄弟传值

1.创建 eventBus.js 模块,并向外共享一个 Vue 的实例对象

2.在数据发送方,调用 bus.$emit(‘事件名称’, 要发送的数据) 方法触发自定义事件

3.在数据接收方,调用 bus.$on(‘事件名称’, 事件处理函数) 方法注册一个自定义事件

2022_黑马vue笔记_第10张图片

2022_黑马vue笔记_第11张图片

ref引用

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

1.使用ref引用DOM元素

<h3 ref="myh3">MyRef组件<h3/>
this.$refs.myh3.style.color='red'

2.使用ref引用组件

情景引入:在父组件上有一个button能重置子组件的值

2.1 在App.vue渲染Left组件时 传入ref属性
<Left ref="comLeft">Left>
2.2 获取vue实例 调用子组件方法
this.$refs.comLeft.resetCount()

3.控制文本框和按钮的按需切换

需求:
1.点击按钮时,按钮消失,输入框呈现
2.输入框失去焦点时,输入框消失,按钮呈现

3.1 创建一个变量用来保存输入框切换状态
  data(){
    return{
       inputVisible:false // 控制输入框按钮的按需切换 默认值false表示展示按钮隐藏输入框
    }
  },
3.2
<input type="text" v-if="inputVisible" @blur="showButton" ref="iptRef"/>
<button v-else @click="showInput">展示输入框button>
3.3 下面这样写会报错
 showButton(){
      this.inputVisible = false;
    },
    // 展示输入框
    showInput(){
      this.inputVisible = true;
      this.$refs.iptRef.focus();
    }

原因在于在生命周期中,数据渲染到页面上需要一定时间,不能直接在数据变化马上获取到dom元素,此时页面上dom元素还未渲染完成,会导致undefined 元素未找到错误

3.4 引入 this.$nextTick(cb) 方法

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

    showInput(){
      // this.$refs.iptRef.focus();
      this.inputVisible = true;
      this.$nextTick(()=>{
        this.$refs.iptRef.focus();
      })
    }

购物车案例

补充前置知识

1.数组some()循环 检测数组元素中是否有元素符合指定条件。

传统foreach循环找到元素后无法被终止,浪费性能,使用some代替
some 可以使用return true 来终止循环

2.数组every()循环 检测数值元素的每个元素是否都符合条件。
3.数组reduce()

接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。

2. 实现步骤

  • 初始化项目基本结构
  • 封装 MyHeader 组件
  • 基于 axios 请求商品列表数据( GET 请求,地址为 https://www.escook.cn/api/cart )
  • 封装 MyFooter 组件
  • 封装 MyGoods 组件
  • 封装 MyCounter 组件
2.1 axios 请求商品列表
<script>
// 导入axios请求库
import axios from "axios";
// 导入Header组件
import Header from "@/components/Header/Header.vue";

export default {
  created() {
    // 调用请求数据的方法
    this.initCartList();
  },
  methods: {
    // 封装请求列表数据的方法
    async initCartList() {
      // 调用axios的get方法,请求列表数据
      const { data: res } = await axios.get("https://www.escook.cn/api/cart");
      console.log(res);
    },
  },
  components: {
    Header,
  },
};
</script>
2.2 将数据保存到data中
  • 创建数据域 list默认为空数组
  • 如果状态码为200 则将数据保存到list数组中
  data(){
    return{
      // 用来存储购物车列表数据
      list:[]
    }
  },
  methods: {
    // 封装请求列表数据的方法
    async initCartList() {
      // 调用axios的get方法,请求列表数据
      const { data: res } = await axios.get("https://www.escook.cn/api/cart");
      if(res.status == 200){
        this.list = res.list
      }
    },
  },

3 循环渲染Goods组件

 <Goods v-for="item in list" :key="item.id">Goods>
3.1 Goods组件 图片文字替换

使用父子属性传值
不要传一个对象,对于组件复用性不好

 <Goods v-for="item in list" :key="item.id" :Flist="item" :title="item.goods_name" :pic="item.goods_img" :price="item.goods_price" :status="item.goods_state" >Goods>
  props: {
    title: {
      title: "",
      type: String,
    },
    pic: {
      title: "",
      type: String,
    },
    price: {
      title: "",
      type: Number,
    },
    status: {
      title: "",
      type: Boolean,
    },
  },

4. 修改商品的勾选状态

子组件勾选状态变化之后,需要通过子–>父 的形式 通知父组件根据id修改对应商品的勾选状态

4.1 复选框默认有一个change事件,当勾选状态改变时会自动调用
  
  <input
    type="checkbox"
    class="custom-control-input"
    id="cb1"
    :checked="status"
    @change="stateChange"
  />

子组件定义stateChange 向父组件传值

methods:{
    stateChange(e){
      const newState = e.target.checked  // 复选框状态值
      this.$emit("state-change",{
        id:this.id,
        value:newState
        })
    }
  }

父组件监听state-change事件

@state-change="getNewState"

使用some方法比对组件id
如果id相同则更新状态值并结束循环

// 接收子组件传递过来的数据
getNewState(val){
  console.log(val)
  this.list.some(item=>{
    if(item.id == val.id){
      item.goods_state = val.value
      return true;
    }
  })
}

5. 计算商品全选状态

5.1 计算属性动态计算
  // 计算属性
  computed:{
    // 动态计算出全选状态是true 还是false
    fullState(){
      return this.list.every(item=>item.goods_state)
    }
  },
5.2 把全选状态传值给子组件

父组件传状态值给子组件
子组件要完成对状态值监听实时渲染

5.1 父组件传值给子组件
<Footer :isFull="fullState">Footer>
5.2 子组件接收参数
  props:{
    // 全选的状态
    isFull:{
      default:true,
      type:Boolean
    }
  }
5.3 子组件渲染
 <input type="checkbox" class="custom-control-input" id="cbFull" :checked="isFull" />

6.实现点击全选功能

6.1 子向父组件传值

使用@change 监听复选框变化状态,绑定fullChange方法

 <div class="custom-control custom-checkbox">
   <input 
   type="checkbox" 
   class="custom-control-input" 
   id="cbFull" 
   :checked="isFull"  
   @change="fullChange"  
  />
6.2 fullChange 向父组件传值

methods

// 监听到了全选的状态变化
    fullChange(e){
      this.$emit('full-change',e.target.checked)
    }
6.3 在渲染子组件时绑定监听事件,接收子组件传值的方法

@full-change=“getFullState”

<Footer :isFull="fullState" @full-change="getFullState">Footer>
6.4 在父组件中处理子组件传过来的值

根据子组件传值,对数组进行赋值

// 接收子组件全选状态
getFullState(val){
  this.list.forEach(item => (item.goods_state = val))
}

7. 计算属性 计算商品总价值

7.1 根据商品选定状态对price累加

在计算属性中定义amt

// 已勾选商品的总价格
amt(){
  // 1.先filter过滤
  // 2. 再reduce累加
  return this.list
  .filter(item => item.goods_state)
  .reduce((total,item)=>total += item.goods_price * item.goods_count,0)

7.2 将计算好的值传给子组件渲染
<Footer 
  :isFull="fullState" 
  @full-change="getFullState"
  :amount="amt"
>Footer>
7.4 子组件接收值渲染
  props:{
    // 总价格
    amount:{
      type:Number,
      default:0
    }
  },
<span class="total-price">{{ amount }}</span>

8.购买数量传给couhnter组件

8.1 父组件App.vue 传给 子组件Goods 再传给 Couter

父组件将商品数量传给Goods

:count="item.goods_count"

Goods接收参数

 // 商品的购买数量
 count:{
   type:Number,
   default:1
 }
 <Counter :num="count"></Counter>

Count.vue

props:{
  num:{
    type:Number,
    default:1
  }
}
<span class="number-box">{{ num }}</span>
8.2 Count发送给App数据

接收商品id值,使用EventBus方案,将数据传递给App.vue的时候,需要通知App组件,更新哪个商品的数量

 props:{
   id:{
     type:Number,
     required:true
   },
   num:{
     type:Number,
     default:1
   }
 }
8.3 在components文件夹下创建中间件eventBus.js

···js
import Vue from ‘vue’
export default new Vue()
···

8.4 Counter组件引入eventBus
import bus from '@/components/eventBus.js'
8.5 在Counter 中发送数据
 add(){
   const obj = { 
     id:this.id,
     value:this.num + 1
     }
   bus.$emit('share',obj)
 }
8.6 App组件中接收数据
  • 引入eventbus
  • 在生命周期函数created中接收数据
  • 通过some方法根据id赋值
  created() {
    // 调用请求数据的方法
    this.initCartList();
    bus.$on('share',val=>{
      this.list.some(item=>{
        if(item.id == val.id){
          item.goods_count = val.value;
          return true;
        }
      })
    })
  },
9 App组件中计算属性 动态计算商品总数量
 // 已勾选商品的总数量
 total(){
   return this.list
   .filter(item => item.goods_state)
   .reduce((t,item)=>t+=item.goods_count,0)
 }
10.Counter组件实现减功能

复制add ,需要注意数量不能为负数,需要做个前置判断

sub(){
    if(this.num - 1 === 0)
      return;
    const obj = { 
    id:this.id,
    value:this.num - 1
    }
  bus.$emit('share',obj)
}

自定义组件

1.动态组件

  • 动态组件指的是 动态切换组件的显示与隐藏

2.如何实现动态组件渲染

  • vue提供了一个内置的组件,专门用来实现动态组件的渲染 相当于占位符
// 1. 当前需要渲染的组件名称
data(){
  return { comName:'Left' }
}
<!-- 2.通过is属性,动态指定要渲染的组件 -->
<component :is="comName"></component>
<!-- 3.点击按钮,动态切换组件名称 -->
<button @click="comName = 'Left' ">展示Left组件</button>
<button @click="comName = 'Right' ">展示Right组件</button>

keep-live

默认情况下,切换组件无法保持组件的状态,此时可以使用vue内置的组件保持动态组件的状态

可以把内部的组件进行缓存,而不是销毁组件

<template>
  <div class="left-container">
    <h3>Left 组件 --- {{count}}h3>
    <button @click="count += 1">+1button>
  div>
template>

<script>
export default {
  data(){
    return{
      count:0
    }
  }
}
script>

父组件在渲染left组件时 使用keep-live

<keep-alive>
  <component :is="comName">component>
keep-alive>

keep-live生命周期

我们有时会需要组件在被缓存时去执行某件事,这时需要用到生命周期

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

keep-alive 的include属性

include属性用来指定:只有名称匹配的组件会被缓存。多个组件名之间使用英文的逗号分隔

使用场景:默认情况下keep-alive标签下的组件都会被缓存,而有时我们并不需要部分组件被缓存,故可以使用include指定部分组件被缓存

keep-alive exclude

exclude不能与include同时使用
exclude 指定某个组件不被缓存

组件注册名称和组件声明时name的区别

如果在“声明组件”的时候,没有为组件指定name名称,则组件的名称默认就是“注册时候的名称”

export default {
  name:"MyRight",  // 指定name
}

如果组件指定name名称,keep-live标签中的include exclude也要匹配响应的name

插槽

1.什么是插槽

插槽(Slot)是 vue 为组件的封装者提供的能力。允许开发者在封装组件时,把不确定的、希望由用户指定的
部分定义为插槽。可以把插槽认为是组件封装期间,为用户预留的内容的占位符。

2.基础用法

App.vue


<Left>
  <p>这是在Left组件中的内容区域声明的p标签p>
Left>

Left.vue


<slot>slot>

具名插槽

  • vue官方规定,每一个slot插槽都要有一个name名称
    • 如果省略了name属性,就会有一个默认名称叫做default
  • 默认情况下,在使用组件的时候,提供的内容都会被填充到名字为default的插槽之中

v-slot

在为具名插槽提供内容时,在