VUE学习笔记

什么是VUE?

vue是一个用于构建用户界面的渐进式框架

vue是一个数据驱动的渐进式框架

一.第一个vue实例

1.1步骤

1.1.1 构建用户界面(前端页面)—>创建VUE实例,初始化渲染.

  1. 准备容器,即div标签容器或者其他标签作为容器
  2. 导包,导包后,在全局环境下,就有了Vue构造函数,即可new Vue()
  3. 创建vue实例 new Vue()
  4. 指定配置项 --> 渲染数据 

      ①el指定挂载点,即vue实例管理哪一个容器

      ②data提供数据

以上两个配置项,实现了在哪一个容器展示数据

<!-- 容器  -->
<div id="app">
    {{msg}}
</div>

<!-- 本地导入 -->
<script src="../day01/files/vue.js" ></script>
<!-- 在线导入 -->
<!-- <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script> -->
<script>
    const vue =new Vue({
        el : '#app',
        data : {
            msg : '明天会更好!'
        }
    })
</script>

2.插值表达式 {{ 表达式 }}

  • 插值表达式是一种Vue的模板语法
  • 作用:利用表达式进行插值,渲染到页面中
  • 注意点
  • ①插值语法中使用到的数据在data中必须存在
  • ②插值语法中支持的是表达式,(表达式是必定求得结果) 而不是语句,即不能在插值表达式中使用  if  等语句
  • ③不能在标签属性中使用插值表达式

2.1插值语法报错信息

1.当使用的数据qq在data中不存在时,控制台报错

Property or method "qq" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. 

属性或方法“qq”未在实例上定义,但在渲染过程中被引用。通过初始化该属性,确保该属性是被动的,无论是在数据选项中,还是对于基于类的组件

2.当在插值表达式中使用了if语句,页面无法渲染,控制台报错

Error compiling template:

avoid using JavaScript keyword as property name: "if"

编译模板时出错:

避免使用JavaScript关键字作为属性名称:“if”

3.当插值表达式使用在标签属性上时,控制台报错

Error compiling template:

title="{{msg}}": Interpolation inside attributes has been removed. Use v-bind or the colon shorthand instead. For example, instead of <div id="{{ val }}">, use <div :id="val">.

编译模板时出错:

title=“{{msg}}”:属性内部的插值已被删除。请改用v-bind或冒号简写。例如,使用<div:id=“val”>而不是<div id=“{{val}}”>。

2.2 vue的核心属性:响应式 :当数据发生变化的时候,视图会自动更新

如何访问或者修改data中的数据呢?

data中的数据最终都会添加到vue实例上的:vue实例就是 const vue = new Vue({})

①访问数据:“实例.属性名”

②修改数据:“实例.属性名”=“值”

浏览器控制台数据访问和修改

2.3vue调试工具的安装

2.4 Vue指令

vue会根据不同的指令,针对标签实现不同的功能

指令:带有v-前缀的特殊标签属性

  • v-html : 设置元素的innerHTML,解析标签 语法:v-html = “表达式”
<!-- 容器  -->
<div id="app">
    <div v-html="msg"></div>
</div>

<!-- 本地导入 -->
<script src="../day01/files/vue.js" ></script>
<!-- 在线导入 -->
<!-- <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script> -->
<script>
    const vue =new Vue({
        el : '#app',
        data : {
            msg : '<h1>明天会更好!</h1>',
            count : '100'
        }
    })
</script>

1. v-show和v-if

代码实现
<!-- 容器  -->
<div id="app">
    <div v-show="tag">这式一个v-show控制显示的盒子</div>
    <div v-if="tag">这式一个v-if控制显示的盒子</div>
</div>

<!-- 本地导入 -->
<script src="../day01/files/vue.js" ></script>
<!-- 在线导入 -->
<!-- <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script> -->
<script>
    const vue =new Vue({
        el : '#app',
        data : {
            msg : '<h1>明天会更好!</h1>',
            count : '100',
            tag : true
        }
    })
</script>

效果:表达式值为flase

效果:表达式值为true

2.v-else 和v-if-else

  • v-else和v-else-if都是辅助v-if进行判断渲染的,v-if必须存在
  • v-else是没有表达式的,v-else-if才有表达式

<!-- 容器  -->
<div id="app">
    <div v-if="good">我会变得更好 </div>
    <div >
        <li v-if="point > 90">成绩A</li>
        <li v-else-if="point > 80">成绩B</li>
        <li v-else-if="point >70">成绩C</li>
        <li v-else>成绩D</li>
    </div>
</div>

<!-- 本地导入 -->
<script src="../day01/files/vue.js" ></script>
<!-- 在线导入 -->
<!-- <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script> -->
<script>
    const vue =new Vue({
        el : '#app',
        data : {
            // 如果值为null,默认为flase
            good : '',
            point : '75'
        }
    })
</script>
当在使用v-else和v-else-if时,没有v-if会报错

 Error compiling template:

v-else-if="point > 80" used on element <li> without corresponding v-if.

3  |      <div>
4  |          <!-- <li v-if="point > 90">成绩A</li> -->
5  |          <li v-else-if="point > 80">成绩B</li>
   |              ^^^^^^^^^^^^^^^^^^^^^^
6  |          <li v-else-if="point >70">成绩C</li>
7  |          <li v-else="">成绩D</li>

编译模板时出错:

在元素<li>上使用的v-else-if=“point>80”,没有相应的v-if。

编译报错的时候,可以去检查语法是否正确

2.v-on:事件名 = "内联语句”

代码
<!-- 容器  -->
<div id="app">
    <button v-on:click="count--">-</button>
    <span>{{count}}</span>
    <button v-on:click="count++">+</button>
</div>

<!-- 本地导入 -->
<script src="../day01/files/vue.js" ></script>
<!-- 在线导入 -->
<!-- <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script> -->
<script>
    const vue =new Vue({
        el : '#app',
        data : {
            count : '100',

        }
    })
</script>

如果少了事件名,会报错

 v-on without argument expects an Object value

没有参数的v-on需要Object值

v-on:事件名 = "methods配置项中对应于的方法名"
代码
<!-- 容器  -->
<div id="app">
    <button v-on:click="show">控制显示与隐藏</button>
    <div v-show="tag">显示</div>
</div>

<!-- 本地导入 -->
<script src="../day01/files/vue.js" ></script>
<script>
    const vue =new Vue({
        el : '#app',
        data : {
            tag : true,
        },
        methods : {
            show(){
                // vue.tag = !vue.tag
                this.tag = !this.tag
            }
        }
    })
</script>

重要知识点

  • methods配置项是无法通过属性名直接使用data中的数据的,因为data配置项和methods配置项是平级的,作用域是局部的
  • methods中该如何使用data配置项中的数据的?data配置是挂载在vue实例中的,可以通过Vue实例.属性名来使用,即vue.tag =!vue.tag
  • methods配置项中 this 指向的是 Vue实例的 ,即可通过 this.属性名调用data中的属性 ,即 this.tag = ! this.tag

当没有通过实例去调用data中的数据时,会报错

Error in v-on handler: "ReferenceError: tag is not defined"

v-on处理程序中出错:“ReferenceError:标记未定义”

