概念介绍

Vue.js介绍

基本概念

  1. Vue.js 是目前最火的一个前端框架,React是最流行的一个前端框架
    React除了开发网站,还可以开发手机原生App, Vue语法也是可以用于进行手机App开发的,需要借助于Weex
  2. Vue.js 是前端的主流框架之一,和Angular.js、React.js 一起,并称为前端三大主流框架
  3. Vue.js 是一套构建用户界面的框架,只关注视图层,它不仅易于上手,还便于与第三方库或已有项目整合
    Vue有丰富的配套三方类库,可以整合起来做大型项目的开发

官网

  1. English Site
  2. 中文网站

介绍

  1. Vue.js是一个渐进式框架,动态构建用户界面
  2. 作者:尤雨溪,华裔,前Google 工程师

特点

  1. Vue.js 是基于MVVM架构
  2. 编码简洁, 体积小, 运行效率高, 适合移动/PC 端开发
  3. 它本身只关注 UI, 可以轻松引入 vue 插件或其它第三库开发项目
  4. 参考了React的组件化和虚拟DOM技术,借鉴了angular的模板和数据绑定技术

Vue的生态

  1. Vue.js :关注UI层面
  2. 工具:DevTools、vue-cli: vue 脚手架、vue-loader
  3. 核心插件
    vue-router: 路由
    vuex: 状态管理
    vue服务器端渲染
    vue-resource(axios): ajax 请求

MVC、MVP、MVVM

MVC

  1. 概念:M是指业务模型,V是指用户界面,C则是控制器
    M即model模型,  数据层,负责数据的处理和获取的数据接口层
    V即View视图, 视图层, 是指用户看到并与之交互的界面。比如由html元素组成的网页界面,或者软件的客户端界面
    C即controller控制器, 控制器层,它是 Model 和 View 之间的胶水或者说是中间人
  2. MVC各个部分之间通信的方式
    图示
    Vue:基础知识_vue.js
    1)Models: 数据层,负责数据的处理和获取的数据接口层
    2)Views: 展示层(GUI),对于web来说所有以.html开头的文件基本都属于这层
    3)Controller: 控制器层,它是 Model 和 View 之间的胶水或者说是中间人
    当用户对 View 有操作时它负责去修改相应 Model
    当 Model 的值发生变化时它负责去更新对应 View
    4)理论上:所有通信都是单向的
    1. M和View应该是完全隔离的,由C作为中间人来负责二者的交互
    2. 同时三者是完全独立分开的,这样可以保证M和V的可测试性和复用性
    3. 由于C都是为特别的应用场景下的M和V做中介者,所以很难复用;而且V和M有时候也会存在耦合
    4. V和M很多时候会存在耦合,view会依赖于Model
  3. 好处
    1)耦合性低
    2)重用性高
    3)拓展性好
    4)可维护性高
  4. MVC并不适用于前端
    1)前端的View其实已经具备了独立处理用户事件的能力,当每个事件都流经Controller时,这层会变得十分臃肿
    2)MVC中View和Controller一般是一一对应的,捆绑起来表示一个组件,视图与控制器间的过于紧密的连接让Controller和View都变得无法复用

MVP

  1. 概念
    1)MVP(Model-View-Presenter)是MVC模式的改良,由IBM的子公司Taligent提出
    2)和MVC的相同之处在于:Controller/Presenter负责业务逻辑,Model管理数据,View负责显示
    3)图示
    Vue:基础知识_ios_02
  2. 特点
    1)在MVC里,View是可以直接访问Model的,但MVP中的View并不能直接使用Model,而是通过为Presenter提供接口,让Presenter去更新Model,再通过观察者模式更新View
    2)与MVC相比,MVP模式通过解耦View和Model,完全分离视图和模型,使职责划分更加清晰
    3)View不依赖Model,可以将View抽离出来做成组件,它只需要提供一系列接口提供给上层操作
  3. 存在问题
    1)Presenter作为View和Model之间的“中间人”,除了基本的业务逻辑外,还有大量代码需要对从View到Model和从Model到View的数据进行“手动同步”,这样Presenter显得很重,维护起来会比较困难
    2)而且由于没有数据绑定,如果Presenter对视图渲染的需求增多,一旦视图需求发生改变,Presenter也需要改动

MVVM

  1. 概念
    1)MVVM(Model-View-ViewModel)最早由微软提出
    2)ViewModel指 “Model of View”——视图的模型
    3)图示

  2. 特点
    1)MVVM把View和Model的同步逻辑自动化了
    2)以前Presenter负责的View和Model同步不再手动地进行操作,而是交给框架所提供的数据绑定功能进行负责,只需要告诉它View显示的数据对应的是Model哪一部分即可
    3)通过ViewModel进行数据绑定,当Model发生变化,ViewModel就会自动更新;ViewModel变化,Model也会更新

  3. MVVM
    1)概念
    1)MVVM(Model-View-ViewModel)最早由微软提出
    2)ViewModel指 “Model of View”——视图的模型
    3)图示
    Vue:基础知识_数据_03
    2)特点
    1)MVVM把View和Model的同步逻辑自动化了
    2)以前Presenter负责的View和Model同步不再手动地进行操作,而是交给框架所提供的数据绑定功能进行负责,只需要告诉它View显示的数据对应的是Model哪一部分即可
    3)通过ViewModel进行数据绑定,当Model发生变化,ViewModel就会自动更新;ViewModel变化,Model也会更新

