Vue学习 +

继上一个文档。因为某些不明问题无法再次编辑。故此开设新档。在此补充之前的内容

奇了怪了,追叙之前的操作,也就引用代码高亮

上个 Vue笔记链接

%title插图%num
Vue 特性演示
插槽

官方介绍

自 2.6.0 起有所更新。已废弃使用 slot

Vue 实现了一套内容分发的 API,这套 API 的设计灵感源自 Web Components 规范草案,将 <slot> 元素作为承载分发内容的出口。资料来源于 Vue中文官网

它允许你像这样合成组件

HTML
<navigation-link url="/profile">
  Your Profile
</navigation-link>

然后你在 <navigation-link> 的模板中可能会写为

HTML
<a v-bind:href="url" class="nav-link">
  <slot></slot>
</a>

当组件渲染的时候,<slot></slot> 将会被替换为“Your Profile”。插槽内可以包含任何模板代码,包括 HTML:

HTML
<navigation-link url="/profile">
  <!-- 添加一个 Font Awesome 图标 -->
  <span class="fa fa-user"></span>
  Your Profile
</navigation-link>

甚至其它的组件

HTML
<navigation-link url="/profile">
  <!-- 添加一个图标的组件 -->
  <font-awesome-icon name="user"></font-awesome-icon>
  Your Profile
</navigation-link>

如果 <navigation-link> 的 template 中没有包含一个 <slot> 元素,则该组件起始标签和结束标签之间的任何内容都会被抛弃

组件插槽介绍

在 Vue 中,组件是嵌套着使用的。子组件 嵌套在 父级组件 的 对应目标 位置就需要用 插槽 方式 来实现

<slot></slot>

slot 定义插槽,它的name 值为 Vue 模板组件的名字,<slot>标签的位置 会直接作为 Vue模板的 渲染位置。实现快速构建;

自定义事件

当定义了模板组件后,除了可以用 v-bind:自定义成员 = 值 传给组件需要的参数以外,
我们还可以使用 v-on:自定义成员 = 方法 传递自定义的事件方法

HTML
   <div id="app">
        <!-- 绑定参数 循环对象 成员绑定给对应接收参数 方法赋值给对应方法 -->
        <todo-list v-for="(item,index) in todo" v-bind:item="item" v-bind:index="index" v-on:remove="removeItem">
        </todo-list>
        <button></button>
    </div>

    <script>
        // 定义 全局组件 todo-list
        Vue.component('todo-list', {
            //绑定 标签上 v-bind:对应参数 取得的值
            props: ['item', 'index'],
            // 定义模板样式 赋值内容
            template: '<li>{{index}}:{{item}} <button v-on:click="remove">删除</button></li>',
            // 定义方法对象
            methods: {
                // remove方法 引用 对应方法
                remove() {
                    this.$emit('remove');
                }
            },
        });
        
        let app = new Vue({
            el: '#app',
            data() {
                return {
                    todo: ['学习', '唱歌', '跳舞'],
                }
            },
            methods: {
                // 传入 index 值
                removeItem(index) {
                    // 删除 对应 index 下标中 1个成员
                    this.todo.splice(index, 1);
                }
            }
        })
    </script>

@Vue/cil 目录介绍

部署完 vue脚手架 后,项目中文件对应的描述

YAML
   vuecil-demo            # 项目目录
    – node_modules        # 项目依赖的第三方包
    – public              # 静态文件目录
      – favicon.ico       # 浏览器小图标
      – index.html        # 单页面的html文件
    – src                 # 业务文件夹
      – assets            # 静态资源
        – login.png       # vue 的 logo 图片
      – components        # 组件目录
        – helloword.vue   # 欢迎页面的 vue 代码文件
      – app.vue           # 整个应用的根目录
      – main.js           # 入口 js 文件
    – .gitignore          # jit 忽略提交配置
     – babel.config.js    # babel 配置
    – package.json        # 依赖包列表
    – README.md           # 项目说明
    – yarn.lock           # 项目包版本锁定和缓存地址
%title插图%num
Vue脚手架文件关系
%title插图%num
修改Vue默认配置

若需要修改 Vue默认配置,需在 vue项目 的根目录新建 vue.config.js 文件,使用 module.exports 导入配置文件的方式修改具体的参数值。

修改端口 自动打开

运行 vue项目时,默认运行在8080端口,如需修改,并实现自动浏览器打开项目,可在 vue.config.js 中添加项

JavaScript
module.exports={
    devServer:{
        // 端口
        port:3000,
        // 自动打开
        open:true,
    }
}

关闭 eslint 检查

实际测试开发,如若需要 可以关闭 eslint 检查

%title插图%num

eslint 是代码检查工具,它会在 vue项目运行时候执行检查,如果代码中违反了 eslint 规则,则会抛出报错 并 提示
若需要关闭该 检查工具 可在 vue.config.js 添加项:

JavaScript
module.exports = {
    // 关闭 eslint 检查 (eslint为代码检查工具,违反该规则就会报错)
    lintOnSave: false,
}

vue 开发思路
单文件开发

Vue推荐采用 .vue 文件来开发项目

<templat> 里只有一个根标签,这样的优势在于:

1. js独立作用域,变量重名互不影响
2. style 配合 scoped 属性,可以保证样式只针对当前 template 内标签生效

一个 .vue文件的结构如下所示

Vue
<!-- 界面层 -->
<template>
  
</template>
<!-- 数据层 -->
<script>
export default {

}
</script>
<!-- 样式层 -->
<style>

</style>

插入本地图片 的方式需要 引入模块,因为在vue的打包程序 webpack 中,万物皆模块

css 与 html 标签中引入图片可依旧以原生方式,但是 js 中若想插入图片,需要引入图片模块

Vue
<template>
    <img :src="img">
</template>

<script>
export default {
    data() {
        return {
            img: './assets/img/01.jpg'
        }
    }
}
</script>

插值表达式

与 jQuery中的 template包 类似,vue也使用了 {{value}} 的方式对 数据 + 界面 进行了关联

  • 数据层将数据对象存放在 data 中,创建成员并给成员赋值对象值
  • 界面层使用 {{ 成员名 or 表达式 }} 直接使用数据层 data 中对应成员名的值

在 dom 标签中,直接插入 vue 数据变量

  • 又叫声明式渲染/文本插值
  • 语法{{ 表达式 }}
JavaScript
<template>
  <div>
    <span>{{sayHi}}</span>
  </div>
</template>

<script>
export default {
   data(){
    return{
        sayHi:'Hello Vue',
    }
   }
}
</script>

<style>

</style>
MVVM 设计模式

vue 采用 MVVM 设计模式来处理 数据 与 界面 之间的联系

  • 设计模式 是对代码的分层,引入的一种架构概念
  • vue 的 MVVM 设计思路 让开发过程中 减少 DOM 操作,提高开发效率
%title插图%num