v-on 调用传参   格式 : @click(参数1,参数2,其他参数)
<!-- 容器  -->
<div id="app">

    <div style="width: 500px;height: 250px;margin: 0 auto;background-color: pink;text-align: center; ">
    小闭售卖机
    <br>
    <button @click="buy(10)">牛奶10元</button>
    <br>
    <button @click="buy(7)">小米粥7元</button>
    <br>
    <span>余额:{{account}}</span>
    </div>

</div>

<!-- 本地导入 -->
<script src="../day01/files/vue.js" ></script>
<script>
    const vue =new Vue({
        el : '#app',
        data : {
            account : "100",
        },
        methods : {
            buy(price){
                this.account = this.account - price
            }
        }
    })
</script>

当在methods配置中,定义了一个函数,函数在调用处传递了实际参数,但在函数中却没有使用形式参数接收,却在函数体中使用,会报错

Error in v-on handler: "ReferenceError: price is not defined"

v-on处理程序中的错误:“ReferenceError:未定义价格”

3. v-bind

代码

<!-- 容器  -->
<div id="app">
    <button v-show="index > 0" v-on:click="index--" style="width: 200px;height: 200px;">上一页</button>
    <br>
    <div style="width: 500px;height: 500px;">
        <img v-bind:src="list[index]"></img>
    </div>
    <br>
    <button v-show="index < list.length-1" v-on:click="index++" style="width: 200px;height: 200px;">下一页</button>

</div>

<!-- 本地导入 -->
<script src="../day01/files/vue.js" ></script>
<script>
    const vue =new Vue({
        el : '#app',
        data : {
            index : 0,

            list : [
                'img/001.webp',
                'img/002.webp',
                'img/003.webp',
                'img/004.webp',
            ]

        },
    })
</script>

4. v-for

代码

<!-- 容器  -->
<div id="app">
    <ul>
        <li v-for="(item,index) in list">
            {{item}}
        </li>
    </ul>

</div>

<!-- 本地导入 -->
<script src="../day01/files/vue.js" ></script>
<script>
    const vue =new Vue({
        el : '#app',
        data : {
            index : 0,

            list : [
                '香蕉',
                '菠萝',
                '苹果'
            ]

        },
    })
</script>

案例:

<!-- 容器  -->
<div id="app">
    <ul>
        <li v-for="(item,index) in list " :key="item.id">
            <span>{{item.name}}</span>
            <span> <button v-on:click="del(item.id)">删除</button></span>
        </li>
    </ul>

</div>

<!-- 本地导入 -->
<script src="../day01/files/vue.js" ></script>
<script>
    const vue =new Vue({
        el : '#app',
        data : {
            list : [
                {id:1,name:'《红楼梦》',author:'曹雪芹'},
                {id:2,name:'《西游记》',author:'吴承恩'},
                {id:3,name:'《水浒传》',author:'施耐盐'},
                {id:4,name:'《三国演义》',author:'罗贯中'},
            ]

        },
        methods:{
            del(id){
                /* function(item){
                    return item.id !==id
                }
                在前端JavaScript中,数组通常有一个 filter() 方法,
                它会创建满足条件的所有元素的新数组
                */
                this.list = this.list.filter(item => item.id !== id)
            }
        }
    })
</script>

v-for中的key

有key和没key的区别,可以通过css样式来观察

没有key时,删除红楼梦,发现样式还保留着(底层并没有删除这个li)

有key时,删除红楼梦,样式跟着被删除

5 . v-model

变量在data配置项中定义

代码

<!-- 容器  -->
<div id="app">
    <form action="#">
        <span> 账户:<input v-model="username" type="text" /></span><br>
        <span> 密码:<input v-model="password" type="password"/></span><br>
        <span><button>登录</button></span>
        <span><button @click="reset">重置</button></span>
    </form>
</div>

<!-- 本地导入 -->
<script src="../day01/files/vue.js" ></script>
<script>
    const vue =new Vue({
        el : '#app',
        data : {
            username : '',
            password : '',
        },
        methods:{
            reset(){
                this.username='',
                this.password=''
            }
        }
    })
</script>

6.指令修饰符

vue的指令修饰符本质是对事件对象做了封装

当指令修饰符使用错误时,如 正确语法 v-on:click.prevent = "函数名"   错误的用成了  v-on.prevent:click="函数名"  控制台报错

v-on without argument does not support modifiers

没有参数的v-on不支持修饰符

7.v-bind操作class

其中以数组形式绑定类名,类名需要加上单引号  v-bind:class ="['类名1','类名1']"

代码

<!-- 容器  -->
<div id="app">

    <div v-bind:class="{box:true,yelloCollor:true}">
        <span v-bind:class="{yelloCollor:true,fontSize:true}">这是v-bind以对象形式操作CSS样式</span>
    </div>
    <div v-bind:class="['boxArr','boxArrColor']">
        <span v-bind:class="['fontSize','yelloCollor']">这是v-bind以数组形式操作CSS样式</span>
    </div>
</div>

<!-- 本地导入 -->
<script src="../day01/files/vue.js" ></script>
<script>
    const vue =new Vue({
        el : '#app',
        data : {
            username : '',
            password : '',
        },
        methods:{
            reset(){
                this.username='',
                this.password=''
            }
        }
    })
</script>

<style>
.yelloCollor {
    background-color: yellow;
}
.fontSize {
    font-size: 25px;
}
.box {
    width: 700px;
    height: 800px;
}
.boxArr{
    width: 500px;
    height: 600px;
}
.boxArrColor{
    background-color: aqua;
}

</style>

案列:京东秒杀tab导航高亮

案例:代码

<!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>
  <style>
    * {
      margin: 0;
      padding: 0;
    }
    ul {
      display: flex;
      border-bottom: 2px solid #e01222;
      padding: 0 10px;
    }
    li {
      width: 100px;
      height: 50px;
      line-height: 50px;
      list-style: none;
      text-align: center;
    }
    li a {
      display: block;
      text-decoration: none;
      font-weight: bold;
      color: #333333;
    }
    li a.active {
      background-color: #e01222;
      color: #fff;
    }

  </style>
</head>
<body>

  <div id="app">
    <ul>
      <li v-for="(item,index) in list" :key="item.id" v-on:click="activeIndex = index">
        <a v-bind:class="{active:index===activeIndex}" href="#">{{item.name}}</a>
      </li>
    </ul>
  </div>
  <script src="./vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        activeIndex : 0,
        list: [
          { id: 1, name: '京东秒杀' },
          { id: 2, name: '每日特价' },
          { id: 3, name: '品类秒杀' }
        ]

      }
    })
  </script>
</body>
</html>

8.v-bind操作style

v-bind绑定style时,css属性名和css属性值要遵守js语法规范

  •  background-color    要写成    ' background-color'    或者     backgroundColor

  • CSS属性值要加上英文单引号

  • v-bind:style="{ ' background-color' : 'red' }"

案例 代码

<!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>
</head>
<body>
  <div id="app">
    <div class="progress">
      <div class="inner" v-bind:style="{width:percent+'%',}">
        <span>{{percent}}%</span>
      </div>
    </div>
    <button  @click="percent=25">设置25%</button>
    <button  @click="percent=50">设置50%</button>
    <button  @click="percent=75">设置75%</button>
    <button  @click="percent=100">设置100%</button>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        percent : 0,
      }
    })
  </script>