总结

  1. 整体上看,MVVM比MVC/MVP精简了很多,不仅仅简化了业务与界面的依赖,还解决了数据频繁更新的问题
    jQuery操作DOM就很频繁
  2. 在MVVM中,View不知道Model的存在,ViewModel和Model也察觉不到View,这种低耦合模式可以使开发过程更加容易,提高应用的可重用性
  3. MVVM更适合视图更多的前端项目进行工程化开发
安装DevTool

DevTool是什么?

vue-devtools是一款基于chrome浏览器的插件,用于调试vue应用,能够极大地提高我们的调试效率

安装

chrome商店直接安装

You Need FQ

手动安装

1)找到vue-devtools的github项目,并将其clone到本地
git clone https://github.com/vuejs/vue-devtools.git
2)安装项目所需要的npm包
npm install
3)编译项目文件
npm run build
4)添加至chrome浏览器
浏览器输入地址“chrome://extensions/”进入扩展程序页面,点击“加载已解压的扩展程序…”按钮,选择vue-devtools>shells下的chrome文件夹
图示

安装vue-cli

vue-cli是什么?

vue-cli是vue官方提供的脚手架工具,默认搭建好了一个项目的基本架子,我们在其基础上进行相应修改即可。
https://github.com/vuejs/vue-cli:下载模板项目
1)全局安装vue-cli
npm install -g @vue/cli
2)查看版本/是否安装成功
vue -V

初始化Vue项目

vue create hk-demo

按键盘上下键可以选择默认(default)还是手动(Manually),如果选择default,一路回车执行下去就行了, 继续手动一下

选择配置:空格键是选中与取消,A键是全选

选项说明

  1. Babel将高阶ES6.7.8语法转换为ES5语法
  2. TypeScript 支持使用 TypeScript 书写源码
  3. Progressive Web App (PWA) Support PWA 支持
    PWA不是API或技术,但它是一种Web开发方法,它使用已有的工具和技术组合来创建有针对性的理想用户体验
    渐进式网络应用程序
  4. Router 支持 vue-router
  5. Vuex 支持 vuex
  6. CSS Pre-processors 支持 CSS 预处理器
  7. Linter / Formatter 支持代码风格检查和格式化
  8. Unit Testing 支持单元测试
  9. E2E Testing 支持 E2E 测试

选择将这些配置文件写入到什么地方 (In dedicated config files)

专用配置文件中

是否保存这份预设配置?(n)

选是的话,下次创建一个vue项目,可以直接使用这个预设文件,而无需再进行配置

运行项目

进入新建的项目中,npm run serve

解读项目的目录结构

main.js

如果想配置在整个项目中都能用的全局的数据,在main.js中进行配置

Vue.config.productionTip = false

阻止启动生产消息
为false的时候,启动项目只有以下输出
Vue:基础知识_mvc_04
true
Vue:基础知识_vue.js_05
这个函数的作用就是生成一个 VNode节点,render 函数得到这个 VNode 节点之后,返回给 Vue.js 的 mount 函数,渲染成真实 DOM 节点,并挂载到根节点上
m o u n t 手 动 挂 载 ‘ mount 手动挂载 ` mount手动挂载‘mount(’#app’)`
挂载在index.html中的app中
Vue:基础知识_vue.js_06

render:h=>h(App) 逆推:

/**
 *
 ES5
 render:function(createElement){
  return createElement(App);
 }

 ES6
 render(createElement){
  return createElement(App);
 }

 简化
 render(h){
  return h(App);
 }

 箭头函数
 render:h =>h(App);
 */

App.vue 类似于模板文件:描述一个组件(结构、样式、脚本)

template 模板:组件的结构 唯一出口,只能有一个div

HelloWorld是一个组件
Vue:基础知识_数据_07
引入组件三部曲:

  1. 引入:import
    Vue:基础知识_vue.js_08
  2. 注册:在components中注册组件
    Vue:基础知识_ios_09
  3. 使用
    Vue:基础知识_数据绑定_10

script 脚本文件:ES6

style 样式文件

HelloWorld.vue:组件

Vue:基础知识_mvc_11
向外界暴露名称HelloWorld
scoped
Vue:基础知识_ios_12
样式仅作用于当前组件

理解Vue的 MVVM

一个组件(例如:HelloWorld.vue就是一个小的MVVM=> V VM M)
例如:在HelloWorld.vue中
V是:
Vue:基础知识_vue.js_13
VM是:一个实例
Vue:基础知识_数据绑定_14
数据在VM中的一个挂载钩子上,如上图中,其实:在二级组件中,钩子以函数的形式出现
Vue:基础知识_数据_15
Vue:基础知识_vue.js_16
model有数据后,通过VM传递数据给vue。数据绑定
通过{{数据标识}}进行数据绑定:例如:{{msg}},即可实现自动化更新数据。
同时,这种更新是双向的,通过更新input中的数据,可以实现更新msg中的数据
Vue:基础知识_vue.js_17
Vue:基础知识_ios_18

综上:model和mv中数据是可以相互传递的,mv中数据和view中数据是双向绑定的

Vue:基础知识_数据绑定_19

切记

1)双向绑定仅仅是一种高级语法糖
2)数据流传递还是单向

基础知识

组件的定义与使用

