带你从Vue入门到进阶

系统学习前端Vue框架,笔记记录于B站的why老师,具体视频链接,在此感谢老师的悉心授课。我的github笔记地址

欢迎访问我的博客

My Blog: https://coderblue.cn/
Github:https://github.com/CoderBleu
My Project:https://coderblue.cn/project/

初始化Vue

初始Vue

<script src="../js/vue.js"></script>
  <div id="hi">Hello {{name}}</div>
  <div class="movie">
      <ul>
          <li v-for="item in movies">
              {{item}}
          </li>
      </ul>
  </div>
  <script>
      const hi = new Vue({
          el: "#hi",
          data: {
              name: 'Vue.js'
          }
      })

      let movie = new Vue({
          el: '.movie',
          data: {
              movies: ["星际穿越", '大话西游', '海贼王之黄金城', '复仇者联盟']
              // 注意:可以通过movie.movies.push('盗梦空间')
          }
      })
  </script>

简易计数器

<body>
<!-- view -->
<div id="count">
    <h2>{{counter}}</h2>
    <button v-on:click="add">+</button>
    <button v-on:click="sub">-</button>
    <!-- <button v-on:click="counter++">+</button>
    <button v-on:click="counter--">-</button> -->
</div>
<ol>
    <li>原因是你的body中的div中没有设置id,vue没有绑定</li>
    <li>解决:body中加 div id="app" </li>
    <li>双向绑定:view,model,ViewModel</li>
</ol>
</body>
<script>
    // proxy: model
    const obj = {
        counter: 0
    }

    // ViewModel
    let count = new Vue({
        el: "#count",
        data: obj,
        methods: {
            add: function() {
                this.counter++;
            },
            sub: function(){
                this.counter--;
            }
        }
    })
</script>

Vue中的MVVM

MVVM简介
  MVVM 是Model-View-ViewModel 的缩写,它是一种基于前端开发的架构模式,其核心是提供对View 和 ViewModel 的双向数据绑定,这使得ViewModel 的状态改变可以自动传递给 View,即所谓的数据双向绑定。
  Vue.js 是一个提供了 MVVM 风格的双向数据绑定的 Javascript 库,专注于View 层。它的核心是 MVVM 中的 VM,也就是 ViewModel。 ViewModel负责连接 View 和 Model,保证视图和数据的一致性,这种轻量级的架构让前端开发更加高效、便捷。

MVVM模型

MVVM拆开来即为Model-View-ViewModel,有View,ViewModel,Model三部分组成。

  • View层:是视图、模版,负责将数据模型转化为UI展现出来。
  • Model层:是模型、数据,可以在Model层中定义数据修改和操作的业务逻辑。
  • ViewModel层:View层和Model层并没有直接联系,而是通过ViewModel层进行交互。ViewModel层通过双向数据绑定将View层和Model层连接了起来,使得View层和Model层的同步工作完全是自动的。

Vue.js中mvvm的体现
  Vue.js的实现方式,对数据(Model)进行劫持,当数据变动时,数据会出发劫持时绑定的方法,对视图进行更新。

实例分析如下:

Vue.js关于双向数据绑定的一些实现细节

  vue是采用Object.defineProperty的getter和setter,并结合观察者模式来实现数据绑定的。当把一个普通的javascript对象传给Vue实例来作为它的data选项时,Vue将遍历它的属性,用Object.defineProperty将它们转为getter/setter。用户看不到getter/setter,但是在内部它们让Vue追踪依赖。在属性被访问和修改时通知变化。

  • Observer相当于Model层观察vue实例中的data数据,当数据发生变化时,通知Watcher订阅者。
  • Compile指令解析器位于View层,初始化View的视图,将数据变化与更新函数绑定,传给Watcher订阅者。
  • Watcher是整个模型的核心,对应ViewModel层,连接Observer和Compile。所有的Watchers存于Dep订阅器中,Watcher将Observer监听到的数据变化对应相应的回调函数,处理数据,反馈给View层更新界面视图。
  • Dep消息订阅器,内部维护了一个数组,用来收集订阅者(watcher),数据变动触发notify函数,再调用订阅者的update方法。

基本模板语法

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <script src="../js/vue.js"></script>
  <style>
    [v-cloak] {
      display: none !important;
    }
  </style>
</head>

<body>
  <div id="app">
    <!-- 让未被编译的重新编译加载 
    Vue加入了延缓响应的指令v-cloak,在与css:[v-cloak] { display: none } 的配合下,
    可以隐藏未编译 Mustache 的标签直到实例准备完毕,v-cloak属性才会被自动去除,对应的标签也才可见了
    -->
    <h4 >Hello {{count}}</h4>
  </div>

  <script>
    setTimeout(() => {
      let app = new Vue({
      el: '#app',
      data: {
        count: 'v-cloak'
      },
      methods: {
      
      },
      beforeMount () {
        alert("有趣");
      }
    });
    }, 1000);
  </script>
</body>

</html>

动态绑定属性

class的绑定

传给 v-bind:class 一个对象,以动态地切换 class(语法糖:’:表示’)

根据isActive的true,false变化,动态绑定单个class

<div :class="{ active: isActive==true }"></div>

计算属性的方式绑定class

<div :class="classObject"></div>
data: {
  isActive: true,
  error: null
},
computed: {
  classObject: function () {
    return {
      active: this.isActive && !this.error,	//isActive为true,且error不为null
      'text-danger': this.error && this.error.type === 'fatal'
        //error为null且this.error.type === 'fatal'
    }
  }
}

数组的方式绑定class

<div v-bind:class="[activeClass, errorClass]"></div>
data: {
  activeClass: 'active',
  errorClass: 'text-danger'
}
渲染为:
<div class="active text-danger"></div>

三元表达式动态切换class(推荐)

<div :class="[isActive ? activeClass : '', errorClass]"></div>

style的绑定

v-bind:style 的对象语法十分直观——看着非常像 CSS,但其实是一个 JavaScript 对象。CSS 属性名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用单引号括起来) 来命名:

<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
data: {
  activeColor: 'red',
  fontSize: 30
}

直接绑定到一个样式对象通常更好,这会让模板更清晰:

<div :style="styleObject"></div>
data: {
  styleObject: {
    color: 'red',
    fontSize: '13px'
  }
}

v-bind:style 的数组语法可以将多个样式对象应用到同一个元素上:

<div v-bind:style="[baseStyles, overridingStyles]"></div>

计算属性

<body>
  <div id="app">
    <h3> {{fullName}}</h3>
  </div>

  <script>
    let app = new Vue({
      el: '#app',
      data: {
        fisrtName: 'Lom',
        lastName: 'Name'
      },
      computed: {
        // fullName: function(){
        //     return this.fisrtName + ' ' + this.lastName;
        // }

        // fullName: {
        //   get: function () {
        //     return this.fisrtName + ' ' + this.lastName;
        //   }
        // }

        // 底层方法
        fullName: {
          set: function (newValue) {
            console.log("---------", newValue);
            //split() 方法用于把一个字符串分割成字符串数组。
            const value = newValue.split(' ');
            this.fisrtName = value[0];
            this.lastName = value[1];
          },
          get: function () {
            return this.fisrtName + ' ' + this.lastName;
          }
        }
      }
    });
  </script>
</body>

计算属性和methods的对比

