Vue学习笔记

一、Vue核心

1.1 初识Vue

  1. 想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象

  2. root容器里的代码依然符合htm1规范,只不过混入了一些特殊的Vue语法

  3. root容器里的代码被称为Vue模板

  4. Vue实例和容器是一一对应的

  5. 真实开发中只有一个Vue实例,并且会配合着组件一起使用

  6. {{xxx}}中的xxx要写js表达式,且xxx可以自动读取到data中的所有属性

  7. 一旦data中的数据发生改变,那么模板中用到该数据的地方也会自动更新

注意区分:js表达式 和 js代码(语句)

  1. 表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方,例如:

    a
    a+b
    demo(1)
    x === y ? 'a' : 'b'
  2. js代码(语句)

    if(){
    ​
    }
    ​
    for(){
    ​
    }

1.2 Vue模板中的语法

  1. 插值语法

    • 功能:用于解析标签体中用到的Vue语法内容

    • 写法:{{xxx}},xxx是 js 表达式,且可以直接读取到data中的所有属性

    • 注意:在调用方法时,{{方法名}}是将方法的代码调出来,而{{方法名()}}是将方法的返回值调出来

  2. 指令语法:

    • 功能:用于解析标签中用到的Vue语法内容

    • 写法:v-???? : 标签的属性 ="js表达式",js表达式也可以直接获取data中的所有属性

    • 举例:<a v-bind:href="js表达式"></a>v-bind可以简写为<a :href="js表达式"></a>

  3. Vue中有2种数据绑定的方式:

    • 单向绑定(v-bind): 数据只能从data流向页面

    • 双向绑定(v-model): 数据不仅能从data流向页面,还可以从页面流向data

      • 备注:

        • 双向绑定一般都应用在表单类元素上(如: input、select等)

        • v-model:value 可以简写为v-model,因为v-model默认收集的就是value值

1.3 Vue实例中的el与data的两种写法

  • el(推荐)与data的第一种写法:

    
           new Vue({
            el: "#root",    //直接在new对象时指定(推荐)
            data: {         //对象式
              name: "张三", 
            },
          });

  • el的第二种写法:使用一个变量来接收Vue实例,然后通过v.$mount("#root");设定与哪个元素挂载

          const v = new Vue({
            data: {
              name: "张三",
            },
          });
          v.$mount("#root");
  • data的第二种写法:函数式(推荐)

          new Vue({
            el: "#root",
            data: function () {
              return {
                name: "李四",
              };
            },
          });
    ​
    ###########可以简写为:
          new Vue({
            el: "#root",
            data(){
              return {
                name: "李四",
              };
            },
          });
  • 注意:

    • Vue所管理函数不能使用箭头函数,因为如果使用了箭头函数这个函数将被Vue外面一层的实例也就是window实例调用,而不是被Vue实例调用

    • 不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等),最好写成箭头函数,这样this往外找一层就是Vue实例了

1.4 MVVM模型

  • M:模型(Model),data种的数据

  • V:视图(View),模板代码

  • VM:视图模型(ViewModel),Vue实例

  • 观察发现:

    • data中的所有键值对,都成为了Vue实例上的某个属性

    • vm也就是Vue实例上的每个属性都可以在模板代码中直接调用

1.5 数据代理

1.5.1 Js中的 Object.defineProperty(操作对象,'操作的变量名', {} )函数

      let number = 19; //变量用 let,常量用 const
      let person = {
        name: "张三",
        sex: "男",
      };
​
      Object.defineProperty(person, "age", {
        // value: 18,
        // enumerable: true, //控制属性是否可以跟对象中的其他属性一起被遍历,默认false
        // writable: true, //控制对象是否可以被修改,默认是false
        // configurable: true, //控制对象是否可以被删除,默认是fasle
​
        //get: function()可简写为get(),当调用对象的这个属性时就会调用这个方法
        get() {
            return number;
        },
​
        //set: function(value)可简写为set(value),当想要修改对象中的这个属性值时,就会调用这个方法,value就是传进来的值
        set(value) {
          number = value;  
        }
      });
  • 注意区分Object.defineProperty()Object.defineProperties()

1.5.2 Vue中的数据代理

  • 利用Object.defineProperty()来通过vm对象来代理data对象中属性的操作(读 / 写)

  • Vue中数据代理的好处:

    • 更方便的操作data中的数据

  • 基本原理:

    • 通过Object.defineProperty()把data中对象中所有属性添加到vm上。为每一个添加到vm上的属性,都指定一个 getter / setter ,在 getter / setter 中去操作vm对象中的_data属性中的值(我们写的data域对象最后会存储到vm的_data属性中)

1.6 事件处理

1.6.1 事件的基本使用--点击事件为例

  • v-on:click="方法名()"可简写为@click=方法名(),没有传参时括号可省=>@click=方法名

  • 这里使用的方法必须放在vm的methods里,且不能用箭头函数,由vm调用

  • @cilick="方法名"@click="方法名($event)"效果一致,都会将发生此事件的event对象传入方法中,区别在与后者可以在$event这个占位符后传入其他的参数,而前者直接传入其他参数则会覆盖event对象

  •     <div id="root">
          <p>你好,{{name}}!!!</p>
          <!-- <button v-on:click="changeName">欢迎下一个</button> -->
          <button @click="changeName($event,'李四')">欢迎李四</button>
        </div>
        <script>
          const vm = new Vue({
            el: "#root",
            data: {
              name: "张三",
            },
            methods: {
              changeName(event, lisi) { //在对象中创建方法不用function关键字
                this.name = lisi;
                event.target.innerText = "欢迎王五";
              },
            },
          });
        </script>
  • 注意:这里事件绑定的方法也可以放到data中,但这样的话就会进行数据代理,浪费性能,也就是说将方法放到methods中是为了避免放到data中后进行数据代理,但方法名依然会出现在vm的属性中

1.6.2 事件修饰符

  1. prevent:阻止默认事件,像这里的a标签在执行完prevent函数后就不会跳转到B站首页了

        <div id="root">
          <!-- prevent:阻止默认事件 -->
          <a href="https://www.bilibili.com" @click.prevent="prevent">阻止默认事件</a>
        </div>
        <script>
          new Vue({
            el: "#root",
            methods: {
              prevent() {
                window.alert("阻止默认事件");
              },
            },
          });
        </script>
  2. stop:阻止事件冒泡,只调用一次stop修饰的那层以内方法,外面的不调用

    • 事件冒泡:事件的触发分成两个阶段,捕获 -> 冒泡,捕获就是从最外层元素从外到内开始找到触发事件的元素;然后就是冒泡过程,从触发事件的元素开始从内到外调用每一层元素与触发事件是同一事件绑定的方法

       <div id="root"> 
         <div id="div1" @click="log1">
            1
            <div id="div2" @click="log2">
              2
              <div id="div3" @click.stop="log3">3</div>
            </div>
          </div>
        </div>
  3. once:事件只触发一次,除非刷新页面,否则第二次点击将无效

    <button @click.once="onceTest">onceTest</button>
    ​
              onceTest() {
                window.alert("欸嘿嘿我只触发一次");
              },