M (模型) V (视图 VM (视图模型)

DOM listeners vue会监听界面层的数据变动,当发生变动时候,会通知数据层更新数据。
Data Bindings vue会让数据层与界面层的对应区域的内容进行绑定,数据发生变化同步界面

vue中文官网 详细文档

MVVM表示如下:

1. Model:模型层,在这里表示 javaScript 对象
2. View:视图层,在这里表示 DOM (HTML操作对象)
3. ViewModel:连接视图和数据的中间件 (Vue.js 就是 MVVM 中的 ViewModel 层的实现者)

ViewModel 是 数据 与 视图 之间的观察者,在MVVM架构中,是不允许 数据 和 视图 直接通信,而是通过 ViewModel 通信

vue 常用指令
v-bind

v-bind 可以给标签属性设置 Vue 变量的值

例如 <a> 标签的 harf ,<img> 标签的 src

v-bind:属性名=”vue变量” 可简写为 :属性名=”vue变量”

Vue
<template>
   <div>
    <a v-bind:href="url">无名喵站</a>
    <img :src="img">
   </div>
</template>

<script>
export default {
    data(){
      return{
        img:'https://www.dmoe.cc/random.php',
        url:'https://smmcat.cn',
      }
    }
}
</script>

上方代码实际打包后,相当于 :

  • <a> 标签的 src 属性为 https://smmcat.cn,
  • <img> 的 src 属性为 https://www.dmoe.cc/random.php
v-on

基本使用

v-on 可以给标签绑定事件

例如 v-on:click=”fnn”,当用户在该标签触发了对应事件,会执行绑定事件中对应的方法

语法

  • v-on:事件名= “要执行的少量代码”
  • v-on:事件名= “methods 中的函数名”
  • v-on:事件名= “methods 中的函数名(实参)”

v-on:事件名=”方法” 可简写为 :事件名=”方法”

Vue
<template>
  <div>
     <p>{{num}}</p>
     <button v-on:click="num++">+1</button>
     <button v-on:click="num--">+1</button>
     <button @click="zero">归零</button>
  </div>
</template>

<script>
export default {
   data(){
    return{
      num:0,
    }
   },
   methods:{
    zero(){
      this.num=0;
    }
   }
}
</script>

阻止默认事件

使用 v-on:事件名.prevent=”方法” 或 v-on:事件名.prevent 来阻止标签中的默认行为

每个标签都有默认的事件,例如 <a> 标签的跳转,<input:submit> 的提交表单。某些情况下我们不需要这些默认行为,需要去阻止默认事件的响应。

Vue HTML
<--! 不再进行跳转 -->
<a @click.prevent="fnn">百度</a>

获取事件对象

v-on:事件名=”方法” 如若不携带任何参数,默认直接在行参中获取 事件对象

Vue
<template>
  <div>
    <button @click="getEven">获取事件对象</button>
  </div>
</template>

<script>
export default {
  methods: {
    // 当 标签绑定 @事件名 未设置行参,默认传入事件对象
    getEven(e) {
      console.log(e);
    }
  }
}
</script>

v-on:事件名=”方法(行参)” 如若携带参数,传入 事件对象 可以在行参中以 $event 表示

  • v-on:click=”fnn(num,$event)”
Vue
<template>
  <div>
    <button @click="getEven(5,$event)">获取事件对象</button>
  </div>
</template>

<script>
export default {
  methods: {
    // 当 标签绑定 @事件名,若有传参,$event 作为事件对象
    getEven(num, e) {
      console.log(e);
    }
  }
}
</script>

修饰符

在事件后面.修饰符名 给事件增加更多的效果

语法

  • v-on:事件名.修饰符=”methods里函数”

修饰符列表

方法名 效果
.stop 阻止事件冒泡
.prevent 阻止默认行为
.once 程序运行期间,只触发一次事件处理函数

可以以连写方式实现多个效果

Vue HTML
<!-- 实现单击时 阻止默认事件 + 阻止冒泡 -->
<a v-on:click.prevent.stop href="http://baidu.com">百度</a>

按钮修饰符

在 @keyup 事件后面.键位修饰符 可以直接针对对应的按钮绑定事件处理函数

语法

  • v-on:keyup.enter – 监听回车按键
  • v-on:keyup.esc – 监听esc按键
  • 更多内容 可以访问 官方文档

通过绑定 input 标签,使用 keyup.enter 实现捕获 按键回车 事件

Vue
<template>
  <div>
    <input type="text" @keydown.enter="entFnn" v-model="text">
  </div>
</template>

<script>
export default {
  data() {
    return {
      text: '',
    }
  },
  methods: {
    entFnn() {
      alert(this.text);
    }
  }
}
</script>

v-model

原理

v-model 实现 value 属性 和 vue 数据实现 双向绑定

适用于针对 表单元素 与数据之间的绑定

双向绑定

  • 当表单的 value 值发生变化时,v-model 所绑定的 成员也会发生该改变
  • 当成员 发生变化后,对应的所绑定 v-model 的 表单的值也发生变化

案例演示

上方的案例,模拟了下方代码的效果:

Vue
<template>
  <div>
    <input type="text" v-model="msg">
    <p>{{ msg }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      msg: '',
    }
  }
}
</script>

多选框、下拉框、单选框 案例

针对不同的 input 类型,对应的绑定操作需要了解

下拉框

v-model 应绑定给 <select> 上,而不是 <option>,但是 model 取的是 option 选中的 value 值

Vue
<template>
  <div>
    <select v-model="area">
      <option value="北京">北京</option>
      <option value="北京">上海</option>
    </select>
  </div>
</template>

<script>
export default {
  data() {
    return {
      area: ''
    }
  }
}
</script>

单选框

按照 radio 类型的特性,radio 对应的 name 需要绑定一致才有 单选的效果

Vue
<template>
  <div>
     <input type="radio" name="sex" value="" v-model="sex">男
     <input type="radio" name="sex" value="" v-model="sex">女
  </div>
</template>

<script>
export default {
  data() {
    return {
      sex:'',
    }
  }
}
</script>

复选框

复选框使用 v-model 需要绑定的数组类型的变量,若非数组类型,则绑定的只是 复选框 的 checked 状态

Vue
<template>
  <div>
     <input type="checkbox" value="篮球" v-model="like">篮球
     <input type="checkbox" value="跳舞" v-model="like">跳舞
     <input type="checkbox" value="rup" v-model="like">rup
     <input type="checkbox" value="画画" v-model="like">画画
     <input type="checkbox" value="敲代码" v-model="like">敲代码
  </div>
</template>

<script>
export default {
  data() {
    return {
      like:[],
    }
  }
}
</script>

model修饰符

v-model 提供了修饰符,方便在对应的需求中使用。让 v-model 拥有更多功能

方法名 效果
.number 以parseFloat转成数字类型
.trim 去除首尾空白字符
.lazy 在change时触发而不是inupt时
v-text v-html

介绍

与原生的 innerText、innerHTML 类似,v-text 是以字符串格式显示,v-html 是尝试标签形式解析内容

语法:

  • v-text=”Vue数据变量”
  • v-html=”Vue数据变量”

该语法会覆盖标签内的插值表达式 {{value}} 的输出结果

演示

Vue
<template>
    <div>
        <p v-text="sayHi"></p>
        <p v-html="sayHi"></p>
    </div>
</template>

<script>
export default {
    data() {
        return {
            sayHi: '<h3>Hello vue</h3>'
        }
    }
}
</script>

输出结果:

%title插图%num

v-show v-if

介绍

v-show 与 v-if 都可以实现将标签隐藏,但是它们隐藏的方式有所不同

  • v-show 使用 display:none 的方式隐藏 (适合频繁切换)
  • v-if 是直接将标签从 DOM树 上移除

当左侧绑定的变量值为 true,会将标签元素显示。否则 v-show 会将其隐藏,而 v-if 会直接移除该标签

语法:

  • v-show=”Vue变量”
  • v-if=”Vue变量”
  • v-if 可以配合 v-else 或者 v-else-if 使用

演示

Vue
<template>
    <div>
        <p v-show="type">Hello</p>
        <p v-if="score == 100">满分</p>
        <p v-else-if="score > 60">及格</p>
        <p v-else>不及格</p>
    </div>
</template>

<script>
export default {
    data() {
        return {
            type: true,
            score: 70,
        }
    }
}
</script>

输出结果:

%title插图%num

v-for

效果

v-for 用于遍历对应数组成员,按照数组数量,循环生成对应标签与标签中值

语法:

  • v-for=” (值变量,索引值) in 目标结构 “

效果:

  • 可以遍历数组 / 对象 /数字
  • v-for 的临时变量不可以用到 v-for 对应标签 范围外

演示

使用 插值表达式,让循环后的结果在对应的位置上显示

Vue
<template>
    <div>
        <ul>
            <li v-for="(item, index) in arr" :key="index">
                {{ index }} --- {{ item.name }} --- {{ item.sex }}
            </li>
        </ul>
    </div>
</template>

<script>
export default {
    data() {
        return {
            arr: [
                { name: '小明', sex: '' },
                { name: '屎猫', sex: '' },
                { name: '七罪', sex: '' },
                { name: '蝶衣', sex: '' },
                { name: '小可', sex: '' },
            ]
        }
    }
}
</script>

输出结果:

%title插图%num

Key的指定

:key=”参照物” 用于优化渲染,以便按照 key 去比较变化,要求为循环中不重复的值

v-for 会最大限度的尝试就地修改/复用相同类型元素,这和 vue 使用 diff算法 有关

%title插图%num

虚拟DOM

与 原生DOM 不同,虚拟DOM剔除了大部分不常用参数,可更有效的提高DOM更新的性能

虚拟DOM 在内存中找到变化部分,再更新到真实DOM中 (打补丁)

%title插图%num

选择 key 的参照物

从数据库中接收到的对象,一般id值是自增生成的,具有唯一性。适合用作于 循环的 key

有 参照物 用 参照物,无 参照物 用 索引