</body>
<style>
  .progress {
    height: 25px;
    width: 400px;
    border-radius: 15px;
    background-color: #272425;
    border: 3px solid #272425;
    box-sizing: border-box;
    margin-bottom: 30px;
  }
  .inner {
    width: 50%;
    height: 20px;
    border-radius: 10px;
    text-align: right;
    position: relative;
    background-color: #409eff;
    background-size: 20px 20px;
    box-sizing: border-box;
    transition: all 1s;
  }
  .inner span {
    position: absolute;
    right: -20px;
    bottom: -25px;
  }
</style>

</html>

9. v-model应用于其他表单元素

<!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>
  <style>
    textarea {
      display: block;
      width: 240px;
      height: 100px;
      margin: 10px 0;
    }
  </style>
</head>
<body>

  <div id="app">
    <h3>小黑学习网</h3>

    姓名:
      <input type="text" v-model="username"> 
      <br><br>

    是否单身:
      <input type="checkbox" v-model="isSingle"> 
      <br><br>

    <!-- 
      前置理解:
        1. name:  给单选框加上 name 属性 可以分组 → 同一组互相会互斥
        2. value: 给单选框加上 value 属性,用于提交给后台的数据
      结合 Vue 使用 → v-model
    -->
    性别: 
      <input v-model="gender" type="radio" name="gender" value="1">男
      <input v-model="gender" type="radio" name="gender" value="2">女
      <br><br>

    <!-- 
      前置理解:
        1. option 需要设置 value 值,提交给后台
        2. select 的 value 值,关联了选中的 option 的 value 值
      结合 Vue 使用 → v-model
    -->
    所在城市:
      <select v-model="cityId">
        <option value="1">北京</option>
        <option value="2">上海</option>
        <option value="3">成都</option>
        <option value="4">南京</option>
      </select>
      <br><br>

    自我描述:
      <textarea v-model="desc"></textarea> 

    <button>立即注册</button>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        username:'',
        isSingle:true,
        gender:'1',
        cityId:'1',
        desc:'',

      }
    })
  </script>
</body>
</html>

10. 计算属性

  • 基于现有的数据(属性),计算出新的属性,如果依赖的数据发生变化,计算出来的新属性会重新计算   如下:已有属性数据 1,2,5 计算出新的属性数据 8
  • 计算属性的声明实在computed配置项中,一个计算属性对应一个函数,计算设计到逻辑处理,逻辑处理一般需要函数
  • 计算属性的用法与普通属性的用法一样  {{ 计算属性名 }}
  • 计算属性名本质上是一个属性,使用时(注意不是调用)不需要(),计算属性名就是computed配置项中的函数名

代码
<!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>
  <style>
    table {
      border: 1px solid #000;
      text-align: center;
      width: 240px;
    }
    th,td {
      border: 1px solid #000;
    }
    h3 {
      position: relative;
    }
  </style>
</head>
<body>

  <div id="app">
    <h3>小黑的礼物清单</h3>
    <table>
      <tr>
        <th>名字</th>
        <th>数量</th>
      </tr>
      <tr v-for="(item, index) in list" :key="item.id">
        <td>{{ item.name }}</td>
        <td>{{ item.num }}个</td>
      </tr>
    </table>

    <!-- 目标:统计求和,求得礼物总数 -->
    <p>礼物总数:{{getSum}}个</p>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        // 现有的数据
        list: [
          { id: 1, name: '篮球', num: 1 },
          { id: 2, name: '玩具', num: 2 },
          { id: 3, name: '铅笔', num: 5 },
        ]
      },
      computed:{
        getSum(){
         let total = this.list.reduce((sum,item) =>sum+item.num ,0)
         return total;
        }
      }
    })
  </script>
</body>
</html>

计算属性与方法的区别

计算属性的完整写法

  • get()  获取计算属性
  • set()   设置计算属性的值,影响到基于现有数据

代码

<!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>
</head>
<body>

  <div id="app">
    姓:<input  v-model="firstname" type="text">+
    名:<input  v-model="lastname" type="text">={{fullName}}
    <button v-on:click="edit">修改姓名</button>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        firstname:'皇甫',
        lastname :'圣华'
      },
      computed: {
        fullName:{
          get(){
            return this.firstname + this.lastname
          },
          set(value){
            this.firstname = value.slice(0,1)
            this.lastname = value.slice(1)
          }
        }

      },
      methods: {
        edit(){
          this.fullName = '刘德华'
        }
      }
    })
  </script>
</body>
</html>
  • 当给计算属性重新赋值时,这个值会默认传到set(value)中使用value接收值
  • 如  :  给计算属性重新赋值  =>   this.fullName = '刘德华'  ,  而在set(value)中的value的值就是刘德华

问题:当给计算属性设置值的时候,并没有set(),控制台报错

 Computed property "fullName" was assigned to but it has no setter.

已将计算属性“fullName”分配给,但它没有setter。

成绩案例:

<!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" />
    <link rel="stylesheet" href="./styles/index.css" />
    <title>Document</title>
  </head>
  <body>
    <div id="app" class="score-case">
      <div class="table">
        <table>
          <thead>
            <tr>
              <th>编号</th>
              <th>科目</th>
              <th>成绩</th>
              <th>操作</th>
            </tr>
          </thead>
          <tbody v-if="list.length > 0">
            <tr v-for="(item,index) in list" v-bind:key="item.id">
              <td>{{index + 1}}</td>
              <td>{{item.subject}}</td>
              <td v-bind:class="{red : item.score < 60 }">{{item.score}}</td>
              <td><a v-on:click.prevent="del(item.id)" href="https://www.baidu.com/index.htm">删除</a></td>
            </tr>
          </tbody>
          <tbody v-else>
            <tr>
              <td colspan="5">
                <span class="none">暂无数据</span>
              </td>
            </tr>
          </tbody>

          <tfoot>
            <tr>
              <td colspan="5">
                <span>总分:{{totalScore}}</span>
                <span style="margin-left: 50px">平均分:{{averageScore}}</span>
              </td>
            </tr>
          </tfoot>
        </table>
      </div>
      <div class="form">
        <div class="form-item">
          <div class="label">科目:</div>
          <div class="input">
            <input
              type="text"
              placeholder="请输入科目"
              v-model.trim="subject"
            />
          </div>
        </div>
        <div class="form-item">
          <div class="label">分数:</div>
          <div class="input">
            <input
              type="text"
              placeholder="请输入分数"
              v-model.number="score"
            />
          </div>
        </div>
        <div class="form-item">
          <div class="label"></div>
          <div class="input">
            <button class="submit" v-on:click="add">添加</button>
          </div>
        </div>
      </div>
    </div>
    <script src="../vue.js"></script>

    <script>
      const app = new Vue({
        el: '#app',
        data: {
          list: [
            { id: 1, subject: '语文', score: 20 },
            { id: 7, subject: '数学', score: 99 },
            { id: 12, subject: '英语', score: 70 },
          ],
          subject: '',
          score: ''
        },
        methods:{
          del(id){
            this.list = this.list.filter(item => item.id !== id)
          },
          add(){
            if(!this.subject){
              alert("科目不能为空")
              return
            }
            if(typeof this.score !== 'number'){
              alert("请正确输入成绩")
              return
            }
             this.list.unshift({
              id : +new Date(),
              subject : this.subject,
              score : this.score,
            })
            
            this.subject = ''
            this.score = ''

          }
        },
        computed : {
          totalScore(){
            // reduce((sum阶段性求和结果,item每一项) =>每次算完的结果, 起始值)
            return this.list.reduce((sum,item) =>sum+item.score,0)
          },
          averageScore(){
            // toFixed 保留两位小数
            return (this.totalScore / this.list.length).toFixed(2)
          }
        }
      })
    </script>
  </body>