1.6.3 键盘事件

  1. Vue中常用的按键别名:

    • 回车(Enter):enter

    • 删除(Del)、退格(Back):delete

    • 退出(Esc):esc

    • 换行(Tab):tab

    • 上:up

    • 下:down

    • 左:left

    • 右:right

  2. 我们可以通过@keyup@keydown搭配别名进行绑定方法,例如下面就是当按下Enter键时调用该方法

    <input type="text" placeholder="回车输出至控制台" @keydown.enter="enterToLog"/>
    ​
              enterToLog(event) {
                console.log(event.target.value);
              },
  3. Vue未提供别名的按键,可以在方法中使用event.key获取按键名,然后再通过按键名去绑定按键。但要注意将驼峰命名转为短横线命名方式,例如大小写键 CapsLock 就要转为 caps-lock第一点说的别名本质上只是为这些按键名起了个别名,方便调用

          <input
            type="text"
            placeholder="大小写转换输出至控制台"
            @keydown.caps-lock="capsLockToLog"
          />
          
          capsLockToLog(event) {
             console.log(event.target.value);
          },
  4. Tap键系统修饰按键:ctrl、alt、shift、meta(即win) 这五个按键有点特殊

    • Tap键:这个按键因为在按下Tap键再弹起后会进行换行操作,无法覆盖

      • @keyup.tap:如果用keyup的话将被系统的换行操作覆盖,无法触发事件

      • @keydown.tap:用keydown才能触发事件,但触发事件之后依然会进行换行操作

    • 系统修饰按键:

      • @keyup.ctrl:在按下的同时再按下其他键,随后释放其他键,这个事件才会被触发

      • @keydown.ctrl:正常触发事件

1.7 计算属性--computed

  1. 定义:需要的属性不存在与data中,需要用data中的属性计算得来

  2. 原理:底层也是运用了Object.defineProperty()提供的getter和setter

  3.     <div id="root">
          姓:<input type="text" v-model="firstName" /><br /><br />
          名:<input type="text" v-model="lastName" /><br /><br />
          全名:{{fullName}}
        </div>
        <script>
          const vm = new Vue({
            el: "#root",
            data: {
              firstName: "张",
              lastName: "三",
            },
            computed: {   //使用computed配置项,与data、methods一样,会数据代理成vm属性
              fullName: {
                get() {
                  return this.firstName + "-" + this.lastName;
                },
                set(value) {
                  const arr = value.split("-");
                  this.firstName = arr[0];
                  this.lastName = arr[1];
                },
              },
            },
          });
        </script>
  4. 优势:与用methods在方法中进行计算后再返回相比,computed里面的属性有缓存机制,效率更高,调试方便

  5. computed中属性的缓存机制:

    • 缓存依赖于get()函数的调用时机:

      • 第一次读取计算属性时调用并加载到缓存区

      • 这个计算属性所依赖的data属性发生变化时会重新加载计算后的值放到缓存区

  6. 注意:

    • 计算属性最后也会出现在vm上,直接读取使用即可

    • 如果计算属性需要进行修改,必须写set()函数,且set()函数中要修改计算属性所依赖的data属性

  7. 当确定数据不会进行修改,只有get()方法时,可以对计算属性进行简写

          const vm = new Vue({
            el: "#root",
            data: {
              firstName: "张",
              lastName: "三",
            },
            computed: {
                //fullName:function() {
                //    return this.firstName + "-" + this.lastName;
                //}
                //又因为function可以省略,因此有了下面的写法
                fullName() {
                    return this.firstName + "-" + this.lastName;
                }
            },
          });
    ​

1.8 监视属性--watch

  • 当被监视的属性变化时,自动调用配置的handler(newVal, oldVal)方法

  • 被监视的方法必须存在,若不存在也不会报错

  • handler方法会被传入两个参数,第一个时修改后的新数据,第二个是修改前的旧数据

  •     <div id="root">
          <h2>今天天气很{{weather}}</h2>
          <button @click="changeWeather">切换天气</button>
        </div>
        <script>
          const vm = new Vue({
            el: "#root",
            data() {
              return {
                isHot: false,
              };
            },
            computed: {
              weather() {
                return this.isHot ? "炎热" : "凉爽";
              },
            },
            methods: {
              changeWeather() {
                this.isHot = !this.isHot;
              },
            },
            watch: { //监视属性
              isHot: { //可以监视data中的属性isHot
                 handler(newVal, oldVal) {
                   console.log("isHost被修改了", newVal, oldVal);
                },
              },
              weather: { //也可以监视computed中的计算属性weather
                handler(newVal, oldVal) {
                  console.log("weather被修改了", newVal, oldVal);
                },
              },
            },
          });
        </script>
  • 除了在new Vue时在配置项中设置监视属性外,也可以通过new 完后的vm来配置

    • 语法:vm.$watch("要监视的属性名", {配置对象,与上面那种一样就行})

          vm.$watch("weather", {
            handler(newVal, oldVal) {
              console.log("weather被修改了", newVal, oldVal);
            },
          });
  • 深度监视:在配置对象里配置一下 deep: true 即可

    • 用处:当我们需要监视data域中的一个对象內部值的变化时,就需要用到深度监视,因为处于效率原因,Vue默认是浅度监视,也就是说只是监视当前监视对象的地址是否发生改变,而对象里面的数据是否发生改变并不能监视到

  • 想在初始化时就调用一下handler可以在配置对象里配置一下immediate: true

  • 当监视属性的配置对象中只有一个handler()函数,没有 immediate 和 deep 配置项时,两种监视属性的写法都可以进行简写

    //=====================简写=====================
          vm.$watch("weather", function (newVal, oldVal) { //配置对象直接传入一个函数
            console.log("weather被修改了", newVal, oldVal);
          });
    ​
    //另一种
            watch: {
              weather(newVal, oldVal) { //由 weather: function(newVal, oldVal){} 简化而来
                console.log("weather被修改了", newVal, oldVal);
              },
            },

1.9 computed 和 watch 之前的区别

  1. computed 能完成的功能,watch 都可以完成。watch能完成的功能computed 不一定能完成,例如watch可以进行异步操作

  2. 所以能用 computed 就用 computed ,当有些操作需要异步进行时就用 watch

1.10 动态绑定class和style

1.10.1动态绑定class

  1. 字符串写法,适用于:需要绑定的样式个数不确定,且类名不确定如果需要绑定多个类就在字符串中用空格隔开,一般只用来绑定单个类,多个就用数组写法

  2. 数组写法,适用于:需要绑定的样式个数不确定,且类名不确定

  3. 对象写法,适用于:需要绑定的样式个数确定,且类名确定

    <div id="root">
          <!-- 绑定class样式--字符串写法 -->
          <div class="basic" :class="mood" @click="change">{{mood}}</div>
          <!-- 绑定class样式--数组写法 -->
          <div class="basic" :class="classArr">{{name}}</div>
          <!-- 绑定class样式--对象写法 -->
          <div class="basic" :class="classObj">{{name}}</div>
        </div>
    ​
        <script>
          new Vue({
            el: "#root",
            data: {
              name: "Hello",
              mood: "normal",
              classArr: ["atguigu1", "atguigu2", "atguigu3"],
              classObj: {
                atguigu1: true,
                atguigu2: true,
                atguigu3: false,
              },
            },
            methods: {
              change() {
                const arr = ["happy", "normal", "sad"];
                const index = Math.floor(Math.random() * 3);
                this.mood = arr[index];
              },
            },
          });
        </script>