Vue
<template>
    <div>
        <ul>
            <li v-for="(item, index) in arr" :key="item.id">
                {{ index }} --- {{ item.name }} --- {{ item.sex }}
            </li>
        </ul>
    </div>
</template>

<script>
export default {
    data() {
        return {
            arr: [
                {id:1, name: '小明', sex: '' },
                {id:2,  name: '屎猫', sex: '' },
                {id:3,  name: '七罪', sex: '' },
                {id:4,  name: '蝶衣', sex: '' },
                {id:5,  name: '小可', sex: '' },
            ]
        }
    }
}
</script>

更新检测

只有部分数组方法会造成 v-for 更新

某些情况下,v-for 绑定的数组发生改变后,v-for对应 循环生成的标签的内容也会更新,且是尽可能减少渲染区域

  • 数组翻转 (会变更)
  • 数组截取 (不会变更)
  • 数组值发生更新 (不会变更)

数组变更方法,就会导致 v-for 更新,页面更新

例如:

  • push() 数组向后插入
  • pop() 删除数组末尾一个元素
  • shift()  删除首个数组元素,并把其他元素索引向前位移补位
  • unshift() 在数组的开头向数组添加新元素,并“反向位移”旧元素
  • splice() 方法可用于向数组添加新项,或用于删除元素
  • sort() 数组排序
  • reverse() 翻转数组

针对数组非变更方法,或返回新数组的方式的方法,不会导致 v-for 更新,可采用 覆盖数组 或者 this.$set()

演示

针对不同的方法,为让视图层也同步更新,对应不同的解决策略

JavaScript
<template>
    <div>
        <ul>
            <li v-for="(item, index) in arr" :key="index">
                {{ item }}
            </li>
        </ul>
    </div>
</template>

<script>
export default {
    data() {
        return {
            arr: [1, 2, 3, 4, 5, 6],
        }
    },
    methods: {
        revBtn() {
            // 翻转数组 会直接改变数组
            this.arr.reverse();
        },
        silceBtn() {
            // 裁剪数组(下标0数量3) 返回一个新数组
            let temp = this.arr.slice(0, 3);
            // 将得到的新数组 覆盖 原数组 触发变更
            this.arr = temp;
        },
        updataBtn() {
            /*
              $set 实现 视图层同步更新 数组中成员值
              参数1:数组目标 参数2:位置 参数3:更新后的值
            */
            this.$set(this.arr, 0, 7);
        }
    }
}
</script>

效果展示

%title插图%num

动态 class

说明

使用 :class 来让 Vue 动态操作选中的 class类 是否被使用

v-bind 的 :class 操作可以动态的设置和切换 标签中的目标 class 是否被引用

语法:

  • :class = “{ 类名:布尔值 }”
  • 当花括号 右侧 表达式的结果为 true 时,标签使用右侧类名

演示

使用 vue 来控制 class 的操作

Vue
<template>
    <div>
        <p :class="{ red: bool }">红色?</p>
        <button @click="btn">变色</button>
    </div>
</template>
  
<script>
export default {
    data() {
        return {
            bool: true,
        }
    },
    methods: {
        btn() {
            this.bool = !this.bool;
        }
    }
}
</script>
  
<style>
.red {
    color: red;
}
</style>

效果展示

%title插图%num
动态 style

说明

使用 :style 动态设置标签中的 style 行内式的 样式属性

v-bind 的 :style 操作可以被vue动态的设置 :style 中的 行内样式 对应的属性

语法:

  • :style=” { css属性名:变量值 } “
  • 花括号 左侧为 css属性样式,右侧 对应 数据层 data 中该属性样式的 值

演示

使用 vue 来对 行内样式 的 属性赋值 操作

Vue
<template>
    <div>
        <p :style="{ color:colorStr }">红色</p>
    </div>
</template>
  
<script>
export default {
    data() {
        return {
            colorStr: 'red',
        }
    }
 }
</script>

效果展示

%title插图%num
自定义方法

自定义方法定义在 methods 对象中,通常用于绑定事件的回调方法

说明

基本使用

methods 对象中定义方法,在视图层由 触发事件 绑定的 方法名 调用执行,

Vue
<template>
  <div>
    <input type="button" @click="entFnn" value="sayHi">
  </div>
</template>

<script>
export default {
  methods: {
    entFnn() {
      alert('hello vue');
    }
  }
}
</script>

效果展示

%title插图%num

获取参数

若想让方法获取参数,首先在方法绑定对应的事件标签中传入 对应的参数
标签的嵌套规则可作为一个作用域的嵌套规则,方法定义后可直接获取对应标签绑定的参数

Vue

<template>
    <div>
        <ul v-for="(item, index) in text" :key="index">
            <li @click="entFnn(item)">{{ item }}</li>
        </ul>
    </div>
</template>
  
<script>
export default {
    data() {
        return {
            text: [1, 2, 3, 4],
        }
    },
    methods: {
        entFnn(i) {
            alert(i);
        }
    }
}
</script>

效果展示

%title插图%num

获取事件对象

当没有传入参数的方法定义行参,默认传入事件对象,直接使用即可
当有参数传入到行参中,需要在行参中传入 $even 作为 事件对象 传入

Vue

<template>
  <div>
    <input type="text" @keydown.enter="entFnn" v-model="text">
  </div>
</template>

<script>
export default {
  data() {
    return {
      text: '',
    }
  },
  methods: {
    entFnn(e) {
      // 事件对象
      console.log(e);
    }
  }
}
</script>

效果展示

%title插图%num
过滤器

过滤器定义在 filter方法 或 filter 对象中,视图层使用 | 绑定过滤器处理的 值

说明

定义过滤器的目的是在界面层将 插值表达式 和 v-bind 动态属性 进行处理 并返回处理后的值

过滤器只能用在 {{ value }} 与 v-bind 动态属性中,使用过滤器的方式为 变量值 | 过滤器名

语法

  • 使用 {{变量值 | 过滤器名 }} 或 v-bind:属性名=” 变量值 | 过滤器名 “
  • 定义全局 main.js – Vue.filter( ‘过滤器名字’,函数体 );
  • 定义局部 filerts:{ 过滤器名字:函数体 };

全局过滤器 定义在 main.js 入口上,所有绑定的 vue 组件均可使用

main.js
 // Vue.filter('过滤器名字',传入的值 => 处理后的值);
 Vue.filter('reverse', val => val.split('').resverse().join(''));

局部过滤器 只能在 单 vue 文件下定义并使用

Vue
<template>
    <div>
        <p>{{ sayHi }}</p>
        <!-- 使用过滤器 -->
        <p>{{ sayHi | reverse }}</p>
    </div>
</template>

<script>
export default {
    data() {
        return {
            sayHi: 'Hello word',
        }
    },
    // 只在当前 vue 文件内使用
    filters: {
        reverse(val) {
            return val.split('').reverse().join('');
        }
    }
}
</script>

效果展示

%title插图%num
计算属性

计算属性定义在在 computed 对象中,视图层使用 计算属性名 绑定

说明

为了优化性能,计算属性将处理后 得到的结果的值 缓存在内存中,用于需要时候使用

计算属性 在 computed 项中定义,它可使用类似当作 data 下的 成员调用方式被调用 例如 this.value、{{value}}

计算属性根据依赖变量结果缓存, 依赖变化重新计算结果存入缓存, 比普通方法性能更高

注意

  • 计算属性和data属性都是变量,不能重名。用法和data相同
  • 函数内变量变化,会自动重新计算结果返回

语法

  • 基本写法 computed: { 计算属性名()=>处理后的值 }
  • 完整写法 computed: { 计算属性名: { get(){} , set(){} } }

演示

Vue
<template>
    <div>
        <p>{{ sayHi }}</p>
        <p>{{ up }}</p>
        <p>{{ up }}</p>
        <p>{{ up }}</p>
        <p>{{ up }}</p>
    </div>
</template>

<script>
export default {
    data() {
        return {
            sayHi: 'Hello word',
        }
    },
    computed:{
        up(){
            console.log('执行计算属性方法');
            return this.sayHi.toUpperCase();
        }
    }
}
</script>

效果展示

%title插图%num

完整操作

如要想要对 计算属性 直接赋值 或者 额外的操作 ( 如 获取 v-model 传入前面页面改变的新值 ),需要使用完整写法