</html>

11.watch 侦听器(监视器)

  • 当被监视的属性或者数据发生变化时,自动执行一些义务逻辑的操作


代码
<!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>
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
        font-size: 18px;
      }
      #app {
        padding: 10px 20px;
      }
      .query {
        margin: 10px 0;
      }
      .box {
        display: flex;
      }
      textarea {
        width: 300px;
        height: 160px;
        font-size: 18px;
        border: 1px solid #dedede;
        outline: none;
        resize: none;
        padding: 10px;
      }
      textarea:hover {
        border: 1px solid #1589f5;
      }
      .transbox {
        width: 300px;
        height: 160px;
        background-color: #f0f0f0;
        padding: 10px;
        border: none;
      }
      .tip-box {
        width: 300px;
        height: 25px;
        line-height: 25px;
        display: flex;
      }
      .tip-box span {
        flex: 1;
        text-align: center;
      }
      .query span {
        font-size: 18px;
      }

      .input-wrap {
        position: relative;
      }
      .input-wrap span {
        position: absolute;
        right: 15px;
        bottom: 15px;
        font-size: 12px;
      }
      .input-wrap i {
        font-size: 20px;
        font-style: normal;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <!-- 条件选择框 -->
      <div class="query">
        <span>翻译成的语言:</span>
        <select>
          <option value="italy">意大利</option>
          <option value="english">英语</option>
          <option value="german">德语</option>
        </select>
      </div>

      <!-- 翻译框 -->
      <div class="box">
        <div class="input-wrap">
          <textarea v-model="words"></textarea>
          <textarea v-model="obj.wordsObj"></textarea>
          <span><i>⌨️</i>文档翻译</span>
        </div>
        <div class="output-wrap">
          <div class="transbox">mela</div>
        </div>
      </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script>
      // 接口地址:https://applet-base-api-t.itheima.net/api/translate
      // 请求方式:get
      // 请求参数:
      // (1)words:需要被翻译的文本(必传)
      // (2)lang: 需要被翻译成的语言(可选)默认值-意大利
      // -----------------------------------------------
      
      const app = new Vue({
        el: '#app',
        data: {
          words: '',
          obj : {
            //需要有引号包裹
            'obj.wordsObj' : ''
          }
        },
        watch : {
          //data属性的监视,通常只需要newValue的值
          words(newValue,oldValue){
            console.log(newValue+" 前面的是新值,后面的是旧值" + oldValue);
          },
          //对象属性的监视
          'obj.wordsObj'(newValue,oldValue){
            console.log(newValue+" 前面的是新值,后面的是旧值" + oldValue);
          }
        },
        // 具体讲解:(1) watch语法 (2) 具体业务实现
      })
    </script>
  </body>
</html>

watch监视属性的完整写法

  • 当需要去监视一整个对象的属性时,以  被监视属性名(新值,旧值){}  实现监视效果,显然需要写多个属性监视器,十分不便
  • 现在通过监视一个对象来达到监视对象全部属性的效果,只需要配置两个配置项 ,注意写法: watch : {被监视的对象名 : { 
  •                                               deep : true
  •                                               handler(新值,旧值){  }                     }
  •                             }
  • 对象中的任何一个属性发生变化,都会被监视到,handler函数就会执行
  • immediate配置项,只要配置了,立刻执行一次handler函数,不需要属性变化

案例:购物车
<!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" />
    <link rel="stylesheet" href="./css/inputnumber.css" />
    <link rel="stylesheet" href="./css/index.css" />
    <title>购物车</title>
  </head>
  <body>
    <div class="app-container" id="app">
      <!-- 顶部banner -->
      <div class="banner-box"><img src="http://autumnfish.cn/static/fruit.jpg" alt="" /></div>
      <!-- 面包屑 -->
      <div class="breadcrumb">
        <span>🏠</span>
        /
        <span>购物车</span>
      </div>
      <!-- 购物车主体 -->
      <div class="main"v-if="this.fruitList.length > 0 ">
        <div class="table">
          <!-- 头部 -->
          <div class="thead">
            <div class="tr">
              <div class="th">选中</div>
              <div class="th th-pic">图片</div>
              <div class="th">单价</div>
              <div class="th num-th">个数</div>
              <div class="th">小计</div>
              <div class="th">操作</div>
            </div>
          </div>
          <!-- 身体 -->
          <div class="tbody" >
            <div  v-for="(item,index) in fruitList" v-bind:key="item.id"  class="tr " v-bind:class="{active : item.isChecked}">
              <div class="td"><input v-model="item.isChecked" type="checkbox" checked /></div>
              <div class="td"><img  v-bind:src="item.icon" alt="" /></div>
              <div class="td">{{item.price}}</div>
              <div class="td">
                <div class="my-input-number">
                  <!-- v-bind:disabled="item.num <= 1"  动态控制按钮失效的 -->
                  <button v-bind:disabled="item.num <= 1" class="decrease" @click="sub(item.id)"> - </button>
                  <span class="my-input__inner">{{item.num}}</span>
                  <button class="increase"  @click="add(item.id)"> + </button>
                </div>
              </div>
              <div class="td">{{item.price * item.num}}</div>
              <div class="td"><button @click="del(item.id)">删除</button></div>
            </div>

          </div>
        </div>
        <!-- 底部 -->
        <div class="bottom">
          <!-- 全选 -->
          <label class="check-all">
            <input type="checkbox" v-model="isAll" />
            全选
          </label>
          <div class="right-box">
            <!-- 所有商品总价 -->
            <span class="price-box">总价&nbsp;&nbsp;:&nbsp;&nbsp;¥&nbsp;<span class="price">{{totalPrice}}</span></span>
            <!-- 结算按钮 -->
            <button class="pay">结算( {{totalCount}} )</button>
          </div>
        </div>
      </div>
      <!-- 空车 -->
      <div class="empty" v-else>🛒空空如也</div>
    </div>
    <script src="../vue.js"></script>
    <script>
      const app = new Vue({
        el: '#app',
        data: {
          // 水果列表
          fruitList: [
            {
              id: 1,
              icon: 'img/火龙果.png',
              isChecked: true,
              num: 2,
              price: 6,
            },
            {
              id: 2,
              icon: 'img/荔枝.png',
              isChecked: false,
              num: 7,
              price: 20,
            },
            {
              id: 3,
              icon: 'img/榴莲.png',
              isChecked: false,
              num: 3,
              price: 40,
            },
            {
              id: 4,
              icon: 'img/鸭梨.png',
              isChecked: true,
              num: 10,
              price: 3,
            },
            {
              id: 5,
              icon: 'img/樱桃.png',
              isChecked: false,
              num: 20,
              price: 34,
            },
          ],
        },
        methods : {
          del(id){
            this.fruitList = this.fruitList.filter((item) =>item.id !==id )
          },
          add(id){
            // 1. 根据id找到数组中相对应的项 ->find
            const fruit = this.fruitList.find(item => item.id === id)
            fruit.num++
          },
          sub(id){
            // 1. 根据id找到数组中相对应的项 ->find
            const fruit = this.fruitList.find(item => item.id === id)
            fruit.num--
          }
        },
        computed:{
          isAll:{
            get(){
              // 必循每一项的小选框都选中,全选按钮才选中 --every
              return this.fruitList.every(item => item.isChecked === true)
            },
            set(valvue){
              // 循环遍数组中的每一个项,让每一个项的isChecked 重新赋值
              // this.fruitList.forEach(item =>item.isChecked = valvue);
              this.fruitList.forEach(element => {
                element.isChecked =valvue
              });
            },
          },
            ///
            totalCount(){
              return this.fruitList.reduce((sum,item) => {
                if(item.isChecked){
                  //选中 
                  return sum + item.num 
                }else{
                  return sum
                }
              },0)
            },
            
            totalPrice(){
              return this.fruitList.reduce((sum,item) => {
                if(item.isChecked){
                  return sum + item.num * item.price
                }else{
                  return sum
                }
              },0)
            }
        },
        watch : {
          fruitList : {
            deep : true,
            handler (newValue) {
              localStorage.setItem('list',JSON.stringify(newValue))
            }
          }
        }


      })
    </script>
  </body>
</html>

二.生命周期

生命周期总结:四个阶段八个函数

  • 创建阶段和挂载阶段只执行一次,更新阶段执行多次,在更新阶段,当数据发生变化时,就会去更新视图,这是一个循环的过程

四个阶段

八个函数

  • beforeCreate是还没有进行创建阶段,类似于站在创建阶段前的门口
  • created是已经响应完数据了
  • 生命周期函数是与data配置项同级的

代码

<!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>
</head>
<body>

  <div id="app">
    <h3>{{ title }}</h3>
    <div>
      <button @click="count--">-</button>
      <span>{{ count }}</span>
      <button @click="count++">+</button>
    </div>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        count: 100,
        title: '计数器'
      },
      // 1. 创建阶段(准备数据)
      beforeCreate () {
        console.log('beforeCreate 响应式数据准备好之前', this.count)
      },
      created () {
        console.log('created 响应式数据准备好之后', this.count)
        // this.数据名 = 请求回来的数据
        // 可以开始发送初始化渲染的请求了
      },

      // 2. 挂载阶段(渲染模板)
      beforeMount () {
        console.log('beforeMount 模板渲染之前', document.querySelector('h3').innerHTML)
      },
      mounted () {
        console.log('mounted 模板渲染之后', document.querySelector('h3').innerHTML)
        // 可以开始操作dom了
      },

      // 3. 更新阶段(修改数据 → 更新视图)
      beforeUpdate () {
        console.log('beforeUpdate 数据修改了,视图还没更新', document.querySelector('span').innerHTML)
      },
      updated () {
        console.log('updated 数据修改了,视图已经更新', document.querySelector('span').innerHTML)
      },

      // 4. 卸载阶段
      beforeDestroy () {
        console.log('beforeDestroy, 卸载前')
        console.log('清除掉一些Vue以外的资源占用,定时器,延时器...')
      },
      destroyed () {
        console.log('destroyed,卸载后')
      }
    })
  </script>