1.10.2 动态绑定style

  • :style="{ color: activeColor, fontSize: fontSize + 'px' }"

  • 其中 activeColor/fontSize 是 data 属性

1.11 条件渲染

  1. v-if

    • 写法:v-if = "js表达式"

            <div v-if="n==0">hello1</div>
            <div v-else-if="n==1">hello2</div>
            <div v-else-if="n==2">hello3</div>
            <div v-else>hello4</div>
    • 特点:不展示的DOM元素直接被删除

    • 适用于:切换频率较低的场景,因为每次表达式为false时会将整个元素从DOM中删除,当表达式为true时又会将元素加入到DOM中,频繁删除增加浪费性能

    • 注意:v-if 可以和 v-else-if、v-else 一起使用,但要求必须连在一起,中间不能有其他元素

  2. v-show

    • 写法:v-show = "js表达式"

            <div v-show="n%3==0">hello1</div>
            <div v-show="n%3==1">hello2</div>
            <div v-show="n%3==2">hello3</div>
    • 特点:表达式为 false 时只是为元素加上display="none",隐藏元素,但仍然存在域DOM中

    • 适用于:切换频率较高的场景

1.12 列表渲染

  • v-for

    • 写法:v-for="(item, index) in xxx" :key="index"

    • 可以遍历数组、对象、次数

          <div id="root">
            <h3>员工信息</h3>
            <ul>
              <!-- 遍历数组 -->
              <li v-for="(p,index) in persons" :key="index">
                {{p.name}} --- {{p.age}} --- {{index}}
              </li>
            </ul>
      ​
            <!-- 遍历对象 -->
            <h3>汽车信息</h3>
            <ul>
              <li v-for="(val, key) in car" :key="key">{{val}} --- {{key}}</li>
            </ul>
      ​
            <!-- 遍历次数 -->
            <h3>遍历次数</h3>
            <ul>
              <li v-for="(num, index) in 5" :key="index">{{num}} --- {{index}}</li> 
            </ul>
            <!-- 遍历出来的num结果是从1-5
              1 --- 0
              2 --- 1
              3 --- 2
              4 --- 3
              5 --- 4 
              -->
          </div>
          <script>
            new Vue({
              el: "#root",
              data() {
                return {
                  persons: [
                    { id: "001", name: "张三", age: 18 },
                    { id: "002", name: "李四", age: 13 },
                    { id: "003", name: "王五", age: 15 },
                  ],
                  car: {
                    name: "宝马S5",
                    price: 133424,
                  },
                };
              },
            });
          </script>

1.13 key的作用与原理

  1. 虚拟DOM中key的作用:

    1. key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据新数据生成新的虚拟DOM,随后Vue进行新虚拟DOM旧虚拟DOM的差异比较,比较规则如下:

  2. 对比规则:

    1. 旧虚拟DOM中找到了与新虚拟DOM相同的key:

      1. 若虚拟DOM中内容没变,直接使用之前的真实DOM!

      2. 若虚拟DOM中内容变了,则生成新的真实DOM,并沿用旧虚拟DOM与新虚拟DOM的相同之处

    2. 旧虚拟DOM中未找到与新虚拟DOM相同的key:

      1. 创建新的真实DOM,随后渲染到到页面。

  3. 用index作为key可能会引发的问题:

    1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实DOM更新 ==> 界面效果没问题,但效率低。

    2. 如果结构中还包含输入类的DOM:

      1. 会产生错误DOM更新 ==> 界面有问题。

  4. 开发中如何选择key?

    1. 最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值。

    2. 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为kev是没有问题的

1.14 列表过滤

    <div id="root">
      <input type="text" placeholder="请输入关键字" v-model="keyword" />
      <ul>
        <li v-for="p in filPersons" :key="p.id">
          {{p.name}} --- {{p.age}} --- {{p.sex}}
        </li>
      </ul>
    </div>
    <script>
      new Vue({
        el: "#root",
        data() {
          return {
            keyword: "",
            persons: [
              { id: "001", name: "马冬梅", age: 18, sex: "女" },
              { id: "002", name: "周冬雨", age: 19, sex: "女" },
              { id: "003", name: "周杰伦", age: 20, sex: "男" },
              { id: "004", name: "温兆伦", age: 21, sex: "男" },
            ],
          };
        },
        computed: {
          filPersons() {
            //用数组的filter函数返回来确定每个元素是否符合筛选条件
            return this.persons.filter((p) => {
              //用字符串的indexOfd函数获取关键字的下标,如果不存在就会返回-1,当关键字为空字符''时返回下标为0
              return p.name.indexOf(this.keyword) !== -1;
            });
          },
        },
      });
    </script>

1.15 列表排序

    <div id="root">
      <input type="text" placeholder="请输入关键字" v-model="keyword" />
      <button @click="sortType = 2">升序</button>
      <button @click="sortType = 1">降序</button>
      <button @click="sortType = 0">原顺序</button>
      <ul>
        <li v-for="p in filPersons" :key="p.id">
          {{p.name}} --- {{p.age}} --- {{p.sex}}
        </li>
      </ul>
    </div>
    <script>
      new Vue({
        el: "#root",
        data() {
          return {
            keyword: "",
            persons: [
              { id: "001", name: "马冬梅", age: 30, sex: "女" },
              { id: "002", name: "周冬雨", age: 19, sex: "女" },
              { id: "003", name: "周杰伦", age: 28, sex: "男" },
              { id: "004", name: "温兆伦", age: 21, sex: "男" },
            ],
            sortType: 0,
          };
        },
        computed: {
          filPersons() {
            //用数组的filter函数返回来确定每个元素是否符合筛选条件
            const arr = this.persons.filter((p) => {
              //用字符串的indexOfd函数获取关键字的下标,如果不存在就会返回-1,当关键字为空字符''时返回下标为0
              return p.name.indexOf(this.keyword) !== -1;
            });
            if (this.sortType !== 0) {
              arr.sort((p1, p2) => {
                return this.sortType === 1 ? p2.age - p1.age : p1.age - p2.age;
              });
            }
            return arr;
          },
        },
      });
    </script>