vue文件的组成

  1. 模板页面
    Vue:基础知识_数据绑定_20
  2. JS 模块对象
    Vue:基础知识_mvc_21
  3. 样式

基本使用

  1. 引入组件
    Vue:基础知识_ios_22
  2. 映射成标签
    Vue:基础知识_数据绑定_23
  3. 使用组件标签
    Vue:基础知识_ios_24

模板语法

类似于Node中的EJS,可以让HTML页面动态化

  1. 大括号表达式
  2. 指令(以v-开头的自定义标签属性)

双大括号表达式

语法:{{exp}}
功能

  1. 向页面输出数据
  2. 内部可以是变量、对象调用、表达式

注意:

  1. 大括号内一定要有结果
  2. 双括号表达式仅适用于标签之间的数据渲染,不适用于标签属性

强制数据绑定

语法:v-bind:aaa='bbb':bbb会作为表达式解析执行
简洁写法::aaa='bbb'
功能

  1. 向页面输出数据
  2. 内部可以是变量、对象调用、表达式
    注意
    一定要有结果
    Vue:基础知识_数据绑定_25

绑定事件监听

语法:v-on:click='aaa':aaa是指绑定指定事件名的回调函数
简洁写法:@click='aaa'
功能:触发一个事件
vm中另外一个钩子,methods
Vue:基础知识_ios_26
Vue:基础知识_ios_27
Vue:基础知识_mvc_28
带有参数的函数
Vue:基础知识_数据_29

计算属性(computed)和侦听器(watch)

计算属性(computed):仅进行运算

  1. 作用
    1)减少模板中的计算逻辑
    2)进行数据缓存
    3)依赖固定的数据类型(响应式数据)

  2. 使用
    1)在computed属性(配置计算属性)对象中定义计算属性的方法
    Vue:基础知识_数据绑定_30
    2)在页面中使用{{方法名}}来显示计算的结果
    ① 单向:实现get
    Vue:基础知识_ios_31

    ② 双向:实现get与set
    Vue:基础知识_mvc_32
    Vue:基础知识_mvc_33
    3)通过getter/setter实现对属性数据的显示和监视

侦听器(watch):可以执行任何逻辑,仅能实现单向

  1. 作用
    1)比computed更加灵活
    2)watch中可以执行任何逻辑,比如:函数节流、Ajax异步数据获取、甚至操作DOM
    3)依赖固定的数据类型(响应式数据)
  2. 使用
    1)通过通过vm对象的$watch()或watch配置来监视指定的属性
    2)当属性变化时, 回调函数自动调用, 在函数内部进行计算
    Vue:基础知识_数据_34
    Vue:基础知识_数据_35

总结

  1. computed能做的,watch都能做,反之则不行
  2. 能用computed的地方,尽可能使用computed
  3. computed 是计算一个新的属性,并将该属性挂载到 vm(Vue 实例)上,而 watch 是监听已经存在且已挂载到 vm 上的数据,所以用 watch 同样可以监听 computed 计算属性的变化(其它还有 data、props)
  4. computed 本质是一个惰性求值的观察者,具有缓存性,只有当依赖变化后,第一次访问 computed 属性,才会计算新的值,而 watch 则是当数据发生变化便会调用执行函数
  5. 从使用场景上说,computed 适用一个数据被多个数据影响,而 watch 适用一个数据影响多个数据

class与style

概念

在模板界面中,某些元素的样式是变化的,class/style用于动态绑定类和样式
class/style绑定技术就是专门用来处理动态样式效果

class

:class='aaa'
aaa取值:字符串、对象、数组
Vue:基础知识_ios_36

style

:style="{ backgroundColor: bgColor, fontSize: fSize}"
其中 bgColor/fSize 都是 data 属性
Vue:基础知识_数据绑定_37

条件渲染指令

v-if/v-else/v-else-if

Vue:基础知识_ios_38

v-show

Vue:基础知识_数据_39

两者区别

v-if是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建
v-show不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换
如果需要频繁切换 v-show 较好

列表渲染

数组遍历

Vue:基础知识_vue.js_40

对象遍历

Vue:基础知识_ios_41

为什么要绑定Key?

key是给每一个vnode的唯一id,可以依靠key,更准确、更快地拿到oldVnode中对应的vnode节点
使用注意

  1. 尽可能不要使用index作为key
    会带来状态bug问题
  2. 最好使用静态key作为Dom的key
    shortid
    Vue:基础知识_vue.js_42

过滤遍历:通过计算属性得到

知识点

  1. 将input与searchName通过v-model进行双向绑定
  2. …ES6语法:展开
  3. {··}=···ES6语法:解构
  4. filter:按照某条件返回
  5. trim:取出字符串头尾空格
    Vue:基础知识_数据_43

排序遍历

Vue:基础知识_数据_44
Vue:基础知识_数据_45

其它常用指令和自定义指令