JavaScript
<template>
    <div class="app">
        <span>全选</span>
        <input type="checkbox" v-model="isAll">
        <button>反选</button>
        <hr />
        <ul>
            <li v-for="(item, index) in arr" :key="index">
                <input type="checkbox" v-model="item.checked">
                <span>{{ item.name }}</span>
            </li>
        </ul>

    </div>
</template>

<script>
export default {
    data() {
        return {
            arr: [
                { name: '屎猫', checked: false, },
                { name: 'AI婆', checked: false, },
                { name: '腐喵', checked: false, },
                { name: '夜夜', checked: false, },
                { name: '泥垢', checked: false, },
            ],
        }
    },
    computed: {
        isAll: {
            // 当 有值 写入到 计算属性中 参数中读取这个值
            set(val) {
                // 执行写入后的操作方法
                this.arr.forEach(item => item.checked = val);
            },
            // 当发生读取 计算属性 操作后
            get() {
                /*
                  执行读取后的操作方法
                * every 方法会遍历验证每个数组
                * 
                * 如若其中有一个 不符合条件 返回 false 
                * 如若都符合条件 返回 true
            */
                return this.arr.every(obj => obj.checked === true);
            },
        }
    }

}
</script>

效果展示

%title插图%num
监听器

监听器定义在 watch 对象中,命名需与监听的 变量 或 计算属性 同名

说明

监听器 用于 操作 对应变量值 的 变化的 返回的 回调函数

可以侦听 data/计算属性 中值的变化
当值发生改变时 会被 watch 对应名字的方法所 捕获

简单写法:

JavaScript
<template>
    <div>
        <input type="text" v-model="data">
    </div>
</template>

<script>
export default {
    data() {
        return {
            data: ''
        }
    },
    watch: {
        // 参数1 当前更新后的值 参数2 上一个状态的值
        data(newVal, oldVal) {
            console.log(newVal, oldVal);
        }
    }
}
</script>

效果展示

%title插图%num

完整写法:

为了监听对象的变化,需要进行深度监听。将 deep 的值设置为 true

JavaScript
<template>
    <div>
        <input type="text" v-model="text.name">
    </div>
</template>

<script>
export default {
    data() {
        return {
            text: { name: '' },
        }
    },
    watch: {
            /*
              * [问题!] 我们发现 newVal 与 oldVal 的值相同
              * watch监听到对象的变化,但是在对对象或数组做变更操作时,删除或添加属性,
              * 监听函数中的新值和旧值都指向了同一个对象或数组,并且Vue不会保留变更之前的副本
              
              * [解决方法]
              * 1. 计算属性 使用 JSON.stringify 将需要监听 目标 转为字符串
              * 2. 引入监听对应名称的 计算属性 值 并使用 JSON.parse 转换成对象 完成比较效果
              * tip. 由于字符串是 简单数据 更换值时是直接覆盖 故新值与旧值不存在相同的指向下标
            */
        text: {
            handler(newVal) {
                // 返回 text 对象
                console.log(newVal);
            },
            // 深度监听 (监听对象属性中的变化)
            deep: true,
            // 立即执行 (刷新页面时 立即执行)
            immediate: true,
        }
    }
}
</script>

效果展示

%title插图%num

解决 watch 监听对象时候 新旧值 一致问题

JavaScript
<template>
    <div>
        <input type="text" v-model="user.name">
    </div>
</template>

<script>
export default {
    data() {
        return {
            user: {
                name: ''
            }
        }
    },
    watch: {
        /*
           * 使用序列化与反序列化的方式 相当于创建了一个新的对象
           * 从字符串转对象 是创建新对象的操作 并对 计算属性 重新赋值
           * [触发更新] 计算属性的值发生变化后 原下标更换 因此得到的结果就不同了
           * https://www.cnblogs.com/MaiRen19970323/p/15160198.html
        */
        username: {
            handler(newVal, oldVal) {
                let temp_1 = JSON.parse(newVal);
                let temp_2 = JSON.parse(oldVal);
                console.log(temp_1.name, temp_2.name);
            },
            deep: true,
        }
    },
    computed: {
        // 定义 计算属性
        username() {
            return JSON.stringify(this.user);
        }
    }
}
</script>

效果展示

%title插图%num
_scoped

说明

使单页面的 CSS 样式就只能作用于当前的组件

当给 单vue文件中的 <style> 加上 scoped 后,会自动给标签添加 data-v-hash(哈希值)
也就是说,该样式只能适用于当前组件元素。通过该属性,可以使得组件之间的样式不互相污染

Vue
<style scoped>
.btn {
  width: 100px;
  height: 70px;
  border-radius: 10px;
  margin: 10px;
  border: 2px solid #66cc;
}
</style>

效果展示

%title插图%num

加上scoped之后Vue会在这段样式的最后一段加上[data-v-hash]属性,然后通过属性选择器选中这个元素,从而实现元素样式的隔离

Vue 组件

组件是可复用的 Vue 实例,封装标签、样式和JS代码

%title插图%num

组件是 vue 的特性,将不同业务或界面划分为组件,并且各组件作用域独立,实现重复利用。

介绍:

  • 组件化:封装的思想,把页面上 可重用的部分 封装成 组件,从而方便项目的开发和维护
  • 一个页面,可以拆分成一个个组件,每个项目可以有自己独立的结构和样式还有行为

优势:

  • 变量区独立,解决频繁命名过多的问题
  • 各标签作用域都相互独立,便于集成复用
  • 减少重复的内容复制粘贴,方便项目的开发和维护
  • 各自独立,互不影响
基本使用

使用 import 引用的 vue组件,该组件被称为 子级组件
引用该 子级组件 的 单文件,称为它的 父级组件

组件的实际操作可分为:

创建组件 / 引入组件 / 注册组件 / 使用组件

创建组件

创建 .vue 文件,封装复用的标签、样式和 JS代码

Vue
<template>
  <div class="title">
    <div>Hello Vue</div>
  </div>
</template>

<script>
export default {

}
</script>

<style>
.title {
  width: 200px;
  height: 120px;
  display: flex;
  justify-content: center;
  align-items: center;
  border: 5px solid #ccc;
  border-radius: 18px;
  margin: 10px;
}
</style>

注册组件 – 全局注册

声明在全局的组件,会允许在所有 vue 组件上使用
在 main.js 中引用该 vue 文件,并用 vue.component( “组件名” , 组件对象 ); 进行注册

JavaScript
import Vue from 'vue'
// 引用 组件
import hellovue from './helloVue.vue'
// 全局注册 参数1:组件名 参数2:组件对象
Vue.component('hellovue',hellovue);

其他 vue 单文件都可以在 <template> 视图层 直接使用 <hellovue> 显示该组件样式

Vue
<template>
  <div>
    <hellovue></hellovue>
    <hellovue></hellovue>
    <hellovue></hellovue>
  </div>
</template>

<script>
export default {

}
</script>

<style>

</style>

效果展示

%title插图%num

注册组件 – 局部注册

声明在局部的组件,只能在已使用 import 引用该组件的 单文件上使用 (可引用多个)
在 vue 文件中,使用 import 组件名 from ‘文件路径’ 方式引入,再与 component 对象成员中注册

Vue
<template>
  <div>
    <hellovue></hellovue>
    <hellovue></hellovue>
    <hellovue></hellovue>
  </div>
</template>

<script>
import hellovue from './helloVue.vue';
export default {
  components: {
    hellovue: hellovue,
  }
}
</script>

<style>

</style>

效果展示

%title插图%num

父向子传参

子级组件允许父级组件向子级组件传递对应数据,以便填充样式中对应的数据操作

%title插图%num

当父级组件需要像子级组件传参,方便显示对应样式时候,子级需要使用 props 对象接收,
props 是可作为 字符串数组,定义的字符串用作 父级传递数据时 子级接收的 变量
props 也可用做对象,来对传参的过程进行更多的约束或指定操作,例如:限制传参类型(type)、默认值(default)、必填项(require)

接收参数

子级组件 定义接收数据的的 props 数组,用于接收父级组件传递过来的参数,可放置在对应位置中 (简单方法)

Vue
<template>
  <div class="title">
    <div>{{  text  }}</div>
  </div>
</template>

<script>
export default {
  props: ['text'],
}
</script>

<style>
.title {
  width: 200px;
  height: 120px;
  display: flex;
  justify-content: center;
  align-items: center;
  border: 5px solid #ccc;
  border-radius: 18px;
  margin: 10px;
}
</style>