1.16 Vue监视数据改变的原理

  1. vue会监视data中所有层次的数据

  2. 如何监测对象中的数据?

    • 通过setter实现监视,且要在new Vue时就传入要监测的数据

      • 对象中后追加的属性,Vue默认不做响应式处理

      • 如需给后添加的属性做响应式,请使用如下API:

        • Vue.set(target,propertyName/index,value)

        • vm.$set(target,propertyName/index,value)

  3. 如何监测数组中的数据?

    • 通过包裹数组更新元素的方法实现,本质就是做了两件事:

      • 调用原生对应的方法对数组进行更新

      • 重新解析模板,进而更新页面

  4. 在Vue修改数组中的某个元素一定要用如下方法:

    • 使用这些API:

      • arr.push(元素或数组):从数组中删除最后一个元素,并返回该元素的值。如果数组为空,则返回undefined。此方法修改原有数组

      • arr.pop():从数组中删除最后一个元素,并返回该元素的值。如果数组为空,则返回undefined。此方法修改原有数组

      • arr.shift():从数组中删除第一个元素,并返回该元素的值。此方法修改原有数组

      • arr.unshift(元素或数组):将一个或多个元素添加到数组的开头,并返回该数组的新长度。此方法修改原有数组

      • arr.splice(start, deleteCount, item?):带问号的item为可选参数,如果不加就是从start下标开始(包含start),删除deleteCount个元素,如果加item就是删除掉后再在star位置插入item

      • arr.sort()对数组的元素进行排序。此方法修改原数组

        • 默认升序,如需降序:

          arr.sort((p1, p2)=>{
              return p2 - p1; //升序就是p1-p2
          })
      • arr.reverse():反转数组,并返回该数组。此方法修改原数组

      • arr.filter():过滤数组,并返回过滤后的数组。此方法不改变原数组

        arr = arr.filter((p) => { //因为不修改元素组,直接覆盖元素组Vue也能检测到
           return p.name.indexOf(this.keyword) !== -1; //判断传入的每一项是否符合条件
        });
      • Vue.set()vm.$set(),也可以修改数组,但用的较少

特别注意:Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象 添加属性!!!

1.17 收集表单数据

  1. 若文本框<input type="text"/>,则v-model收集的是value值,用户输入的就是value值。

  2. 若单选<input type="radio"/>,则v-model收集的是value值,且要给标签配置value值。

  3. 若多选<input type="checkbox"/>

    1. 没有配置input的value属性,那么收集的就是checked (勾选 or 未勾选,是布尔值)

    2. 配置了input的value属性,v-model绑定的data属性的初始值是非数组,那么收集的就是checked

    3. 配置了input的value属性,v-model绑定的data属性的初始值是数组,那么收集的的就是value组成的数组

  4. v-model的三个修饰符:

    1. lazy: 失去焦点再收集数据

    2. number:输入字符串转为有效的数字

    3. trim: 输入首尾空格过滤

 <div id="root">
    <form>
      账号:<input type="text" v-model.trim="account" /> <br><br>
      密码:<input type="password" v-model="password" /> <br /><br />
      性别:
      男<input type="radio" name="sex" value="male" v-model="sex" />
      女<input type="radio" name="sex" value="female" v-model="sex" /><br /><br />
      <!-- 
        type为number的作用: 输入框只能输入正整数,且显示输入框尾部的加减按键
        v-model的修饰符number的作用:输入框拿到的value值是字符串类型,加上修饰符后就会转为number类型再赋值给data中绑定的属性
      -->
      年龄:<input type="number" v-model.number="age"><br /><br />
      爱好:
      吃饭 <input type="checkbox" value="eat" v-model="hobbys">
      睡觉 <input type="checkbox" value="sleep" v-model="hobbys">
      打豆豆 <input type="checkbox" value="play" v-model="hobbys"><br><br>
      所属校区:
      <select v-model="city">
        <option value="">请选择所属校区</option>
        <option value="beijing">北京</option>
        <option value="shanghai">上海</option>
        <option value="wuhan">武汉</option>
        <option value="guangzhou">广州</option>
      </select><br><br>
      其他信息:
      <textarea v-model.lazy="otherInfo"></textarea><br><br>
      <!-- 当多选没有设置value值或data中的属性没有初始化为数组时,默认获取当前选项卡是否被勾选 -->
      <input type="checkbox" v-model="agree"> 阅读并接受
      <a href="#">《用户协议》</a><br><br>
      <button>提交</button>
    </form>
  </div>
  <script>
    new Vue({
      el: "#root",
      data () {
        return {
          account: "",
          password: "",
          sex: "male",
          age: "",
          hobbys: [],
          city: "",
          otherInfo: "",
          agree: ""
        }
      },
    })
  </script>

1.18 Vue中其他的内置指令

  • v-text:

    • 作用:向其所在的节点中渲染文本内容

    • 与插值语法的区别: v-text会替换掉节点中的内容,而插值语法{{xx}}则不会

    • <div id="root">
        <p>你好,{{name}}</p>
        <p v-text="name">你好,</p>
      </div>
      <script>
        new Vue({
          el: "#root",
          data () {
            return {
              name: "张三",
            }
          },
        })
      </script> 
  • v-html:

    • 作用:向指定节点中渲染包含html结构的内容

    • 与插值语法的区别:

      • v-html会替换掉节点中所有的内容,{{xx]}则不会

      • v-htm1可以识别html结构,而插值语法不行

    • 严重注意:v-htm1有安全性问题!! !!

      • 在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。

      • 一定要在可信的内容上使用v-htm1,永不要用在用户提交的内容上!

  • v-cloak:

    • 作用:本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。

    • 使用以下Css代码配合v-cloak可以解决网速慢时页面展示出{{xxxx}}的问题。

      • [v-cloak]: { #属性选择器
            display: none;
        }
  • v-once指令:

    • 作用:v-once所在节点在初次动态渲染后,就视为静态内容了。

    • 以后当v-once所在节点依赖的data数据改变时,不会再更新显示出来的内容,可以用于优化性能。

  • v-pre指令:

    • 作用:告诉Vue跳过其所在节点的编译过程,也就是说Vue不会去渲染模板

    • 可利用它跳过: 没有使用指令语法、没有使用插值语法的节点,会加快编译。

1.19 Vue的生命周期

  1. 创建vm对象

    • 前:beforeCreate()

      • 特点:钩子(即此方法)中无法通过vm访问到data中的数据、methods中的方法,因为还没有创建完

    • 后:created()

      • 特点:钩子中可以通过vm访问到data中的数据、methods中配置的方法,此时已经创建完vm了

  2. Vue解析模板,生成虚拟DOM,此过程无钩子

  3. 将真实DOM挂载到页面

    • 前:beforeMount()

      • 特点:此时页面呈现出Vue解析前的DOM,调用完这个方法后就开始将虚拟DOM转为真实DOM并挂载到页面上,而虚拟DOM在这之前就已经生成过了,此时再在这个方法中修改DOM对象最后也不会呈现到页面上,但是内存中相应的数据会被修改

    • 后:mounted()

      • 特点:此时页面呈现出Vue解析后的DOM,至此初始化过程结束,一般在此进行:

        • 开启定时器

        • 发送ajax请求

        • 订阅消息

        • 绑定自定义事件

  4. data域数据更新

    • 前:beforeUpdate()

      • 特点:内存中的数据是新的,但页面是旧的,也就是页面尚未来得及和数据保持同步

    • 后:updated()

      • 特点:数据是新的,页面也是新的,也就是说页面和数据已经保持同步。

  5. vm对象销毁

    • 前:beforeDestroy()

      • 特点:vm中所有的: data、 methods、指令等等,都处于可用状态,马上要执行销毁过程,一般在此进行:

        • 关闭定时器

        • 取消订阅消息

        • 绑自定义事件

    • 后:destroyed()