常用内置指令

  1. v-text:更新元素的 textContent
    Vue:基础知识_数据绑定_46
    v-text注入数据后,标签内部无法再添加新的数据

  2. v-html:更新元素的 innerHTML
    Vue:基础知识_vue.js_47
    不建议使用
    在网站上动态渲染任意 HTML 是非常危险的,因为容易导致 XSS 攻击

  3. v-show

  4. v-if

  5. v-else

  6. v-else-if

  7. v-for:Array | Object | number | string | Iterable

  8. v-on

  9. v-bind

  10. v-model

  11. v-slot:提供具名插槽或需要接收 prop 的插槽

  12. v-pre:跳过这个元素和它的子元素的编译过程。可以用来显示原始 Mustache 标签。跳过大量没有指令的节点会加快编译。
    Vue:基础知识_数据绑定_48
    Vue:基础知识_vue.js_49

  13. v-cloak:防止闪现
    和 CSS 规则如 [v-cloak] { display: none } 一起用时,这个指令可以隐藏未编译的 Mustache 标签直到实例准备完毕

  14. v-once:只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。
    Vue:基础知识_ios_50

  15. 特殊特性:ref 绑定后,通过$ref获取标签
    Vue:基础知识_数据绑定_51

自定义指令

  1. 自定义全局组件
    在main.js中进行指令注册
    Vue:基础知识_数据绑定_52
    Vue:基础知识_ios_53
  2. 自定义局部指令
    Vue:基础知识_vue.js_54

事件处理

事件绑定监听

v-on:xxx="func"
@xxx="func"
@xxx="func(参数)"
v-on:xxx="函数名"

参数

默认事件形参: event
隐含属性对象: $event
在原生事件中,$event是事件对象
在自定义事件中,$event是传递过来的数据

事件修饰符

.prevent:阻止事件的默认行为 event.preventDefault()
.stop:停止事件冒泡 event.stopPropagation()
Vue:基础知识_ios_55

按键修饰符

.keycode: 操作的是某个keycode值的健
.enter: 操作的是enter键
.tab
.delete: (捕获“删除”和“退格”键)
.esc
.space
.up
.down
.left
.right
Vue:基础知识_vue.js_56

综合小练习

指令综合小练习

Vue:基础知识_数据_57
text.vue

<template>
    <div id="app">
        <!--第一部分-->
        <fieldset>
            <legend>指令综合小练习</legend>
            <div>
                <span>姓名: </span>
                <input type="text" placeholder="请输入姓名" v-model="newPerson.name">
            </div>
            <div>
                <span>年龄: </span>
                <input type="text" placeholder="请输入年龄" v-model="newPerson.age">
            </div>
            <div>
                <span>性别: </span>
                <select  v-model="newPerson.sex">
                    <option value="男">男</option>
                    <option value="女">女</option>
                </select>
            </div>
            <div>
                <span>手机: </span>
                <input type="text" placeholder="请输入手机号码"  v-model="newPerson.phone">
            </div>
            <button @click="createNewPerson">创建新用户</button>
        </fieldset>
        <!--第二部分-->
        <table>
            <thead>
            <tr>
                <td>姓名</td>
                <td>性别</td>
                <td>年龄</td>
                <td>手机</td>
                <td>删除</td>
            </tr>
            </thead>
            <tbody>
               <tr v-for="(p, index) in persons">
                   <td v-text="p.name"></td>
                   <td v-text="p.age"></td>
                   <td v-text="p.sex"></td>
                   <td v-text="p.phone"></td>
                   <td>
                       <button @click="delPerson(index)">删除</button>
                   </td>
               </tr>
            </tbody>
        </table>
    </div>
</template>

<script>
    export default {
        name: "Demo",
        data(){
           return {
               persons: [
                   {name: '张三', age: 20, sex: '男', phone: '18919988999'},
                   {name: '李四', age: 30, sex: '女', phone: '18912121212'},
                   {name: '王五', age: 40, sex: '男', phone: '18912222229'},
                   {name: '赵六', age: 10, sex: '女', phone: '18921218999'}
               ],
               newPerson: {name: '', age: 0, sex: '男', phone: ''}
           }
        },
        methods: {
            // 1. 添加
            createNewPerson(){
                // 1. 验证
                let {name, age, sex, phone} = this.newPerson;
                if(name === ''){
                    alert('姓名不能为空!');
                    return;
                }
                if(age <= 0){
                    alert('年龄不正确!');
                    return;
                }
                if(phone === ''){
                    alert('手机号码不正确!');
                    return;
                }

                // 2. 插入数据
                this.persons.unshift(this.newPerson);

                // 3. 清空数据
                this.newPerson =  {name: '', age: 0, sex: '男', phone: ''};

            },
            // 2. 删除
            delPerson(index){
                this.persons.splice(index, 1);
            }
        }
    }
</script>

<style scoped>
    #app{margin: 50px auto;width: 600px;}
    fieldset{border: 1px solid orangered;margin-bottom: 20px;}
    fieldset input{width: 200px;height: 30px;margin: 10px 0;}
    table{width: 600px;border: 2px solid orangered;text-align: center;}
    thead{background-color: orangered;}
</style>

过滤器

概念

  1. 对要显示的数据进行特定格式化后再显示
  2. 并没有改变原本的数据, 可是产生新的对应的数据

定义和使用过滤器

  1. 定义过滤器
    ① 全局定义
    main.js中定义
    Vue:基础知识_数据绑定_58
    ② 局部定义
    Vue:基础知识_mvc_59
  2. 使用过滤器
    Vue:基础知识_数据_60

过渡&动画

概念

vue通过操作 css 的 trasition 或 animation可以给特定的目标元素添加/移除特定的 class

过渡的相关类名

xxx-enter/xxx-leave-to: 指定隐藏时的样式
xxx-enter-active: 指定显示的 transition
xxx-leave-active: 指定隐藏的 transition