<body>
  <div id="app">
    <!-- 1. 通过字符串直接拼接,不美观 -->
    <h3> {{fisrtName}} {{lastName}}</h3>
    <!-- 2. 通过方法:调用方法一次就重新执行一次 -->
    <h3> {{getFullName()}}</h3>
    <!-- 3. 通过计算属性:执行一次后,会将此缓存起来,后面在调用会直接有结果显示,不会频繁调用 -->
    <h3> {{fullName}}</h3>
  </div>

  <script>
    const app = new Vue({
      el: '#app',
      data: {
        fisrtName: 'Lom',
        lastName: 'Name'
      },
      methods: {
        getFullName: function(){
          console.log("fullName");
          return this.fisrtName + ' ' + this.lastName;
        }
      },
      computed: {
        fullName: {
          get: function () {
            console.log("fullName");
            return this.fisrtName + ' ' + this.lastName;
          }
        }
      }
    });
  </script>
</body>

事件监听

v-on的基本使用

<body>
    <div id="app">
        <h3>{{counter}}</h3>
        <!-- v-on语法糖使用:@ -->
        <button @click="increment">+</button>
        <!-- 不需要传参数,故函数的双括号可以省略 -->
        <button v-on:click="decrement()">-</button>
    </div>

    <script>
        let app = new Vue({
            el: '#app',
            data: {
                counter: 0
            },
            methods: {
                increment(){
                    this.counter++
                },
                decrement(){
                    this.counter--
                }
            }
        });
    </script>
</body>

v-on的参数问题

<body>
    <div id="app">
        <!-- 不需要传参数,故函数的双括号可以省略 -->
        <button @click="btn0Click">+</button>
        <!-- 不传参数,会显示event对象 -->
        <button @click="btn1Click()">+</button>
        <!-- 带括号传参数 -->
        <button @click="btn2Click(value)">+</button>
        <!-- 定义方法时,我们既需要传参数,又需要获取到event对象,可以通过$event获得event对象 -->
        <button @click="btn3Click(value, $event)">+</button>
    </div>

    <script>
        let app = new Vue({
            el: '#app',
            data: {
                value: 13
            },
            methods: {
                btn0Click() {
                    console.log("btn1Click");
                },
                btn1Click() {
                    console.log("======", event);
                },
                btn2Click(value) {
                    console.log("------", value);
                },
                btn3Click(value, event) {
                    console.log("+++++", value, event);
                }
            },
        });
    </script>
</body>

v-on的修饰符

<body>
  <div id="app">
    <!-- 1. .stop修饰符的使用:阻止单击事件继续传播给后续定义的函数 -->
    <div @click="divClick">
      <button @click.stop="btnClick">点击</button>
    </div>

    <!-- 2. .prevent修饰符的使用:阻止事件的自带默认行为, -->
    <form action="">
      <input type="submit" value="提交" @click.prevent="submitClick"></button>
    </form>

    <!-- 3. .keyup修饰符的使用:当键盘被释放时触发事件 -->
    <input @keyup="keyup">松开</input>
    <br>
    <input @click.keyup="keyup">鼠标点击松开</input>

    <br>
    <!-- 3. .enter修饰符的使用:当键盘按下回车键触发事件 -->
    <input @keyup.enter="enter">回车</input>

    <br>
    <!-- 4. .once修饰符的使用: 只能触发一次回调 -->
    <button @click.once="once">只能点击一次</button>
  </div>
    
  <script>
    let app = new Vue({
      el: '#app',
      data: {

      },
      methods: {
        btnClick(){
          console.log("btnClick")
        },
        divClick(){
          console.log("divClick")
        },
        submitClick(){
          console.log("submitClick")
        },
        keyup(){
          console.log("keyup")
        },
        enter(){
          console.log("enter")
        },
        once(){
          console.log("once")
        }
      }
    });
  </script>
</body>

条件判断

v-if的基本使用

<body>
  <div id="app">
    <!-- 从控制台输入 app.isShow = false就可以将其隐藏 -->
    <h2 v-if="isShow">
        <div>abc</div>
        <div>abc</div>
        <div>abc</div>
        <div>abc</div>
        <div>abc</div>
        {{message}}
    </h2>
  </div>

  <script>
    let app = new Vue({
      el: '#app',
      data: {
        message: '哈哈',
        isShow: true
      },
      methods: {

      }
    });
  </script>
</body>

v-if和v-else的使用

<body>
  <div id="app">
    <!-- 从控制台输入 app.isShow = false就可以将其隐藏 -->
    <h2 v-if="isShow">
        <div>abc</div>
        <div>abc</div>
        <div>abc</div>
        <div>abc</div>
        <div>abc</div>
        {{message}}
    </h2>
    <h2 v-else>当v-if为false的时候,我就开始显示了,我和v-if要紧连着使用</h2>
  </div>

  <script>
    let app = new Vue({
      el: '#app',
      data: {
        message: '哈哈',
        isShow: true
      },
      methods: {

      }
    });
  </script>
</body>

v-if和v-else-if和v-else的使用

<body>
  <div id="app">
    <p v-if="score >= 90">优秀</p>
    <p v-else-if="score >= 80">良好</p>
    <p v-else-if="score >= 60">及格</p>
    <p v-else>不及格</p>
  </div>

  <script>
    let app = new Vue({
      el: '#app',
      data: {
        score: 99
      }
    });
  </script>
</body>

用户登录切换的案例

<body>
  <div id="app">
    <span v-if="isShowUserName">
      <label for="userName">用户名</label>
      <input type="text" id="userName" placeholder="请输入用户名">
    </span>
    <span v-else>
      <label for="email">用户邮箱</label>
      <input type="text" id="email" placeholder="请输入用户邮箱">
    </span>
    <button @click="isShowUser">切换类型1</button>
    <button @click="isShowUserName = !isShowUserName">切换类型2</button>
  </div>

  <script>
    let app = new Vue({
      el: '#app',
      data: {
        isShowUserName: true
      },
      methods: {
        isShowUser() {
          this.isShowUserName = !this.isShowUserName
        }
      }
    });
  </script>
</body>

用户登录切换的案例(复用的小问题)

<body>
  <div id="app">
    <!-- 
      注意:Vue在进行DOM渲染时,出于性能考虑,会尽可能的复用先前的input框,而不是重新创建元素
      如若我们不需要复用input框,只需要添加一个 key就好,相同的key值才会复用
    -->
    <span v-if="isShowUserName">
      <!-- label中的for指向input后,点击label可以将光标聚焦给input框 -->
      <label for="userName">用户名</label>
      <input type="text" id="userName" placeholder="请输入用户名" key="userName">
    </span>
    <span v-else>
      <label for="email">用户邮箱</label>
      <input type="text" id="email" placeholder="请输入用户邮箱" key="userName">
    </span>
    <button @click="isShowUser">切换类型1</button>
  </div>

  <script>
    let app = new Vue({
      el: '#app',
      data: {
        isShowUserName: true
      },
      methods: {
        isShowUser() {
          this.isShowUserName = !this.isShowUserName,
          //第二种方式:使用js将input框值清空
          document.getElementById("userName").value = '';
          document.getElementById("email").value = '';
        }
      }
    });
  </script>
</body>

v-show的使用

<body>
  <div id="app">
    <!-- 
      v-if 和 v-show的区别:
      v-if为false的时候,压根不会存在dom里面,
      v-show为false的时候,只是会增加个行内样式:display:none;

      建议:
      如果需要频繁切换的时候,使用v-show,
      只有一次切换时通常使用v-if
     -->
    <span v-if="isShow" id="isShow">V-if</span>
    <span v-show="isShow" id="VShow">V-show</span>
  </div>

  <script>
    const app = new Vue({
      el: '#app',
      data: {
        isShow: true
      },
      methods: {
      
      }
    });
  </script>
</body>

循环遍历

v-for遍历数组