关于销毁Vue实例:

  1. 销毁后借助Vue开发者工具看不到任何信息

  2. 销毁后自定义事件会失效,但原生DOM事件依然有效

  3. 一般不会在beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了

钩子函数的具体使用:

  <div id="root">
    <h2 :style="{opacity}">你好呀,张三</h2><br><br>
    <button @click="destroy()">点我停止渐变</button>
  </div>
  <script>
    new Vue({
      el: "#root",
      data () {
        return {
          opacity: 1,
        }
      },
      methods: {
        destroy () {
          //把自己刀了,也会调用beforeDestroy(),在删除后已经渲染到页面中的DOM依然保存,实现停止渐变的效果
          this.$destroy();
        }
      },
      mounted () {
        //开启定时器,并将返回的id值记录到Vue实例身上,方便关闭
        this.timer = setInterval(() => {
          console.log(1);
          this.opacity -= 0.01;
          if (this.opacity <= 0) {
            this.opacity = 1;
          }
        }, 16)
      },
      beforeDestroy () {
        clearInterval(this.timer);
      },
    })
  </script>

二、Vue组件化编程

2.1 非单文件组件

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

  1. 定义组件(创建组件)

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

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

      2. data必须写成函数,为了避免组件被复用时,数据存在引用关系,在写成函数之后每次获取data都会重新new一个对象返回

        const school = Vue.extend({
          //使用template可以配置组件结构,必须用一个div包起来
          template: `
          <div>
            <h2>学校名称:{{schoolName}}</h2>
            <h2>学校人数: {{schoolPeople}}</h2>
          </div>
          `,
          data () {
            return {
              schoolName: "河实",
              schoolPeople: 19987,
            }
          },
        })
        const student = Vue.extend({
          template: `
          <div>
            <h2>学生姓名:{{studentName}}</h2>
            <h2>学生年龄:{{studentAge}}</h2>
          </div>
          `,
          data () {
            return {
              studentName: "张三",
              studentAge: 18,
            }
          },
        })

  2. 注册组件

    • 局部注册: 靠new Vue的时候传入components选项,只有当前这个vm实例所绑定的容器可使用这个组件

    • 全局注册: 靠Vue.component("组件名’,组件),所有容器都可以使用这个组件

       //局部配置
        new Vue({
          el: "#root",
          components: {
            // 因为组件名跟接收到的中间变量名一样,所以school:school简化成school
            school,
            student,
          }
        })
        
        //全局配置
        Vue.component("school", school)

  3. 使用组件(写组件标签)

      <div id="root">
        <school></school>
        <hr>
        <student></student>
      </div>

2.1.2 组件的几个注意点

  1. 关于组件名:

    1. 组件名由一个单词组成:

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

      2. 第二种写法(首字母大写): Schoo1

    2. 组件名由多个单词组成:

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

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

    3. 备注:

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

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

  2. 关于组件标签:

    1. 第一种写法<school></school>

    2. 第二种写法:<school/>

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

  3. 一个简写方式: const school = Vue.extend(options) 可简写为: const school = options

2.1.3 组件的VueComponent构造函数

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

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

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

  4. 关于this指向:

    1. 组件配置中:data数、methods中的函数、wat 美惠函数、computed中的函数 它们的this均是VueComponent实例对象

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

  5. VueComponent的实例对象,以后简称vc (也可称之为: 组件实例对象); Vue的实例对象,以后简称vm。

2.1.4 VueComponent继承了Vue

  1. 一个重要的内置关系: VueComponent.prototype. __proto__=== Vue.prototype

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

原型对象知识补充:

  • 个人理解就是每一个构造函数(类)都会保留一份父类的原型对象(继承),当这个构造函数被 new 后,得到的实例对象也会保留一份构造函数中存储的原型对象,即:Vue.prototype === vm.__proto__

2.2 单文件组件

2.2.1 创建Vue工程

  • 第一步(仅第一次执行):全局安装@vue/cli。

    • npm install -g @vue/cli

  • 第二步:切换到你要创建项目的目录,然后使用命令创建项目

    • vue create xxxx

  • 第三步:启动项目

    • npm run serve

2.2.2 分析Vue工程的各个子文件作用

  • node_modules:下载的包依赖

  • public

    • favicon.ico: 页签图标

    • index.html: 主页面

  • src

    • assets: 存放静态资源

      • logo.png

    • component: 存放组件

      • HelloWorld.vue:组件

    • App.vue: 汇总所有组件

    • main.js: 入口文件

  • .gitignore: git 版本管制忽略的配置

  • babel.config.js: babel 的配置文件(babel是es6转es5的工具)

  • package.json: 应用包配置文件

  • README.md: 应用描述文件

  • vue.config.js:配置文件

2.2.2 main.js中render函数

#main.js文件
import Vue from 'vue'
import App from './App.vue'
​
Vue.config.productionTip = false
​
new Vue({
  render: h => h(App),
}).$mount('#app') //相当于 el: "#app"
​
  • 可以看见这里Vue导入时是用了import Vue from 'vue',并没有指明导入的是什么vue文件,是脚手架底层帮忙确定了导入vue.runtime.esm,js文件,这个文件没有解析template模板的能力,所以如果这里先注册App组件再用配置项 template:`<App></App>` 来将App组件放到页面上的话,将没有人来解析他,所以就有了render(createElement)这个函数,createElement是一个函数,用来创建并解析传入的元素

  • new Vue({
      render(createElement) {
          //return createElement("h1", "你好呀");
          return createElement(App);
      }
    }).$mount('#app')
    ​
    //========简写为========
    new Vue({
      render: createElement =>createElement(App);
    }).$mount('#app')
    ​
    //========变量名改为h========
    new Vue({
      render: h =>h(App);
    }).$mount('#app')

2.2.3 vue.config.js配置文件

  1. 使用vue inspect > output.js可以查看到Vue脚手架的默认配置。

  2. 使用vue.config.js可以对脚手架进行个性化定制,详情见:Vue CLI

2.2.4 template里元素的ref属性

  1. 被用来给元素或子组件注册引用信息(id的替代者)

  2. 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)、

    • 注意:用id标识后组件再用document.getElementById(id)获取时获取的是真实的DOM,而ref拿到的是组件实例对象

  3. 使用方式:

    1. 打标识:<h1 ref="xxx">.....</h1><School ref="xxx"></School>

    2. 获取:this.$refs.xxx