</body>
</html>

控制台截图

案例

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <!-- CSS only -->
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
    />
    <style>
      .red {
        color: red!important;
      }
      .search {
        width: 300px;
        margin: 20px 0;
      }
      .my-form {
        display: flex;
        margin: 20px 0;
      }
      .my-form input {
        flex: 1;
        margin-right: 20px;
      }
      .table > :not(:first-child) {
        border-top: none;
      }
      .contain {
        display: flex;
        padding: 10px;
      }
      .list-box {
        flex: 1;
        padding: 0 30px;
      }
      .list-box  a {
        text-decoration: none;
      }
      .echarts-box {
        width: 600px;
        height: 400px;
        padding: 30px;
        margin: 0 auto;
        border: 1px solid #ccc;
      }
      tfoot {
        font-weight: bold;
      }
      @media screen and (max-width: 1000px) {
        .contain {
          flex-wrap: wrap;
        }
        .list-box {
          width: 100%;
        }
        .echarts-box {
          margin-top: 30px;
        }
      }
    </style>
  </head>
  <body>
    <div id="app">
      <div class="contain">
        <!-- 左侧列表 -->
        <div class="list-box">

          <!-- 添加资产 -->
          <form class="my-form">
            <input v-model.trim="name" type="text" class="form-control" placeholder="消费名称" />
            <input v-model.number="price" type="text" class="form-control" placeholder="消费价格" />
            <button @click="add" type="button" class="btn btn-primary">添加账单</button>
          </form>

          <table class="table table-hover">
            <thead>
              <tr>
                <th>编号</th>
                <th>消费名称</th>
                <th>消费价格</th>
                <th>操作</th>
              </tr>
            </thead>
            <tbody>
              <tr v-for="(item, index) in list" :key="item.id">
                <td>{{ index + 1 }}</td>
                <td>{{ item.name }}</td>
                <td :class="{ red: item.price > 500 }">{{ item.price.toFixed(2) }}</td>
                <td><a @click="del(item.id)" href="javascript:;">删除</a></td>
              </tr>
            </tbody>
            <tfoot>
              <tr>
                <td colspan="4">消费总计: {{ totalPrice.toFixed(2) }}</td>
              </tr>
            </tfoot>
          </table>
        </div>
        
        <!-- 右侧图表 -->
        <div class="echarts-box" id="main"></div>
      </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.0/dist/echarts.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script>
      /**
       * 接口文档地址:
       * https://www.apifox.cn/apidoc/shared-24459455-ebb1-4fdc-8df8-0aff8dc317a8/api-53371058
       * 
       * 功能需求:
       * 1. 基本渲染
       *    (1) 立刻发送请求获取数据 created
       *    (2) 拿到数据,存到data的响应式数据中
       *    (3) 结合数据,进行渲染 v-for
       *    (4) 消费统计 => 计算属性
       * 2. 添加功能
       *    (1) 收集表单数据 v-model
       *    (2) 给添加按钮注册点击事件,发送添加请求
       *    (3) 需要重新渲染
       * 3. 删除功能
       *    (1) 注册点击事件,传参传 id
       *    (2) 根据 id 发送删除请求
       *    (3) 需要重新渲染
       * 4. 饼图渲染
       *    (1) 初始化一个饼图 echarts.init(dom)  mounted钩子实现
       *    (2) 根据数据实时更新饼图 echarts.setOption({ ... })
       */
      const app = new Vue({
        el: '#app',
        data: {
          list: [],
          name: '',
          price: ''
        },
        computed: {
          totalPrice () {
            return this.list.reduce((sum, item) => sum + item.price, 0)
          }
        },
        created () {
          // 将以下的代码封装进getList方法重
          // const res = await axios.get('https://applet-base-api-t.itheima.net/bill', {
          //   params: {
          //     creator: '小黑'
          //   }
          // })
          // this.list = res.data.data

          this.getList()
        },
        mounted () {
          this.myChart = echarts.init(document.querySelector('#main'))
          this.myChart.setOption({
            // 大标题
            title: {
              text: '消费账单列表',
              left: 'center'
            },
            // 提示框
            tooltip: {
              trigger: 'item'
            },
            // 图例
            legend: {
              orient: 'vertical',
              left: 'left'
            },
            // 数据项
            series: [
              {
                name: '消费账单',
                type: 'pie',
                radius: '50%', // 半径
                data: [
                  // { value: 1048, name: '球鞋' },
                  // { value: 735, name: '防晒霜' }
                ],
                emphasis: {
                  itemStyle: {
                    shadowBlur: 10,
                    shadowOffsetX: 0,
                    shadowColor: 'rgba(0, 0, 0, 0.5)'
                  }
                }
              }
            ]
          })
        },

        methods: {
          async getList () {
            const res = await axios.get('https://applet-base-api-t.itheima.net/bill', {
              params: {
                creator: '小黑'
              }
            })
            this.list = res.data.data

            // 更新图表
            this.myChart.setOption({
              // 数据项
              series: [
                {
                  // data: [
                  //   { value: 1048, name: '球鞋' },
                  //   { value: 735, name: '防晒霜' }
                  // ]
                  data: this.list.map(item => ({ value: item.price, name: item.name}))
                }
              ]
            })
          },
          async add () {
            if (!this.name) {
              alert('请输入消费名称')
              return
            }
            if (typeof this.price !== 'number') {
              alert('请输入正确的消费价格')
              return
            }

            // 发送添加请求
            const res = await axios.post('https://applet-base-api-t.itheima.net/bill', {
              creator: '小黑',
              name: this.name,
              price: this.price
            })
            // 重新渲染一次
            this.getList()

            this.name = ''
            this.price = ''
          },
          async del (id) {
            // 根据 id 发送删除请求
            const res = await axios.delete(`https://applet-base-api-t.itheima.net/bill/${id}`)
            // 重新渲染
            this.getList()
          }
        }
      })
    </script>
  </body>