<body>
  <div id="app">
    <!-- 1.简单遍历数组 -->
    <ul>
      <li v-for="item in movies">{{item}}</li>
    </ul>
    <br>
    <!-- 2.带索引遍历数组 -->
    <ul>
      <li v-for="(item, index) in movies">{{index + 1}}.{{item}}</li>
    </ul>
    <br>
    <!-- 3.带索引遍历数组,且添加监听事件 -->
    <ul>
      <li v-for="(item, index) in movies" @click="showIndex(index)">{{index + 1}}.{{item}}</li>
    </ul>
  </div>

  <script>
    const app = new Vue({
      el: '#app',
      data: {
        movies: ['海王', '大话西游', '星际争霸', '三傻大闹宝莱坞']
      },
      methods: {
        showIndex(index) {
          console.log('第' + (index + 1) + '个值');
        }
      }
    });
  </script>
</body>

v-for遍历对象

<body>
  <div id="app">
    <!-- 1.在遍历对象的过程中,如果只是获得一个值,那么获取到的就是整个对象的value值 -->
    <ul>
      <li v-for="item in obj">{{item}}</li>
    </ul>
    <br>
    <!-- 2.在遍历对象的同时附带格式:value和key (value, key) -->
    <ul>
      <li v-for="(item, key) in obj">{{item}}--{{key}}</li>
    </ul>
    <br>
    <!-- 3.在遍历对象的同时附带格式:value和key和index (value, key, index) -->
    <ul>
      <li v-for="(value, key, index) in obj">{{value}}--{{key}}--{{index+1}}</li>
    </ul>
    <br>
  </div>

  <script>
    const app = new Vue({
      el: '#app',
      data: {
        obj: {
          id: 12,
          name: 'Luck',
          height: 1.78
        }
      },
      methods: {

      }
    });
  </script>
</body>

v-for使用过程添加key

<body>
  <!-- 
    splice() 方法向/从数组中添加/删除项目,然后返回被删除的项目。
    1.app.obj.splice(4) 去除数组第五个值
    2.app.obj.splice(3,0,'F') 在数组第四个值的时候添加F
  -->
  <div id="app">
    <ul>
      <!-- 不添加key属性: 在数组中间插入数据,需要将插入位置后面的值都后移,效率慢 -->
      <!-- 添加key属性:类似于链表一样,我插入中间只需要将两端值的指向指给我,效率高-->
      <li v-for="item in obj" :key="item">{{item}}</li>
    </ul>
  </div>

  <script>
    const app = new Vue({
      el: '#app',
      data: {
        obj: ['A', 'B', 'C', 'D', 'E']
      },
      methods: {
      
      }
    });
  </script>
</body>

哪些数组的方法是响应式的

<body>
  <div id="app">
    <ul>
      <li v-for="item in obj" :key="item">{{item}}</li>
    </ul>
    <button @click="btnClick">点击</button>
  </div>

  <script>
    let myDate = new Date();
    const app = new Vue({
      el: '#app',
      data: {
        obj: ['R', 'C', 'B', 'D', 'E'],
      },
      methods: {
        btnClick() {
          //1.push()方法
          // this.obj.push('F')
          //2.pop()方法,从末尾依次删除
          // this.obj.pop()
          //3.shift()方法,从头开始依次删除
          // this.obj.shift()
          //4.unshift(),从头开始依次添加元素
          // this.obj.unshift('G','L')
          //5.splice(),方法向/从数组中添加/删除/替换元素,然后返回被删除的项目。
          //this.obj.splice(4) //去除数组第五个值
          //6.sort(),对数组的元素进行排序
          // this.obj.sort()
          //7.reverse(),对数组进行反转
          // this.obj.reverse()

          //注意:通过索引值修改数组中的元素方法,不可取,vue不会帮我渲染修改后的值
          // this.obj[0] = 'M'
          //解决方法:
          //删除第五个
          // this.obj.splice(3, 4, 'N')
          //①:在第三个位置修改成 N
          // this.obj.splice(2, 1, 'N')
          //②:建议:Vue自带的修改方法
          Vue.set(this.obj, 2, 'G')
        }
      }
    });
  </script>
</body>

对遍历出的列表,点击后对应的能变红

<body>
  <div id="app">
    <ul>
      <!-- 对遍历出来的结果列表,我能实现点击列表对应的值,能让它变成红色 -->
      <li v-for="(item, index) in obj" :key="item"
        :class="{active: currentIndex === index}" @click="changeColor(index)">{{item}}</li>
    </ul>
  </div>

  <script>
    let myDate = new Date();
    const app = new Vue({
      el: '#app',
      data: {
        obj: ['R', 'C', 'B', 'D', 'E'],
        currentIndex: 0
      },
      methods: {
        changeColor(index) {
          this.currentIndex = index;
        }
      }
    });
  </script>
</body>

书籍购物车案例

此案例包含JavaScript的高阶函数用法

// javascript的高阶函数:
const nums = [10, 20, 30, 40, 50];

// 编程式范式:命令式编程/声明式编程
// 编程式编程(第一公民:对象),函数式编程(第一公民:函数)
// filter/map/reduce高阶函数
// ①:
// filter高阶函数的使用: 它的回调函数有一个要求,必须返回一个boolean值
// true:当返回true时,函数内部会自动将这次回调的n加入到新的数组中去
// false:当返回false时,函数内部会过滤掉这次的 n
let newNum1 = nums.filter(function (n) {
  return n < 100;
})
console.log('newNum1==filter==' + newNum1);

// ②:
// map高阶函数的使用
let newNum2 = newNum1.map(function (n) {
  return n * 2;
})
console.log('newNum2==map==' + newNum2);
let newNum21 = nums.map(function (n) {
  // 判断条件无效,输出结果与上面一样,看来还是需要filter来过滤
  if (n < 80) {
    return n * 2;
  } else {
    return;
  }
})
console.log('newNum21==map==' + newNum21);

// ③:
//6.reduce高阶函数的使用: 对数组中的所有数据进行汇总(相加,相乘......)
let total = newNum2.reduce(function (preValue, value) {
  return preValue + value;
}, 0)
console.log('total===' + total);
// 长度为 5
//         preValue   value
//第一次:    0         20
//第二次:    20        40
//第三次:    60        60
//第四次:    120       80
//第五次:    200       100
//输出        300

//④:将上面三个函数综合起来使用:
let sum = nums.filter(function (n) {
  return n < 50
}).map(function (n) {
  return n * 2
}).reduce(function (preValue, value) {
  return preValue + value
}, 0)
console.log('sum===' + sum);

//⑤:使用箭头函数将上面三个函数综合起来使用(类似lombda表达式)
let sum1 = nums.filter(n => n < 50).map(n => n * 2).reduce((pre, value) => pre + value);
console.log('sum1===' + sum1);

index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <link rel="stylesheet" href="style.css">
</head>

<body>
  <div id="app">
    <div v-if="isFull">
      <table>
        <thead>
          <tr>
            <th>编号</th>
            <th>书名</th>
            <th>出版日期</th>
            <th>价格</th>
            <th>数量</th>
            <th>操作</th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="(item,index) in books" :class="{changeColor: number == 1}" @mouseenter="change(index)"
            @mouseleave="remove(index)">
            <td>{{item.id}}</td>
            <td>{{item.name}}</td>
            <td>{{item.date}}</td>
            <!-- 使用函数:<td>{{getfinalPrice(item.price)}}</td> -->
            <!-- 使用过滤器: -->
            <td>{{item.price | showPrice}}</td>
            <td>
              <button @click="subBtn(index)">-</button>
              {{item.count}}
              <button @click="addBtn(index)">+</button>
            </td>
            <td>
              <button v-if="exchange" @click="addItemBtn(index)">新增</button>
              <button v-else @click="removeBtn(index)">移除</button>
              <button @click="changeType()">切换类型</button>
            </td>
          </tr>
        </tbody>
      </table>
      <br>
      <span>总价格:{{showTotalPrice | showPrice}}</span>
    </div>
    <h2 v-else>购物车清空</h2>
  </div>