2.2.5 组件配置对象中的props配置项

  1. 功能:让组件接收外部传过来的数据

  2. 传递数据:在调用组件时,<Demo name="xxx"/>

    1. 注意:这里的对象和方法的传递都是引用传递,可以传数据也可以传方法

  3. 接收数据:

    1. 第一种方式(只接收):props:['name']

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

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

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

    备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。能够这么做的原因是先将props中的数据代理到 VC 身上,再将data中的数据代理到 VC 身上,因此在data配置项中可以通过 this.prop名来获取传进来的数据

2.2.6 mixin混入

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

  2. 创建一个mixin.js来专门存放混入对象(可以是多个),并将其暴露

    export const showName = {
      methods: {
        showName () {
          alert(this.name)
        }
      },
      //人户配置项都可以,data、mounted()、compute....
    }
  3. 在需要用到混入对象的地方导入:import {showName} from '../mixin'

  4. 在组件中注册混入对象:

    • 局部混入:通过配置项mixins

      <script>
      import {showName} from '../mixin'
      export default {
        name: "MySchool",
        data () {
          return {
            name: "河实",
            nums: 29834,
          }
        },
        mixins: [showName],
      }
      </script>
    • 全局混入:通过Vue设置

      • 在main.js中:Vue.mixin(showName)

      • 全局混入后每一个组件都会混入,包括vm实例

  5. 注意:Vue是先加载混入的对象,再加载组件配置项中设置的信息,如:

    • 组件配置项中的data与混入对象中的data中的属性出现冲突时,组件配置项中的data属性会覆盖掉混入对象中的data属性

    • 但像mount()这种生命周期方法不会覆盖,而是先调用混入对象中的mount(),再调用vc 配置项中的mount()

2.2.7 插件

  1. 功能:用于增强Vue,install方法中可以配置一些东西

  2. 定义插件:创建一个包含install方法的对象,当Vue启动时会调用install方法,install的第一个参数是Vue的构造方法,第二个以后的参数是插件使用者传递的数据。

    //plugins.js
    export default {
      install (Vue, use中传进来的参数) {
        // 1. 添加全局过滤器
        Vue.filter(....)
    ​
        // 2. 添加全局指令
        Vue.directive(....)
    ​
        // 3. 配置全局混入(合)
        Vue.mixin(....)
    ​
        // 4. 添加实例方法
        Vue.prototype.$myMethod = function () {...}
        Vue.prototype.$myProperty = xxxx
      }
    }
  3. 使用插件:在main.js中用import plugin from './plugins'导入后,用Vue.use(plugin,参数s)配置好即可

2.2.8 scoped标识style标签

  1. 作用:让样式在当前组件中局部生效,防止冲突。因为所有的组件的 style 标签中的样式都会整合为一个css文件,所以当组件之间的style样式名相同的话就会出现冲突。

  2. 写法:

    <style scoped>
    .orangeFont {
      color: orange;
    }
    </style>

2.2.9 浏览器本地存储

  1. 存储内容大小一般支持5MB左右(不同浏览器可能还不一样)

  2. 浏览器端通过 Window.sessionStorageWindow.localStorage 属性来实现本地存储机制。

  3. 相关API:

    1. xxxxxStorage.setItem('key', 'value'); 该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。

    2. xxxxxStorage.getItem('person');

      该方法接受一个键名作为参数,返回键名对应的值。

    3. xxxxxStorage.removeItem('key');

      该方法接受一个键名作为参数,并把该键名从存储中删除。

    4. xxxxxStorage.clear()

      该方法会清空存储中的所有数据。

  4. 备注:

    1. SessionStorage存储的内容会随着浏览器窗口关闭而消失。

    2. LocalStorage存储的内容,需要手动清除才会消失。

    3. xxxxxStorage.getItem(xxx)如果xxx对应的value获取不到,那么getItem的返回值是null。

    4. JSON.parse(null)的结果依然是null。

    5. 当对象作为 value 时,需要先用JSON.stringify(obj)转为JSON字符串

2.2.10 组件的自定义事件

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

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

  3. 绑定自定义事件:

    1. 第一种方式,在父组件中:<School @schoolName="getSchoolName"></School>

      <School v-on: schoolName="getSchoolName"></School>on:atguigu="test"/>

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

      <Demo ref="demo"/>
      ......
      mounted(){
         this.$refs.xxx.$on('atguigu',this.test)
      }
    3. 若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法。

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

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

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

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

2.2.11 全局事件总线

  1. 一种组件间通信的方式,适用于任意组件间通信。

  2. 安装全局事件总线:

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

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

      methods(){
        demo(data){......}
      }
      ......
      mounted() {
        this.$bus.$on('xxxx',this.demo)
      }
    2. 提供数据:this.$bus.$emit('xxxx',数据)

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

2.2.12 消息订阅与发布

  1. 一种组件间通信的方式,适用于任意组件间通信

  2. 使用步骤:

    1. 安装pubsub:npm i pubsub-js

    2. 引入: import pubsub from 'pubsub-js'

    3. 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身

      methods(){
        demo(data){......}
      }
      ......
      mounted() {
        this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息
      }
    4. 提供数据:pubsub.publish('xxx',数据)

    5. 最好在beforeDestroy钩子中,用PubSub.unsubscribe(pid)取消订阅

2.2.13 nextTick

  1. 语法:this.$nextTick(回调函数)

  2. 作用:在下一次 DOM 更新结束后执行其指定的回调。

  3. 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。

2.2.14 vue脚手架配置代理

常用来解决跨域问题

  • 方法一

    • 在vue.config.js中添加如下配置:

      devServer:{
        proxy:"http://localhost:5000"
      }
    • 说明:

      1. 优点:配置简单,请求资源时直接发给前端(8080)即可。

      2. 缺点:不能配置多个代理,不能灵活的控制请求是否走代理。

      3. 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)

  • 方法二

    • 编写vue.config.js配置具体代理规则:

      module.exports = {
          devServer: {
            proxy: {
                
            '/api1': {// 匹配所有以 '/api1'开头的请求路径
              target: 'http://localhost:5000',// 代理目标的基础路径
              pathRewrite: {'^/api1': ''} //向服务端发请求时将/api2替换成空串
              changeOrigin: true, //跟服务端说谎,说自己是跟服务端同一域名的
            },
                
            '/api2': {// 匹配所有以 '/api2'开头的请求路径
              target: 'http://localhost:5001',// 代理目标的基础路径
              pathRewrite: {'^/api2': ''} //向服务端发请求时将/api2替换成空串
              changeOrigin: true, //跟服务端说谎,说自己是跟服务端同一域名的
            }
          }
        }
      }
      /*
         changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
         changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
         changeOrigin默认值为true
      */
    • 说明:

      • 优点:可以配置多个代理,且可以灵活的控制请求是否走代理。

      • 缺点:配置略微繁琐,请求资源时必须加前缀。

