blog

vuePress-theme-reco blog    2019 - 2020
blog blog

Choose mode

  • dark
  • auto
  • light
Home
Category
  • Linux
  • java
  • java 后端
  • typora
  • vue
  • java 基础
  • 编程方法
  • Mysql
Tag
TimeLine
在线工具
  • PDF 转换器
  • JSON 编辑器
  • MD 表格生成器
  • CRON 表达式
  • 代码格式化
  • 公式编辑器
  • 二维码生成器
  • 在线编码转换
  • YAML <-> Properties
  • 在线 Web 练习
Contact
  • GitHub
  • 简书
  • CSDN
  • 博客圆
  • WeChat
author-avatar

blog

23

文章

16

标签

Home
Category
  • Linux
  • java
  • java 后端
  • typora
  • vue
  • java 基础
  • 编程方法
  • Mysql
Tag
TimeLine
在线工具
  • PDF 转换器
  • JSON 编辑器
  • MD 表格生成器
  • CRON 表达式
  • 代码格式化
  • 公式编辑器
  • 二维码生成器
  • 在线编码转换
  • YAML <-> Properties
  • 在线 Web 练习
Contact
  • GitHub
  • 简书
  • CSDN
  • 博客圆
  • WeChat
  • Vue

    • 前端知识体系
      • 前端三要素
      • 三端统一
      • 后端技术
      • 附:扩展阅读
    • 前后端分离的演变史
      • 为什么需要前后分离
      • 什么是前后分离
      • NodeJS 带来的全栈时代
      • 总结
    • 前端 MVVM 模式
      • 什么是 MVVM
      • 为什么要使用 MVVM
      • MVVM 的组成部分
    • Vue 简介
      • 概述
      • MVVM 模式的实现者
      • 为什么要使用 Vue.js
      • Vue.js 的两大核心要素
    • Vue 的使用
      • 兼容性
      • 下载地址
      • 第一个 Vue 应用程序
      • 测试 Vue
    • Vue实例的生命周期
      • 什么是生命周期
    • Vue 语法
      • 条件判断语句
      • 连续的条件判断语句
      • 循环遍历语句
    • Vue 事件
      • 监听事件
      • HTML
      • JavaScript
      • 测试效果
      • 完整的 HTML
    • Vue 通信
      • 什么是 Axios
      • 为什么要使用 Axios
      • 第一个 Axios 应用程序
    • Vue 表单输入
      • 什么是双向数据绑定
      • 为什么要实现数据的双向绑定
      • 在表单中使用双向数据绑定
    • Vue 组件基础
      • 什么是组件
      • 第一个 Vue 组件
    • Vue 计算属性
      • 什么是计算属性
      • 计算属性与方法的区别
    • Vue 内容分发与自定义事件
      • Vue 中的内容分发
      • 使用自定义事件删除待办事项
    • VueCli
      • 什么是 vue-cli
      • 安装
      • 第一个 vue-cli 应用程序
    • VueRouter 第一个路由
      • 概述
      • 安装
      • 使用
    • VueRouter 第一个工程
      • 创建工程
      • 安装依赖
      • 启动项目
      • 运行项目
    • 第一个 ElementUI 页面
      • 目录结构
      • 创建首页视图
      • 创建登录页视图
      • 创建路由
      • 配置路由
      • 效果演示
      • 附:扩展阅读
    • VueRouter 嵌套路由
      • 什么是嵌套路由
      • 创建嵌套视图组件
      • 配置嵌套路由
      • 修改首页视图
      • 效果演示
    • VueRouter 参数传递与重定向
      • 参数传递
      • 重定向
    • VueRouter 路由模式与 404
      • 路由模式
      • 处理 404
    • /views/%E6%8C%87%E5%8D%97/%E5%89%8D%E7%AB%AF%E6%8A%80%E6%9C%AF/Vue.html#
      • VueRouter 路由钩子与异步请求
        • 路由中的钩子函数
        • 在钩子函数中使用异步请求
      • Vuex 状态管理
        • 概述
        • 安装
        • 状态管理
        • 浏览器刷新 Vuex 数据消失
        • 模块化

    Vue

    vuePress-theme-reco blog    2019 - 2020

    Vue


    blog 2019-09-02 vue

    # Vue

    # 前端知识体系

    # 前端三要素

    • HTML(结构):超文本标记语言(Hyper Text Markup Language),决定网页的结构和内容
    • CSS(表现):层叠样式表(Cascading Style Sheets),设定网页的表现样式
    • JavaScript(行为):是一种弱类型脚本语言,其源代码不需经过编译,而是由浏览器解释运行,用于控制网页的行为

    # 结构层(HTML)

    略

    # 表现层(CSS)

    CSS 层叠样式表是一门标记语言,并不是编程语言,因此不可以自定义变量,不可以引用等,换句话说就是不具备任何语法支持,它主要缺陷如下:

    • 语法不够强大,比如无法嵌套书写,导致模块化开发中需要书写很多重复的选择器;
    • 没有变量和合理的样式复用机制,使得逻辑上相关的属性值必须以字面量的形式重复输出,导致难以维护;

    这就导致了我们在工作中无端增加了许多工作量。为了解决这个问题,前端开发人员会使用一种称之为 【CSS 预处理器】 的工具,提供 CSS 缺失的样式层复用机制、减少冗余代码,提高样式代码的可维护性。大大提高了前端在样式上的开发效率。

    # 什么是 CSS 预处理器

    CSS 预处理器定义了一种新的语言,其基本思想是,用一种专门的编程语言,为 CSS 增加了一些编程的特性,将 CSS 作为目标生成文件,然后开发者就只要使用这种语言进行 CSS 的编码工作。转化成通俗易懂的话来说就是“用一种专门的编程语言,进行 Web 页面样式设计,再通过编译器转化为正常的 CSS 文件,以供项目使用”。

    # 常用的 CSS 预处理器有哪些
    • SASS:基于 Ruby,通过服务端处理,功能强大。解析效率高。需要学习 Ruby 语言,上手难度高于 LESS。
    • LESS:基于 NodeJS,通过客户端处理,使用简单。功能比 SASS 简单,解析效率也低于 SASS,但在实际开发中足够了,所以我们后台人员如果需要的话,建议使用 LESS。

    # 行为层(JavaScript)

    JavaScript 一门弱类型脚本语言,其源代码在发往客户端运行之前不需经过编译,而是将文本格式的字符代码发送给浏览器由浏览器解释运行。

    # Native 原生 JS 开发

    原生 JS 开发,也就是让我们按照 【ECMAScript】 标准的开发方式,简称是 ES,特点是所有浏览器都支持。截止到当前博客发布时间(2018 年 12 月 04 日),ES 标准已发布如下版本:

    • ES3
    • ES4(内部,未正式发布)
    • ES5(全浏览器支持)
    • ES6(常用,当前主流版本)
    • ES7
    • ES8
    • ES9(草案阶段)

    区别就是逐步增加新特性。

    # TypeScript 微软的标准

    TypeScript 是一种由微软开发的自由和开源的编程语言。它是 JavaScript 的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。由安德斯·海尔斯伯格(C#、Delphi、TypeScript 之父;.NET 创立者)主导。

    该语言的特点就是除了具备 ES 的特性之外还纳入了许多不在标准范围内的新特性,所以会导致很多浏览器不能直接支持 TypeScript 语法,需要编译后(编译成 JS)才能被浏览器正确执行。

    # JavaScript 框架
    • jQuery:大家熟知的 JavaScript 框架,优点是简化了 DOM 操作,缺点是 DOM 操作太频繁,影响前端性能;在前端眼里使用它仅仅是为了兼容 IE6、7、8;
    • Angular:Google 收购的前端框架,由一群 Java 程序员开发,其特点是将后台的 MVC 模式搬到了前端并增加了模块化开发的理念,与微软合作,采用 TypeScript 语法开发;对后台程序员友好,对前端程序员不太友好;最大的缺点是版本迭代不合理(如:1代 -> 2代,除了名字,基本就是两个东西;截止发表博客时已推出了 Angular6)
    • React:Facebook 出品,一款高性能的 JS 前端框架;特点是提出了新概念 【虚拟 DOM】 用于减少真实 DOM 操作,在内存中模拟 DOM 操作,有效的提升了前端渲染效率;缺点是使用复杂,因为需要额外学习一门 【JSX】 语言;
    • Vue:一款渐进式 JavaScript 框架,所谓渐进式就是逐步实现新特性的意思,如实现模块化开发、路由、状态管理等新特性。其特点是综合了 Angular(模块化) 和 React(虚拟 DOM) 的优点;
    • Axios:前端通信框架;因为 Vue 的边界很明确,就是为了处理 DOM,所以并不具备通信能力,此时就需要额外使用一个通信框架与服务器交互;当然也可以直接选择使用 jQuery 提供的 AJAX 通信功能;
    # UI 框架
    • Ant-Design:阿里巴巴出品,基于 React 的 UI 框架
    • ElementUI:饿了么出品,基于 Vue 的 UI 框架
    • Bootstrap:Twitter 推出的一个用于前端开发的开源工具包
    • AmazeUI:又叫“妹子 UI”,一款 HTML5 跨屏前端框架
    # JavaScript 构建工具
    • Babel:JS 编译工具,主要用于浏览器不支持的 ES 新特性,比如用于编译 TypeScript
    • WebPack:模块打包器,主要作用是打包、压缩、合并及按序加载

    注:以上知识点已将 WebApp 开发所需技能全部梳理完毕

    # 三端统一

    # 混合开发(Hybrid App)

    主要目的是实现一套代码三端统一(PC、Android、iOS)并能够调用到设备底层硬件(如:传感器、GPS、摄像头等),打包方式主要有以下两种:

    • 云打包:HBuild -> HBuildX,DCloud 出品;API Cloud
    • 本地打包: Cordova(前身是 PhoneGap)

    # 微信小程序

    详见微信官网,这里就是介绍一个方便微信小程序 UI 开发的框架:WeUI

    # 后端技术

    前端人员为了方便开发也需要掌握一定的后端技术,但我们 Java 后台人员知道后台知识体系极其庞大复杂,所以为了方便前端人员开发后台应用,就出现了 NodeJS 这样的技术。

    NodeJS 的作者已经声称放弃 NodeJS(说是架构做的不好再加上笨重的 node_modules,可能让作者不爽了吧),开始开发全新架构的 Deno

    既然是后台技术,那肯定也需要框架和项目管理工具,NodeJS 框架及项目管理工具如下:

    • Express:NodeJS 框架
    • Koa:Express 简化版
    • NPM:项目综合管理工具,类似于 Maven
    • YARN:NPM 的替代方案,类似于 Maven 和 Gradle 的关系

    # 附:扩展阅读

    # 当前主流前端框架

    # Vue.js
    # iView

    iview 是一个强大的基于 Vue 的 UI 库,有很多实用的基础组件比 elementui 的组件更丰富,主要服务于 PC 界面的中后台产品。使用单文件的 Vue 组件化开发模式 基于 npm + webpack + babel 开发,支持 ES2015 高质量、功能丰富 友好的 API ,自由灵活地使用空间。

    • 官网地址
    • Github
    • iview-admin

    备注:属于前端主流框架,选型时可考虑使用,主要特点是移动端支持较多

    # ElementUI

    Element 是饿了么前端开源维护的 Vue UI 组件库,组件齐全,基本涵盖后台所需的所有组件,文档讲解详细,例子也很丰富。主要用于开发 PC 端的页面,是一个质量比较高的 Vue UI 组件库。

    • 官网地址
    • Github
    • vue-element-admin

    备注:属于前端主流框架,选型时可考虑使用,主要特点是桌面端支持较多

    # ICE

    飞冰是阿里巴巴团队基于 React/Angular/Vue 的中后台应用解决方案,在阿里巴巴内部,已经有 270 多个来自几乎所有 BU 的项目在使用。飞冰包含了一条从设计端到开发端的完整链路,帮助用户快速搭建属于自己的中后台应用。

    • 官网地址
    • Github

    备注:主要组件还是以 React 为主,截止 2019 年 02 月 17 日更新博客前对 Vue 的支持还不太完善,目前尚处于观望阶段

    # VantUI

    Vant UI 是有赞前端团队基于有赞统一的规范实现的 Vue 组件库,提供了一整套 UI 基础组件和业务组件。通过 Vant,可以快速搭建出风格统一的页面,提升开发效率。

    • 官网地址
    • Github
    # AtUI

    at-ui 是一款基于 Vue 2.x 的前端 UI 组件库,主要用于快速开发 PC 网站产品。 它提供了一套 npm + webpack + babel 前端开发工作流程,CSS 样式独立,即使采用不同的框架实现都能保持统一的 UI 风格。

    • 官网地址
    • Github
    # CubeUI

    cube-ui 是滴滴团队开发的基于 Vue.js 实现的精致移动端组件库。支持按需引入和后编译,轻量灵活;扩展性强,可以方便地基于现有组件实现二次开发。

    • 官网地址
    • Github
    # 混合开发
    # Flutter

    Flutter 是谷歌的移动端 UI 框架,可在极短的时间内构建 Android 和 iOS 上高质量的原生级应用。Flutter 可与现有代码一起工作, 它被世界各地的开发者和组织使用, 并且 Flutter 是免费和开源的。

    • 官网地址
    • Github

    备注:Google 出品,主要特点是快速构建原生 APP 应用程序,如做混合应用该框架为必选框架

    # Ionic

    Ionic 既是一个 CSS 框架也是一个 Javascript UI 库,Ionic 是目前最有潜力的一款 HTML5 手机应用开发框架。通过 SASS 构建应用程序,它提供了很多 UI 组件来帮助开发者开发强大的应用。它使用 JavaScript MVVM 框架和 AngularJS/Vue 来增强应用。提供数据的双向绑定,使用它成为 Web 和移动开发者的共同选择。

    • 官网地址
    • 官网文档
    • Github
    # 微信小程序
    # mpvue

    mpvue 是美团开发的一个使用 Vue.js 开发小程序的前端框架,目前支持 微信小程序、百度智能小程序,头条小程序 和 支付宝小程序。 框架基于 Vue.js,修改了的运行时框架 runtime 和代码编译器 compiler 实现,使其可运行在小程序环境中,从而为小程序开发引入了 Vue.js 开发体验。

    • 官网地址
    • Github

    备注:完备的 Vue 开发体验,并且支持多平台的小程序开发,推荐使用

    # WeUI

    WeUI 是一套同微信原生视觉体验一致的基础样式库,由微信官方设计团队为微信内网页和微信小程序量身设计,令用户的使用感知更加统一。包含 button、cell、dialog、toast、article、icon 等各式元素。

    • 官网地址
    • Github

    # 前后端分离的演变史

    # 为什么需要前后分离

    # 后端为主的 MVC 时代

    为了降低开发的复杂度,以后端为出发点,比如:Struts、SpringMVC 等框架的使用,就是后端的 MVC 时代

    以 SpringMVC 流程为例:

    img

    • 发起请求到前端控制器(DispatcherServlet)
    • 前端控制器请求 HandlerMapping 查找 Handler,可以根据 xml 配置、注解进行查找
    • 处理器映射器 HandlerMapping 向前端控制器返回 Handler
    • 前端控制器调用处理器适配器去执行 Handler
    • 处理器适配器去执行 Handler
    • Handler 执行完成给适配器返回 ModelAndView
    • 处理器适配器向前端控制器返回 ModelAndView,ModelAndView 是 SpringMVC 框架的一个底层对象,包括 Model 和 View
    • 前端控制器请求视图解析器去进行视图解析,根据逻辑视图名解析成真正的视图(JSP)
    • 视图解析器向前端控制器返回 View
    • 前端控制器进行视图渲染,视图渲染将模型数据(在 ModelAndView 对象中)填充到 request 域
    • 前端控制器向用户响应结果
    # 优点

    MVC 是一个非常好的协作模式,能够有效降低代码的耦合度,从架构上能够让开发者明白代码应该写在哪里。为了让 View 更纯粹,还可以使用 Thymeleaf、Freemarker 等模板引擎,使模板里无法写入 Java 代码,让前后端分工更加清晰。

    # 缺点
    • 前端开发重度依赖开发环境,开发效率低,这种架构下,前后端协作有两种模式:
      • 第一种是前端写 DEMO,写好后,让后端去套模板。好处是 DEMO 可以本地开发,很高效。不足是还需要后端套模板,有可能套错,套完后还需要前端确定,来回沟通调整的成本比较大;
      • 另一种协作模式是前端负责浏览器端的所有开发和服务器端的 View 层模板开发。好处是 UI 相关的代码都是前端去写就好,后端不用太关注,不足就是前端开发重度绑定后端环境,环境成为影响前端开发效率的重要因素。
    • 前后端职责纠缠不清:模板引擎功能强大,依旧可以通过拿到的上下文变量来实现各种业务逻辑。这样,只要前端弱势一点,往往就会被后端要求在模板层写出不少业务代码。还有一个很大的灰色地带是 Controller,页面路由等功能本应该是前端最关注的,但却是由后端来实现。 Controller 本身与 Model 往往也会纠缠不清,看了让人咬牙的业务代码经常会出现在 Controller 层。这些问题不能全归结于程序员的素养,否则 JSP 就够了。
    • 对前端发挥的局限性:性能优化如果只在前端做空间非常有限,于是我们经常需要后端合作,但由于后端框架限制,我们很难使用 【Comet】 、【BigPipe】 等技术方案来优化性能。

    注:在这期间(2005 年以前),包括早期的 JSP、PHP 可以称之为 Web 1.0 时代。在这里想说一句,如果你是一名 Java 初学者,请你不要再把一些陈旧的技术当回事了,比如 JSP,因为时代在变、技术在变、什么都在变(引用扎克伯格的一句话:唯一不变的是变化本身);当我们 千锋教育 机构去给大学做实训时,有些同学会认为我们没有讲什么 干货 ,其实不然,只能说是你认知里的干货对于市场来说早就过时了而已。

    # 什么是前后分离

    # 基于 AJAX 带来的 SPA 时代

    时间回到 2005 年 AJAX(Asynchronous JavaScript And XML,异步 JavaScript 和 XML,老技术新用法) 被正式提出并开始使用 CDN 作为静态资源存储,于是出现了 JavaScript 王者归来(在这之前 JS 都是用来在网页上贴狗皮膏药广告的)的 SPA(Single Page Application)单页面应用时代。

    img

    # 优点

    这种模式下,前后端的分工非常清晰,前后端的关键协作点是 AJAX 接口。看起来是如此美妙,但回过头来看看的话,这与 JSP 时代区别不大。复杂度从服务端的 JSP 里移到了浏览器的 JavaScript,浏览器端变得很复杂。类似 Spring MVC,这个时代开始出现浏览器端的分层架构:

    img

    # 缺点
    • 前后端接口的约定: 如果后端的接口一塌糊涂,如果后端的业务模型不够稳定,那么前端开发会很痛苦;不少团队也有类似尝试,通过接口规则、接口平台等方式来做。有了和后端一起沉淀的 接口规则 ,还可以用来模拟数据,使得前后端可以在约定接口后实现高效并行开发。
    • 前端开发的复杂度控制: SPA 应用大多以功能交互型为主,JavaScript 代码过十万行很正常。大量 JS 代码的组织,与 View 层的绑定等,都不是容易的事情。

    # 前端为主的 MV* 时代

    此处的 MV* 模式如下:

    • MVC(同步通信为主):Model、View、Controller
    • MVP(异步通信为主):Model、View、Presenter
    • MVVM(异步通信为主):Model、View、ViewModel

    为了降低前端开发复杂度,涌现了大量的前端框架,比如:AngularJS、React、Vue.js、EmberJS 等,这些框架总的原则是先按类型分层,比如 Templates、Controllers、Models,然后再在层内做切分,如下图:

    img

    # 优点
    • 前后端职责很清晰: 前端工作在浏览器端,后端工作在服务端。清晰的分工,可以让开发并行,测试数据的模拟不难,前端可以本地开发。后端则可以专注于业务逻辑的处理,输出 RESTful 等接口。
    • 前端开发的复杂度可控: 前端代码很重,但合理的分层,让前端代码能各司其职。这一块蛮有意思的,简单如模板特性的选择,就有很多很多讲究。并非越强大越好,限制什么,留下哪些自由,代码应该如何组织,所有这一切设计,得花一本书的厚度去说明。
    • 部署相对独立: 可以快速改进产品体验
    # 缺点
    • 代码不能复用。比如后端依旧需要对数据做各种校验,校验逻辑无法复用浏览器端的代码。如果可以复用,那么后端的数据校验可以相对简单化。
    • 全异步,对 SEO 不利。往往还需要服务端做同步渲染的降级方案。
    • 性能并非最佳,特别是移动互联网环境下。
    • SPA 不能满足所有需求,依旧存在大量多页面应用。URL Design 需要后端配合,前端无法完全掌控。

    # NodeJS 带来的全栈时代

    前端为主的 MV* 模式解决了很多很多问题,但如上所述,依旧存在不少不足之处。随着 NodeJS 的兴起,JavaScript 开始有能力运行在服务端。这意味着可以有一种新的研发模式:

    img

    在这种研发模式下,前后端的职责很清晰。对前端来说,两个 UI 层各司其职:

    • Front-end UI layer 处理浏览器层的展现逻辑。通过 CSS 渲染样式,通过 JavaScript 添加交互功能,HTML 的生成也可以放在这层,具体看应用场景。
    • Back-end UI layer 处理路由、模板、数据获取、Cookie 等。通过路由,前端终于可以自主把控 URL Design,这样无论是单页面应用还是多页面应用,前端都可以自由调控。后端也终于可以摆脱对展现的强关注,转而可以专心于业务逻辑层的开发。

    通过 Node,Web Server 层也是 JavaScript 代码,这意味着部分代码可前后复用,需要 SEO 的场景可以在服务端同步渲染,由于异步请求太多导致的性能问题也可以通过服务端来缓解。前一种模式的不足,通过这种模式几乎都能完美解决掉。

    与 JSP 模式相比,全栈模式看起来是一种回归,也的确是一种向原始开发模式的回归,不过是一种螺旋上升式的回归。

    基于 NodeJS 的全栈模式,依旧面临很多挑战:

    • 需要前端对服务端编程有更进一步的认识。比如 TCP/IP 等网络知识的掌握。
    • NodeJS 层与 Java 层的高效通信。NodeJS 模式下,都在服务器端,RESTful HTTP 通信未必高效,通过 SOAP 等方式通信更高效。一切需要在验证中前行。
    • 对部署、运维层面的熟练了解,需要更多知识点和实操经验。
    • 大量历史遗留问题如何过渡。这可能是最大最大的阻力。

    注意: 看到这里,相信很多同学就可以理解,为什么我总在课堂上说:“前端想学后台很难,而我们后端程序员学任何东西都很简单”;就是因为我们后端程序员具备相对完善的知识体系。**

    # 总结

    综上所述,模式也好,技术也罢,没有好坏优劣之分,只有适合不适合;前后分离的开发思想主要是基于 SoC(关注度分离原则),上面种种模式,都是让前后端的职责更清晰,分工更合理高效。

    # 前端 MVVM 模式

    # 什么是 MVVM

    MVVM(Model-View-ViewModel)是一种软件架构设计模式,由微软 WPF(用于替代 WinForm,以前就是用这个技术开发桌面应用程序的)和 Silverlight(类似于 Java Applet,简单点说就是在浏览器上运行的 WPF) 的架构师 Ken Cooper 和 Ted Peters 开发,是一种简化用户界面的**事件驱动编程方式**。由 John Gossman(同样也是 WPF 和 Silverlight 的架构师)于 2005 年在他的博客上发表。

    MVVM 源自于经典的 MVC(Model-View-Controller)模式(期间还演化出了 MVP(Model-View-Presenter) 模式)。MVVM 的核心是 ViewModel 层,负责转换 Model 中的数据对象来让数据变得更容易管理和使用,其作用如下:

    • 该层向上与视图层进行双向数据绑定
    • 向下与 Model 层通过接口请求进行数据交互

    img

    MVVM 已经相当成熟了,主要运用但不仅仅在网络应用程序开发中。当下流行的 MVVM 框架有 Vue.js,AngularJS 等。

    # 为什么要使用 MVVM

    MVVM 模式和 MVC 模式一样,主要目的是分离视图(View)和模型(Model),有几大好处

    • 低耦合: 视图(View)可以独立于 Model 变化和修改,一个 ViewModel 可以绑定到不同的 View 上,当 View 变化的时候 Model 可以不变,当 Model 变化的时候 View 也可以不变。
    • 可复用: 你可以把一些视图逻辑放在一个 ViewModel 里面,让很多 View 重用这段视图逻辑。
    • 独立开发: 开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计。
    • 可测试: 界面素来是比较难于测试的,而现在测试可以针对 ViewModel 来写。

    # MVVM 的组成部分

    img

    # View

    View 是视图层,也就是用户界面。前端主要由 HTML 和 CSS 来构建,为了更方便地展现 ViewModel 或者 Model 层的数据,已经产生了各种各样的前后端模板语言,比如 FreeMarker、Thymeleaf 等等,各大 MVVM 框架如 Vue.js,AngularJS,EJS 等也都有自己用来构建用户界面的内置模板语言。

    # Model

    Model 是指数据模型,泛指后端进行的各种业务逻辑处理和数据操控,主要围绕数据库系统展开。这里的难点主要在于需要和前端约定统一的 接口规则

    # ViewModel

    ViewModel 是由前端开发人员组织生成和维护的视图数据层。在这一层,前端开发者对从后端获取的 Model 数据进行转换处理,做二次封装,以生成符合 View 层使用预期的视图数据模型。

    需要注意的是 ViewModel 所封装出来的数据模型包括视图的状态和行为两部分,而 Model 层的数据模型是只包含状态的

    • 比如页面的这一块展示什么,那一块展示什么这些都属于视图状态(展示)
    • 页面加载进来时发生什么,点击这一块发生什么,这一块滚动时发生什么这些都属于视图行为(交互)

    视图状态和行为都封装在了 ViewModel 里。这样的封装使得 ViewModel 可以完整地去描述 View 层 由于实现了双向绑定,ViewModel 的内容会实时展现在 View 层,这是激动人心的,因为前端开发者再也不必低效又麻烦地通过操纵 DOM 去更新视图

    MVVM 框架已经把最脏最累的一块做好了,我们开发者只需要处理和维护 ViewModel,更新数据视图就会自动得到相应更新,真正实现 事件驱动编程。

    View 层展现的不是 Model 层的数据,而是 ViewModel 的数据,由 ViewModel 负责与 Model 层交互,这就完全解耦了 View 层和 Model 层,这个解耦是至关重要的,它是前后端分离方案实施的重要一环。

    # Vue 简介

    # 概述

    Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架,发布于 2014 年 2 月。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库(如:vue-router,vue-resource,vuex)或既有项目整合。

    官方网站

    # MVVM 模式的实现者

    我们知道 MVVM 表示如下:

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

    img

    在 MVVM 架构中,是不允许 数据 和 视图 直接通信的,只能通过 ViewModel 来通信,而 ViewModel 就是定义了一个 Observer 观察者

    • ViewModel 能够观察到数据的变化,并对视图对应的内容进行更新
    • ViewModel 能够监听到视图的变化,并能够通知数据发生改变

    至此,我们就明白了,Vue.js 就是一个 MVVM 的实现者,他的核心就是实现了 DOM 监听 与 数据绑定

    其它 MVVM 实现者

    • AngularJS
    • ReactJS
    • 微信小程序

    # 为什么要使用 Vue.js

    • 轻量级,体积小是一个重要指标。Vue.js 压缩后有只有 20多kb (Angular 压缩后 56kb+,React 压缩后 44kb+)
    • 移动优先。更适合移动端,比如移动端的 Touch 事件
    • 易上手,学习曲线平稳,文档齐全
    • 吸取了 Angular(模块化)和 React(虚拟 DOM)的长处,并拥有自己独特的功能,如:计算属性
    • 开源,社区活跃度高

    # Vue.js 的两大核心要素

    # 数据驱动

    img

    当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是为什么 Vue 不支持 IE8 以及更低版本浏览器。

    这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。这里需要注意的问题是浏览器控制台在打印数据对象时 getter/setter 的格式化并不同,所以你可能需要安装 vue-devtools 来获取更加友好的检查接口。

    每个组件实例都有相应的 watcher 实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新。

    # 组件化

    • 页面上每个独立的可交互的区域视为一个组件
    • 每个组件对应一个工程目录,组件所需的各种资源在这个目录下就近维护
    • 页面不过是组件的容器,组件可以嵌套自由组合(复用)形成完整的页面

    # Vue 的使用

    # 兼容性

    Vue 不支持 IE8 及以下版本,因为 Vue 使用了 IE8 无法模拟的 ECMAScript 5 特性。但它支持所有 兼容 ECMAScript 5 的浏览器。

    # 下载地址

    • 开发版本

      • 包含完整的警告和调试模式:https://vuejs.org/js/vue.js
      • 删除了警告,30.96KB min + gzip:https://vuejs.org/js/vue.min.js
    • CDN

      <script src="https://cdn.jsdelivr.net/npm/vue"></script>
      
      1

    # 第一个 Vue 应用程序

    Vue.js 的核心是实现了 MVVM 模式,她扮演的角色就是 ViewModel 层,那么所谓的第一个应用程序就是展示她的 数据绑定 功能,操作流程如下:

    # 创建一个 HTML 文件

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>第一个 Vue 应用程序</title>
    </head>
    <body>
    </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    # 引入 Vue.js

    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    
    1

    # 创建一个 Vue 的实例

    <script type="text/javascript">
        var vm = new Vue({
            el: '#vue',
            data: {
                message: 'Hello Vue!'
            }
        });
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8

    # 说明

    • el:'#vue':绑定元素的 ID
    • data:{message:'Hello Vue!'}:数据对象中有一个名为 message 的属性,并设置了初始值 Hello Vue!

    # 将数据绑定到页面元素

    <div id="vue">
        {{message}}
    </div>
    
    1
    2
    3

    说明:只需要在绑定的元素中使用 双花括号 将 Vue 创建的名为 message 属性包裹起来,即可实现数据绑定功能,也就实现了 ViewModel 层所需的效果,是不是和 EL 表达式非常像?

    #{message} => {{message}}
    
    1

    # 完整的 HTML

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>第一个 Vue 应用程序</title>
        <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    </head>
    <body>
        <div id="vue">
            {{message}}
        </div>
    <script type="text/javascript">
        var vm = new Vue({
            el: '#vue',
            data: {
                message: 'Hello Vue!'
            }
        });
    </script>
    </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    注:我是在 IDEA 上创建的 HTML,并使用 IDEA 内置的 HTTP 服务器运行

    # 测试 Vue

    为了能够更直观的体验 Vue 带来的数据绑定功能,我们需要在浏览器测试一番,操作流程如下:

    • 在 Chrome 浏览器上运行第一个 Vue 应用程序,并按 F12 进入 开发者工具

    img

    • 在控制台输入 vm.message = 'Hello World' ,然后 回车,你会发现浏览器中显示的内容会直接变成 Hello World

    img

    # 说明

    在之前的代码中,我们创建了一个名为 vm 的 Vue 实例

    var vm = new Vue({
        el: '#vue',
        data: {
            message: 'Hello Vue!'
        }
    });
    
    1
    2
    3
    4
    5
    6

    此时就可以在控制台直接输入 vm.message 来修改值,中间是可以省略 data 的,在这个操作中,我并没有主动操作 DOM,就让页面的内容发生了变化,这就是借助了 Vue 的 数据绑定 功能实现的;MVVM 模式中要求 ViewModel 层就是使用 观察者模式 来实现数据的监听与绑定,以做到数据与视图的快速响应。

    # Vue实例的生命周期

    # 什么是生命周期

    Vue 实例有一个完整的生命周期,也就是从 开始创建、初始化数据、编译模板、挂载 DOM、渲染→更新→渲染、卸载 等一系列过程,我们称这是 Vue 的生命周期。通俗说就是 Vue 实例从创建到销毁的过程,就是生命周期。

    在 Vue 的整个生命周期中,它提供了一系列的事件,可以让我们在事件触发时注册 JS 方法,可以让我们用自己注册的 JS 方法控制整个大局,在这些事件响应方法中的 this 直接指向的是 Vue 的实例。

    img

    注意: created 钩子函数和 mounted 钩子函数的区别

    # 钩子函数的触发时机

    # beforeCreate

    在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用。

    # created

    实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算, watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。

    # beforeMount

    在挂载开始之前被调用:相关的 render 函数首次被调用。

    # mounted

    el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。

    # beforeUpdate

    数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。 你可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。

    # updated

    由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。

    # beforeDestroy

    实例销毁之前调用。在这一步,实例仍然完全可用。

    # destroyed

    Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。

    # Vue 语法

    # 条件判断语句

    • v-if
    • v-else

    什么是条件判断语句,就不需要我说明了吧( ̄▽ ̄),直接看语法上效果

    # HTML

    <div id="vue">
        <h1 v-if="ok">YES</h1>
        <h1 v-else>NO</h1>
    </div>
    
    1
    2
    3
    4

    # JavaScript

    <script type="text/javascript">
        var vm = new Vue({
            el: '#vue',
            data: {
                ok: true
            }
        });
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8

    # 测试效果

    • 在 Chrome 浏览器上运行,并按 F12 进入 开发者工具

    img

    • 在控制台输入 vm.ok = false ,然后 回车,你会发现浏览器中显示的内容会直接变成 NO

    img

    注意: 使用 v-* 属性绑定数据是不需要 双花括号 包裹的

    # 完整的 HTML

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>语法篇 v-if</title>
        <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    </head>
    <body>
    <div id="vue">
        <h1 v-if="ok">YES</h1>
        <h1 v-else>NO</h1>
    </div>
    <script type="text/javascript">
        var vm = new Vue({
            el: '#vue',
            data: {
                ok: true
            }
        });
    </script>
    </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22

    # 连续的条件判断语句

    • v-if
    • v-else-if
    • v-else

    # HTML

    <div id="vue">
        <h1 v-if="type === 'A'">A</h1>
        <h1 v-else-if="type === 'B'">B</h1>
        <h1 v-else-if="type === 'C'">C</h1>
        <h1 v-else>你看不见我</h1>
    </div>
    
    1
    2
    3
    4
    5
    6

    注:=== 三个等号在 JS 中表示绝对等于(就是数据与类型都要相等)

    # JavaScript

    <script type="text/javascript">
        var vm = new Vue({
            el: '#vue',
            data: {
                type: 'A'
            }
        });
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8

    # 测试效果

    • 在 Chrome 浏览器上运行,并按 F12 进入 开发者工具

    img

    • 分别观察在控制台输入 vm.type = 'B'、'C'、'D' 的变化

    img

    # 完整的 HTML

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>语法篇 v-else-if</title>
        <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    </head>
    <body>
    <div id="vue">
        <h1 v-if="type === 'A'">A</h1>
        <h1 v-else-if="type === 'B'">B</h1>
        <h1 v-else-if="type === 'C'">C</h1>
        <h1 v-else>你看不见我</h1>
    </div>
    <script type="text/javascript">
        var vm = new Vue({
            el: '#vue',
            data: {
                type: 'A'
            }
        });
    </script>
    </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24

    # 循环遍历语句

    • v-for

    # HTML

    <div id="vue">
        <li v-for="item in items">
            {{ item.message }}
        </li>
    </div>
    
    1
    2
    3
    4
    5

    注:items 是源数据数组并且 item 是数组元素迭代的别名。是不是像极了 Thymeleaf

    # JavaScript

    <script type="text/javascript">
        var vm = new Vue({
            el: '#vue',
            data: {
                items: [
                    {message: 'Foo'},
                    {message: 'Bar'}
                ]
            }
        });
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    # 测试效果

    • 在 Chrome 浏览器上运行,并按 F12 进入 开发者工具

    img

    • 在控制台输入 vm.items.push({message: 'Baz'}) ,尝试追加一条数据,你会发现浏览器中显示的内容会增加一条 Baz

    img

    # 完整的 HTML

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>语法篇 v-for</title>
        <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    </head>
    <body>
    <div id="vue">
        <li v-for="item in items">
            {{ item.message }}
        </li>
    </div>
    <script type="text/javascript">
        var vm = new Vue({
            el: '#vue',
            data: {
                items: [
                    {message: 'Foo'},
                    {message: 'Bar'}
                ]
            }
        });
    </script>
    </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26

    # Vue 事件

    # 监听事件

    • v-on

    # HTML

    <div id="vue">
        <button v-on:click="sayHi">点我</button>
    </div>
    
    1
    2
    3

    注:在这里我们使用了 v-on 绑定了 click 事件,并指定了名为 sayHi 的方法

    # JavaScript

    方法必须定义在 Vue 实例的 methods 对象中

    <script type="text/javascript">
        var vm = new Vue({
            el: '#vue',
            data: {
                message: 'Hello World'
            },
            // 在 `methods` 对象中定义方法
            methods: {
                sayHi: function (event) {
                    // `this` 在方法里指向当前 Vue 实例
                    alert(this.message);
                }
            }
        });
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    # 测试效果

    img

    # 完整的 HTML

    <!DOCTYPE html>
    <html xmlns:v-on="">
    <head>
        <meta charset="UTF-8">
        <title>事件篇 v-on</title>
        <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    </head>
    <body>
    <div id="vue">
        <button v-on:click="sayHi">点我</button>
    </div>
    <script type="text/javascript">
        var vm = new Vue({
            el: '#vue',
            data: {
                message: 'Hello World'
            },
            // 在 `methods` 对象中定义方法
            methods: {
                sayHi: function (event) {
                    // `this` 在方法里指向当前 Vue 实例
                    alert(this.message);
                }
            }
        });
    </script>
    </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28

    # Vue 通信

    # 什么是 Axios

    Axios 是一个开源的可以用在浏览器端和 NodeJS 的异步通信框架,它的主要作用就是实现 AJAX 异步通信,其功能特点如下:

    • 从浏览器中创建 XMLHttpRequests
    • 从 node.js 创建 http 请求
    • 支持 Promise API
    • 拦截请求和响应
    • 转换请求数据和响应数据
    • 取消请求
    • 自动转换 JSON 数据
    • 客户端支持防御 XSRF(跨站请求伪造)

    GitHub:https://github.com/axios/axios

    # 为什么要使用 Axios

    由于 Vue.js 是一个 视图层框架 并且作者(尤雨溪)严格准守 SoC (关注度分离原则),所以 Vue.js 并不包含 AJAX 的通信功能,为了解决通信问题,作者单独开发了一个名为 vue-resource 的插件,不过在进入 2.0 版本以后停止了对该插件的维护并推荐了 Axios 框架

    # 第一个 Axios 应用程序

    咱们开发的接口大部分都是采用 JSON 格式,可以先在项目里模拟一段 JSON 数据,数据内容如下:

    {
      "name": "广州千锋",
      "url": "http://www.funtl.com",
      "page": 88,
      "isNonProfit": true,
      "address": {
        "street": "元岗路.",
        "city": "广东广州",
        "country": "中国"
      },
      "links": [
        {
          "name": "Google",
          "url": "http://www.google.com"
        },
        {
          "name": "Baidu",
          "url": "http://www.baidu.com"
        },
        {
          "name": "SoSo",
          "url": "http://www.SoSo.com"
        }
      ]
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25

    创建一个名为 data.json 的文件并填入上面的内容,放在项目的根目录下,如图所示:

    img

    # 创建 HTML

    <div id="vue">
        <div>名称:{{info.name}}</div>
        <div>地址:{{info.address.country}}-{{info.address.city}}-{{info.address.street}}</div>
        <div>链接:<a v-bind:href="info.url" target="_blank">{{info.url}}</a> </div>
    </div>
    
    1
    2
    3
    4
    5

    注意: 在这里使用了 v-bind 将 a:href 的属性值与 Vue 实例中的数据进行绑定

    # 引入 JS 文件

    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    
    1
    2

    # JavaScript

    <script type="text/javascript">
        var vm = new Vue({
            el: '#vue',
            data() {
                return {
                    info: {
                        name: null,
                        address: {
                            country: null,
                            city: null,
                            street: null
                        },
                        url: null
                    }
                }
            },
            mounted() {
                axios
                    .get('data.json')
                    .then(response => (this.info = response.data));
            }
        });
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    使用 axios 框架的 get 方法请求 AJAX 并自动将数据封装进了 Vue 实例的数据对象中

    # 数据对象

    这里的数据结构与 JSON 数据结构是匹配的

    info: {
        name: null,
        address: {
            country: null,
            city: null,
            street: null
        },
        url: null
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 调用 get 请求

    调用 axios 的 get 请求并自动装箱数据

    axios
        .get('data.json')
        .then(response => (this.info = response.data));
    
    1
    2
    3

    # 测试效果

    img

    # 完整的 HTML

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>网络篇 Axios</title>
        <script src="https://cdn.jsdelivr.net/npm/vue"></script>
        <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    </head>
    <body>
    <div id="vue">
        <div>名称:{{info.name}}</div>
        <div>地址:{{info.address.country}}-{{info.address.city}}-{{info.address.street}}</div>
        <div>链接:<a v-bind:href="info.url" target="_blank">{{info.url}}</a> </div>
    </div>
    <script type="text/javascript">
        var vm = new Vue({
            el: '#vue',
            data() {
                return {
                    info: {
                        name: null,
                        address: {
                            country: null,
                            city: null,
                            street: null
                        },
                        url: null
                    }
                }
            },
            mounted() {
                axios
                    .get('data.json')
                    .then(response => (this.info = response.data));
            }
        });
    </script>
    </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39

    # Vue 表单输入

    # 什么是双向数据绑定

    Vue.js 是一个 MVVM 框架,即数据双向绑定,即当数据发生变化的时候,视图也就发生变化,当视图发生变化的时候,数据也会跟着同步变化。这也算是 Vue.js 的精髓之处了。值得注意的是,我们所说的数据双向绑定,一定是对于 UI 控件来说的,非 UI 控件不会涉及到数据双向绑定。单向数据绑定是使用状态管理工具的前提。如果我们使用 vuex,那么数据流也是单项的,这时就会和双向数据绑定有冲突。

    # 为什么要实现数据的双向绑定

    在 Vue.js 中,如果使用 vuex,实际上数据还是单向的,之所以说是数据双向绑定,这是用的 UI 控件来说,对于我们处理表单,Vue.js 的双向数据绑定用起来就特别舒服了。即两者并不互斥,在全局性数据流使用单项,方便跟踪;局部性数据流使用双向,简单易操作。

    # 在表单中使用双向数据绑定

    你可以用 v-model 指令在表单 、 及 `` 元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但 v-model 本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。

    注意: v-model 会忽略所有表单元素的 value、checked、selected 特性的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data 选项中声明初始值。

    # 单行文本

    <div id="vue">
        单行文本:<input type="text" v-model="message" />  单行文本是:{{message}}
    </div>
    
    1
    2
    3
    <script type="text/javascript">
        var vm = new Vue({
            el: '#vue',
            data: {
                message: "Hello Vue"
            }
        });
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8

    img

    # 多行文本

    <div id="vue">
        多行文本:<textarea v-model="message"></textarea>  多行文本是:{{message}}
    </div>
    
    1
    2
    3
    <script type="text/javascript">
        var vm = new Vue({
            el: '#vue',
            data: {
                message: "Hello Textarea"
            }
        });
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8

    img

    # 单复选框

    <div id="vue">
        单复选框:<input type="checkbox" id="checkbox" v-model="checked">  <label for="checkbox">{{ checked }}</label>
    </div>
    
    1
    2
    3
    <script type="text/javascript">
        var vm = new Vue({
            el: '#vue',
            data: {
                checked: false
            }
        });
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8

    img

    # 多复选框

    <div id="vue">
        多复选框:
        <input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
        <label for="jack">Jack</label>
        <input type="checkbox" id="john" value="John" v-model="checkedNames">
        <label for="john">John</label>
        <input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
        <label for="mike">Mike</label>
        <span>选中的值: {{ checkedNames }}</span>
    </div>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <script type="text/javascript">
        var vm = new Vue({
            el: '#vue',
            data: {
                checkedNames: []
            }
        });
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8

    img

    # 单选按钮

    <div id="vue">
        单选按钮:
        <input type="radio" id="one" value="One" v-model="picked">
        <label for="one">One</label>
        <input type="radio" id="two" value="Two" v-model="picked">
        <label for="two">Two</label>
        <span>选中的值: {{ picked }}</span>
    </div>
    
    1
    2
    3
    4
    5
    6
    7
    8
    <script type="text/javascript">
        var vm = new Vue({
            el: '#vue',
            data: {
                picked: ''
            }
        });
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8

    img

    # 下拉框

    <div id="vue">
        下拉框:
        <select v-model="selected">
            <option disabled value="">请选择</option>
            <option>A</option>
            <option>B</option>
            <option>C</option>
        </select>
        <span>选中的值: {{ selected }}</span>
    </div>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <script type="text/javascript">
        var vm = new Vue({
            el: '#vue',
            data: {
                selected: ''
            }
        });
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8

    img

    注意: 如果 v-model 表达式的初始值未能匹配任何选项,`` 元素将被渲染为“未选中”状态。在 iOS 中,这会使用户无法选择第一个选项。因为这样的情况下,iOS 不会触发 change 事件。因此,更推荐像上面这样提供一个值为空的禁用选项。

    # Vue 组件基础

    # 什么是组件

    组件是可复用的 Vue 实例,说白了就是一组可以重复使用的模板,跟 JSTL 的自定义标签、Thymeleaf 的 th:fragment 以及 Sitemesh3 框架有着异曲同工之妙。通常一个应用会以一棵嵌套的组件树的形式来组织:

    img

    例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。

    # 第一个 Vue 组件

    **注意:**在实际开发中,我们并不会用以下方式开发组件,而是采用 vue-cli 创建 .vue 模板文件的方式开发,以下方法只是为了让大家理解什么是组件。

    # 使用 Vue.component() 方法注册组件

    # JavaScript
    <script type="text/javascript">
        // 先注册组件
        Vue.component('my-component-li', {
            template: '<li>Hello li</li>'
        });
        // 再实例化 Vue
        var vm = new Vue({
            el: '#vue'
        });
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # HTML
    <div id="vue">
        <ul>
            <my-component-li></my-component-li>
        </ul>
    </div>
    
    1
    2
    3
    4
    5
    # 说明
    • Vue.component():注册组件
    • my-component-li:自定义组件的名字
    • template:组件的模板
    # 测试效果

    img

    # 使用 props 属性传递参数

    像上面那样用组件没有任何意义,所以我们是需要传递参数到组件的,此时就需要使用 props 属性了

    注意: 默认规则下 props 属性里的值不能为大写;感谢来自 Java微服务技术交流群2 的群友 [CV战士蛋蛋面] 帮助大家踩坑;

    # JavaScript
    <script type="text/javascript">
        // 先注册组件
        Vue.component('my-component-li', {
            props: ['item'],
            template: '<li>Hello {{item}}</li>'
        });
        // 再实例化 Vue
        var vm = new Vue({
            el: '#vue',
            data: {
                items: ["张三", "李四", "王五"]
            }
        });
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # HTML
    <div id="vue">
        <ul>
            <my-component-li v-for="item in items" v-bind:item="item"></my-component-li>
        </ul>
    </div>
    
    1
    2
    3
    4
    5
    # 说明
    • v-for="item in items":遍历 Vue 实例中定义的名为 items 的数组,并创建同等数量的组件
    • v-bind:item="item":将遍历的 item 项绑定到组件中 props 定义的名为 item 属性上;= 号左边的 item 为 props 定义的属性名,右边的为 item in items 中遍历的 item 项的值
    # 测试效果

    img

    # 完整的 HTML

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>布局篇 组件基础</title>
        <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    </head>
    <body>
    <div id="vue">
        <ul>
            <my-component-li v-for="item in items" v-bind:item="item"></my-component-li>
        </ul>
    </div>
    <script type="text/javascript">
        // 先注册组件
        Vue.component('my-component-li', {
            props: ['item'],
            template: '<li>Hello {{item}}</li>'
        });
        // 再实例化 Vue
        var vm = new Vue({
            el: '#vue',
            data: {
                items: ["张三", "李四", "王五"]
            }
        });
    </script>
    </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29

    # Vue 计算属性

    # 什么是计算属性

    计算属性的重点突出在 属性 两个字上(属性是名词),首先它是个 属性 其次这个属性有 计算 的能力(计算是动词),这里的 计算 就是个函数;简单点说,它就是一个能够将计算结果缓存起来的属性(将行为转化成了静态的属性),仅此而已;

    # 计算属性与方法的区别

    # 完整的 HTML

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>布局篇 计算属性</title>
        <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    </head>
    <body>
    <div id="vue">
        <p>调用当前时间的方法:{{currentTime1()}}</p>
        <p>当前时间的计算属性:{{currentTime2}}</p>
    </div>
    <script type="text/javascript">
        var vm = new Vue({
            el: '#vue',
            data: {
                message: 'Hello Vue'
            },
            methods: {
                currentTime1: function () {
                    return Date.now();
                }
            },
            computed: {
                currentTime2: function () {
                    this.message;
                    return Date.now();
                }
            }
        });
    </script>
    </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    # 说明
    • methods:定义方法,调用方法使用 currentTime1(),需要带括号
    • computed:定义计算属性,调用属性使用 currentTime2,不需要带括号;this.message 是为了能够让 currentTime2 观察到数据变化而变化

    注意: methods 和 computed 里不能重名

    # 测试效果

    仔细看图中说明,观察其中的差异

    img

    # 结论

    调用方法时,每次都需要进行计算,既然有计算过程则必定产生系统开销,那如果这个结果是不经常变化的呢?此时就可以考虑将这个结果缓存起来,采用计算属性可以很方便的做到这一点;计算属性的主要特性就是为了将不经常变化的计算结果进行缓存,以节约我们的系统开销

    # Vue 内容分发与自定义事件

    # Vue 中的内容分发

    在 Vue.js 中我们使用 `` 元素作为承载分发内容的出口,作者称其为 插槽,可以应用在组合组件的场景中

    # 利用插槽功能实现一个组合组件

    比如准备制作一个待办事项组件(todo),该组件由待办标题(todo-title)和待办内容(todo-items)组成,但这三个组件又是相互独立的,该如何操作呢?

    # 定义一个名为 todo 的待办事项组件
    Vue.component('todo', {
        template: '<div>\
                        <slot name="todo-title"></slot>\
                        <ul>\
                            <slot name="todo-items"></slot>\
                        </ul>\
                   </div>'
    });
    
    1
    2
    3
    4
    5
    6
    7
    8

    该组件中放置了两个插槽,分别为 todo-title 和 todo-items

    # 定义一个名为 todo-title 的待办标题组件
    Vue.component('todo-title', {
        props: ['title'],
        template: '<div>{{title}}</div>'
    });
    
    1
    2
    3
    4
    # 定义一个名为 todo-items 的待办内容组件
    Vue.component('todo-items', {
        props: ['item', 'index'],
        template: '<li>{{index + 1}}. {{item}}</li>'
    });
    
    1
    2
    3
    4
    # 实例化 Vue 并初始化数据
    var vm = new Vue({
        el: '#vue',
        data: {
            todoItems: ['《刀剑神域3》', '《关于我转生成为史莱姆这件事》', '《实力至上主义教室》']
        }
    });
    
    1
    2
    3
    4
    5
    6
    # HTML
    <div id="vue">
        <todo>
            <todo-title slot="todo-title" title="今日动漫推荐"></todo-title>
            <todo-items slot="todo-items" v-for="(item, index) in todoItems" v-bind:item="item" v-bind:index="index" :key="index"></todo-items>
        </todo>
    </div>
    
    1
    2
    3
    4
    5
    6

    此时,我们的 todo-title 和 todo-items 组件分别被分发到了 todo 组件的 todo-title 和 todo-items 插槽中

    # 测试效果

    img

    # 使用自定义事件删除待办事项

    通过以上代码不难发现,数据项在 Vue 的实例中,但删除操作要在组件中完成,那么组件如何才能删除 Vue 实例中的数据呢?此时就涉及到参数传递与事件分发了,Vue 为我们提供了自定义事件的功能很好的帮助我们解决了这个问题;使用 this.$emit('自定义事件名', 参数),操作过程如下

    # 修改创建 Vue 实例代码

    var vm = new Vue({
        el: '#vue',
        data: {
            todoItems: ['《刀剑神域3》', '《关于我转生成为史莱姆这件事》', '《实力至上主义教室》']
        },
        methods: {
            // 该方法可以被模板中自定义事件触发
            removeTodoItems: function (index) {
                console.log("删除 " + this.todoItems[index] + " 成功");
                // splice() 方法向/从数组中添加/删除项目,然后返回被删除的项目,其中 index 为添加/删除项目的位置,1 表示删除的数量
                this.todoItems.splice(index, 1);
            }
        }
    });
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    增加了 methods 对象并定义了一个名为 removeTodoItems 的方法

    # 修改 todo-items 待办内容组件的代码

    Vue.component('todo-items', {
        props: ['item', 'index'],
        template: '<li>{{index + 1}}. {{item}} <button @click="remove">删除</button></li>',
        methods: {
            remove: function (index) {
                // 这里的 remove 是自定义事件的名称,需要在 HTML 中使用 v-on:remove 的方式指派
                this.$emit('remove', index);
            }
        }
    });
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    增加了 删除 元素并绑定了组件中定义的 remove 事件

    # 修改 todo-items 待办内容组件的 HTML 代码

    <todo-items slot="todo-items" v-for="(item, index) in todoItems" v-bind:item="item" v-bind:index="index" :key="index" v-on:remove="removeTodoItems(index)"></todo-items>
    
    1

    增加了 v-on:remove="removeTodoItems(index)" 自定义事件,该事件会调用 Vue 实例中定义的名为 removeTodoItems 的方法

    # 测试效果

    img

    img

    # 完整的 HTML

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>布局篇 内容分发与自定义事件</title>
        <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    </head>
    <body>
    <div id="vue">
        <todo>
            <todo-title slot="todo-title" title="今日动漫推荐"></todo-title>
            <todo-items slot="todo-items" v-for="(item, index) in todoItems" v-bind:item="item" v-bind:index="index" :key="index" v-on:remove="removeTodoItems(index)"></todo-items>
        </todo>
    </div>
    <script type="text/javascript">
        // 定义一个待办事项组件
        Vue.component('todo', {
            template: '<div>\
                            <slot name="todo-title"></slot>\
                            <ul>\
                                <slot name="todo-items"></slot>\
                            </ul>\
                       </div>'
        });
        // 定义一个待办事项标题组件
        Vue.component('todo-title', {
            props: ['title'],
            template: '<div>{{title}}</div>'
        });
        // 定义一个待办事项内容组件
        Vue.component('todo-items', {
            props: ['item', 'index'],
            template: '<li>{{index + 1}}. {{item}} <button @click="remove">删除</button></li>',
            methods: {
                remove: function (index) {
                    this.$emit('remove', index);
                }
            }
        });
        var vm = new Vue({
            el: '#vue',
            data: {
                todoItems: ['《刀剑神域3》', '《关于我转生成为史莱姆这件事》', '《实力至上主义教室》']
            },
            methods: {
                // 该方法可以被模板中自定义事件触发
                removeTodoItems: function (index) {
                    console.log("删除 " + this.todoItems[index] + " 成功");
                    this.todoItems.splice(index, 1);
                }
            }
        });
    </script>
    </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55

    # VueCli

    # 什么是 vue-cli

    vue-cli 官方提供的一个脚手架(预先定义好的目录结构及基础代码,咱们在创建 Maven 项目时可以选择创建一个骨架项目,这个骨架项目就是脚手架)用于快速生成一个 vue 的项目模板

    • 统一的目录结构
    • 本地调试
    • 热部署
    • 单元测试
    • 集成打包上线

    # 安装

    # 环境准备

    • Node.js(>= 6.x,首选 8.x)
    • git

    # 安装 Node.js

    请自行前往 http://nodejs.cn/download 官网下载安装,此处不再赘述

    img

    # 安装 vue-cli

    npm install vue-cli -g --registry=https://registry.npm.taobao.org
    # 输出如下
    npm WARN deprecated coffee-script@1.12.7: CoffeeScript on NPM has moved to "coffeescript" (no hyphen)
    C:\Users\Administrator\AppData\Roaming\npm\vue -> C:\Users\Administrator\AppData\Roaming\npm\node_modules\vue-cli\bin\vue
    C:\Users\Administrator\AppData\Roaming\npm\vue-init -> C:\Users\Administrator\AppData\Roaming\npm\node_modules\vue-cli\bin\vue-init
    C:\Users\Administrator\AppData\Roaming\npm\vue-list -> C:\Users\Administrator\AppData\Roaming\npm\node_modules\vue-cli\bin\vue-list
    + vue-cli@2.9.6
    added 241 packages from 206 contributors in 24.481s
    
    1
    2
    3
    4
    5
    6
    7
    8

    # 测试是否安装成功

    # 查看可以基于哪些模板创建 vue 应用程序,通常我们选择 webpack
    vue list
    # 输出如下
    Available official templates:
    ★  browserify - A full-featured Browserify + vueify setup with hot-reload, linting & unit testing.
    ★  browserify-simple - A simple Browserify + vueify setup for quick prototyping.
    ★  pwa - PWA template for vue-cli based on the webpack template
    ★  simple - The simplest possible Vue setup in a single HTML file
    ★  webpack - A full-featured Webpack + vue-loader setup with hot reload, linting, testing & css extraction.
    ★  webpack-simple - A simple Webpack + vue-loader setup for quick prototyping.
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    # 第一个 vue-cli 应用程序

    创建一个基于 webpack 模板的 vue 应用程序

    # 这里的 hello-vue-cli 是项目名称,可以根据自己的需求起名
    vue init webpack hello-vue-cli /vue-cli2
    vue create my-project /vue-cli3
    # 输出如下
    ? Project name hello-vue-cli
    ? Project description A Vue.js project
    ? Author Lusifer <topsale@vip.qq.com>
    ? Vue build standalone
    ? Install vue-router? No
    ? Use ESLint to lint your code? No
    ? Set up unit tests No
    ? Setup e2e tests with Nightwatch? No
    ? Should we run `npm install` for you after the project has been created? (recommended) no
       vue-cli · Generated "hello-vue-cli".
    # Project initialization finished!
    # ========================
    To get started:
      cd hello-vue-cli
      npm install (or if using yarn: yarn)
      npm run dev
    Documentation can be found at https://vuejs-templates.github.io/webpack
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    说明

    • Project name:项目名称,默认 回车 即可
    • Project description:项目描述,默认 回车 即可
    • Author:项目作者,默认 回车 即可
    • Install vue-router:是否安装 vue-router,选择 n 不安装(后期需要再手动添加)
    • Use ESLint to lint your code:是否使用 ESLint 做代码检查,选择 n 不安装(后期需要再手动添加)
    • Set up unit tests:单元测试相关,选择 n 不安装(后期需要再手动添加)
    • Setup e2e tests with Nightwatch:单元测试相关,选择 n 不安装(后期需要再手动添加)
    • Should we run npm install for you after the project has been created:创建完成后直接初始化,选择 n,我们手动执行

    # 初始化并运行

    # 初始化
    cd hello-vue-cli
    npm install --registry=https://registry.npm.taobao.org
    # 输出如下
    npm WARN deprecated browserslist@2.11.3: Browserslist 2 could fail on reading Browserslist >3.0 config used in other tools.
    npm WARN deprecated bfj-node4@5.3.1: Switch to the `bfj` package for fixes and new features!
    npm WARN deprecated browserslist@1.7.7: Browserslist 2 could fail on reading Browserslist >3.0 config used in other tools.
    > core-js@2.6.9 postinstall D:\Workspace\Study\other\hello-vue-cli\node_modules\core-js
    > node scripts/postinstall || echo "ignore"
    Thank you for using core-js ( https://github.com/zloirock/core-js ) for polyfilling JavaScript standard library!
    The project needs your help! Please consider supporting of core-js on Open Collective or Patreon:
    > https://opencollective.com/core-js
    > https://www.patreon.com/zloirock
    Also, the author of core-js ( https://github.com/zloirock ) is looking for a good job -)
    > uglifyjs-webpack-plugin@0.4.6 postinstall D:\Workspace\Study\other\hello-vue-cli\node_modules\webpack\node_modules\uglifyjs-webpack-plugin
    > node lib/post_install.js
    npm notice created a lockfile as package-lock.json. You should commit this file.
    npm WARN ajv-keywords@3.4.1 requires a peer of ajv@^6.9.1 but none is installed. You must install peer dependencies yourself.
    npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.2.9 (node_modules\fsevents):
    npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.2.9: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})
    added 1207 packages from 667 contributors and audited 11765 packages in 81.571s
    found 10 vulnerabilities (6 moderate, 4 high)
      run `npm audit fix` to fix them, or `npm audit` for details
    # 运行
    npm run dev
    # 输出如下
     DONE  Compiled successfully in 3226ms                                   
     I  Your application is running here: http://localhost:8080
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28

    安装并运行成功后在浏览器输入:http://localhost:8080

    img

    # 目录结构

    • build 和 config:WebPack 配置文件

    • node_modules:用于存放 npm install 安装的依赖文件

    • src:项目源码目录

    • static:静态资源文件

    • .babelrc:Babel 配置文件,主要作用是将 ES6 转换为 ES5

    • .editorconfig:编辑器配置

    • eslintignore:需要忽略的语法检查配置文件

    • .gitignore:git 忽略的配置文件

    • .postcssrc.js:css 相关配置文件,其中内部的 module.exports 是 NodeJS 模块化语法

    • index.html:首页,仅作为模板页,实际开发时不使用

    • package.json:项目的配置文件
      
      1
      • name:项目名称
      • version:项目版本
      • description:项目描述
      • author:项目作者
      • scripts:封装常用命令
      • dependencies:生产环境依赖
      • devDependencies:开发环境依赖

    # 源码目录

    │  App.vue
    │  main.js
    │
    ├─assets
    │      logo.png
    │
    └─components
            HelloWorld.vue
    
    1
    2
    3
    4
    5
    6
    7
    8

    # main.js

    项目的入口文件,我们知道所有的程序都会有一个入口

    // The Vue build version to load with the `import` command
    // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
    import Vue from 'vue'
    import App from './App'
    Vue.config.productionTip = false
    /* eslint-disable no-new */
    new Vue({
      el: '#app',
      components: { App },
      template: '<App/>'
    })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    • import Vue from 'vue':ES6 写法,会被转换成 require("vue"); (require 是 NodeJS 提供的模块加载器)

    • import App from './App':意思同上,但是指定了查找路径,./ 为当前目录

    • Vue.config.productionTip = false:关闭浏览器控制台关于环境的相关提示

    • new Vue({...}):实例化 Vue
      
      1
      • el: '#app':查找 index.html 中 id 为 app 的元素
      • template: '':模板,会将 index.html 中 替换为
      • components: { App }:引入组件,使用的是 import App from './App' 定义的 App 组件

    # App.vue

    组件模板

    <template>
      <div id="app">
        <img src="./assets/logo.png">
        <HelloWorld/>
      </div>
    </template>
    <script>
    import HelloWorld from './components/HelloWorld'
    export default {
      name: 'App',
      components: {
        HelloWorld
      }
    }
    </script>
    <style>
    #app {
      <!-- 字体 -->
      font-family: 'Avenir', Helvetica, Arial, sans-serif;
      <!-- 文字平滑效果 -->
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    • template:HTML 代码模板,会替换 <App /> 中的内容

    • import HelloWorld from './components/HelloWorld':引入 HelloWorld 组件,用于替换 template 中的 <HelloWorld/>

    • export default{...}:导出 NodeJS 对象,作用是可以通过import 关键字导入
      
      1

      关键字导入

      • name: 'App':定义组件的名称
      • components: { HelloWorld }:定义子组件

    # HelloWorld.vue

    基本同上,不解释..

    关于 <style scoped> 的说明:CSS 样式仅在当前组件有效,声明了样式的作用域

    # VueRouter 第一个路由

    # 概述

    Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。包含的功能有:

    • 嵌套的路由/视图表
    • 模块化的、基于组件的路由配置
    • 路由参数、查询、通配符
    • 基于 Vue.js 过渡系统的视图过渡效果
    • 细粒度的导航控制
    • 带有自动激活的 CSS class 的链接
    • HTML5 历史模式或 hash 模式,在 IE9 中自动降级
    • 自定义的滚动条行为

    # 安装

    vue-router 是一个插件包,所以我们还是需要用 npm/cnpm 来进行安装的。打开命令行工具,进入你的项目目录,输入下面命令。

    npm install vue-router --save-dev --registry=https://registry.npm.taobao.org
    # 输出如下
    npm WARN ajv-keywords@3.4.1 requires a peer of ajv@^6.9.1 but none is installed. You must install peer dependencies yourself.
    npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.2.9 (node_modules\fsevents):
    npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.2.9: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})
    + vue-router@3.0.7
    added 1 package from 1 contributor and audited 11766 packages in 11.33s
    found 10 vulnerabilities (6 moderate, 4 high)
      run `npm audit fix` to fix them, or `npm audit` for details
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    如果在一个模块化工程中使用它,必须要通过 Vue.use() 明确地安装路由功能:

    import Vue from 'vue'
    import VueRouter from 'vue-router'
    Vue.use(VueRouter);
    
    1
    2
    3

    # 使用

    以下案例在 vue-cli 项目中使用 vue-router

    # 创建组件页面

    创建一个名为 src/components 的目录专门放置我们开发的 Vue 组件,在 src/components 目录下创建一个名为 Content.vue 的组件,代码如下:

    <template>
        <div>
          我是内容页
        </div>
    </template>
    <script>
        export default {
            name: "Content"
        }
    </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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    # 安装路由

    创建一个名为 src/router 的目录专门放置我们的路由配置代码,在 src/router 目录下创建一个名为 index.js 路由配置文件,代码如下:

    import Vue from 'vue'
    // 导入路由插件
    import Router from 'vue-router'
    // 导入上面定义的组件
    import Content from '@/components/Content'
    // 安装路由
    Vue.use(Router);
    // 配置路由
    export default new Router({
      routes: [
        {
          // 路由路径
          path: '/content',
          // 路由名称
          name: 'Content',
          // 跳转到组件
          component: Content
        }
      ]
    });
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    # 配置路由

    修改 main.js 入口文件,增加配置路由的相关代码

    import Vue from 'vue'
    import App from './App'
    // 导入上面创建的路由配置目录
    import router from './router'
    Vue.config.productionTip = false;
    new Vue({
      el: '#app',
      // 配置路由
      router,
      components: { App },
      template: '<App/>'
    });
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    # 使用路由

    修改 App.vue 组件,代码如下:

    <template>
      <div id="app">
        <router-link to="/">首页</router-link>
        <router-link to="/content">内容</router-link>
        <router-view></router-view>
      </div>
    </template>
    <script>
    export default {
      name: 'App'
    }
    </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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22

    说明:

    • router-link: 默认会被渲染成一个 `` 标签,to 属性为指定链接
    • router-view: 用于渲染路由匹配到的组件

    # 效果演示

    img

    # VueRouter 第一个工程

    概述

    我们结合 ElementUI 组件库,将所需知识点应用到实际中,以最快速度带领大家掌握 Vue 的使用

    # 创建工程

    注意: 使用 NPM 安装相关组件依赖时可能会遇到权限问题,此时使用 PowerShell 管理员模式运行即可;开始菜单 -> 鼠标右击 -> Windows PowerShell (管理员)

    img

    创建一个名为 hello-vue-element 的工程

    # 使用 webpack 打包工具初始化一个名为 hello-vue-element 的工程
    vue init webpack hello-vue-element
    
    1
    2

    # 安装依赖

    我们需要安装 vue-router、element-ui、sass-loader 和 node-sass 四个插件

    # 进入工程目录
    cd hello-vue-element
    # 安装 vue-router
    npm install vue-router --save-dev --registry=https://registry.npm.taobao.org
    # 安装 element-ui
    npm i element-ui -S --registry=https://registry.npm.taobao.org
    # 安装 SASS 加载器
    npm install sass-loader node-sass --save-dev --registry=https://registry.npm.taobao.org
    
    1
    2
    3
    4
    5
    6
    7
    8
    # 安装依赖
    npm install --registry=https://registry.npm.taobao.org
    
    1
    2

    # 启动项目

    npm run dev
    
    1

    # 运行项目

    在浏览器打开 http://localhost:8080

    # 第一个 ElementUI 页面

    # 目录结构

    在源码目录中创建如下结构:

    • assets:用于存放资源文件
    • components:用于存放 Vue 功能组件
    • views:用于存放 Vue 视图组件
    • router:用于存放 vue-router 配置

    # 创建首页视图

    在 views 目录下创建一个名为 Main.vue 的视图组件;该组件在当前章节无任何作用,主要用于登录后展示登录成功的跳转效果;

    <template>
        <div>
          首页
        </div>
    </template>
    <script>
        export default {
            name: "Main"
        }
    </script>
    <style scoped>
    </style>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    # 创建登录页视图

    在 views 目录下创建一个名为 Login.vue 的视图组件,其中 el-* 的元素为 ElementUI 组件;

    <template>
      <div>
        <el-form ref="loginForm" :model="form" :rules="rules" label-width="80px" class="login-box">
          <h3 class="login-title">欢迎登录</h3>
          <el-form-item label="账号" prop="username">
            <el-input type="text" placeholder="请输入账号" v-model="form.username"/>
          </el-form-item>
          <el-form-item label="密码" prop="password">
            <el-input type="password" placeholder="请输入密码" v-model="form.password"/>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" v-on:click="onSubmit('loginForm')">登录</el-button>
          </el-form-item>
        </el-form>
        <el-dialog
          title="温馨提示"
          :visible.sync="dialogVisible"
          width="30%"
          :before-close="handleClose">
          <span>请输入账号和密码</span>
          <span slot="footer" class="dialog-footer">
            <el-button type="primary" @click="dialogVisible = false">确 定</el-button>
          </span>
        </el-dialog>
      </div>
    </template>
    <script>
      export default {
        name: "Login",
        data() {
          return {
            form: {
              username: '',
              password: ''
            },
            // 表单验证,需要在 el-form-item 元素中增加 prop 属性
            rules: {
              username: [
                {required: true, message: '账号不可为空', trigger: 'blur'}
              ],
              password: [
                {required: true, message: '密码不可为空', trigger: 'blur'}
              ]
            },
            // 对话框显示和隐藏
            dialogVisible: false
          }
        },
        methods: {
          onSubmit(formName) {
            // 为表单绑定验证功能
            this.$refs[formName].validate((valid) => {
              if (valid) {
                // 使用 vue-router 路由到指定页面,该方式称之为编程式导航
                this.$router.push("/main");
              } else {
                this.dialogVisible = true;
                return false;
              }
            });
          }
        }
      }
    </script>
    <style lang="scss" scoped>
      .login-box {
        border: 1px solid #DCDFE6;
        width: 350px;
        margin: 180px auto;
        padding: 35px 35px 15px 35px;
        border-radius: 5px;
        -webkit-border-radius: 5px;
        -moz-border-radius: 5px;
        box-shadow: 0 0 25px #909399;
      }
      .login-title {
        text-align: center;
        margin: 0 auto 40px auto;
        color: #303133;
      }
    </style>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81

    # 创建路由

    在 router 目录下创建一个名为 index.js 的 vue-router 路由配置文件

    import Vue from 'vue'
    import Router from 'vue-router'
    import Login from "../views/Login"
    import Main from '../views/Main'
    Vue.use(Router);
    export default new Router({
      routes: [
        {
          // 登录页
          path: '/login',
          name: 'Login',
          component: Login
        },
        {
          // 首页
          path: '/main',
          name: 'Main',
          component: Main
        }
      ]
    });
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    # 配置路由

    • 修改 main.js 入口代码
    import Vue from 'vue'
    import VueRouter from 'vue-router'
    import router from './router'
    // 导入 ElementUI
    import ElementUI from 'element-ui'
    import 'element-ui/lib/theme-chalk/index.css'
    import App from './App'
    // 安装路由
    Vue.use(VueRouter);
    // 安装 ElementUI
    Vue.use(ElementUI);
    new Vue({
      el: '#app',
      // 启用路由
      router,
      // 启用 ElementUI
      render: h => h(App)
    });
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    • 修改 App.vue 组件代码
    <template>
      <div id="app">
        <router-view/>
      </div>
    </template>
    <script>
      export default {
        name: 'App',
      }
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    # 效果演示

    在浏览器打开 http://localhost:8080/#/login 你会看到如下效果

    img

    # 附:扩展阅读

    # NPM 相关命令说明

    • npm install moduleName:安装模块到项目目录下
    • npm install -g moduleName:-g 的意思是将模块安装到全局,具体安装到磁盘哪个位置,要看 npm config prefix 的位置
    • npm install -save moduleName:--save 的意思是将模块安装到项目目录下,并在 package 文件的 dependencies 节点写入依赖,-S 为该命令的缩写
    • npm install -save-dev moduleName:--save-dev 的意思是将模块安装到项目目录下,并在 package 文件的 devDependencies 节点写入依赖,-D 为该命令的缩写

    # VueRouter 嵌套路由

    # 什么是嵌套路由

    嵌套路由又称子路由,在实际应用中,通常由多层嵌套的组件组合而成。同样地,URL 中各段动态路径也按某种结构对应嵌套的各层组件,例如:

    /user/foo/profile                     /user/foo/posts
    +------------------+                  +-----------------+
    | User             |                  | User            |
    | +--------------+ |                  | +-------------+ |
    | | Profile      | |  +------------>  | | Posts       | |
    | |              | |                  | |             | |
    | +--------------+ |                  | +-------------+ |
    +------------------+                  +-----------------+
    
    1
    2
    3
    4
    5
    6
    7
    8

    # 创建嵌套视图组件

    # 用户信息组件

    在 views/user 目录下创建一个名为 Profile.vue 的视图组件;该组件在当前章节无任何作用,主要用于展示嵌套效果;

    <template>
        <div>
          个人信息
        </div>
    </template>
    <script>
        export default {
            name: "UserProfile"
        }
    </script>
    <style scoped>
    </style>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    # 用户列表组件

    在 views/user 目录下创建一个名为 List.vue 的视图组件;该组件在当前章节无任何作用,主要用于展示嵌套效果;

    <template>
        <div>
          用户列表
        </div>
    </template>
    <script>
        export default {
            name: "UserList"
        }
    </script>
    <style scoped>
    </style>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    # 配置嵌套路由

    修改 router 目录下的 index.js 路由配置文件,代码如下:

    import Vue from 'vue'
    import Router from 'vue-router'
    import Login from "../views/Login"
    import Main from '../views/Main'
    // 用于嵌套的路由组件
    import UserProfile from '../views/user/Profile'
    import UserList from '../views/user/List'
    Vue.use(Router);
    export default new Router({
      routes: [
        {
          // 登录页
          path: '/login',
          name: 'Login',
          component: Login
        },
        {
          // 首页
          path: '/main',
          name: 'Main',
          component: Main,
          // 配置嵌套路由
          children: [
            {path: '/user/profile', component: UserProfile},
            {path: '/user/list', component: UserList},
          ]
        }
      ]
    });
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29

    说明:主要在路由配置中增加了 children 数组配置,用于在该组件下设置嵌套路由

    # 修改首页视图

    接着上一节的代码,我们修改 Main.vue 视图组件,此处使用了 ElementUI 布局容器组件 ,代码如下:

    <template>
        <div>
          <el-container>
            <el-aside width="200px">
              <el-menu :default-openeds="['1']">
                <el-submenu index="1">
                  <template slot="title"><i class="el-icon-caret-right"></i>用户管理</template>
                  <el-menu-item-group>
                    <el-menu-item index="1-1">
                      <router-link to="/user/profile">个人信息</router-link>
                    </el-menu-item>
                    <el-menu-item index="1-2">
                      <router-link to="/user/list">用户列表</router-link>
                    </el-menu-item>
                  </el-menu-item-group>
                </el-submenu>
                <el-submenu index="2">
                  <template slot="title"><i class="el-icon-caret-right"></i>内容管理</template>
                  <el-menu-item-group>
                    <el-menu-item index="2-1">分类管理</el-menu-item>
                    <el-menu-item index="2-2">内容列表</el-menu-item>
                  </el-menu-item-group>
                </el-submenu>
              </el-menu>
            </el-aside>
            <el-container>
              <el-header style="text-align: right; font-size: 12px">
                <el-dropdown>
                  <i class="el-icon-setting" style="margin-right: 15px"></i>
                  <el-dropdown-menu slot="dropdown">
                    <el-dropdown-item>个人信息</el-dropdown-item>
                    <el-dropdown-item>退出登录</el-dropdown-item>
                  </el-dropdown-menu>
                </el-dropdown>
                <span>Lusifer</span>
              </el-header>
              <el-main>
                <router-view />
              </el-main>
            </el-container>
          </el-container>
        </div>
    </template>
    <script>
        export default {
            name: "Main"
        }
    </script>
    <style scoped lang="scss">
      .el-header {
        background-color: #B3C0D1;
        color: #333;
        line-height: 60px;
      }
      .el-aside {
        color: #333;
      }
    </style>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58

    说明:

    • 在 元素中配置了 用于展示嵌套路由
    • 主要使用 个人信息 展示嵌套路由内容

    # 效果演示

    img

    # VueRouter 参数传递与重定向

    # 参数传递

    我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件。例如,我们有一个 User 组件,对于所有 ID 各不相同的用户,都要使用这个组件来渲染。此时我们就需要传递参数了;

    # 使用路径匹配的方式

    • 修改路由配置,主要是在 path 属性中增加了 :id 这样的占位符
    {path: '/user/profile/:id', name:'UserProfile', component: UserProfile}
    
    1
    • router-link 方式传递
    <router-link :to="{name: 'UserProfile', params: {id: 1}}">个人信息</router-link>
    
    1

    注意: 此时我们将 to 改为了 :to,是为了将这一属性当成对象(Model)使用,注意 router-link 中的 name 属性名称 一定要和 路由中的 name 属性名称匹配,因为这样 Vue 才能找到对应的路由路径;

    • 代码方式传递
    this.$router.push({ name: 'UserProfile', params: {id: 1}});
    
    1
    • 在目标组件中使用以下方式接收参数
    {{ $route.params.id }}
    
    1

    # 使用 props 的方式

    • 修改路由配置,主要增加了 props: true 属性
    {path: '/user/profile/:id', name:'UserProfile', component: UserProfile}
    
    1
    • router-link 方式传递
    <router-link :to="{name: 'UserProfile', params: {id: 1}}">个人信息</router-link>
    
    1
    • 代码方式传递
    this.$router.push({ name: 'UserProfile', params: {id: 1}});
    
    1
    • 接收参数,为目标组件增加 props 属性
      export default {    props: ['id'],    name: "UserProfile"  }
    
    1
    • 模板中使用接收参数
    {{ id }}
    
    1

    # 重定向

    重定向的意思大家都明白,但 Vue 中的重定向是作用在路径不同但组件相同的情况下

    # 修改路由配置

    {
          path: '/main',
          name: 'Main',
          component: Main
        },
        {
          path: '/goHome',
          redirect: '/main'
        }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    说明:这里定义了两个路径,一个是 /main ,一个是 /goHome,其中 /goHome 重定向到了 /main 路径,由此可以看出重定向不需要定义组件;

    # 重定向到组件

    设置对应路径即可

    <router-link to="/goHome">回到首页</router-link>
    
    1

    # 带参数的重定向

    • 修改路由配置
     {
          // 首页
          path: '/main/:username',
          name: 'Main',
          component: Main
        },
        {
          path: '/goHome/:username',
          redirect: '/main/:username'
        }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    • 重定向到组件
    <router-link to="/goHome/Lusifer">回到首页</router-link>
    
    1

    # VueRouter 路由模式与 404

    # 路由模式

    路由模式有两种

    • hash:路径带 # 符号,如 http://localhost/#/login
    • history:路径不带 # 符号,如 http://localhost/login

    修改路由配置,代码如下:

    export default new Router({
      mode: 'history',
      routes: [
      ]
    });
    
    1
    2
    3
    4
    5

    # 处理 404

    创建一个名为 NotFound.vue 的视图组件,代码如下:

    <template>
        <div>
          页面不存在,请重试!
        </div>
    </template>
    <script>
        export default {
            name: "NotFount"
        }
    </script>
    <style scoped>
    </style>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    修改路由配置,代码如下:

      {
          path: '*',
          component: NotFound
        }
    
    1
    2
    3
    4

    #

    # VueRouter 路由钩子与异步请求

    # 路由中的钩子函数

    • beforeRouteEnter:在进入路由前执行
    • beforeRouteLeave:在离开路由前执行

    案例代码如下:

    export default {
        props: ['id'],
        name: "UserProfile",
        beforeRouteEnter: (to, from, next) => {
          console.log("准备进入个人信息页");
          next();
        },
        beforeRouteLeave: (to, from, next) => {
          console.log("准备离开个人信息页");
          next();
        }
      }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    参数说明:

    • to:路由将要跳转的路径信息

    • from:路径跳转前的路径信息

    • next
      
      1

      :路由的控制参数

      • next() 跳入下一个页面
      • next('/path') 改变路由的跳转方向,使其跳到另一个路由
      • next(false) 返回原来的页面
      • next((vm)=>{}) 仅在 beforeRouteEnter 中可用,vm 是组件实例

    # 在钩子函数中使用异步请求

    安装 Axios

    npm install axios -s --registry=https://registry.npm.taobao.org
    
    1

    引用 Axios

    import axios from 'axios'
    Vue.prototype.axios = axios;
    
    1
    2

    在 beforeRouteEnter 中进行异步请求,案例代码如下:

    export default {
        props: ['id'],
        name: "UserProfile",
        beforeRouteEnter: (to, from, next) => {
          console.log("准备进入个人信息页");
          // 注意,一定要在 next 中请求,因为该方法调用时 Vue 实例还没有创建,此时无法获取到 this 对象,在这里使用官方提供的回调函数拿到当前实例
          next(vm => {
            vm.getData();
          });
        },
        beforeRouteLeave: (to, from, next) => {
          console.log("准备离开个人信息页");
          next();
        },
        methods: {
          getData: function () {
            this.axios({
              method: 'get',
              url: 'http://localhost:8080/static/data.json'
            }).then(function (repos) {
              console.log(repos);
            }).catch(function (error) {
              console.log(error);
            });
          }
        }
      }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27

    # Vuex 状态管理

    # 概述

    Vuex 是一个专为 Vue.js 应用程序开发的 状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

    # 安装

    • 在项目根目录执行如下命令来安装 Vuex
    npm install vuex --save --registry=https://registry.npm.taobao.org
    
    1
    • 修改 main.js 文件,导入 Vuex,关键代码如下:
    import Vuex from 'vuex'
    Vue.use(Vuex);
    
    1
    2

    # 状态管理

    我们利用路由钩子 beforeEach 来判断用户是否登录,期间会用到 sessionStorage 存储功能

    # 修改 Login.vue

    在表单验证成功方法内增加如下代码:

    // 设置用户登录成功
    sessionStorage.setItem('isLogin', 'true');
    
    1
    2

    # 修改 main.js

    利用路由钩子 beforeEach 方法判断用户是否成功登录,关键代码如下:

    // 在跳转前执行
    router.beforeEach((to, form, next) => {
      // 获取用户登录状态
      let isLogin = sessionStorage.getItem('isLogin');
      // 注销
      if (to.path == '/logout') {
        // 清空
        sessionStorage.clear();
        // 跳转到登录
        next({path: '/login'});
      }
      // 如果请求的是登录页
      else if (to.path == '/login') {
        if (isLogin != null) {
          // 跳转到首页
          next({path: '/main'});
        }
      }
      // 如果为非登录状态
      else if (isLogin == null) {
        // 跳转到登录页
        next({path: '/login'});
      }
      // 下一个路由
      next();
    });
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26

    # 配置 Vuex

    • 创建 Vuex 配置文件

    在 src 目录下创建一个名为 store 的目录并新建一个名为 index.js 文件用来配置 Vuex,代码如下:

    import Vue from 'vue'
    import Vuex from 'vuex'
    Vue.use(Vuex);
    // 全局 state 对象,用于保存所有组件的公共数据
    const state = {
      // 定义一个 user 对象
      // 在组件中是通过 this.$store.state.user 来获取
      user: {
        username: ''
      }
    };
    // 实时监听 state 值的最新状态,注意这里的 getters 可以理解为计算属性
    const getters = {
      // 在组件中是通过 this.$store.getters.getUser 来获取
      getUser(state) {
        return state.user;
      }
    };
    // 定义改变 state 初始值的方法,这里是唯一可以改变 state 的地方,缺点是只能同步执行
    const mutations = {
      // 在组件中是通过 this.$store.commit('updateUser', user); 方法来调用 mutations
      updateUser(state, user) {
        state.user = user;
      }
    };
    // 定义触发 mutations 里函数的方法,可以异步执行 mutations 里的函数
    const actions = {
      // 在组件中是通过 this.$store.dispatch('asyncUpdateUser', user); 来调用 actions
      asyncUpdateUser(context, user) {
        context.commit('updateUser', user);
      }
    };
    export default new Vuex.Store({
      state,
      getters,
      mutations,
      actions
    });
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    • 修改 main.js 增加刚才配置的 store/index.js,关键代码如下:
    import Vue from 'vue'
    import Vuex from 'vuex'
    import store from './store'
    Vue.use(Vuex);
    new Vue({
      el: '#app',
      store
    });
    
    1
    2
    3
    4
    5
    6
    7
    8

    # 浏览器刷新 Vuex 数据消失

    # 问题描述

    Vuex 的状态存储是响应式的,当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。但是有一个问题就是:vuex 的存储的数据只是在页面的中,相当于我们定义的全局变量,刷新之后,里边的数据就会恢复到初始化状态。但是这个情况有时候并不是我们所希望的。

    # 解决方案

    监听页面是否刷新,如果页面刷新了,将 state 对象存入到 sessionStorage 中。页面打开之后,判断 sessionStorage 中是否存在 state 对象,如果存在,则说明页面是被刷新过的,将 sessionStorage 中存的数据取出来给 vuex 中的 state 赋值。如果不存在,说明是第一次打开,则取 vuex 中定义的 state 初始值。

    # 修改代码

    在 App.vue 中增加监听刷新事件

     export default {
        name: 'App',
        mounted() {
          window.addEventListener('unload', this.saveState);
        },
        methods: {
          saveState() {
            sessionStorage.setItem('state', JSON.stringify(this.$store.state));
          }
        }
      }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    修改 store/index.js 中的 state

    const state = sessionStorage.getItem('state') ? JSON.parse(sessionStorage.getItem('state')) : {
      user: {
        username: ''
      }
    };
    
    1
    2
    3
    4
    5

    # 模块化

    由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割

    # 创建 user 模块

    在 store 目录下创建一个名为 modules 的目录并创建一个名为 user.js 的文件,代码如下:

    const user = {
      // 因为模块化了,所以解决刷新问题的代码需要改造一下
      state: sessionStorage.getItem('userState') ? JSON.parse(sessionStorage.getItem('userState')) : {
        user: {
          username: ''
        }
      },
      getters: {
        getUser(state) {
          return state.user;
        }
      },
      mutations: {
        updateUser(state, user) {
          state.user = user;
        }
      },
      actions: {
        asyncUpdateUser(context, user) {
          context.commit('updateUser', user);
        }
      }
    };
    export default user;
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24

    # 修改 store/index.js

    import Vue from 'vue'
    import Vuex from 'vuex'
    import user from './modules/user'
    Vue.use(Vuex);
    export default new Vuex.Store({
      modules: {
        // this.$store.state.user
        user
      }
    });
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    备注: 由于组件中使用的是 getters 和 actions 处理,所以调用代码不变

    # 修改 App.vue

     export default {
        name: 'App',
        mounted() {
          window.addEventListener('unload', this.saveState);
        },
        methods: {
          saveState() {
            // 模块化后,调用 state 的代码修改为 this.$store.state.user
            sessionStorage.setItem('userState', JSON.stringify(this.$store.state.user));
          }
        }
      }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12