</body>
<script src="../js/vue.js"></script>
<script src="main.js"></script>

</html>

main.js

const app = new Vue({
  el: '#app',
  data: {
    books: [{
        id: 1,
        name: '算法导论',
        date: '2019-2',
        price: 87.21,
        count: 1,
        exchange: true
      },
      {
        id: 2,
        name: 'UNIX编程艺术',
        date: '2019-4',
        price: 97.21,
        count: 2,
        exchange: true
      },
      {
        id: 3,
        name: '编程珠玑',
        date: '2012-2',
        price: 77.21,
        count: 1,
        exchange: true
      },
      {
        id: 4,
        name: '大话西游',
        date: '2019-7',
        price: 100,
        count: 1,
        exchange: true
      }
    ],
    number: 2,
    exchange: false,
    isFull: true
  },
  computed: {
    showTotalPrice() {
      let totalPrice = 0;
      //1.普通for循环
      // for (let i = 0; i < this.books.length; i++) {
      //   totalPrice += this.books[i].price * this.books[i].count
      // }
      // return totalPrice

      //2.index是索引
      // for (let index in this.books) {
      //   totalPrice += this.books[index].price * this.books[index].count
      // }

      //3.for of
      // for (let item of this.books) {
      //   totalPrice += item.price * item.count
      // }

      //4.利用reduce函数来写
      return this.books.reduce(function (preValue, book) {
        return preValue + book.price * book.count
      }, 0)

      return totalPrice
    }
  },
  methods: {
    // 行内按钮操作
    subBtn(index) {
      if (this.books[index].count > 0) {
        this.books[index].count--
      }
    },
    addBtn(index) {
      this.books[index].count++
    },
    removeBtn(index) {
      this.books.splice(index, 1)
      if (this.books.length <= 0) {
        this.isFull = !this.isFull
      }
    },
    // 鼠标移动进区域,改变背景颜色
    change(index) {
      // this
      this.number = 1;
      this.active = ".changeColor{ background-color: #cae6e6}"
    },
    remove(index) {
      this.number = 2
    },
    // 改变按钮类型
    changeType() {
      this.exchange = !this.exchange
    },
    addItemBtn() {
      const obj = [5, '数值分析', '2018-8', 96.10, 2];
      this.books.push(obj)
    },
    // 格式化价格
    getfinalPrice(price) {
      return '¥' + price.toFixed(2);
    }
  },
  filters: {
    showPrice(price) {
      //.toFixed(2):保留小数点后两位
      return '¥' + price.toFixed(2);
    }
  }
})

style.css

table {
  border: 1px solid #cccccc;
  /* 消除表格的边框内距 */
  border-collapse: collapse;
  border-spacing: 0;
  width: 700px;
}
table thead {
  background-color: lightskyblue;
}
/* table tr:hover {
  background-color: pink;
} */
table tr th {
  border: 1px solid #cccccc;
}
table tr td {
  border: 1px solid #cccccc;
  text-align: center;
  padding: 20px;
}
.changeColor {
  background-color: #cae6e6
} 

v-model的使用

v-model双向绑定的基本原理

<body>
  <div id="app">
    <!-- v-model的基本使用 -->
    <input type="text" v-model="message">{{message}}
    <br>
    <!-- v-model的原理: -->
    <!-- 监听input内容改变事件 -->
    <input type="text" v-on:input="changeInput($event)">
    <input type="text" @input="changeInput">
    <br>
    <!-- 此方法:input中会直接有message的值 , 其中target是事件源-->
    <input type="text" v-bind:value="message" v-on:input="message = $event.target.value">
    <input type="text" :value="message" @input="message = $event.target.value">
  </div>

  <script>
    const app = new Vue({
      el: '#app',
      data: {
        message: '你好呀'
      },
      methods: {
        changeInput(event) {
          this.message = event.target.value;
        }
      }
    });
  </script>
</body>

v-model结合radio单选框使用

<body>
  <div id="app">
    <!-- name要一致,不然选择一个单选框,再选择另一个,之前那个还是被选中状态 -->
    <label for="male">
      <input type="radio" name="sex" value="" id="male" v-model="message"></label>
    <label for="female">
      <input type="radio" name="sex" value="" id="female" v-model="message"></label>
    <br>
    {{'你选中的值:' + message}}
  </div>

  <script>
    const app = new Vue({
      el: '#app',
      data: {
        message: ''
      },
      methods: {
        
      }
    });
  </script>
</body>

v-model结合checkbox多选框使用

<body>
  <div id="app">
    <!-- 单个多选框: 同意协议示范 -->
    <label for="agreeLisence">
      <input type="checkbox" v-model="isAgree">统一协议
    </label>
    <button :disabled="!isAgree">下一步</button>
    <br>
    <!-- 多个多选框:爱好 -->
    <input type="checkbox" value="" v-model="hobbies"><input type="checkbox" value="" v-model="hobbies"><input type="checkbox" value="rap" v-model="hobbies">rap
    <input type="checkbox" value="打篮球" v-model="hobbies">打篮球
    你选择的兴趣爱好是:{{hobbies}}
  </div>

  <script>
    const app = new Vue({
      el: '#app',
      data: {
        isAgree: false, //单选框
        hobbies: [] //多选框
      },
      methods: {
      
      }
    });
  </script>
</body>

v-model结合select下拉框使用

<body>
  <div id="app">
    <!-- 下拉框的单个使用 -->
    <select name="demo" v-model="fruit">
      <option value="香蕉">香蕉</option>
      <option value="苹果">苹果</option>
      <option value="葡萄">葡萄</option>
      <option value="梨子">梨子</option>
    </select>
    <h3>你选择的水果是:{{fruit}}</h3>
    <br>
    <!-- 下拉框的多个选中使用: 注意添加multiple,然后选择多个需要按住ctrl键 -->
    <select name="demo" v-model="fruits" multiple>
      <option value="香蕉">香蕉</option>
      <option value="苹果">苹果</option>
      <option value="葡萄">葡萄</option>
      <option value="梨子">梨子</option>
    </select>
    <h3>你选择的水果是:{{fruits}}</h3>
  </div>

  <script>
    const app = new Vue({
      el: '#app',
      data: {
        fruit: '香蕉',
        fruits: []
      },
      methods: {
      
      }
    });
  </script>
</body>

v-model结合v-for使用

<body>
  <div id="app">
    <!-- 单个多选框: 同意协议示范 -->
    <label v-for="(item, index) in originalHobbies">
      <!-- 如果绑定originalHobbies,点击下对应的多选框就会消失 -->
      <input type="checkbox" v-model="hobbies" :id="index+1" :value="item">{{item}}
    </label>
    你选择的兴趣爱好是:{{hobbies}}
  </div>

  <script>
    const app = new Vue({
      el: '#app',
      data: {
        isAgree: false, //单选框
        hobbies: [], //多选框
        originalHobbies: ['唱', '跳', 'rap', '打篮球']
      },
      methods: {
      
      }
    });
  </script>
</body>

v-model的修饰符使用