2.2.15 插槽

  1. 作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于 父组件 ===> 子组件

  2. 分类:默认插槽、具名插槽、作用域插槽

  3. 使用方式:

    1. 默认插槽:

      父组件中:
              <Category>
                 <div>html结构1</div>
              </Category>
      子组件中:
              <template>
                  <div>
                     <!-- 定义插槽 -->
                     <slot>插槽默认内容...</slot>
                  </div>
              </template>
    2. 具名插槽:

      父组件中:
              <Category>
                  <template slot="center">
                    <div>html结构1</div>
                  </template>
      ​
                  <template v-slot:footer>
                     <div>html结构2</div>
                  </template>
              </Category>
      子组件中:
              <template>
                  <div>
                     <!-- 定义插槽 -->
                     <slot name="center">插槽默认内容...</slot>
                     <slot name="footer">插槽默认内容...</slot>
                  </div>
              </template>
    3. 作用域插槽:

      1. 理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)

      2. 具体编码:

        父组件中:
                <Category>
                    <template scope="scopeData">
                        <!-- 生成的是ul列表 -->
                        <ul>
                            <li v-for="g in scopeData.games" :key="g">{{g}}</li>
                        </ul>
                    </template>
                </Category>
        ​
                <Category>
                    <template slot-scope="scopeData">
                        <!-- 生成的是h4标题 -->
                        <h4 v-for="g in scopeData.games" :key="g">{{g}}</h4>
                    </template>
                </Category>
        子组件中:
                <template>
                    <div>
                        <slot :games="games"></slot>
                    </div>
                </template>
                
                <script>
                    export default {
                        name:'Category',
                        props:['title'],
                        //数据在子组件自身
                        data() {
                            return {
                                games:['红色警戒','穿越火线','劲舞团','超级玛丽']
                            }
                        },
                    }
                </script>

2.2.16 Vuex

  1. 概念:在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信,适用于多个组件需要共享数据时。

  2. 搭建vuex环境:

    1. 创建文件:src/store/index.js

      //引入Vue核心库
      import Vue from 'vue'
      //引入Vuex
      import Vuex from 'vuex'
      //应用Vuex插件
      Vue.use(Vuex)
      ​
      //准备actions对象——响应组件中用户的动作
      const actions = {}
      //准备mutations对象——修改state中的数据
      const mutations = {}
      //准备state对象——保存具体的数据
      const state = {}
      ​
      //创建并暴露store
      export default new Vuex.Store({
          actions,
          mutations,
          state
      })
  3. 在main.js中创建vm时传入store配置项:

    ......
    //引入store
    import store from './store'
    ......
    ​
    //创建vm
    new Vue({
        el:'#app',
        render: h => h(App),
        store
    })
  4. 基本使用:

    1. 初始化数据、配置actions、配置mutations,操作文件store.js

      //引入Vue核心库
      import Vue from 'vue'
      //引入Vuex
      import Vuex from 'vuex'
      //引用Vuex
      Vue.use(Vuex)
      ​
      const actions = {
          //响应组件中加的动作
          jia(context,value){
              // console.log('actions中的jia被调用了',miniStore,value)
              context.commit('JIA',value)
          },
      }
      ​
      const mutations = {
          //执行加
          JIA(state,value){
              // console.log('mutations中的JIA被调用了',state,value)
              state.sum += value
          }
      }
      ​
      //初始化数据
      const state = {
         sum:0
      }
      ​
      //创建并暴露store
      export default new Vuex.Store({
          actions,
          mutations,
          state,
      })
    2. 组件中读取vuex中的数据:$store.state.sum

    3. 组件中修改vuex中的数据:$store.dispatch('action中的方法名',数据)$store.commit('mutations中的方法名',数据)

备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写dispatch,直接编写commit

  1. getters的使用,相当于computed配置项

    1. 概念:当state中的数据需要经过加工后再使用时,可以使用getters加工。

    2. store.js中追加getters配置

      ......
      ​
      const getters = {
          bigSum(state){
              return state.sum * 10
          }
      }
      ​
      //创建并暴露store
      export default new Vuex.Store({
          ......
          getters
      })
    3. 组件中读取数据:$store.getters.bigSum

  2. 四个map方法的使用

    1. mapState方法:用于帮助我们映射state中的数据为计算属性

      computed: {
          //借助mapState生成计算属性:sum、school、subject(对象写法)
           ...mapState({sum:'sum',school:'school',subject:'subject'}),
               
          //借助mapState生成计算属性:sum、school、subject(数组写法)
          ...mapState(['sum','school','subject']),
      },
    2. mapGetters方法:用于帮助我们映射getters中的数据为计算属性

      computed: {
          //借助mapGetters生成计算属性:bigSum(对象写法)
          ...mapGetters({bigSum:'bigSum'}),
      ​
          //借助mapGetters生成计算属性:bigSum(数组写法)
          ...mapGetters(['bigSum'])
      },
    3. mapActions方法:用于帮助我们生成与actions对话的方法,即:包含$store.dispatch(xxx)的函数

      methods:{
          //靠mapActions生成:incrementOdd、incrementWait(对象形式)
          ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
      ​
          //靠mapActions生成:incrementOdd、incrementWait(数组形式)
          ...mapActions(['jiaOdd','jiaWait'])
      }
    4. mapMutations方法:用于帮助我们生成与mutations对话的方法,即:包含$store.commit(xxx)的函数

      methods:{
          //靠mapActions生成:increment、decrement(对象形式)
          ...mapMutations({increment:'JIA',decrement:'JIAN'}),
          
          //靠mapMutations生成:JIA、JIAN(对象形式)
          ...mapMutations(['JIA','JIAN']),
      }

    备注:mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象。

  3. 模块化+命名空间

    1. 目的:让代码更好维护,让多种数据分类更加明确。

    2. 修改store.js

      const countAbout = {
        namespaced:true,//开启命名空间
        state:{x:1},
        mutations: { ... },
        actions: { ... },
        getters: {
          bigSum(state){
             return state.sum * 10
          }
        }
      }
      ​
      const personAbout = {
        namespaced:true,//开启命名空间
        state:{ ... },
        mutations: { ... },
        actions: { ... }
      }
      ​
      const store = new Vuex.Store({
        modules: {
          countAbout,
          personAbout
        }
      })
    3. 开启命名空间后,组件中读取state数据:

      //方式一:自己直接读取
      this.$store.state.personAbout.list
      //方式二:借助mapState读取:
      ...mapState('countAbout',['sum','school','subject']),
    4. 开启命名空间后,组件中读取getters数据:

      //方式一:自己直接读取
      this.$store.getters['personAbout/firstPersonName']
      //方式二:借助mapGetters读取:
      ...mapGetters('countAbout',['bigSum'])
    5. 开启命名空间后,组件中调用dispatch

      //方式一:自己直接dispatch
      this.$store.dispatch('personAbout/addPersonWang',person)
      //方式二:借助mapActions:
      ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
    6. 开启命名空间后,组件中调用commit

      //方式一:自己直接commit
      this.$store.commit('personAbout/ADD_PERSON',person)
      //方式二:借助mapMutations:
      ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),

三、路由

  1. 理解: 一个路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理

  2. 前端路由:key是路径,value是组件