</html>

三.工程化开发和脚手架

1.脚手架 vue-cli

2. 标准化目录

注意:使用vscode打开工程化的项目时,规范化:在根目录打开  -->把跟文件拖进vscode中

根组件的介绍
<template>
  <div class="App">
    <div class="box" @click="fn"></div>
  </div>
</template>

<script>
// 导出的是当前组件的配置项
// 里面可以提供 data(特殊) methods computed watch 生命周期八大钩子
export default {
  created () {
    console.log('我是created')
  },
  methods: {
    fn () {
      alert('你好')
    }
  }
}
</script>

<style lang="less">
/* 让style支持less
   1. 给style加上 lang="less"
   2. 安装依赖包 less less-loader
      yarn add less less-loader -D (开发依赖)
*/
.App {
  width: 400px;
  height: 400px;
  background-color: pink;
  .box {
    width: 100px;
    height: 100px;
    background-color: skyblue;
  }
}
</style>

main.js的介绍
// 文件核心作用:导入App.vue,基于App.vue创建结构渲染index.html
// 1. 导入 Vue 核心包
import Vue from 'vue'

// 2. 导入 App.vue 根组件
import App from './App.vue'

// 提示:当前处于什么环境 (生产环境 / 开发环境)
Vue.config.productionTip = false

// 3. Vue实例化,提供render方法 → 基于App.vue创建结构渲染index.html
new Vue({
  // el: '#app', 作用:和$mount('选择器')作用一致,用于指定Vue所管理容器
  // render: h => h(App),
  render: (createElement) => {
    // 基于App创建元素结构
    return createElement(App)
  }
}).$mount('#app')

3. 组件化开发

4. 组件的三个部分

扩展

当在style标签中,有嵌套的css写法时,style的lang设置为less

  • 需要安装依赖包
  • yarn add less less-loader -D            -----  -D  开发环境
  • 嵌套css 
  •     <style lang="less">
  •            .APP{
  •                 .box {
  •                  }                    
  •             } 
  •    <style>
  • }

5.组件的注册

1.组件的局部注册

标签补齐快捷键设置 : tab建  :设置 -->搜 trigger  on tab  勾选

代码 组件的局部组测

<template>
  <div id="app">
    <!-- 头 -->
    <Header></Header>
    <!-- 体 -->
    <Main></Main>
    <!-- 低 -->
    <HmFooter></HmFooter>
  </div>
</template>

<script>
import HmHeader from './components/HmHeader.vue'
import HmMain from './components/HmMain.vue'
import HmFooter from './components/HmFooter.vue'

export default {
  name: 'App',
  components: {
    Header: HmHeader,
    Main: HmMain,
    // 同名,可以缩写
    HmFooter 
    
  }

}
</script>

<style>

</style>