操作步骤

  1. 在目标元素外包裹<transition name="xxx">
  2. 定义class样式
    ① 指定过渡样式: transition
    ② 指定隐藏时的样式: opacity/其它

图示

  1. 过渡
    Vue:基础知识_数据绑定_61
    Vue:基础知识_mvc_62
  2. 动画
    Vue:基础知识_数据绑定_63
  3. 集成三方库
    Vue:基础知识_mvc_64

组件的生命周期

生命周期图示

Vue:基础知识_ios_65
Vue:基础知识_mvc_66

钩子函数

  1. 初始化显示
    beforeCreate()
    created()
    beforeMount()
    mounted()
  2. 更新状态
    beforeUpdate()
    updated()
  3. 销毁 vue 实例
    beforeDestory()
    destoryed()

常用的生命周期方法

  1. created()/mounted():发送ajax请求,启动定时器等异步任务
  2. beforeDestory():收尾操作,比如: 清除定时器、数据缓存
<template>
    <div>
        <p v-if="isShow">{{str1}}</p>
        <p v-else>{{str2}}</p>
        <button @click="destory">销毁</button>
    </div>
</template>

<script>
    export default {
        name: "LifeCircle",

        beforeCreate(){
             console.log('1:beforeCreate()');
        },

        data(){
            return {
                isShow: false,
                str1: '撩课学院',
                str2: 'itLike.com'
            }
        },
        methods:{
            destory(){
                this.$destroy();
            }
        },

        created() {
            console.log('2:created()');
        },
        beforeMount() {
            console.log('3:beforeMount()');
        },
        mounted() {
            console.log('4:mounted()');
            // 定时器
            this.intervalId = setInterval(()=>{
                console.log('+++++++++++---++++++++++');
                this.isShow = !this.isShow;
            }, 1000);
        },

        beforeUpdate() {
            console.log('5:beforeUpdate()');
        },
        updated() {
            console.log('6:updated()');
        },

        beforeDestroy() {
            console.log('7:beforeDestroy()');
            // 清除定时器
            clearInterval(this.intervalId);
        },
        destroyed() {
            console.log('8:destroyed()');
        }
    }
</script>

<style scoped>

</style>

组件之间通信

通信基本原则

  1. 不要在子组件中直接修改父组件的状态数据
  2. 数据和处理数据的函数应该在同一模块内

组件通信常用方式

  1. props
  2. 自定义事件
  3. 消息订阅与发布
  4. vuex

组件通信方式1-props:适合父组件传递给子组件

  1. 在组件内声明所有的 props
    ① 只指定名称
    props: ['name', 'age', 'logDog']
    
    ② 指定名称和类型
    props: {
         name: String,
         age: Number,
         logDog: Function
    }
    
    ③ 指定名称/类型/必要性/默认值
    props: {
        name: {type: String, required: true, default:xxx},
    }
    

Vue:基础知识_数据绑定_67
Vue:基础知识_vue.js_68
2. 使用注意
① 此方式用于父组件向子组件传递数据
② 所有标签属性都会成为组件对象的属性, 模板页面可以直接引用
③ 父组件向子组件传递参数时,除了字符串,都需要动态绑定
④ 存在缺陷
- 如果需要向非子后代传递数据必须多层逐层传递
- 兄弟组件间也不能直接 props 通信, 必须借助父组件才可以

组件通信方式2-自定义事件

  1. 案例操作
    ① 绑定事件监听
    <button @click="btnClick">删除父组件的P标签</button>
    
    this.$emit('btnClick', {name: '哈哈哈', sex:'男'});
    
    ② 触发事件
    deleteP(args){
        console.log(args);
        this.$refs.word.remove();
    }
    
    ③ 指定名称/类型/必要性/默认值
  2. 使用注意
    ① 此方式只用于子组件向父组件发送消息(数据)
    ② 隔代组件或兄弟组件间通信此种方式不合适
    Vue:基础知识_vue.js_69
    Vue:基础知识_数据_70

组件通信方式3-PubSub 发布订阅

  1. 案例操作
    ① 安装npm install --save pubsub-js
    ② 引入import PubSub from ‘pubsub-js’
    ③ 界面操作
    //发布删除P标签消息
     PubSub.publish("delete-p", {name:"大撩撩", sex:"女", age: 45});
    			 // 订阅关闭弹窗事件 参数event指"delete-p",没什么用
      PubSub.subscribe("delete-p", (event, data) => {
             console.log(data);
             // 删除P标签
             this.deleteP();
       });
    
  2. 使用注意
    此方式可实现任意关系组件间通信(数据)

TODOLIST

深度监视

  1. 概念:为了发现对象内部值的变化,可以在选项参数中指定 deep: true ;注意监听数组的变动不需要这么做
  2. 方法使用
    第一个handler:其值是一个回调函数;即监听到变化时应该执行的函数
    第二个是deep:其值是true或false;确认是否深入监听(一般监听时是不能监听到对象属性值的变化的,数组的值变化可以听到。)
    第三个是immediate:其值是true或false;确认是否以当前的初始值执行handler的函数

插槽使用

  1. 概念:和 HTML 元素一样,我们经常需要向一个组件传递内容,Vue 自定义的 <slot>元素可以很愉快的帮助我们实现
  2. 使用:子组件通过slot预留插槽,父组件通过name关键字进行插槽。
    Vue:基础知识_mvc_71
    Vue:基础知识_ios_72