3.1 基本使用

  1. 安装vue-router,命令:npm i vue-router

  2. 应用插件:Vue.use(VueRouter)

  3. 编写router配置项:

    import VueRouter from 'vue-router'
    import Vue from 'vue'
    ​
    import About from '../pages/AboutComponent.vue'
    import Home from '../pages/HomeComponent.vue'
    ​
    Vue.use(VueRouter)
    ​
    export default new VueRouter({
      routes: [
        {
          path: '/about',
          component: About,
        },
        {
          path: '/home',
          component: Home,
        },
      ],
    })
  4. 实现切换(active-class可配置高亮样式)

    // active-class是如果被点击了就点击的class样式
    <router-link active-class="active" to="/about">About</router-link>
  5. 指定展示位置

    <router-view></router-view>

3.2 几个注意点

  1. 路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹。

  2. 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。

  3. 每个组件都有自己的$route属性,里面存储着自己的路由信息。

  4. 整个应用只有一个router,可以通过组件的$router属性获取到。

3.3 多级路由

  1. 配置路由规则,使用children配置项:

    routes:[
        {
            path:'/about',
            component:About,
        },
        {
            path:'/home',
            component:Home,
            children:[ //通过children配置子级路由
                {
                    path:'news', //此处一定不要写:/news
                    component:News
                },
                {
                    path:'message',//此处一定不要写:/message
                    component:Message
                }
            ]
        }
    ]
  2. 跳转(要写完整路径):

    <router-link to="/home/news">News</router-link>

3.4 路由的query参数

  1. 传递参数

    <!-- 跳转并携带query参数,to的字符串写法 -->
    <router-link :to="/home/message/detail?id=666&title=你好">跳转</router-link>
                    
    <!-- 跳转并携带query参数,to的对象写法 -->
    <router-link 
        :to="{
            path:'/home/message/detail',
            query:{
               id:666,
                title:'你好'
            }
        }"
    >跳转</router-link>
  2. 接收参数:

    this.$route.query.id //$route是vc身上的一个属性
    this.$route.query.title

3.5 命名路由

  1. 作用:可以简化路由的跳转。

  2. 如何使用

    1. 给路由命名:

      {
          path:'/demo',
          component:Demo,
          children:[
              {
                  path:'test',
                  component:Test,
                  children:[
                      {
                          name:'hello' //给路由命名
                          path:'welcome',
                          component:Hello,
                      }
                  ]
              }
          ]
      }
    2. 简化跳转:

      <!--简化前,需要写完整的路径 -->
      <router-link to="/demo/test/welcome">跳转</router-link>
      ​
      <!--简化后,直接通过名字跳转 -->
      <router-link :to="{name:'hello'}">跳转</router-link>
      ​
      <!--简化写法配合传递参数 -->
      <router-link 
          :to="{
              name:'hello',
              query:{
                 id:666,
                  title:'你好'
              }
          }"
      >跳转</router-link>

3.6 路由的params参数

  1. 配置路由,声明接收params参数

    {
        path:'/home',
        component:Home,
        children:[
            {
                path:'news',
                component:News
            },
            {
                component:Message,
                children:[
                    {
                        name:'xiangqing',
                        path:'detail/:id/:title', //使用占位符声明接收params参数
                        component:Detail
                    }
                ]
            }
        ]
    }
  2. 传递参数

    <!-- 跳转并携带params参数,to的字符串写法 -->
    <router-link :to="/home/message/detail/666/你好">跳转</router-link>
                    
    <!-- 跳转并携带params参数,to的对象写法 -->
    <router-link 
        :to="{
            name:'xiangqing',
            params:{
               id:666,
                title:'你好'
            }
        }"
    >跳转</router-link>

    特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!

  3. 接收参数:

    this.$route.params.id
    this.$route.params.title

3.7 路由的props配置

作用:让路由组件更方便的收到参数

{
    name:'xiangqing',
    path:'detail/:id',
    component:Detail,
​
    //第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
    // props:{a:900}
​
    //第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件
    // props:true
    
    //第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
    props(route){
        return {
            id:route.query.id,
            title:route.query.title
        }
    }
}
​
​
​
组件中接收:
props:['id', 'title']

3.8 <router-link>的replace属性

  1. 作用:控制路由跳转时操作浏览器历史记录的模式

  2. 浏览器的历史记录有两种写入方式:分别为pushreplacepush是追加历史记录,replace是替换当前记录。路由跳转时候默认为push

  3. 如何开启replace模式:<router-link replace .......>News</router-link>

3.9 编程式路由导航

  1. 作用:不借助<router-link>实现路由跳转,让路由跳转更加灵活

  2. 具体编码:

    //$router的两个API
    this.$router.push({
        name:'xiangqing',
            params:{
                id:xxx,
                title:xxx
            }
    })
    ​
    this.$router.replace({
        name:'xiangqing',
            params:{
                id:xxx,
                title:xxx
            }
    })
    this.$router.forward() //前进
    this.$router.back() //后退
    this.$router.go() //可前进也可后退

3.10 缓存路由组件

  1. 作用:让不展示的路由组件保持挂载,不被销毁。

  2. 具体编码:

    <keep-alive include="News"> 
        <router-view></router-view>
    </keep-alive>
  3. 如果想缓存多个组件就用数组

    <keep-alive include=["News", "Student"]> 
        <router-view></router-view>
    </keep-alive>

3.11 两个新的生命周期钩子

  1. 作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。

  2. 具体名字:

    1. activated路由组件被激活时触发。

    2. deactivated路由组件失活时触发。

3.12 路由守卫

  1. 作用:对路由进行权限控制

  2. 分类:全局守卫、独享守卫、组件内守卫

  3. 全局守卫:

    //全局前置守卫:初始化时执行、每次路由切换前执行
    router.beforeEach((to,from,next)=>{
        console.log('beforeEach',to,from)
        if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
            if(localStorage.getItem('school') === 'atguigu'){ //权限控制的具体规则
                next() //放行
            }else{
                alert('暂无权限查看')
                // next({name:'guanyu'})
            }
        }else{
            next() //放行
        }
    })
    ​
    //全局后置守卫:初始化时执行、每次路由切换后执行
    router.afterEach((to,from)=>{
        console.log('afterEach',to,from)
        if(to.meta.title){ 
            document.title = to.meta.title //修改网页的title
        }else{
            document.title = 'vue_test'
        }
    })
  4. 独享守卫:

    beforeEnter(to,from,next){
        console.log('beforeEnter',to,from)
        if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
            if(localStorage.getItem('school') === 'atguigu'){
                next()
            }else{
                alert('暂无权限查看')
                // next({name:'guanyu'})
            }
        }else{
            next()
        }
    }
  5. 组件内守卫:

    //进入守卫:通过路由规则,进入该组件时被调用
    beforeRouteEnter (to, from, next) {
    },
    //离开守卫:通过路由规则,离开该组件时被调用
    beforeRouteLeave (to, from, next) {
    }

3.13 路由器的两种工作模式

  1. 对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。

  2. hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。

  3. hash模式:

    1. 地址中永远带着#号,不美观 。

    2. 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。

    3. 兼容性较好。

  4. history模式:

    1. 地址干净,美观 。

    2. 兼容性和hash模式相比略差。

    3. 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值