2.组件的全局注册

  • 在main.js中注册,一个只能注册一个全局组件
  • Vue.component(‘组件名’, 导入的组件对象

四.组件之间的通信

1.组件三大组成部分的说明

  • 组件的默认样式是全局样式,会影响到其他组件的样式,实际中,每一个组件都应该有自己独立的样式,也就是局部样式,局部样式是在 <style scoped></style> 标签上加上scoped

2.组件中的data配置项必须是一个函数 ,只有这样,才能每次执行data方法时,才会得到一个新的数据对象,同一个组件生成的多个数据对象才不会互相影响

视图名.vue的配置项是一个对象

3.组件通信

1.父传子

  1. 在父组件里面的子组件标签中,以添加属性的方式传值
  2. 在子组件中,通过props进行接收
  3. 最后在子组件中渲染使用
代码
app.vue根组件
<template>
  <div id="app">
    <!-- 1.给组件标签,添加属性方式 赋值 -->
    <son v-bind:title="mytitle"></son>
  </div>
</template>

<script>
import Son from './components/Son.vue'

export default {
  name: 'App',

  data() {
    return {
      mytitle : '组件通信 : 父传子'
    }
  },
  components: {
    Son : Son
  }
}
</script>

<style>

</style>
son.vue子组件
<template>
  <div class="son" style="border:3px solid #000;margin:10px">
    <!-- 3.直接使用props的值 -->
    我是Son组件 {{title}}
  </div>
</template>

<script>
export default {
  name: 'Son-Child',
  // 2.通过props来接受,数组中的函数名要与被绑定的属性名相同。 <son v-bind:title="mytitle"></son>
  props : ['title']
}
</script>

<style>

</style>

2.子传父

son.vue子组件
<template>
  <div class="son" style="border:3px solid #000;margin:10px">
    我是Son组件 {{title}}
    <!--  1. 绑定一个函数,通过函数来触发$emit -->
    <button v-on:click="changeFn">子组件向父组件通信</button>
  </div>
</template>

<script>
export default {
  name: 'Son-Child',
  props: ['title'],
  methods: {
    changeFn() {
      // 2. 通过this.$emit() 向父组件发送通知  第一个参数是自定义名称,这个名称在父组件中绑定使用,
      // 第二参数是要修改的新值
      this.$emit("changTitle",'工业大学')
    }
  }
}
</script>

<style>

</style>
app.vue父组件
<template>
  <div id="app">
    <!-- 3. 绑定一个函数来接收,而绑定的事件不在是click这类事件,而是子组件中自定义名称-->
    <son v-bind:title="mytitle" v-on:changTitle="handleChange"></son>
  </div>
</template>

<script>
import Son from './components/Son.vue'

export default {
  name: 'App',

  data() {
    return {
      mytitle : '组件通信 : 父传子'
    }
  },
  methods: {
    // 4.newTitle 就是修改的新值 --> 工业大学
    handleChange(newTitle) {
      this.mytitle = newTitle
    }
  },
  components: {
    Son : Son
  }
}
</script>

<style>

</style>

注意点
  • vm.$emit("自定义的事件名" , "修改的值") 第一个参数:自定义事件名在父组件中作为绑定事件名使用,而第二个参数修改的值:就是子组件向父组件传递的数据 

3.props     

 property属性

代码
app.vue
<template>
  <div id="app">
    <!-- 3. 绑定一个函数来接收,而绑定的事件不在是click这类事件,而是子组件中自定义名称-->
    <UserInfo  
         :username="username" 
         :age="age" 
         :isSingle="isSingle" 
         :car="car" 
         :hobby="hobby">
    </UserInfo>
  </div>
</template>

<script>
import UserInfo from './components/UserInfo.vue'

export default {
  name: 'App',

  data() {
    return {
      username: '小帅',
      age: 28,
      isSingle: true,
      car: {
        brand: '宝马',
      },
      hobby: ['篮球', '足球', '羽毛球'],
    }
    },
  components: {
    UserInfo : UserInfo
  }
}
</script>

<style>

</style>
UserInfo.vue
<template>
  <div class="userinfo">
    <h3>我是个人信息组件</h3>
    <div>姓名:{{username}}</div>
    <div>年龄:{{age}}</div>
    <div>是否单身:{{isSingle}}</div>
    <div>座驾:{{car.brand}}</div>
    <div>兴趣爱好:{{hobby.join('、')}}</div>
  </div>
</template>

<script>
export default {
  props:['username','age','isSingle','car','hobby']
}
</script>

<style>
.userinfo {
  width: 300px;
  border: 3px solid #000;
  padding: 20px;
}
.userinfo > div {
  margin: 20px 10px;
}
</style>
props校验
  • 校验的属性名 : 校验的数据类型        -->  类型校验,校验传入值的类型是否符合校验的类型。

app.vue代码
<template>
  <div class="app">
    <BaseProgress :w="width" @WEmitEvent="receiveWEmitEvent"></BaseProgress>
  </div>
</template>

<script>
import BaseProgress from './components/BaseProgress.vue'
export default {
  data() {
    return {
      width: 50,
    }
  },
  components: {
    BaseProgress:BaseProgress, 
  },
  methods: {
    receiveWEmitEvent(newValue) {
      this.width = newValue
    }
  }
}
</script>

<style>
</style>

BaseProgress.vue代码
<template>
  <div class="base-progress">
    <div class="inner" :style="{ width: w + '%' }">
      <span>{{ w }}%</span><br><br>
      <button @click="changeW">修改</button>
    </div>
  </div>
</template>

<script>
export default {

  // 1.基础写法(类型校验)
  // props: {
  //   w: Number,
  // },

  // 2.完整写法(类型、默认值、非空、自定义校验)
  props: {
    w: {
      type: Number,
      required: true,
      default: 0,
      //  validator(val)中的参数val是传过来的值
      validator(val) {
        // console.log(val)
        if (val >= 100 || val <= 0) {
          // 自定义错误提示
          console.error('传入的范围必须是0-100之间')
          //  return false 表示校验不通过
          return false
        } else {
           //  return true 表示校验通过
          return true
        }
      },
    },
  },
  methods: {
    changeW() {
      this.$emit("WEmitEvent",30)
    }
  }
}
</script>

<style scoped>
.base-progress {
  height: 26px;
  width: 400px;
  border-radius: 15px;
  background-color: #272425;
  border: 3px solid #272425;
  box-sizing: border-box;
  margin-bottom: 30px;
}
.inner {
  position: relative;
  background: #379bff;
  border-radius: 15px;
  height: 25px;
  box-sizing: border-box;
  left: -3px;
  top: -2px;
}
.inner span {
  position: absolute;
  right: 0;
  top: 26px;
}
</style>
prop和data的区别

App.vue父组件的代码
<template>
  <div class="app">
    <BaseCount :count="count" @changeCount="handleChange"></BaseCount>
  </div>
</template>

<script>
import BaseCount from './components/BaseCount.vue'
export default {
  components:{
    BaseCount
  },
  data(){
    return {
      count:100
    }
  },
  methods:{
    handleChange(newVal){
      // console.log(newVal);
      this.count = newVal
    }
  }
}
</script>

<style>

</style>
BaseCount.vue子组件的代码
<template>
  <div class="base-count">
    <button @click="handleSub">-</button>
    <span>{{ count }}</span>
    <button @click="handleAdd">+</button>
  </div>
</template>

<script>
export default {
  // 1.自己的数据随便修改  (谁的数据 谁负责)
  // data () {
  //   return {
  //     count: 100,
  //   }
  // },
  // 2.外部传过来的数据 不能随便修改
  props: {
    count: {
      type: Number,
    },
  },
  methods: {
    handleSub() {
      // count不能++,因为count本质上是父组件的数据
      this.$emit('changeCount', this.count - 1)
    },
    handleAdd() {
      this.$emit('changeCount', this.count + 1)
    },
  },
}
</script>

<style>
.base-count {
  margin: 20px;
}
</style>

综合案例

app.vue父组件

<template>
  <!-- 主体区域 -->
  <section id="app">
    <TodoHeader @add="handleAdd"></TodoHeader>
    <TodoMain :list="list" @del="handelDel"></TodoMain>
    <TodoFooter :list="list" @clear="clear"></TodoFooter>
  </section>
</template>

<script>
import TodoHeader from './components/TodoHeader.vue'
import TodoMain from './components/TodoMain.vue'
import TodoFooter from './components/TodoFooter.vue'

// 渲染功能:
// 1.提供数据: 提供在公共的父组件 App.vue
// 2.通过父传子,将数据传递给TodoMain
// 3.利用 v-for渲染

// 添加功能:
// 1.手机表单数据  v-model
// 2.监听事件(回车+点击都要添加)
// 3.子传父,讲任务名称传递给父组件 App.vue
// 4.进行添加 unshift(自己的数据自己负责)
// 5.清空文本框输入的内容
// 6.对输入的空数据 进行判断

// 删除功能
// 1.监听事件(监听删除的点击) 携带id
// 2.子传父,讲删除的id传递给父组件的App.vue
// 3.进行删除filter(自己的数据 自己负责)

// 底部合计:父传子  传list 渲染
// 清空功能:子传父  通知父组件 → 父组件进行更新
// 持久化存储:watch深度监视list的变化 -> 往本地存储 ->进入页面优先读取本地数据
export default {
  data() {
    return {
      list: JSON.parse(localStorage.getItem('list')) || [
        { id: 1, name: '打篮球' },
        { id: 2, name: '看电影' },
        { id: 3, name: '逛街' },
      ],
    }
  },
  components: {
    TodoHeader,
    TodoMain,
    TodoFooter,
  },
  watch: {
    list: {
      deep: true,
      handler(newVal) {
        // 持久化到本地,将js对象转成json对象
        localStorage.setItem('list', JSON.stringify(newVal))
      },
    },
  },
  methods: {
    handleAdd(todoName) {
      // console.log(todoName)
      this.list.unshift({
        id: +new Date(),
        name: todoName,
      })
    },
    handelDel(id) {
      // console.log(id);
      this.list = this.list.filter((item) => item.id !== id)
    },
    clear() {
      this.list = []
    },
  },
}
</script>

<style>
</style>

TodoHeader.vue

<template>
   <!-- 输入框 -->
  <header class="header">
    <h1>小黑记事本</h1>
    <input placeholder="请输入任务" class="new-todo" v-model="todoName" @keyup.enter="handleAdd"/>
    <button class="add" @click="handleAdd">添加任务</button>
  </header>
</template>

<script>
export default {
  data(){
    return {
      todoName:''
    }
  },
  methods:{
    handleAdd(){
      // console.log(this.todoName)
      this.$emit('add',this.todoName)
      this.todoName = ''
    }
  }
}
</script>

<style>

</style>

TodoMain.vue

<template>
  <!-- 列表区域 -->
  <section class="main">
    <ul class="todo-list">
      <li class="todo" v-for="(item, index) in list" :key="item.id">
        <div class="view">
          <span class="index">{{ index + 1 }}.</span>
          <label>{{ item.name }}</label>
          <button class="destroy" @click="handleDel(item.id)"></button>
        </div>
      </li>
    </ul>
  </section>
</template>

<script>
export default {
  props: {
    list: {
      type: Array,
    },
  },
  methods: {
    handleDel(id) {
      this.$emit('del', id)
    },
  },
}
</script>

<style>
</style>

TodoFooter.vue

<template>
  <!-- 统计和清空 -->
  <footer class="footer">
    <!-- 统计 -->
    <span class="todo-count"
      >合 计:<strong> {{ list.length }} </strong></span
    >
    <!-- 清空 -->
    <button class="clear-completed" @click="clear">清空任务</button>
  </footer>
</template>

<script>
export default {
  props: {
    list: {
      type: Array,
    },
  },
  methods:{
    clear(){
      this.$emit('clear')
    }
  }
}
</script>

<style>
</style>

4.非父子通信

4.1 event bus事件总线

代码
1. 新建 utils/EventBus.js 文件代码   
// 事件总线,创建一个Vue实例

import Vue from 'vue'

const Bus  =  new Vue()

export default Bus
 2. App.vue 代码
<template>
  <div class="app">
    <BaseA></BaseA>
    <BaseB></BaseB>
    <BaseC></BaseC>
  </div>
</template>

<script>
import BaseA from './components/BaseA.vue'
import BaseB from './components/BaseB.vue'
import BaseC from './components/BaseC.vue'
export default {
  components:{
    BaseA,
    BaseB,
    BaseC
  }
}
</script>

<style>

</style>
3. BaseB.vue发送方的代码
<template>
  <div class="base-b">
    <div>我是B组件(发布方)</div>
    <button @click="sendMsgFn">发送消息</button>
  </div>
</template>

<script>
import Bus from '../utils/EventBus'
export default {
  methods: {
    sendMsgFn() {
      // vm.$emit() 第一个参数是自定义事件名,在接收方时,要一一对应
      // vm.$emit() 第二个参数是传输的数据,在接收方时,使用形参接收
      Bus.$emit('sendMsg', '今天天气不错,适合旅游')
    },
  },
}
</script>

<style scoped>
.base-b {
  width: 200px;
  height: 200px;
  border: 3px solid #000;
  border-radius: 3px;
  margin: 10px;
}
</style>
4. BaseA.vue 接收方的代码
<template>
  <div class="base-b">
    <div>我是B组件(发布方)</div>
    <button @click="sendMsgFn">发送消息</button>
  </div>
</template>

<script>
import Bus from '../utils/EventBus'
export default {
  methods: {
    sendMsgFn() {
      // vm.$emit() 第一个参数是自定义事件名,在接收方时,要一一对应
      // vm.$emit() 第二个参数是传输的数据,在接收方时,使用形参接收
      Bus.$emit('sendMsg', '今天天气不错,适合旅游')
    },
  },
}
</script>

<style scoped>
.base-b {
  width: 200px;
  height: 200px;
  border: 3px solid #000;
  border-radius: 3px;
  margin: 10px;
}
</style>
5. BaseC.vue 接收方的代码
<template>
  <div class="base-c">
    我是C组件(接受方)
    <p>{{msg}}</p>  
  </div>
</template>

<script>
import Bus from '../utils/EventBus'
export default {
  data() {
    return {
      msg: '',
    }
  },
  created() {
    Bus.$on('sendMsg', (msg) => {
      // console.log(msg)
      this.msg = msg
    })
  },
}
</script>

<style scoped>
.base-c {
  width: 200px;
  height: 200px;
  border: 3px solid #000;
  border-radius: 3px;
  margin: 10px;
}
</style>

事件总线是一个一对多的关系,一个组件发布信息,其他组件只要正确接收,都能收到传输的数据

4.2 跨层级传输数据,如爷孙,祖孙等等

  • provide : 提供;规定;供应;给予  -->  作为数据的发送者
  • inject : 添加,增加 --> 作为数据的接收者
1. app.vue 代码
<template>
  <div class="app">
    我是APP组件
    <button @click="change">修改数据</button>
    <SonA></SonA>
    <SonB></SonB>
  </div>
</template>

<script>
import SonA from './components/SonA.vue'
import SonB from './components/SonB.vue'
export default {

  provide() {
    return {
      // 简单类型 是非响应式的,this.color就是一个普通的值
      color: this.color,
      // 复杂类型 是响应式的,this.userInfo,是一个对象
      userInfo: this.userInfo,
    }
  },

  data() {
    return {
      color: 'pink',
      userInfo: {
        name: 'zs',
        age: 18,
      },
    }
  },
  methods: {
    change() {
      this.color = 'red'
      this.userInfo.name = 'ls'
    },
  },
  components: {
    SonA,
    SonB,
  },
}
</script>

<style>
.app {
  border: 3px solid #000;
  border-radius: 6px;
  margin: 10px;
}
</style>
2. SonA.vue代码
<template>
  <div class="SonA">我是SonA组件
    <GrandSon></GrandSon>
  </div>
</template>

<script>
import GrandSon from '../components/GrandSon.vue'
export default {
  components:{
    GrandSon
  }
}
</script>

<style>
.SonA {
  border: 3px solid #000;
  border-radius: 6px;
  margin: 10px;
  height: 200px;
}
</style>
3. SonB.vue代码
<template>
  <div class="SonB">
    我是SonB组件
  </div>
</template>

<script>
export default {

}
</script>

<style>
.SonB {
  border: 3px solid #000;
  border-radius: 6px;
  margin: 10px;
  height: 200px;
}
</style>
4. GrandSon.vue 是 SonA.vue 的子组件 
<template>
  <div class="grandSon">
    我是GrandSon
    {{ color }} -{{ userInfo.name }} -{{ userInfo.age }}
  </div>
</template>

<script>
export default {
  // 接收者,color等变量名要与provide中声明的一致
  inject: ['color', 'userInfo'],
}
</script>

<style>
.grandSon {
  border: 3px solid #000;
  border-radius: 6px;
  margin: 10px;
  height: 100px;
}
</style>

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值