[扩展] props 另外有复杂写法,可以约束接收的值的类型、默认值、是否必填等 相关文档

JavaScript
export default {
  props: {
    background: String, // 外部插入此变量的值 必须是字符串 否则报错
    color: {
      type: String, // 约束 color值的类型
      default: '#fff' // color变量 默认值 (若不传值 默认为白色)
    },
    title: {
      type: String,
      require: true, // 约束必须传值 否则报错
    }
  }
}

传入参数

父级通过 子级 props 数组中对应标识的 变量名,使用 v-model 或 :变量名 传递给子级数据

Vue
<template>
  <div>
    <hellovue v-for="(item, index) in arr" :key="index" :text="item"></hellovue>
  </div>
</template>

<script>
import hellovue from './helloVue.vue';
export default {
  components: {
    hellovue,
  },
  data() {
    return {
      arr: ['hello word', 'hello dart', 'hello vue'],
    }
  }
}
</script>

<style>

</style>

效果展示

%title插图%num
子向父传参

单向数据流

父级传给子级数据,但当子级组件的数据发生改变但父级不变时候,这状态叫做单向数据流

%title插图%num

子级获取父级传递过来的参数后,虽然在子级的数据层看起来是可以修改这个参数,
但是这次修改的操作是单向的。并没有通知父级。因此父级值并不因此受到影响

正确的操作是需要 同时影响父级 对应的属性,

tip

  • 父子数据不一致,而且子组件是依赖父级传入的值
  • 从父级到子级的数据流向,叫做单向数据流
  • props 的变量本身是只读的

因此直接在子级里面修改传入的参数的操作是错误的!

子级向父级传参

子级应使用 $emit() 向父级 绑定的 对应自定义事件 传递数据

this.$emit(‘自定义事件名’,传递的值);

%title插图%num

子级组件 (实现样式)

Vue
<template>
  <div class="btn">
    <div>{{ num }}</div>
    <button @click="addbtn">点赞</button>
  </div>
</template>

<script>
export default {
  props: ['num', 'ind'],
  methods: {
    addbtn() {
      // 发送给父级 对应的事件 需要的参数
      this.$emit('clickbtn', this.num, this.ind, 1);
    }
  }
}
</script>

<style scoped>
.btn {
  width: 100px;
  height: 70px;
  border-radius: 10px;
  margin: 10px;
  border: 2px solid #66cc;
}
</style>

父级组件 (操作数据)

Vue
<template>
  <div>
    <!-- 传给子级参数 -->
    <hellovue v-for="(item, index) in numArr"
     :key="index"
     :ind="index"
     :num="item"
     <!-- 无需再写传值 否则会被忽略 -->
     @clickbtn="fn"></hellovue>
  </div>
</template>

<script>
import hellovue from './addDemo.vue';
export default {
  components: {
    hellovue,
  },
  data() {
    return {
      numArr: [0, 0],
    }
  },
  methods: {
    // 欲从 子级 获取传入的参数
    fn(num, index, add) {
      let temp = num + add;
      // 通知组件更新
      this.$set(this.numArr, index, temp);
    },
  }
}
</script>

<style>

</style>

效果展示

%title插图%num
跨组件传参

说明

使用一个空白的vue类,作为组件的公共区域,组件引入 此公共区域 进行传值

通过此公共区域,各个组件引用该公共区域后,事件参数就可以 通过约定的 自定义事件 实现 发送参数 $emit 与接收参数 $on

%title插图%num

实现

创建公共区域 例如: EvenBus/index.js

JavaScript
import Vue from 'vue';
// 创建 vue 空白对象
export default new Vue();

引用该公共区域 App.vue

Vue
<template>
  <div>
    <div style="float:left;">
      <likeMenu v-for="(item, index) in arr"
       :key="index"
       :ind="index"
       :num="item.num"
       :name="item.name"
       @checkOn="fn"></likeMenu>
    </div>
    <likeDemo :list="arr" style="float:left;"></likeDemo>
  </div>
</template>

<script>
import likeMenu from './likeMenu.vue';
import likeDemo from './likeDemo.vue';
export default {
  components: {
    likeMenu,
    likeDemo,
  },
  data() {
    return {
      arr: [
        { name: '张三', num: 0 },
        { name: '李四', num: 0 },
      ],
    }
  },
  methods: {
    fn(index, i) {
      this.arr[index].num = this.arr[index].num + i;
    }
  }
}
</script>

<style>

</style>

子组件(发送参数方) likeMenu.vue

Vue
<template>
    <div class="btn">
        <p>{{ name }}</p>
        <button @click="fn">点赞({{ num }})</button>
    </div>
</template>

<script>
import evenBus from '../EvenBus/index.js';
export default {
    props: ['num', 'ind', 'name'],
    methods: {
        fn() {
            this.$emit('checkOn', this.ind, 1);
            evenBus.$emit('putNum', this.ind, this.num);
        }
    }
}
</script>

<style>
.btn {
    padding: 10px;
    border: 2px solid #66cc !important;
    text-align: center;
    background: #66cc;
    margin: 4px;
}
</style>

子组件(接收参数方) likeDemo.vue

Vue
<template>
    <div class="demo">
        <li v-for="(item, index) in list" :key="index" @click="fn(index)">
            <span>{{ item.name }}</span> ---- <span>点赞数:{{ item.num }}</span>
        </li>
    </div>
</template>

<script>
import evenBus from '../EvenBus/index.js';
export default {
    props: ['list'],
    created() {
        evenBus.$on('putNum', (index, num) => {
           console.log(`子组件传过来参数为:${index} ${num}`);
        });
    },
}
</script>

<style>
.demo {
    border: 2px solid #66cc !important;
    padding: 5px;
    margin: 4px;
}
</style>

效果展示

%title插图%num

注意

即使你的子组件能修改数据源的数据
也尽量保证数据源操作等使用在同一个区域,使用该区域去影响所有组件变化

当父级组件传递给子组件为对象或数组时,实际上传递的为 数组或对象对应的 下标,
子组件 修改父级传入的对象成员值,若对象下标一致是会影响父级 对应的 值

%title插图%num
name 属性

组件 name 可用做注册组件的名字

组件在 name 成员中定义的字符串值,可以在被调用时用作组件名称

  • 组件定义 name 属性和值
JavaScript
<script>
export default {
  name: 'ComNameDemo'
}
</script>

  • 注册组件可用上面 name 的值
JavaScript
<template>
  <div>
    <ComNameDemo></ComNameDemo>
  </div>
</template>

<script>
import ComName from './nameDemo.vue'
export default {
  components: {
    // ES6语法 将数组中对应的对象原地复制出来
    // 实际下方的操作等同于 'ComNameDemo':ComName
    [ComName.name]: ComName,
  }
}
</script>

效果同理之前在父级 自定义组件命名 的操作,只不过该组件提前在 name 中被赋予了名字,
可直接调用子组件内置的 name 名字作为 子组件 命名在components 上

动态组件

委派 <components> 来切换需显示的组件配合 :is 传值实现动态组件效果

  1. 使用 <components> 标签
  2. 在 <components> 标签中加入 vue属性 :is=’值’
  3. 在数据层的引入组件并在 components 中注册组件
  4. <components> 会根据 :is 中字符串对应的组件名 动态显示 对应的组件
Vue
<template>
    <div>
      <button @click="comName='UserName'">账号信息填写</button>
      <button @click="comName='UserInfo'">个人信息填写</button>
      <p>下方显示注册组件-动态切换</p>
      <div style="border:1px solid red;">
          <components :is="comName"></components>
      </div>
    </div>
</template>

<script>
import UserInfo from './01_UserInfo.vue';
import UserName from './UserName.vue';
export default {
  data(){
    return{
      comName:'UserName'
    }
  },
   components:{
    UserInfo,
    UserName
   }
}
</script>

效果展示

%title插图%num
组件缓存

使用 <keep-alive> 包裹在 <components> 外侧,实现组件缓存

当组件频繁切换的时候 实际上是进行了多次组件的创建和销毁 这样的操作将降低性能

  1. 使用 <keep-alive> 包裹在 <components> 外侧
  2. <keep-alive> 将会使包裹着的 <components> 首次创建后 将动态组件放置在缓存中
  3. 当动态组件切换状态时 需要创建或销毁 时会与内存交互 而不直接销毁和创建

不嵌套 <keep-alive>