<body>
  <div id="app">
    <!-- 1.lazy修饰符: 懒加载,可以让数据被按下回车失去焦点后才会更新 -->
    <input type="text" v-model.lazy="message">{{message}}
    <hr>
    <!-- 2.number修饰符:可以将 只能输入数字 的类型转换成String -->
    <input type="number" v-model="age"> 
    <h3>{{age}}--{{typeof age}}</h3>
    <!-- 如果不想转换成String类型,只要添加 .number -->
    <input type="number" v-model.number="height"> 
    <h3>{{height}}--{{typeof height}}</h3>
    <hr>
    <!-- 3.去掉两端的空格 -->
    <input type="text" v-model.trim="name"> 
    <!-- 添加多个修饰符只需叠加后面就行,无先后顺序要求 -->
    <input type="number" v-model.lazy.number="height"> 
    <h3>{{name}}</h3>
  </div>

  <script>
    const app = new Vue({
      el: '#app',
      data: {
        message: '你好呀',
        age: 0,
        height: 1,
        name: ''
      },
      methods: {
      
      }
    });
  </script>
</body>

组件化开发

组件化的基本使用

<body>
  <div id="app">
    <my-cpn></my-cpn>
    <my-cpn></my-cpn>
    <my-cpn></my-cpn>
    <my-cpn></my-cpn>
  </div>

  <script>
    // 创建组件构造器
    const cpnC = Vue.extend({
      // ES6语法:` 号可以支持内容里面换行比 ''更好使用
      //如果有多个标签使用,必须有个div包裹起来,否则内容显示不完全
      template: `
        <div>
          <h2>组件化</h2>
          <h3>我是,哈哈哈哈</h3>
          <h3>我是,呵呵呵呵</h3>
        </div>
      `
    })
    // 注册组件
    Vue.component('my-cpn', cpnC)

    const app = new Vue({
      el: '#app',
      data: {

      },
      methods: {

      }
    });
  </script>
</body>

全局组件和局部组件

<body>
  <div id="app">
    <cpn></cpn>
    <cpn></cpn>
    <cpn></cpn>
    <cpn></cpn>
  </div>
  <div id="app2">
    <cpn></cpn>
  </div>
  <script>
    const cpnC = Vue.extend({
      template: `
        <div>
          <h2>组件化</h2>
          <h3>我是,哈哈哈哈</h3>
          <h3>我是,呵呵呵呵</h3>
        </div>
      `
    })

    // 注册全局组件
    Vue.component("cpn", cpnC)

    const app = new Vue({
      el: '#app',
      data: {

      },
      components: {
        // 注册局部组件,即只能在app里使用cpn这个组件
        cpn: cpnC
      }
    });

    const app2 = new Vue({
      el: '#app2',
    });
  </script>
</body>

父组件和子组件

<body>
  <div id="app">
    <cpn2></cpn2>
  </div>
  <script>
    const cpnC = Vue.extend({
      template: `
        <div>
          <h2>子组件</h2>
          <h3>我是,哈哈哈哈</h3>
          <h3>我是,呵呵呵呵</h3>
        </div>
      `
    })

    // 父组件:root组件
    const cpnC2 = Vue.extend({
      template: `
        <div>
          <h2>父组件</h2>
          <h3>我是,哈哈哈哈</h3>
          <h3>我是,呵呵呵呵</h3>
          // 这个子组件需要先注册
          <cpn1><cpn1/>
        </div>
      `,
      components: {
        cpn1: cpnC
      }
    })

    // 注册全局组件
    Vue.component("cpn", cpnC)
    const app = new Vue({
      el: '#app',
      data: {

      },
      components: {
        // 注册局部组件,即只能在app里使用这个组件
        cpn1: cpnC,
        cpn2: cpnC2
      }
    });

  </script>
</body>

组件的语法糖注册方式

<body>
  <div id="app">
    <cpn1></cpn1>
    <cpn2></cpn2>
  </div>
  <script>
    // const cpnC = Vue.extend()

    // 语法糖注册全局组件
    Vue.component("cpn1", {
      template: `
        <div>
          <h2>我是cpn1</h2>
          <h3>我是,哈哈哈哈</h3>
        </div>
      `
    })

    const app = new Vue({
      el: '#app',
      data: {

      },
      components: {
        // 语法糖注册局部组件,即只能在app里使用这个组件
        'cpn2': {
          template: `
          <div>
            <h2>我是cpn2</h2>
            <h3>我是,呵呵呵呵</h3>
          </div>
        `
        }
      }
    });

  </script>
</body>

组件模块的分离写法

<body>

  <div id="app">
    <cpn></cpn>
    <cpn1></cpn1>
  </div>

  <!-- 1.使用script标签:注意类型需要添加:text/x-template -->
  <script type="text/x-template" id="cpn">
    <div>
      <h2>我是cpn1</h2>
      <h3>我是,哈哈哈哈</h3>
    </div>
  </script>

  <!-- 2.使用template标签(推荐) -->
  <template id="cpn1">
    <div>
      <h2>我是cpn1</h2>
      <h3>我是,哈哈哈哈</h3>
    </div>
  </template>


  <script>
    // const cpnC = Vue.extend()

    // 语法糖注册全局组件
    Vue.component("cpn", {
      // '#cpn' 
      template: '#cpn1'
    })

    const app = new Vue({
      el: '#app',
      data: {

      }
    });
  </script>
</body>

组件中的数据存放问题

<body>

  <div id="app">
    <cpn></cpn>
    <cpn></cpn>
  </div>

  <template id="cpn1">
    <div>
      <h2>我是cpn1</h2>
      <h3>我是,哈哈哈哈</h3>
      <!-- 想要获取title,必须在组件里面定义一个函数,且有返回值 -->
      <h3>{{title}}</h3>
    </div>
  </template>


  <script>
    // const cpnC = Vue.extend()

    // 语法糖注册全局组件
    Vue.component("cpn", {
      // '#cpn' 
      template: '#cpn1',
      data() {
        return {
          title: '好好学习,天天向上'
        }
      }
    })

    const app = new Vue({
      el: '#app',
      data: {
        // 模板里的title不能获取到此值
        title: '好好学习,天天向上'
      }
    });
  </script>
</body>

组件中的data为什么必须是函数

<body>

  <div id="app">
    <cpn></cpn>
    <cpn></cpn>
    <hr>
    <cpn1></cpn1>
    <cpn1></cpn1>
  </div>

  <template id="cpn1">
    <div>
      <h3>当前计数:{{count}}</h3>
      <button @click="increment">+</button>
      <button @click="decrement">-</button>
    </div>
  </template>

  <template id="cpn2">
    <div>
      <h3>当前计数:{{count}}</h3>
      <button @click="increment">+</button>
      <button @click="decrement">-</button>
    </div>
  </template>

  <script>
    // 推荐:count数据不会共享
    // 使用data函数:不会引起连锁反应。即每个都是个新对象,值地址不一样,
    Vue.component("cpn", {
      // '#cpn' 
      template: '#cpn1',
      data() {
        return {
          count: 0
        }
      },
      methods: {
        increment() {
          this.count++
        },
        decrement() {
          this.count--
        }
      }
    })

    // count数据共享
    // 都是使用的这个obj常量
    const obj = {
      count: 0
    };
    Vue.component("cpn1", {
      // '#cpn' 
      template: '#cpn2',
      data() {
        return obj
      },
      methods: {
        increment() {
          this.count++
        },
        decrement() {
          this.count--
        }
      }
    })

    const app = new Vue({
      el: '#app',
    });
  </script>
</body>

组件通信-父组件向子组件传递数据