常用的UI框架

Vue生态常用的UI组件库

vant

  1. 介绍
    轻量、可靠的移动端 Vue 组件库
    有赞前端团队出品
    `https://github.com/youzan/vant``

  2. 特性

    • 60+ 个组件
    • 90% 单元测试覆盖率
    • 完善的中英文文档和示例
    • 支持按需引入
    • 支持主题定制
    • 支持国际化
    • 支持 TS
    • 支持 SSR
  3. 使用
    ① 安装

    1. 通过 npm 安装
      npm i vant -S
    2. 通过 yarn 安装
      yarn add vant

    ② 配置按需加载

    1. 安装babel-plugin-import
      babel-plugin-import 是一款 babel 插件,它会在编译过程中将 import 的写法自动转换为按需引入的方式
      安装插件:npm i babel-plugin-import -D
    2. babel.config.js
      module.exports = {
        plugins: [
          ['import', {
            libraryName: 'vant',
            libraryDirectory: 'es',
            style: true
          }, 'vant']
        ]
      };
      

    ③ 引入
    // 接着你可以在代码中直接引入 Vant 组件
    // 插件会自动将代码转化为方式二中的按需引入形式

    import { Button } from 'vant';
    

#Ant Design Vue

  1. 介绍
    社区主导
    蚂蚁金服技术支持
    https://github.com/vueComponent/ant-design-vue
  2. 特性
    • 55+ 个组件
    • 87% 单元测试覆盖率

Element UI

  1. Mint UI
    http://mint-ui.github.io/#!/zh-cn
    饿了么开源的基于 vue 的移动端 UI 组件库
  2. Elment
    http://element-cn.eleme.io/#/zh-CN
    饿了么开源的基于 vue 的 PC 端 UI 组件库
  3. Elment使用
    • vue-cli@3快速安装 Element 插件
      vue create my-app
      cd my-app
      vue add element
    • 使用
      vue create my-app
      cd my-app
      vue add element

建议使用

移动端

vant

后台管理系统

Ant Design Vue
Element UI

Vue-Router

概念

Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得更加轻松
Vue Router主要功能

  1. 嵌套的路由/视图表
  2. 模块化的、基于组件的路由配置
  3. HTML5 历史模式或 hash 模式
  4. 路由参数
  5. 基于 Vue.js 过渡系统的视图过渡效果
  6. 细粒度的导航控制
  7. 带有自动激活的 CSS class 的链接

总结:

  • 将组件(components)映射到路由(routes),然后告诉 vue-router 在哪里渲染它们
  • 组件与组件之间可以通过路由进行灵活切换
  • 在切换过程中可以进行各种参数传递、权限控制等

基本配置

初始化工程项目

vue create lk-vue-router
npm install vue-router --save安装Vue-Router
npm run serve

路由默认配置

  1. main.js
    import router from './router'
    new Vue({
      router,
      render: h => h(App)
    }).$mount('#app');
    
  2. router.js
    import Vue from 'vue'
    import Router from 'vue-router'
    // 引入页面
    import Home from './views/Home'
    import About from './views/About'
    
    Vue.use(Router);
    
    export default new Router({
        routes: [
        	// 配置不同路由对应界面
            { path: '/', redirect: '/home' },	// redirect 重定向 一般用于访问根目录重定向到别的路由
            // { path: '/', redirect: {name: 'about'} },
        	// { path: '/', redirect: to => { return '/home'}},
            {path: '/home', name: 'home', component: Home},
            {path: '/about', name: 'about', component: About},
        ]
    })
    

概念认知

  1. 路由导航

    <div id="nav">
       <router-link to="/">首页</router-link>|
       <router-link to="/about">关于</router-link>
    </div>
    
  2. 路由出口

    <router-view/>
    
  3. 处理选中路由样式

    #nav a.router-link-exact-active{
    	···
    }
    

history模式

概念

vue-router 默认 hash 模式 ,url使用#后面定位路由,对seo不利,设置history,就可以使用普通的url模式
history 模式即普通url模式,这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面
修改的只是URL中的hash,则不会导致页面被刷新

使用

const router = new VueRouter({
  // 设置mode为history
  mode: 'history',
  routes: [...]
})

hash和history区别

  1. hash模式url带#号,history模式不带#号
  2. 如果考虑url的规范,那么就需要使用history模式,因为history模式没有#号,是个正常的url,适合开发习惯
  3. 把用Vue或者React=做的页面分享至三方app,有的app里面url是不允许带有#号的,所以要将#号去除那么就要使用history模式
  4. 使用history模式还有一个问题就是,在访问二级页面的时候,做刷新操作,会出现404错误,那么就需要和后端人配合让他配置一下apache或是nginx的url重定向,重定向到你的首页路由
  5. 区别图示
    Vue:基础知识_数据_73

动态路由

概念

路由可以携带一些参数,使用this.$router获取

使用

通过配置路由的时候,路由名称后面+’/:'进行参数传递

{path: '/mine/:name', name: 'mine', component: Mine}

在路由中,通过this.$route获取当前路由信息

export default {
    name: "Mine",
    created() {
        // 输出当前路由
        console.log(this.$route);
    }
}

Vue:基础知识_ios_74

参数属性传递

设置props属性,获取路由的变量就和普通的属性传递没什么区别
方式

  1. 对象模式
    如果 props 是一个对象,它会被按原样设置为组件属性。当 props 是静态的时候有用
  2. 布尔模式
    如果 props 被设置为 true,route.params 将会被设置为组件属性
  3. 函数模式
    可以创建一个函数返回 props。这样你便可以将参数转换成另一种类型,将静态值与基于路由的值结合等等
    示例
    数据传递
    Vue:基础知识_数据绑定_75
    数据获取
    Vue:基础知识_ios_76

嵌套路由

概念

一级界面中可以通过嵌套路由配置二级界面切换,使用children配置嵌套路由

使用

{path: '/mine/:name', name: 'mine', component: Mine}
export default {
    name: "Mine",
    created() {
        // 输出当前路由
        console.log(this.$route);
    }
}

router.js
Vue:基础知识_mvc_77
home.vue设置路由出口及路由导航
Vue:基础知识_数据_78

路由(导航)守卫

概念

通过路由守卫可以对每次刷新或进入的路由界面进行权限验证,相当于react中的全局中间件

全局守卫

  1. router.beforeEach 注册一个全局前置守卫
  2. 操作
    const router = new VueRouter({ ... })
    router.beforeEach((to, from, next) => {
         // ...
    })
    
    参数
    1. to: Route: 即将要进入的目标 路由对象
    2. from: Route: 当前导航正要离开的路由
    3. next: Function: 一定要调用该方法来 resolve 这个钩子
  3. 代码实现
    router.beforeEach((to,from,next)=>{
        if (to.path !== '/login') { // 要求登录
          if (window.isLogin) {
            next();
          } else {
          // 进行重定向的原因是,登录成功后自动跳转到重定向的页面
            next('/login?redirect='+to.path);
          }
        } else {
          next();
        }
        next();
    });
    
    Vue:基础知识_数据绑定_79
    登录按钮事件
    login(){
    // 1. 登录成功
          window.isLogin = true;
          // 2. 获取回调地址
          const redirect = this.$route.query.redirect;
          if(redirect){ // 有回调地址
              this.$router.push(redirect);
          }else { // 没有回调地址
              // 去首页
              this.$router.replace('/');
          }
      }
    
    Vue:基础知识_数据绑定_80

全局后置钩子

  1. 概念
    可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身

  2. 操作

    router.afterEach((to, from) => {
      // ...
    })
    

路由独享守卫

  1. 概念
    可以在路由配置上直接定义 beforeEnter 守卫

  2. 操作

    const router = new VueRouter({
      routes: [
        {
          path: '/foo',
          component: Foo,
          beforeEnter: (to, from, next) => {
            // ...
          }
        }
      ]
    })
    
  3. 注意
    这些守卫与全局前置守卫的方法参数是一样的

组件内的守卫

概念

可以在组件内部实现相应钩子,在组件内部进行相应的守卫

方法

beforeRouteEnter
beforeRouteUpdate (2.2 新增)
beforeRouteLeave

官方使用介绍

const Foo = {
  template: `...`,
  beforeRouteEnter (to, from, next) {
    // 在渲染该组件的对应路由被 confirm 前调用
    // 不!能!获取组件实例 `this`
    // 因为当守卫执行前,组件实例还没被创建
  },
  beforeRouteUpdate (to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 可以访问组件实例 `this`
  },
  beforeRouteLeave (to, from, next) {
    // 导航离开该组件的对应路由时调用
    // 可以访问组件实例 `this`
  }
}

使用

beforeRouteEnter(to, from, next) {
    console.log("Mine路由进入前调用");
    next();
},
beforeRouteUpdate(to, from, next) {
    console.log("Mine路由,但是参数变了");
    next();
},
beforeRouteLeave(to, from, next) {
    console.log("Mine路由离开前");
    next();
}

Vue:基础知识_数据_81

路由守卫流程

  1. 导航被触发。
  2. 在失活的组件里调用离开守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
  5. 在路由配置里调用 beforeEnter。
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter。
  8. 调用全局的 beforeResolve 守卫 (2.5+)。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。

异步组件

概念

按需加载,用到时加载

使用

{
  path:'/login',
 component: () => import('./views/About.vue')
}

Vue:基础知识_数据绑定_82

相关API

  • this.$router.push(path):相当于点击路由链接(可以返回到当前路由界面)
  • this.$router.replace(path):用新路由替换当前路由(不可以返回到当前路由界面)
  • this.$router.back(): 请求(返回)上一个记录路由
  • this.$router.go(-1):请求(返回)上一个记录路由
  • this.$router.go(1):请求下一个记录路由

$router 和 $route的区别

router

router是VueRouter的一个对象,通过Vue.use(VueRouter)和VueRouter构造函数得到一个router的实例对象
这个对象中是一个全局的对象,他包含了所有的路由包含了许多关键的对象和属性

route

route是一个跳转的路由对象,每一个路由都会有一个route对象,是一个局部的对象
可以获取对应的name,path,params,query等

VueX

单向数据流理念

组成部分

  • state,驱动应用的数据源
  • view,以声明方式将 state 映射到视图
  • actions,响应在 view 上的用户输入导致的状态变化

图示

Vue:基础知识_数据绑定_83

传统数据通信存在问题

当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏

  1. 多个视图依赖于同一状态
    传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力
  2. 来自不同视图的行为需要变更同一状态
    经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝

解决问题的钥匙

把组件的共享状态抽取出来,以一个全局单例模式管理
在这种模式下,项目中的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为
Vuex

Vuex是什么

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式
它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化
一句话:Vuex相当于一个数据银行,对 vue 应用中多个组件的共享状态进行集中式的管理(读/写)

Vuex的组成

图示

Vue:基础知识_mvc_84
Actions:处理异步操作
Mutations:处理同步操作
State:同步数据

组成

  1. state:vuex管理的状态对象
    它应该是唯一的

    const state = {
            name: 'zhangsan'
    }
    
  2. mutations
    1)包含多个直接更新 state 的方法(回调函数)的对象
    2)在action中通过commit(‘mutation 名称’)触发mutations中更新state的方法
    3)只能包含同步的代码, 不能写异步代码

    const mutations = {
             aaaa (state, {data}) {
                   // 更新 state 的data属性
              }
    }
    
  3. actions
    1)包含多个事件回调函数的对象
    2)Mutation必须是同步的,Action是异步的
    3) 组件中通过 $store.dispatch(‘action 名称’, data)触发
    4) 可以包含异步代码(定时器, ajax)

    const actions = {
          bbbb({commit, state}, data1) {
                commit('aaaa', {data1})
          }
    }
    
  4. getters
    1)有时候我们需要从 store 中的 state 中派生出一些状态,我们可以理解为vuex中数据的computed功能
    2)示例

    store.js

    getters:{
      money: state => `¥${state.count*1000}`
    },
    

    page.vue

    computed: {
      money() {
        return this.$store.getters.money;
     }
    }
    
  5. mapState
    1)更方便的使用api,当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余
    2)为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性

    ...mapState({
        count:state=>state.count
    }),
    
  6. mapActions
    1)方便快捷的使用action

    methods:{
        ...mapActions(['dealCount']),
        ...mapMutations(['count'])
    },
    

    2)this.$store.dispatch可以变为

    this.dealCount({
        amount: 10
    })
    
  7. mapMutions

    ...mapMutations(['add'])
    this.add()
    
  8. Modules
    1)概念
    面对复杂的应用程序,当管理的状态比较多时, 我们需要将Vuex的store对象分割成多个模块(modules)
    2)举例:

    const  moduleA = {
        state: { ... },
        mutations: { ... },
        actions: { ... },
        getters: { ... }
    }
    
    const  moduleB = {
        state: { ... },
        mutations: { ... },
        actions: { ... },
        getters: { ... }
    }
    
    const store = new Vuex.Store({
         modules: {
               a:  moduleA,
               b:  moduleB
         }
    });
    

计算器案例

this.$store.dispatch()调用actions中的方法
this.$store.commit()调用mutations中的方法

初始化项目

vue create lk-vuex-demo
vue add vuex
npm run serve

实现

  1. main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store'

Vue.config.productionTip = false;

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

  1. store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex);

export default new Vuex.Store({
    state: {
        count: 0 // 初始化数据
    },
    mutations: {
        INCREMENT(state){
            state.count++;
        },
        DECREMENT(state){
            state.count--;
        }
    },
    actions: {
        increment({commit}){
            commit('INCREMENT');
        },
        decrement({commit}){
            commit('DECREMENT');
        }
    }
})

  1. App.vue
<template>
    <div id="app">
         <Counter />
    </div>
</template>

<script>
    import Counter from './components/Counter'
    export default {
        name: 'app',
        components: {
            Counter
        }
    }
</script>

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

  1. Counter.vue
<template>
    <div>
        <p>点击了{{count}}次</p>
        <button @click="increment">增加+1</button>
        <button @click="decrement">减少-1</button>
    </div>
</template>

<script>
    // import {mapMutations} from 'vuex'
    export default {
        name: "Counter",
        computed: {
            count(){
                return this.$store.state.count
            }
        },
        methods:{
           //  ...mapMutations(['INCREMENT', 'DECREMENT']),
            increment(){
                // this.INCREMENT();
                // this.$store.commit('INCREMENT');
                this.$store.dispatch('increment');
            },
            decrement(){
                // this.DECREMENT();
                // this.$store.commit('DECREMENT');
                this.$store.dispatch('decrement');
            }
        }
    }
</script>

<style scoped>

</style>

counter优化

store.js
Vue:基础知识_ios_85
counter.vue
Vue:基础知识_vue.js_86

store简化调用

Vue:基础知识_数据_87

TODOList改造

Vue-Ajax

vue 项目中常用的 2 个 ajax

vue-resource

vue 插件, 非官方库, vue1.x 使用广泛

axios

通用的 ajax 请求库, 官方推荐, vue2.x 使用广泛

axios 的使用

  1. 文档:https://github.com/pagekit/vue-resource/blob/develop/docs/http.md
  2. 安装:npm install axios --save
  3. 操作
    // 引入模块
    import axios from 'axios'
    // 发送 ajax 请求
    axios.get(url)
    .then(response => {
          console.log(response.data) ; // 得到返回结果数据
    }).catch(error => {
          console.log(error.message);
    }
    

测试接口

https://www.easy-mock.com/mock/5d40032d6a3ae527e747fea9/example/itlike/p_list

GET请求实操

axios.get('https://www.easy-mock.com/mock/5d40032d6a3ae527e747fea9/example/itlike/p_list').then((response) => {
    console.log(response);
}).catch(function (error) {
    console.log(error);
});