Vue
  <!-- 组件切换时 会不断的执行销毁创建的动作 -->
  <components :is="comName"></components>
  <script>
export default {
  created(){
    console.log('组件创建完成');
   },
   destroyed(){
    console.log('组件销毁完成');
   }
}
</script>

效果展示

%title插图%num

外层嵌套 <keep-alive>

Vue
<!-- vue 内置 keep-alive 组件 实现组件的缓存 -->
<keep-alive>
   <components :is="comName"></components>
</keep-alive>
<script>
export default {
  created(){
    console.log('组件创建完成');
   },
   destroyed(){
    console.log('组件销毁完成');
   }
}
</script>

效果展示

%title插图%num
激活/非激活 钩子

可用于获取 缓存组件 是否在页面中显示 的钩子函数 (需要搭配 <keep-alive>)

当需判断缓存的组件是被切走时候,使用 activated 获得显示时的回调,deactivated 获得切走时的回调

  • activated 获得激活状态时触发
  • deactivated 失去激活状态时触发

给在 <components> 中注册的动态组件监听 激活/取消激活 事件的钩子函数

  • 被缓存的组件,无法取得 组件 创建销毁 的状态,因此需要使用 激活 未激活 状态
  • 依据 激活 未激活 状态,我们可以让数据层得知当前界面已显示的缓存组件
JavaScript
export default {
    activated(){
        console.log('xxx 组件被激活');
    },
    deactivated(){
        console.log('xxx 组件取消激活');
    }
}

效果展示

%title插图%num
slot 插槽
基础使用

当不确定一个组件中标签的类型,可以用 <slot></slot> 进行占位

通过 slot 标签,让组件内可以接收不同的标签结构显示。给 slot 组件自由插入标签样式

描述

“ 就像照片展示用的相框一样,可以按着喜好插入需要的照片 ”

%title插图%num

说明

  • 子级组件定义 <slot></slot> 标签,实际就是划分 父级 在给子级组件中写 标签 的占位区域
  • 当子级组件 被 父级引用时候,在标签内容嵌套的标签会显示在 子级组件 定义 slot 标签的区域
JavaScript
<!-- 子级 定义组件中的插槽 -->

<template>
    <div class="Box">
        <hr />
        <p>展示的内容</p>
        <slot></slot>
        <hr />
    </div>
</template>

<script>
export default {}
</script>
    
<style>
.Box {
    text-align: center;
    width: 300px;
    height: 230px;
    padding: 10px;
    border: 2px solid #000;
    margin: 10px;
}
</style>
JavaScript
<!-- 父级 引用组件并插入标签 -->

<template>
  <div>
    <Pannel>
      <img src="https://www.dmoe.cc/random.php" width="70%">
    </Pannel>
    <Pannel>
      <ul>
        <li>锄禾日当午</li>
        <li>汗滴禾下土</li>
        <li>谁知盘中餐</li>
        <li>粒粒皆辛苦</li>
      </ul>
    </Pannel>
  </div>
</template>

<script>
import Pannel from './插槽.vue';
export default {
  components: {
    Pannel,
  }
}
</script>

效果展示

%title插图%num
默认内容

在子级组件的 <slot></slot> 标签内部布置的标签,就是 slot 的默认内容

%title插图%num

描述

  • 子级组件定义了默认内容后,父级若不在组件内写标签,默认子级的 slot 标签将显示自己嵌套着的内容

效果

  • 不给组件传标签,slot 内容原地显示
  • 给组件传标签,则slot 整体被换掉

演示

子级标签
<template>
    <div class="Box">
        <hr />
        <p>展示的内容</p>
        <slot>
            <p>无内容</p>
        </slot>
        <hr />
    </div>
</template>
父级标签
<template>
  <div>
    <Pannel>
      <ul>
        <li>锄禾日当午</li>
        <li>汗滴禾下土</li>
        <li>谁知盘中餐</li>
        <li>粒粒皆辛苦</li>
      </ul>
    </Pannel>
    <Pannel></Pannel>
  </div>
</template>

<script>
import Pannel from './插槽.vue';
export default {
  components: {
    Pannel,
  }
}
</script>

效果展示

%title插图%num
具名插槽

子组件中当中存在多个 slot 标签时,可依据 name 属性进行区分

%title插图%num

若子组件中有多个 slot 标签,父级想将标签插入到子级,需要使用 <template v-shot:slot名字> 进行区分

用法

  • slot 使用 name 属性区分名字
  • template 分发需要配合 v-slot:名字 来区分对应的插槽
  • v-slot:名字 可以简化成 #名字 方式区分

语法

子级组件
<template>
    <div>
        <slot name="filst"></slot>
        <slot name="item"></slot>
    </div>
</template>
父级组件
<template>
  <div>
    <Pannel>
      <template v-slot:filst>
        插入的内容
      </template>
      <template v-slot:item>
        插入的内容
      </template>
    </Pannel>
  </div>
</template>

演示

子级组件
<template>
    <div class="Box">
        <hr />
        <p>展示的内容</p>
        <slot name="item_1">
            <p>展示内容1</p>
        </slot>
        <slot name="item_2">
            <p>展示内容2</p>
        </slot>
        <hr />
    </div>
</template>

<script>
export default {

}
</script>
父级组件
<template>
  <div>
    <Pannel>
      <template v-slot:item_1>
        <img src="https://www.dmoe.cc/random.php" width="70%">
      </template>
      <template v-slot:item_2>
        <p>无名喵站</p>
      </template>
    </Pannel>
  </div>
</template>

<script>
import Pannel from './插槽.vue';
export default {
  components: {
    Pannel,
  }
}
</script>

效果展示

%title插图%num
作用域插槽

子组件中的插槽若想传值给父级 使用 :自定义变量名=”值” 方式发送父级 进行接收

%title插图%num

说明

  • 子级组件中 slot插槽 传给父级 <slot :自定义变量名=’值’></slot>
  • 父级 <template> 接收子级 slot插槽 数据 v-slot=“自定义变量名” (注意不是v-slot:插槽名)

方法

  1. 子组件在 slot 上绑定属性和子组件内的值,使用 :自定义变量名=”值”
  2. 使用组件,传入自定义标签,用 templatev-slot=“自定义变量名”
  3. 自定义变量名 自动绑定 slot 上所有 属性
    • 自定义变量名 = {子级传入的变量}

演示

子级组件
<template>
    <div class="Box">
        <hr />
        <!-- 向父级 传入变量 seyHi -->
        <slot :row="sayHi"></slot>
        <hr />
    </div>
</template>

<script>
export default {
    data() {
        return {
            sayHi: {
                one: 'Hello',
                tow: 'Vue',
            }
        }
    }

}
</script>
父级组件
<template>
  <div>
    <Pannel>
      <!-- v-slot 会收集传入的所有数据 并以子级中书写的 :变量 作为它们的变量名 -->
      <template v-slot="scope">
        <p>{{ scope.row.one }}</p>
        <p>{{ scope.row.tow }}</p>
      </template>
    </Pannel>
  </div>
</template>

<script>
import Pannel from './插槽.vue';
export default {
  components: {
    Pannel,
  }
}
</script>

效果展示

%title插图%num
Vue 进程
生命周期

一个人从出生到逝去的过程

关于生命周期

vue 框架从 创建 销毁 的整个过程,叫 Vue 生命周期

vue组件 的创建到销毁也是一个 vue实例 的生命周期

%title插图%num
钩子函数

vue 框架内置钩子函数,随着组件的生命周期阶段,自动执行

初始化 / 挂载 / 更新 / 销毁

  • 随着特定的时间点,执行特定的操作
  • 组件创建完毕后,可以在 crearetd 生命周期函数中发起 Ajax 请求,从而初始化 data数据
  • vue 的钩子函数由4大阶段8个方法组成
描述 方法名[之前] 方法名[之后]
初始化 beforeCreate created
挂载 beforeMount mounted
更新 beforeUpdate update
销毁 beforeDestroy destroyed
初始化

初始化流程

  1. new Vue() 实例化 [组件也有生命周期]
  2. init Events & Lifecycle 初始化时间何生命周期函数
  3. beforeCreate 生命周期钩子函数被执行
  4. Init injections & reactivity Vue内部添加 data 和 methods 等
  5. created 生命周期钩子函数被执行 实例创建
  6. 编译模板阶段 – 开始分析
    1. 是否有 el选项 – 检查要挂在哪里 (会选择挂载在成员 el 指定的位置)
      1. 没有 调用 $mount()方法
      2. 有 继续检查 template 选项