<body>
  <div id="app">
    {{movies.toString()}}
    <hr>
    <!-- 添加了前缀v-bind,vue会帮我们解析movies,不会当成字符串处理 -->
    <cpn v-bind:vmoives="movies" :vmessage="message"></cpn>
    <hr>
    <!-- 当成字符串处理 -->
    <cpn vmoives="movies" vmessage="message"></cpn>
  </div>

  <template id="cpn">
    <div>
      <h2>{{vmessage}}</h2>
      <ul v-for="(item,index) in vmoives">
        <li>{{index}}.{{item}}</li>
      </ul>
    </div>
  </template>

  <script>
    const cpn = {
      template: '#cpn',
      props: ['vmoives', 'vmessage'], //这种其实是表示变量名字,不能当成字符串
      data() {
        return {}
      },
      methods: {

      }
    }

    // 注册全局组件
    // Vue.component('cpn', cpn)
    
    const app = new Vue({
      el: '#app',
      data: {
        movies: ['海王', '海贼王', '航空母舰'],
        message: '真香'
      },
      components: {
        // ES6中的高阶写法,等同于 cpn: cpn
        cpn
      }
    });
  </script>
</body>

组件通信-props用法详解

<body>
  <div id="app">
    {{movies.toString()}}
    <hr>
    <cpn v-bind:propF="movies" :propC="message"></cpn>
    <hr>
  </div>

  <template id="cpn">
    <div>
      <h2>{{propC}}</h2>
      <ul v-for="(item,index) in propF">
        <li>{{index}}.{{item}}</li>
      </ul>
    </div>
  </template>

  <script>
    const cpn = {
      template: '#cpn',
      props: {
        // 基础的类型检查('null'匹配任何类型)
        propA: Number,
        // 多个可能的类型
        propB: [String, Number],
        // 必填的字符串
        propC: {
          type: String,
          required: true,
          default: '你好呀'
        },
        // 带有默认值的数字
        propD: {
          type: Number,
          default: 100
        },
        // 注意:类型是对象/数组时,默认值必须是一个函数
        // 带有默认值的对象
        propE: {
          type: Object,
          default: function () {
            return {
              message: 'hello'
            }
          }
        },
        // 带有默认值的对象
        propF: {
          type: Array,
          default() {
            return ['大话西游', '造梦西游']
          }
        },
        // 自定义验证函数
        propG: {
          validator: function (value) {
            // 这个值必须匹配下列字符串的一个
            return ['success', 'warning', 'danger'].indexOf(value) !== -1
          }
        }
      },
      data() {
        return {}
      }
    }

    const app = new Vue({
      el: '#app',
      data: {
        movies: ['海王', '海贼王', '航空母舰'],
        message: '真香'
      },
      components: {
        cpn
      }
    });
  </script>
</body>

组件通信-父传子(props不支持驼峰标识)

<body>
  <div id="app">
    <cpn v-bind:prop-f="movies" v-bind:prop-g="message"></cpn>
    <hr>
  </div>

  <template id="cpn">
    <div>
      <h2>{{propG}}</h2>
      <ul v-for="(item,index) in propF">
        <li>{{index}}.{{item}}</li>
      </ul>
    </div>
  </template>

  <script>
    const cpn = {
      template: '#cpn',
      props: {
        // 带有默认值的对象
        propE: {
          type: Object,
          default: function () {
            return {
              message: 'hello'
            }
          }
        },
        // 带有默认值的对象
        propF: {
          type: Array,
          default() {
            return ['大话西游', '造梦西游']
          }
        },
        // 自定义验证函数
        propG: {
          validator: function (value) {
            // 这个值必须匹配下列字符串的一个:如果要检索的字符串值没有出现,则该方法返回 -1。
            console.log(['success', 'warning', 'danger'].indexOf(value) !== -1);
            // 校验失败:Invalid prop: custom validator check failed for prop "propG".
            return ['success', 'warning', 'danger'].indexOf(value) !== -1
          }
        }
      },
      data() {
        return {}
      }
    }

    const app = new Vue({
      el: '#app',
      data: {
        movies: ['海王', '海贼王', '航空母舰'],
        message: 'succe'
      },
      components: {
        cpn
      }
    });
  </script>
</body>

组件通信-父子组件通信的案例

<body>
  <div id="app">
    <!-- cpnClick在父组件中定义的方法 -->
    <cpn v-on:item-click="cpnClick"></cpn>
    <hr>
    <cpn @item-click="cpnClick($event)"></cpn>
  </div>

  <template id="cpn">
    <div>
      <button v-for="(item,index) in categories" @click="btnClick(item)">{{item.name}}</button>
    </div>
  </template>

  <script>
    /*步骤:
    1.子组件:触发监听的事件,比如被点击了,
    2.然后发送自定义事件this.$emit('cpn中的自定义事件名', item)
    3.调用Vue中的事件监听函数,如若在html文件中,不饿能使用驼峰命名自定义函数
    */
    // 子组件
    const cpn = {
      template: '#cpn',
      data() {
        return {
          categories: [
            {id: 'a1', name: '热门推荐'},
            {id: 'a2', name: '手机数码'},
            {id: 'a3', name: '家用家电'},
            {id: 'a4', name: '电脑办公'}
          ]
        }
      },
      methods: {
        btnClick(item) {
          //发射事件:自定义事件(父组件的cpn中接收此事件的名字)
          // html不区分大小写,这里不能使用驼峰命名
          this.$emit('item-click', item)
        }
      }
    }

    // <!-- 父组件 -->
    const app = new Vue({
      el: '#app',
      components: {
        cpn
      },
      methods: {
        cpnClick(item) {
          console.log('cpnClick', item);
        }
      },
    });
  </script>
</body>

组件通信-数字游戏-1

<body>
  <div id="app">
    <cpn :number1="num1" :number2="num2"></cpn>
  </div>

  <template id="cpn">
    <div>
      <h2>双向绑定的是num1:{{dnum1}}</h2>
      <h2>props:{{number1}}</h2>
      <input type="text" v-model="dnum1">
      <h2>双向绑定的是num2:{{dnum2}}</h2>
      <h2>props:{{number2}}</h2>
      <input type="text"  v-model="dnum2">
    </div>
  </template>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        num1: 0,
        num2: 1
      },
      components: {
        cpn: {
          template: '#cpn',
          props: {
            number1: Number,
            number2: Number
          },
          data() {
            return {
              /* 
              Property or method "num2" 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
              即需要添加data(){}
              */
              dnum1: this.number1,
              dnum2: this.number2
            }
          }
        }
      },
      methods: {

      }
    });
  </script>
</body>

组件通信-数字游戏-2

<body>
  <div id="app">
    <!-- number1的值来源与Vue中data的num1 但是在组件中取值要用{{number1}},也就是props对应的值-->
    <cpn :number1="num1" :number2="num2" @change1props="change1props" @change2props="change2props"></cpn>
    <!-- 3.父组件接收传过来的自定义事件,Vue中的 "change2props"方法 -->
  </div>

  <template id="cpn">
    <div>
      <h2>双向绑定的是num1:{{dnum1}}</h2>
      <h2>props:{{number1}}</h2>
      <!-- 这方式同 v-model: 监听input框,调用组件中的 changeInputValue1事件 -->
      <input type="text" :value="dnum1" @input="changeInputValue1">
      <h2>双向绑定的是num2:{{dnum2}}</h2>
      <h2>props:{{number2}}</h2>
      <input type="text" :value="dnum2" @input="changeInputValue2">
    </div>
  </template>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        num1: 0,
        num2: 1
      },
      components: {
        cpn: {
          template: '#cpn',
          props: {
            number1: Number,
            number2: Number
          },
          data() {
            return {
              dnum1: this.number1,
              dnum2: this.number2
            }
          },
          methods: {
            changeInputValue1(event) {
              // 1.将input的值赋值到 dnum1 中去
              this.dnum1 = event.target.value;

              // 2.为了让父组件可以修改值,发送一个事件
              this.$emit('change1props', this.dnum1)

              // 将下面输入框的props值: 变成1/2
              this.dnum2 = this.dnum1 / 2
              this.$emit('change2props', this.dnum2)
            },
            changeInputValue2() {
              this.dnum2 = event.target.value;
              this.$emit('change2props', this.dnum2)

              // 将上面输入框的props值: 变成2倍
              this.dnum1 = this.dnum2 * 2
              this.$emit('change1props', this.dnum1)
            }
          },
        }
      },
      methods: {
        change1props(value) {
          // 改变 prop 中的num1的值
          console.log('dum1' + value);
          this.num1 = parseFloat(value);
        },
        change2props(value) {
          console.log('dum2' + value);
          this.num2 = parseFloat(value);
        }
      }
    });
  </script>
</body>

组件通信-父访问子-children-refs

<body>
  <div id="app">
    <cpn></cpn>
    <cpn></cpn>
    <cpn ref="refA"></cpn>
    <button @click="btnClick">按钮</button>
  </div>

  <template id="cpn">
    <div>
      我是子组件
    </div>
  </template>

  <script>
    const app = new Vue({
      el: '#app',
      data: {
        message: 'Lemon'
      },
      methods: {
        btnClick() {
          // 1. $.chlidren
          for (let item of this.$children) {
            console.log(item.name);
            item.showMessage()
          }

          // 2. $.refs: 仅仅会调动带有refs标示的
          // 这样有时候我们想即使在新增数据后,依旧能操控它
          console.log('refs' + this.$refs.refA.name);
        }
      },
      components: {
        cpn: {
          template: '#cpn',
          data() {
            return {
              name: 'Lemon',
              id: 1,
              height: 1.78
            }
          },
          methods: {
            showMessage() {
              console.log(this.id);
            }
          },
        }
      }
    });
  </script>
</body>

组件通信-子访问父-parent-root

<body>
  <div id="app">
    <cpn></cpn>
    <hr>
    <ccpn></ccpn>
  </div>

  <template id="cpn">
    <div>我是cpn子组件</div>
  </template>

  <!-- cpn的子组件 -->
  <template id="ccpn">
    <div>
      <h3>我是cpn的子组件</h3>
      <button @click="btnClick">ccpn按钮</button>
    </div>
  </template>

  <script>
    const ccpn = Vue.component('ccpn', {
              template: '#ccpn',
              methods: {
                btnClick() {
                  // 按理这个是cpn的子组件,this应该指ccpn对象,
                  // 调用this.$parent时,访问的是ccpn的父组件cpn,即返回对象是vuecomponents
                  // 调用this.$root时,访问的才应该是cpn的父组件,即返回对象是vue
                  // 可是我放在cpn的components里说ccpn没有注册
                  console.log('ccpn=', this.$root.message)
                }
              }
            })

    const app = new Vue({
      el: '#app',
      data: {
        message: 'Lemon'
      },
      methods: {

      },
      components: {
        cpn: {
          template: '#cpn',
          data() {
            return {
              name: '我是cpn的name'
            }
          },
          components: {
            ccpn
          }
        }
      }
    });
  </script>
</body>

组件化高级

slot-插槽的基本使用

<body>
  <!-- 
    1.插槽的基本使用:<slot></slot>
    2.插槽的默认值:<slot>传的元素/值:eg 哈哈哈</slot>
    3.如果有多个值,同时被放入到组件中进行替换,会一起作为替换元素
   -->
  <div id="app">
    <cpn>嘻嘻嘻</cpn>
    <cpn><button>按钮</button></cpn>
    <cpn>哈哈哈</cpn>
    <cpn></cpn>
  </div>

  <template id="cpn">
    <div>
      <h3>组件化开发</h3>
      <slot><button>按钮</button></slot>
    </div>
  </template>

  <script>
    const app = new Vue({
      el: '#app',
      data: {

      },
      methods: {

      },
      components: {
        cpn: {
          template: '#cpn'
        }
      }
    });
  </script>
</body>

slot-具名插槽的使用

<body>
  <div id="app">
    <cpn>嘻嘻嘻</cpn>
    <cpn><span slot="right">哈哈哈</span></cpn>
  </div>

  <template id="cpn">
    <div>
      <slot name="left">左边</slot>
      <slot name="center">中间</slot>
      <slot name="right">右边</slot>
      <slot>右边</slot>
    </div>
  </template>

  <script>
    const app = new Vue({
      el: '#app',
      data: {

      },
      methods: {

      },
      components: {
        cpn: {
          template: '#cpn'
        }
      }
    });
  </script>
</body>

什么是编译的作用域

<body>
  <!-- 总结:
  父组件模板的所有东西都会在父级作用域内编译
  子组件模板的所有东西都会在子级作用域内编译 -->
  <div id="app">
    <!-- 这个里面的isShow会先从所在模板里面顺下去找,即从Vue里找寻,
    因此Vue中的data的isShow才能影响显示与否 -->
    <cpn v-show="isShow"></cpn>
  </div>

  <template id="cpn">
    <div>
      <h2>我是组件</h2>
      <!-- 这个里面的isShowCpn会先从所在模板里面顺下去找,即从cpn里找寻, -->
      <h3 v-show="isShowCpn">我是哈哈哈</h3>
    </div>
  </template>

  <script>
    const app = new Vue({
      el: '#app',
      data: {
        isShow: true
      },
      methods: {

      },
      components: {
        cpn: {
          template: '#cpn',
          data() {
            return {
              isShow: false,
              isShowCpn: true
            }
          }
        }
      }
    });
  </script>
</body>

作用域插槽的准备

<body>
  <!-- 作用域插槽:父组件替换插槽的标签,但是内容由子组件来提供 -->
  <div id="app">
    <cpn></cpn>
    <hr>

    <cpn>
      哈哈哈哈·
      <!-- 目标获得子组件中的pLanguages -->
      <template>
        <div slot-scope="slot">
          <span v-for="(item,index) in slot.data">- {{item}} {{item}} - </span>
          <hr>
        </div>
      </template>
    </cpn>
    <hr>
    
    <cpn>
      <!-- 目标获得子组件中的pLanguagesv-slot:todo todo指向slot中的name="todo" -->
      <template v-slot:todo="slotProps">
        <div>
          加join():<span>- {{slotProps.data.join(' - ')}}</span><br>
          <span>{{slotProps.data}}</span>
        </div>
      </template>
    </cpn>
  </div>

  <template id="cpn">
    <div>
      <!-- :名字="cpn中对应需要获取的值" -->
      <slot :data="pLanguages"  name="todo">
        <ul>
          <li v-for="(item,index) in pLanguages">
            {{item}}
          </li>
        </ul>
      </slot>
    </div>
  </template>

  <script>
    const app = new Vue({
      el: '#app',
      data: {

      },
      methods: {

      },
      components: {
        cpn: {
          template: '#cpn',
          data() {
            return {
              pLanguages: ['Java', 'C', 'C++', 'Python', 'C#']
            }
          }
        }
      }
    });
  </script>
</body>

前端模块化

ES模块化的实现

aaa.js

var name = '小红'
let age = 18
var flag = true

function sum(num1, num2) {
  return num1 + num2
}

if (flag) {
  console.log(sum(200, 300));
}

export {
  flag, sum
}

bbb.js

var name = '小红'
var flag = false

// var name = '小明'
// let age = 18
// var flag = true

// function sum(num1, num2) {
//   return num1 + num2
// }

// if (flag) {
//   console.log(sum(20, 30));
// }

// export {
//   flag, sum
// }

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <!-- 出现了跨域的问题,但是原理基本这样 -->
  <script src="aaa.js" type="module"></script>
  <script src="bbb.js" type="module"></script>
  <script src="mmm.js" type="module"></script>