%title插图%num

初始化的过程执行 (初始化前 → 初始化后)

beforeCreate() ➡️ created()

在初始化完成 (cerated) 的时候,能获取 data 中的成员,但是无法获取真实 DOM

实际演示

  1. 在 App.vue 上建立对应的 钩子函数 检查效果
  2. created() 中常用于 网络请求的场景 或者 注册全局事件
JavaScript
<template>
  <div>
    <p>{{ msg }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      msg: 'Hello Vue',
    }
  },
  beforeCreate() {
    /*
    - beforeCreate 是组件创建前的 钩子函数
    - data 与 methods 创建之前
  */
    console.log('准备初始化 Vue实例阶段');
    console.log(this.msg); // undefind (因为 data 未创建)
  },
  created() {
    /*
    - created 是组件创建后的 钩子函数
    - data 与 methods 创建完成后
    - 常用于 网络请求的场景 或者 注册全局事件
  */
    console.log('创建完毕 Vue实例');
    console.log(this.msg); // Hello Vue
  }
}
</script>

效果展示

%title插图%num
挂载阶段

挂载流程

  1. template 选项检查
    1. 有 编译 template 返回的 render 渲染函数
    2. 无 编译 el 选项对应标签作为 template (要渲染的模板)
  2. 虚拟DOM挂载成真实DOM之前
  3. beforeMount 生命周期钩子函数被执行
  4. Create … 把虚拟DOM和渲染的数据一并挂载到真实DOM上
  5. 真实DOM挂载完毕
  6. mounted 生命周期钩子函数被执行
%title插图%num

挂载虚拟DOM到真实DOM的过程执行 (挂载前→挂载后)

beforeMount() ➡️ mounted()

在 creared 钩子函数中无法获取对应的 真实DOM ,但是在 mounted 中可以获取到对应的 真实DOM

实际演示

  1. 在 App.vue 上建立对应的 钩子函数 检查效果
  2. beforeMount 进行预处理 data,不会触发生命周期中的 updated 钩子函数
JavaScript
<template>
  <div id="appDemo">
    <p>{{ msg }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      msg: 'Hello Vue',
    }
  },
  beforeMount(){
    /*
    - beforeMount 是虚拟DOM挂载到真实DOM操作之前的 钩子函数
    - 未在真实 DOM 中绑定 vue实例 中建立的 虚拟DOM内容
  */
    console.log('虚拟DOM 挂载到 原生DOM 前');
     // 尝试使用原生获取 vue项目创建的 DOM
     console.log(document.querySelector('#appDemo')); // null (初次操作返回空 因为未挂载)
  },
  mounted(){
    /*
    - mounted 是虚拟DOM挂载到真实DOM操作完成的 钩子函数
    - 已完成 虚拟DOM 绑定到 真实DOM 的操作
  */
    console.log('虚拟DOM 完成挂载到 原生DOM');
    // 尝试使用原生获取 vue项目创建的 DOM
    console.log(document.querySelector('#appDemo')); // <div id="appDemo">...</div>
  }
}
</script>

效果展示

%title插图%num
更新阶段

更新流程

  1. 当data里数据改变,更新DOM之前
  2. beforeUpdate 生命周期钩子函数被执行
  3. Virtual DOM …虚拟DOM重新渲染,打补丁到真实DOM
  4. updated 生命周期钩子函数被执行
  5. 当有 data 数据改变 重复这个循环
%title插图%num

[循环执行] 更新虚拟DOM到真实DOM的过程执行 (更新前→更新后)

beforeUpdate() ↩️updated()

在 beforeUpate 中可以取到更新前的内容,在 updated 中获取更新后的内容

实际操作

  1. 在 App.vue 上建立对应的 钩子函数 检查效果
  2. 数据发生改变后被监听器捕获,依次触发 beforeUpdate、updated 钩子函数
JavaScript
<template>
  <div>
    <ul id="appDemo">
      <li v-for="item, index in arr" :key="index">{{ item }}</li>
    </ul>
    <button @click="arr.push(1)">增加值</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      arr: [1, 2, 3, 4],
    }
  },
  /*
    - beforeUpdate 是数据更新到 真实DOM 前的钩子函数
  */
  beforeUpdate() {
    console.log('准备更新');
    // 虚拟DOM未更新到真实DOM 因此无法获取增加的值
    console.log(document.querySelectorAll('#appDemo>li')[4]); // undefined
  },
  updated() {
    /*
    - beforeUpdate 是数据更新到 真实DOM 后的钩子函数
    - 用于获取更新后的真实 DOM
  */
    console.log('更新完成');
    // 虚拟DOM完成更新到真实DOM阶段 因此可以取得最后更新的内容
    console.log(document.querySelectorAll('#appDemo>li')[4]); // <li>1</li>
  }

}
</script>

效果展示

%title插图%num
销毁阶段

例如 给组件绑定 v-if 并变更值为 false 的时候,触发销毁过程

销毁过程

  1. 当 $destroy() 被调用 – 例如组件DOM被移除(v-if)
  2. beforeDestroy 生命周期钩子函数被执行
  3. 拆卸数据监视器、子组件 和 事件监听器
  4. 实例销毁后,最后触发一个钩子函数
  5. destroyed 生命周期钩子函数被执行
%title插图%num

销毁组件前到销毁组件完成 (销毁前→销毁后)

beforeDestroy() ➡️ destroyed()

销毁组件过程 原生DOM 也随之移除

实际操作

  1. 在 delvue.vue 上建立对应的 钩子函数,App.vue 根组件引用,并检查效果
  2. v-if 可以直接移除组件,触发 销毁过程 的生命周期,步骤依次为 beforeDestroy、 destroyed
  3. 多用于移除全局事件、定时器、当前页组件、(eventBus中 $off 移除全局自定义事件方法)

定义定时器后销毁组件 即使销毁成功后 定时器依然在占用后台资源

HTML
<!-- App.vue -->
<template>
  <div>
    <deldemo v-if="isLife"></deldemo>
    <button @click="isLife = false">销毁组件</button>
  </div>
</template>

<script>
import deldemo from './delvue.vue';
export default {
  components: {
    deldemo
  },
  data() {
    return {
      isLife: true,
    }
  },
}
</script>

<!-- delvue.vue -->
<template>
    <div id="deldemo">
        <p>{{ msg }}</p>
    </div>
</template>

<script>
export default {
    data() {
        return {
            msg: 'hello Vue',
            timer: null,
        }
    },
    // 组件创建完成后 创建定时器
    created() {
        this.tiemr = setInterval(() => { console.log('组件在运行'); }, 1000);
    },
}
</script>

效果展示

%title插图%num

正确的方式是 在销毁阶段前的 beforeDestroy 钩子函数下,应移除当前组件占用的全局资源

JavaScript
<template>
    <div id="deldemo">
        <p>{{ msg }}</p>
    </div>
</template>

<script>
export default {
    data() {
        return {
            msg: 'hello Vue',
            timer: null,
        }
    },
    created() {
        // 组件创建完成后 创建定时器
        this.timer = setInterval(() => {
            console.log('组件在运行');
        }, 1000);
    },
    beforeDestroy() {
        console.log('准备销毁');
        // 移除当前组件占用的全局资源
        this.timer && clearInterval(this.timer);
        this.timer = null;
    },
    destroyed() {

    }

}
</script>

效果展示

%title插图%num
axios 请求

Axios 是专门用于发送 ajax 请求的库

  • ajax 是一种前端异步请求后端的技术 [ 参考 Ajax笔记 ]
  • ajax请求 的底层使用的是 XMLhttpRequert 方式
  • axios 是基于原生 ajax + Promise 技术封装通用于前后端的请求库

介绍

  • 官网:http://www.axios-js.com
  • 文档:axios 基本文档
  • 特点:
    • 支持 客户端发送 ajax 请求
    • 支持服务端 Node.js 发送请求
    • 支持 Promise 相关用法
    • 支持请求和响应的拦截器功能
    • 自动转换 JSON 数据
%title插图%num
下载

npm 下载

PowerShell
npm install axios

yarn 下载

PowerShell
yarn add axios
发起请求

import 引用

JavaScript
import axios from 'axios';

Get 请求

一般 axios 是在 钩子函数 mounted() 完成数据的赋值,默认请求为 GET请求
下方演示为 查询指定书籍 的操作,parame为 GET 传递的参数

JavaScript
<template>
  <div>
    <input type="text" placeholder="输入要查询的书名" v-model="bookname">
    <button @click="getFn">获取数据</button>
  </div>
</template>

<script>
import axios from 'axios';
export default {
  data() {
    return {
      bookname: '',
    }
  },
  methods: {
    getFn() {
      console.log('获取数据');
      // 发起 ajax请求
      axios({
        // 请求地址
        url: 'http://123.57.109.30:3006/api/getbooks',
        // 请求方式 (默认GET)
        method: 'GET',
        // 查询参数最终会拼接在 url? 后面
        params: {
          bookname: this.bookname,
        }
      })
        // 返回结果后 返回 Promise 对象 用 then 接收
        .then(res => { console.log(res); })
        // 接收 错误结果
        .catch(err => { console.log(err); });
    }
  }
}
</script>

效果展示

%title插图%num

POST 请求

在 POST 请求中,传递的参数放置在请求体里,可在axios对象中 data 成员里挂载请求体内容
下方演示的为添加图书操作,axios 默认发给后台的请求体数据为 json字符串格式

JavaScript
<template>
  <div>
    <input type="text" placeholder="书名" v-model="obj.bookname">
    <input type="text" placeholder="作者" v-model="obj.author">
    <input type="text" placeholder="出版社" v-model="obj.publisher">
    <br/>
    <button @click="getFn">上传图书</button>
  </div>
</template>

<script>
import axios from 'axios';
export default {
  data() {
    return {
      obj:{
          bookname:'',
          author:'',
          publisher:'',
      }
    }
  },
  methods: {
    getFn() {
      console.log('获取数据');
      // 发起 ajax请求
      axios({
        // 请求地址
        url: 'http://123.57.109.30:3006/api/addbook',
        // 请求方式 (默认GET)
        method: 'POST',
        // 查询参数最终会拼接在 url? 后面
        data:{
          appkey:'7250d3eb-18e1-41bc-8bb2-11483665535a',
          ...this.obj,
        }
      })
        // 返回结果后 返回 Promise 对象 用 then 接收
        .then(res => { console.log(res); })
        // 接收 错误结果
        .catch(err => { console.log(err); });
    }
  }
}
</script>

效果展示

%title插图%num
配置公共 url

使用 axios.defaults.baseURL=”公共URL地址” 设置公共请求 url

  • 配置 公共url地址 后,会在每次请求时进行 公共url + url路径 的拼接,axios.defaults.baseURL 相当于进行全局公共配置
  • 该操作极大降低了维护成本,当后台地址发生变动时候,只需要修改 axios.defaults.baseURL 项目即可
JavaScript
import axios from 'axios';
axios.defaults.baseURL='http://123.57.109.30:3006';
export default {
  methods: {
    getFn() {
      console.log('获取数据');
      // 发起 ajax请求
      axios({
        // 请求地址
        url: '/api/addbook',
        // 请求方式 (默认GET)
        ...
      })
        // 返回结果后 返回 Promise 对象 用 then 接收
        .then(res => { console.log(res); })
        // 接收 错误结果
        .catch(err => { console.log(err); });
    }
  }
}
参数查阅

请求参数

成员名 参数 介绍
url string 用于请求的服务器 URL
method string 创建请求时使用的方法
baseURL string url 公共链接 (将会拼接在所有 url 参数前面)
params object 与请求一起发送的 URL 参数
data object 作为请求主体被发送的数据(只适用于这些请求方法 ‘PUT’, ‘POST’, 和 ‘PATCH’)
paramsSerializer function 负责 `params` 序列化的函数
transformRequest array 允许在向服务器发送前,修改请求数据(只能用在 ‘PUT’, ‘POST’ 和 ‘PATCH’ 这几个请求方法)
transformResponse array 在传递给 then/catch 前,允许修改响应数据
headers object 发送的自定义请求头,例如({‘X-Requested-With’: ‘XMLHttpRequest’})
timeout number 指定请求超时的毫秒数(0 表示无超时时间)
withCredentials bool 表示跨域请求时是否需要使用凭证
adapter function 允许自定义处理请求,以使测试更轻松
auth object 应该使用 HTTP 基础验证,并提供凭据
responseType string 表示服务器响应的数据类型,可以是 ‘arraybuffer’, ‘blob’, ‘document’, ‘json’, ‘text’, ‘stream’
responseEncoding string 指示用于解码响应的编码 例如:’utf8′
xsrfCookieName string 用作 xsrf token 的值的cookie的名称 默认:’X-XSRF-TOKEN’
onUploadProgress function 允许为上传处理进度事件 参数1:progressEvent
onDownloadProgress function 允许为下载处理进度事件 参数1:progressEvent
maxContentLength number 定义允许的响应内容的最大尺寸 例如:2000
validateStatus function 定义对于给定的HTTP 响应状态码是 resolve 或 reject promise 。
如果 `validateStatus` 返回 `true` (或者设置为 `null` 或 `undefined`),
promise 将被 resolve; 否则,promise 将被 rejecte
maxRedirects number 定义在 node.js 中 follow 的最大重定向数目(如果设置为0,将不会 follow 任何重定向)
proxy object 定义代理服务器的主机名称和端口

响应结构

成员名 参数 介绍
data object 由服务器提供的响应
status number 来自服务器响应的 HTTP 状态码
statusText string 来自服务器响应的 HTTP 状态信息
headers object 服务器响应的头
Vue $指令

在vue的数据层中,有部分指令前缀为$,一般为 this.$指令,该指令的 this 需需注意指向

指令名 介绍
this.$refs 获取在标签上对应ref属性值的原生DOM(无论有多少个只能拿到一个)
this.$set 更新对应数组中下标成员值(参数1:数组目标 参数2:位置,参数3:更新后的值)
this.$emit 发送到对应自定义事件数据并触发事件(参数1:自定义事件,参数2:发送的参数…)
this.$on 接收对应自定义事件 返回的值(参数1:自定义事件,参数2:接收的参数…)
this.$off 删除对应自定义事件
如果没有提供参数,则移除所有的事件监听器
如果只提供了事件,则移除该事件所有的监听器
如果同时提供了事件与回调,则只移除这个回调的监听器
this.$nextTick 由于数据更新是异步操作,$nextTick相当于获取更新完成后的数据(相当于updated())
this.refs

获取在标签上对应ref属性值的原生DOM

在恰当的时机,例如在 mounted 钩子函数中获得需要的 原生DOM对象

JavaScript
<template>
  <div>
    <p ref="sayHi">Hello vue</p>
  </div>
</template>

<script>
export default {
  mounted() {
    // 获取原生DOM
    console.log(this.$refs.sayHi);
    // 获取内容
    console.log(this.$refs.sayHi.innerText);
  }
}
</script>

效果展示

%title插图%num

获取或执行子组件中的定义方法

在子组件标签中添加 ref 属性,使用 this.$refs.该属性.子属性方法 可以直接调用子组件方法
* 该方法较少用,一般仍然使用 $emit依赖自定义事件传参让父级执行方式通信 了解即可 *

JavaScript
// 父级组件
<template>
  <div>
    <demo ref="child"></demo>
  </div>
</template>

<script>
import demo from './demo.vue'
export default {
  components:{
    demo
  },
  mounted() {
    this.$refs.child.sayhi();
  }
}
</script>

// 子级组件
<template>
 <div>我是子组件</div>
</template>

<script>
export default {
    methods:{
        sayhi(){
            console.log('我是子组件功能');
        }
    }
}
</script>

效果展示

%title插图%num
this.$nextTick

数据更新完成后的回调函数

  • 由于vue数据更新的操作是异步的,因此当值发生改变后,原生DOM方法 需要在 updated 中获取最新的内容
  • this.$nextTick(()=>{ }) 让后续取值的操作 相当于在 updated() 函数体中操作
JavaScript
<template>
  <div>
    <p ref="num">{{ num }}</p>
    <button @click="add">+1</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      num: 0
    }
  },
  methods: {
    add() {
      this.num++;
      /*
        匿名函数的 this 指向的是 作用域区域 也就是组件对象
        this.$nextTick 为数据更新完成后的回调函数
      */
      this.$nextTick(() => {
        // 使用 取原生DOM 方式 获取目标 DOM对象值
        console.log(this.$refs.num.innerText);
      })
    }
  }
}
</script>

效果展示

%title插图%num