</body>
</html>

mmm.js

import {flag} from "./aaa.js";

if (flag) {
  console.log('小明是天才,哈哈哈哈');
}

webpack使用

webpack的起步

info.js

export const name = 'why'
export const age = 18
export const height = 1.78

main.js

// 1.使用commonjs的模块化规范
const {add, mul} = require('./mathUtils.js')

console.log(add(20, 30));
console.log(mul(25, 30));

// 2.使用ES6的模块化的规范
import {name, age, height} from "./info";

console.log(name);
console.log(age);
console.log(height);

mathUtils.js

function add(num1, num2) {
  return num1 + num2
}

function mul(num1, num2) {
  return num1 * num2
}

module.exports = {
  add,
  mul
}

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <!-- 
    PS D:\Web_Study\vue.js学习> cd  12-webpack使用\1-webpack的起步
    PS D:\Web_Study\vue.js学习\12-webpack使用\1-webpack的起步> webpack ./src/main.js ./dist/bundle.js -->
    <!-- 生成文件的位置 -->
  <script src="./dist/bundle.js"></script>
</body>
</html>

webpack的配置

webpack.config.js

const path = require('path')

// npm init; npm install

module.exports = {
  entry: './src/main.js',
  output: {
    // 动态获取路径:resolve拼接地址
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js'
  },
}

package.json

{
  "name": "meetwebpack",
  "version": "1.0.0",
  "description": "",
  "main": "webpack.config.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^3.6.0"
  }
}

webpack配置loader和vue

webpack.config.js

const path = require('path')

// npm init; npm install

module.exports = {
  entry: './src/main.js',
  output: {
    // 动态获取路径:resolve拼接地址
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
    // 可以显示加载后的图片
    // publicPath: '/dist'
    publicPath: 'dist/'
  },
  module: {
    rules: [{
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      }, {
        test: /\.less$/,
        use: [{
          loader: "style-loader" // creates style nodes from JS strings
        }, {
          loader: "css-loader" // translates CSS into CommonJS
        }, {
          loader: "less-loader" // compiles Less to CSS
        }]
      }, {
        test: /\.(png|jpg|gif)$/,
        use: [{
          loader: 'url-loader',
          options: {
            // 如果limit小于文件大小 * 1024,就会报错,Cannot find module 'file-loader'
            // 一般配置成8kb
            limit: 8192,
            name: 'img/[name].[hash:8].[ext]'
          }
        }]
      }, {
        test: /\.js$/,
        // 排除
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['es2015']
          }
        }
      }
    ],
  },
}

html

index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>

<body>
  <!-- 
    webpack官网:https://www.webpackjs.com/loaders/babel-loader/
    重命名会让配置正确的出错,需要重新安装
    如若出现,ERROR in Entry module not found: Error: Can't resolve 'babel-loader' in 'D:\Web_Study\vue.js学习\12-webpack使用\4-webpack配置vue'
    请 cnpm install babel-loader@8.0.0-beta.0 @babel/core @babel/preset-env webpack

    0. 动态获取webpack的dist配置路径
        //入口
        entry: './src/main.js',
        //输出
        output: {
          // 动态获取路径:resolve拼接地址
          path: path.resolve(__dirname, 'dist'),
          filename: 'bundle.js',
        },
    使用 cnpm run build 启动方式配值:
    1.安装本地webpack
    2.在package.json文件中的script添加 "build": "webpack"
    3.然后输入cnpm run build,就会在webpack.config.js中根据动态路径创建
    4.加载css文件:cnpm install --save-dev css-loader
    5.解析加载进去的css文件:cnpm install --save-dev style-loader
    6.在webpack.config.js中的module.exports = {}中添加
    module: {
      rules: [
        {
          test: /\.css$/i,
          use: ['style-loader', 'css-loader'],
        },
      ],
    },

    7.导入less文件,并转换成css文件:cnpm install --save-dev less-loader less
    8.在webpack.config.js中的module.exports = {}中添加
     module: {
        rules: [{
            test: /\.less$/,
            use: [{
                loader: "style-loader" // creates style nodes from JS strings
            }, {
                loader: "css-loader" // translates CSS into CommonJS
            }, {
                loader: "less-loader" // compiles Less to CSS
            }]
        }]
    }

    9.加载图片:cnpm install --save-dev url-loader
    10.{
        test: /\.(png|jpg|gif)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              // 如果limit小于文件大小 * 1024,就会报错,Cannot find module 'file-loader'
              // 如果要加载的图片小于8kb,就会将加载的图片转换成base64
              // 一般配置成8kb
              limit:8192
            }
          }
        ]
      }
      11.404  会把图片发布在dist文件夹里,但是我们css文件中还是在找img里的图片
      GET file:///D:/Web_Study/vue.js%E5%AD%A6%E4%B9%A0/12-webpack%E4%BD%BF%E7%94%A8/4-webpack%E7%9A%84lcss%E6%A0%B7%E5%BC%8F-
        less%E6%96%87%E4%BB%B6/c67dcb9e8b50af7c2550f6da0c40f7e0.jpg net::ERR_FILE_NOT_FOUND
      12. 解决方法:可以显示加载后的图片 publicPath: '/dist'
      在webpack.config.js中的output里添加
      // 可以显示加载后的图片
      publicPath: '/dist'
      13. 让加载后的图片在dist里的指定目录:
      图片名字  img文件夹/[name]此为变量名/hash为32位,截取8位/ext:拓展名
      注意:若只写name就是固定值,即一直都是这个名字
      name: 'img/[name].[hash:8].[ext]'
      14. 显示图片:publicPath: 'dist/'

      15. ES6语法 转换成 ES5语法:
      ①:cnpm install --save-dev babel-loader@7 babel-core babel-preset-es2015
      ②:配置文件中添加:
            module: {
        rules: [
          {
            test: /\.js$/,
            exclude: /(node_modules|bower_components)/,
            use: {
              loader: 'babel-loader',
              options: {
                presets: ['@babel/preset-env']
              }
            }
          }
        ]
      }
    -->
  <script src="./dist/bundle.js"></script>
</body>

</html>

js

main.js

// 1.使用commonjs的模块化规范
const {add, mul} = require('./js/mathUtils.js')

console.log(add(20, 30));
console.log(mul(25, 30));

// 2.使用ES6的模块化的规范
import {name, age, height} from "./js/info";

console.log(name);
console.log(age);
console.log(height);

// 3.依赖css文件
require('./css/normal.css')

// 4.依赖less文件
require('./css/special.less')
document.writeln('<h2>你好呀,李银河</h2>')

info.js

export const name = 'why'
export const age = 18
export const height = 1.78

mathUtils.js

function add(num1, num2) {
  return num1 + num2
}

function mul(num1, num2) {
  return num1 * num2
}

module.exports = {
  add,
  mul
}

css

normal.css

body {
  /* background: mediumaquamarine; */
  background: url("../img/timg1.jpg")
}

special.less

@fontSize: 50px;
@fontColor: orange;

body {
  font-size: @fontSize;
  color: @fontColor
}

模块展示

vue-cli3使用

App.vue

<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js App"/>
  </div>
</template>

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

export default {
  name: 'app',
  components: {
    HelloWorld
  }
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

HelloWorld.vue

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  }
}
</script>

<style scoped>
h3 {
  margin: 40px 0 0;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
</style>

main.js

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

Vue.config.productionTip = false

new Vue({
  // 使用的是runtime-only
  render: h => h(App),
  // render: h => {
  //   return h(App)
  // }
}).$mount('#app')
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值