Vue基础
- vue-devtools调试工具
- Vue简介
- Vue的指令
- 品牌列表案例
- Vue过滤器
- Vue侦听器
- Vue计算属性
- axios
- Vue CLI中axios的使用
- Vue CLI
- Vue组件
- Vue组件的实例对象
- Vue生命周期
- 组件之间的数据共享
- Ref引用
- 动态组件
- 插槽
- 自定义指令
- 路由
- Vant组件库
- VueX使用
- /---- Vue3.x ----/
- Vue3.x和Vue2.x版本的对比
- Vue3.x新特性介绍
- Vite
- Vue3.x组合式API
- /---- Vue3.x+TS ----/
- Vue+TS
- 模板语法&Vue指令
- ref全家桶
- reacive全家桶
- to系列全家桶(toRef/toRefs/toRaw)
- computed计算属性
- watch侦听器
- watchEffect高级侦听器
- 认识组件和生命周期
- 父子组件传参
- 全局组件、局部组件和递归组件
- 动态组件
- 插槽
- 异步组件&代码分包&suspense
- Teleport传送组件
- keep-alive缓存组件
- transition动画组件
- transition-group过渡列表
- 依赖注入(Provide/Inject)
- 兄弟组件传参(Mitt)
- TSX
- unplugin-auto-import/vite无须import
- 自定义指令directive
- 自定义Hooks(VueUse开源库)
- 自定义全局函数和变量
- 自定义Vue3插件
- UI库ElementUI【模板】
- UI库AntDesign【模板】
- 详解Scoped和样式穿透
- CSS完整新特性
- Vue3集成Tailwind CSS *
- Pinia(适用Vue2.x和Vue3.x)
- Router
参考
- 参考:Vue3
- 参考:黑马程序员Vue全套视频教程,从vue2.0到vue3.0一套全覆盖,前端必会的框架教程 哔哩哔哩 bilibili *
- 参考:https://github.com/24kcs/vue3_study
- 参考:B站讲的最好的Vue3+TS从入门到精通
- 参考:https://cn.vuejs.org/
- 参考:Vue3 + vite + Ts + pinia + 实战 + 源码 +全栈 哔哩哔哩 bilibili *
- 参考:Vue.js教程 | 菜鸟教程
- 参考:Vue.js技术揭秘
- 参考:Vue-Cli(客户端)
- 参考:创建一个Vue项目、Vue-Cli创建一个Router项目
- 参考:https://codesandbox.io
- 参考:https://codepen.io/
API参考
插件参考
- 参考:TroisJS:ThreeJS + VueJS 3 + ViteJS
- 参考:Vue Tour:这是一个轻巧、简单且可自定义的新手指引插件
- 参考:Vue-Toastification
- 参考:VeeValidate:Vue.js的表单验证
- 参考:Vue.Draggable:Vue.Draggable是一款基于Sortable.js实现的vue拖拽插件
- 参考:Vue Grid Layout -️ 适用Vue.js的栅格布局系统
- 参考:Home | vue-chartjs:使用Chart.js和Vue.js的简单漂亮的图表
- 参考:Vue Meta:让你更优雅的管理头部标签
- 参考:VuePress: Vue驱动。享受Vue + webpack 的开发体验,可以在Markdown中使用Vue组件,又可以使用Vue来开发自定义主题
- 参考:vuex-persistedstate - npm:应用程序开发的状态管理模式
- 参考:Nuxt.js - Vue.js 通用应用框架| Nuxt.js 中文网
组件库参考
- 参考:Vant组件库:轻量、可靠的移动端 Vue 组件库
- 参考:Vue Tables-2:提供功能齐全的工具及以便Vue创建漂亮而实用的数据表格
- 参考:Handsontable:页面端的表格交互插件,以及加载显示表格内容
- 参考:Ag-Grid Vue:基于Vue的数据表格组件
- 参考:Vue-Easytable:强大的Vue表格组件之一
- 参考:Vue-Good-Table:基于Vue的数据表格组件,简单干净,具有排序、列过滤、分页等基本功能
- 参考:Vue Toastification:提供轻巧、简单和漂亮的吐司提示,支持Vue 3
- 参考:Vue Toasted:Vue最好的toast(提示)插件之一
- 参考:Vue Notifaction
- 参考:Vue Awesome Notifaction:轻量级,完全可自定义
- 参考:Vue Wait:用于Vue、VueX等应用的复杂装载器和进度管理插件
- 参考:Vue Content Loader:基于Vue.js的SVG占位符加载,可自定义SVG组件
- 参考:Epic Spinners:纯CSS打造的网页Loading效果
- 参考:Vue Radial Progress:径向进度条效果的加载器组件
- 参考:Vue Feather Icons:开源图标库
- 参考:Vue Awesome:流行的图标字体库
- 参考:Vue Material Design Icons:单文件组件的SVG Material Design图标集合
- 参考:Vue Apexcharts:现代的JS图表库
- 参考:Vue-ECharts:基于Vue2.0和EChart封装的图标组件
- 参考:Vue Timer Hook:计时器模块
调试参考
- 参考:代码管理
- 参考:iconfont(图标管理)
日志
- 2022年03月08日19:55:26 初始版本
- 2022年06月14日10:18:04 提取【模板】内容
内部模板参考
- [新建Vue CLI工程(基础工程)【模板】](# 新建Vue CLI工程(基础工程)【模板】)
- [新建Vue CLI工程(带路由工程)【模板】](# 新建Vue CLI工程(带路由工程)【模板】)
- [Vant组件库基本使用【模板】](# Vant组件库基本使用【模板】)
- [Vite创建Vue3.x项目【模板】](# Vite创建Vue3.x项目【模板】)
- [Vite安装和配置less【模板】](# Vite安装和配置less【模板】)
- [Vue CLI创建Vue3.x项目【模板】](# Vue CLI创建Vue3.x项目【模板】)
- [Vite创建Vue3.x项目【模板】](# Vite创建Vue3.x项目【模板】)
- [UI库ElementUI【模板】](# UI库ElementUI【模板】)
- [UI库AntDesign【模板】](# UI库AntDesign【模板】)
- [Vite安装和配置Router【模板】](# Vite安装和配置Router【模板】)
vue-devtools调试工具
安装完成后需要进行配置。
- “管理扩展程序 > Vue.js devtools”,“有权访问的网站”配置为“在所有网站上”。
- “管理扩展程序 > Vue.js devtools”,“允许访问文件网址”配置为开启。
Vue简介
什么是Vue
- 构建用户界面
- 用 vue 往 html 页面中填充数据,非常的方便
- 框架
- 框架是一套现成的解决方案,程序员只能遵守框架的规范,去编写自己的业务功能!
- 要学习 vue,就是在学习 vue 框架中规定的用法!
vue全家桶
- vue(核心库)vu
- vue-router(路由方案)
- vuex(状态管理方案)
- vue组件库(快速搭建页面UI效果的方案)
以及辅助 vue 项目开发的一系列工具
- vue-cli(npm 全局包:一键生成工程化的 vue 项目 - 基于 webpack、大而全)
- vite(npm 全局包:一键生成工程化的 vue 项目 - 小而巧)
- vue-devtools(浏览器插件:辅助调试的工具)
- vetur(vscode 插件:提供语法高亮和智能提示)
模块化、组件化、规范化、自动化
Vue的两个特性
-
数据驱动视图:在使用了 vue 的页面中,vue 会监听数据的变化,从而自动重新渲染页面的结构。
- 数据的变化会驱动视图自动更新。
- 好处:程序员只管把数据维护好,那么页面结构会被 vue 自动渲染出来!
-
双向数据绑定: 在填写表单时,双向数据绑定可以辅助开发者在不操作 DOM 的前提下,自动把用户填写的内容同步到数据源 中。
在网页中,form 表单负责采集数据,Ajax 负责提交数据。
- js 数据的变化,会被自动渲染到页面上
- 页面上表单采集的数据发生变化的时候,会被 vue 自动获取到,并更新到JS数据中
注意:数据驱动视图和双向数据绑定的底层原理是 MVVM(Mode 数据源、View 视图、ViewModel 就是 vue 的实例)
MVVM
MVVM 是 vue 实现数据驱动视图和双向数据绑定的核心原理。MVVM 指的是 Model、View 和 ViewModel,它把每个 HTML 页面都拆分成了这三个部分。
在 MVVM 概念中:
- Model 表示当前页面渲染时所依赖的数据源。
- View 表示当前页面所渲染的 DOM 结构。
- ViewModel 表示 vue 的实例,它是 MVVM 的核心。
MVVM 的工作原理
ViewModel 作为 MVVM 的核心,是它把当前页面的数据源(Model)和页面的结构(View)连接在了一起。
- 当数据源发生变化时,会被 ViewModel 监听到,VM 会根据最新的数据源自动更新页面的结构
- 当表单元素的值发生变化时,也会被 VM 监听到,VM 会把变化过后最新的值自动同步到 Model 数据源中
HTML中引入Vue
<!-- Vue.js 2.X -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- Vue.js 3.X -->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
Vue的基本使用
- 导入 vue.js 的 script 脚本文件
- 在页面中声明一个将要被 vue 所控制的 DOM 区域
- 创建 vm 实例对象(vue 实例对象)
在VS Code中新增一个自定义模板“template-vue2.x”:
-
Ctrl + Alt + P调出控制台,输入“snippets”。
-
在弹出的选项中选择“Preferences:Configure User Snippets”。
-
选择“新建全局代码片段文件”。
-
输入名称“template-vue2.x”。
-
将以下代码形成一个自定义模板。
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>My test page</title> <!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <!-- 希望Vue能够控制下面的这个div,帮我们再把数据填充到div内部 --> <div id="app"> <p>{{ message1 }}</p> <p>{{ message2 }}</p> </div> <script> // 创建Vue实例对象 new Vue({ // el属性是固定的写法,表示当前VM实例要控制页面的哪个区域,接收的值是一个选择器 el: "#app", // data对象为需要渲染到页面的数据 data: { message1: "Hello Vue.js来了!", message2: "This is my first project!", }, }); </script> </body> </html>
-
模板中填写内容。
{ "Print to console": { "prefix": "tepmlate-Vue2.x", "body": [ "<!DOCTYPE html>", "<html>", " <head>", " <meta charset='utf-8' />", " <title>My test page</title>", " <!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->", " <script src='https://cdn.jsdelivr.net/npm/vue/dist/vue.js'></script>", " </head>", "", " <body>", " <!-- 希望Vue能够控制下面的这个div,帮我们再把数据填充到div内部 -->", " <div id='app'>", " <p>{{ message1 }}</p>", " <p>{{ message2 }}</p>", " </div>", "", " <script>", " // 创建Vue实例对象", " new Vue({", " // el属性是固定的写法,表示当前VM实例要控制页面的哪个区域,接收的值是一个选择器", " el: '#app',", " // data对象为需要渲染到页面的数据", " data: {", " message1: 'Hello Vue.js来了!',", " message2: 'This is my first project!',", " },", " });", " </script>", " </body>", "</html>", "", ], "description": "A Vue2.x file template.", } }
解析:
- data指向的对象,就是Model数据源。
- el指向的选择器,就是View视图区域。
- new Vue()构造函数,得到的vm实例对象,就是ViewModel。
Vue的指令
指令(Directives)是 vue 为开发者提供的模板语法,用于辅助开发者渲染页面的基本结构。
vue 中的指令按照不同的用途可以分为如下 6 大类:
- 内容渲染指令
- 属性绑定指令
- 事件绑定指令
- 双向绑定指令
- 条件渲染指令
- 列表渲染指令
注意:指令是 vue 开发中最基础、最常用、最简单的知识点。
内容渲染指令
内容渲染指令用来辅助开发者渲染 DOM 元素的文本内容。常用的内容渲染指令有如下 3 个:
- v-text
- {{ }}
- v-html
说明:
v-text
指令的缺点:会覆盖元素内部原有的内容!{{ }}
插值表达式:在实际开发中用的最多,只是内容的占位符,不会覆盖原有的内容!v-html
指令的作用:可以把带有标签的字符串,渲染成真正的 HTML 内容!
示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
<div id="app">
<p v-text="username"></p>
<p v-text="gender">性别:</p>
<hr>
<p>姓名:{{ username }}</p>
<p>性别:{{ gender }}</p>
<hr>
<div v-text="info"></div>
<div>{{ info }}</div>
<div v-html="info"></div>
</div>
<!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 -->
<script src="./lib/vue-2.6.12.js"></script>
<!-- 2. 创建 Vue 的实例对象 -->
<script>
// 创建 Vue 的实例对象
const vm = new Vue({
// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
el: '#app',
// data 对象就是要渲染到页面上的数据
data: {
username: 'zhangsan',
gender: '女',
info: '<h4 style="color: red; font-weight: bold;">欢迎大家来学习 vue.js</h4>'
}
})
</script>
</body>
</html>
v-text
<!-- 把username对应的值,渲染到第一个p标签中 -->
<p v-text="username"></p>
<!-- 把gender对应的值,渲染到第二个p标签中 -->
<!-- 注意:第二个p标签中,默认的文本“性别”会被gender的值覆盖掉 -->
<p v-text="gender">性别:</p>
注意:v-text 指令会覆盖元素内默认的值。
{{ }} 语法
vue 提供的 {{ }} 语法,专门用来解决 v-text 会覆盖默认文本内容的问题。这种 {{ }} 语法的专业名称是插值表达式(英文名为:Mustache)。
<!-- 使用{{}}插值表达式,将对应的值渲染到元素的内容节点中 -->
<!-- 同时保留元素自身的默认值 -->
<p>姓名:{{username}}</p>
<p>性别:{{gender}}</p>
注意:相对于 v-text 指令来说,插值表达式在开发中更常用一些!因为它不会覆盖元素中默认的文本内容。
v-html
v-text 指令和插值表达式只能渲染纯文本内容。如果要把包含 HTML 标签的字符串渲染为页面的 HTML 元素, 则需要用到 v-html 这个指令:
<!-- 假设data中定义了名为info的数据,数据的值为包含HTML标签的字符串 -->
<!-- '<h4 style="color: red; font-weight: bold;">欢迎大家来学习 vue.js</h4>' -->
<div v-html="info"></div>
属性绑定指令(v-bind/:)
如果需要为元素的属性动态绑定属性值,则需要用到 v-bind 属性绑定指令。
注意:插值表达式只能用在元素的内容节点中,不能用在元素的属性节点中!
-
在 vue 中,可以使用
v-bind:
指令,为元素的属性动态绑定值; -
简写是英文的
:
-
在使用 v-bind 属性绑定期间,如果绑定内容需要进行动态拼接,则字符串的外面应该包裹单引号,例如:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>My test page</title> <!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 --> <div id="app"> <input type="text" :placeholder="tips" /> <hr /> <!-- vue 规定 v-bind: 指令可以简写为 : --> <img :src="photo" alt="" style="width: 150px" /> <hr /> <div>1 + 2 的结果是:{{ 1 + 2 }}</div> <!-- 1 + 2 的结果是:3 -->> <div> {{ tips }} 反转的结果是:{{ tips.split('').reverse().join('') }} <!-- 请输入用户名 反转的结果是:名户用入输请 -->> </div> <div :title="'box' + index">这是一个 div</div> <a :href="url">百度一下</a> </div> <!-- 2. 创建 Vue 的实例对象 --> <script> // 创建 Vue 的实例对象 const vm = new Vue({ // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器 el: "#app", // data 对象就是要渲染到页面上的数据 data: { tips: "请输入用户名", photo: "https://cn.vuejs.org/images/logo.svg", index: 3, url: "www.baidu.com", }, }); </script> </body> </html>
使用 Javascript 表达式
在 vue 提供的模板渲染语法中,除了支持绑定简单的数据值之外,还支持 Javascript 表达式的运算,例如:
{{ number + 1 }}
{{ message.split('').reverse().join('') }}
{{ ok ? 'YES' : 'NO' }}
<div v-bind:id>"'list-' + id"</div>
Class与Style绑定
绑定class
- 直接绑定方式
- 绑定数组方式
- 绑定对象方式
举例
-
方式一:直接绑定方式
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta http-equiv="Content-Type" content="text/html; charset=gb2312" /> <meta http-equiv="Content-Language" content="zh-cn" /> <title>My test page</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <style> .class-active { color: red; } .class-font-size { font-size: 75px; } </style> </head> <body> <div id="app"> <div v-bind:class="{'class-active':isActive, 'class-font-size':isFontSet}" > 我是一些文字的内容 </div> </div> <script> var app = new Vue({ el: "#app", data: { isActive: true, isFontSet: false, }, }); </script> </body> </html>
-
方式二(1):绑定数组方式
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta http-equiv="Content-Type" content="text/html; charset=gb2312" /> <meta http-equiv="Content-Language" content="zh-cn" /> <title>My test page</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <style> .class-active { color: red; } .class-font-size { font-size: 75px; } </style> </head> <body> <div id="app"> <div v-bind:class="className">我是一些文字的内容</div> </div> <script> var app = new Vue({ el: "#app", data: { className: ["class-active", "class-font-size"], }, }); </script> </body> </html>
-
方式二(2):绑定数组方式
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta http-equiv="Content-Type" content="text/html; charset=gb2312" /> <meta http-equiv="Content-Language" content="zh-cn" /> <title>My test page</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <style> .class-active { color: red; } .class-font-size { font-size: 75px; } </style> </head> <body> <div id="app"> <div v-bind:class="[classActive, classFontSize]">我是一些文字的内容</div> <!-- classActive 和 classFontSize 都是变量 --> </div> <script> var app = new Vue({ el: "#app", data: { classActive: "class-active", classFontSize: "class-font-size", }, }); </script> </body> </html>
-
方式三:绑定对象方式
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta http-equiv="Content-Type" content="text/html; charset=gb2312" /> <meta http-equiv="Content-Language" content="zh-cn" /> <title>My test page</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <style> .class-active { color: red; } .class-font-size { font-size: 75px; } </style> </head> <body> <div id="app"> <div v-bind:class="classObject">我是一些文字的内容</div> </div> <script> var app = new Vue({ el: "#app", data: { classObject: { "class-active": true, "class-font-size": true, }, }, }); </script> </body> </html>
绑定内联样式
-
方式一(1):对象方式
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta http-equiv="Content-Type" content="text/html; charset=gb2312" /> <meta http-equiv="Content-Language" content="zh-cn" /> <title>My test page</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <div v-bind:style="{color: theColor, 'font-size': fontSize + 'px'}"> 我是一些文字的内容 </div> </div> <script> var app = new Vue({ el: "#app", data: { theColor: "red", fontSize: 40, }, }); </script> </body> </html>
-
方式一(2):对象方式
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>My test page</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <div :style="styleObj" @click="handleDivClick">我是一些文字的内容</div> </div> <script> var app = new Vue({ el: "#app", data: { styleObj: { color: "black", "font-size": "35px", }, }, methods: { handleDivClick: function () { this.styleObj.color = this.styleObj.color === "black" ? "red" : "black"; }, }, }); </script> </body> </html>
-
方式二:数组方式
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>My test page</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <div :style="[styleObj, {'font-size': '35px'}]" @click="handleDivClick"> 我是一些文字的内容 </div> </div> <script> var app = new Vue({ el: "#app", data: { styleObj: { color: "black", }, }, methods: { handleDivClick: function () { this.styleObj.color = this.styleObj.color === "black" ? "red" : "black"; }, }, }); </script> </body> </html>
事件绑定指令(v-on/@)
vue 提供了 v-on 事件绑定指令,用来辅助程序员为 DOM 元素绑定事件监听。
通过 v-on 绑定的事件处理函数,需要在 methods 节点中进行声明。
由于 v-on 指令在开发中使用频率非常高,因此,vue 官方为其提供了简写形式(简写为英文的 @ )。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>My test page</title>
<!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
<div id="app">
<p>count 的值是:{{ count }}</p>
<!-- 在绑定事件处理函数的时候,可以使用 () 传递参数 -->
<!-- v-on: 指令可以被简写为 @ -->
<button @click="add(1)">+1</button>
<button @click="sub">-1</button>
</div>
<!-- 2. 创建 Vue 的实例对象 -->
<script>
// 创建 Vue 的实例对象
const vm = new Vue({
// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
el: "#app",
// data 对象就是要渲染到页面上的数据
data: {
count: 0,
},
// methods 的作用,就是定义事件的处理函数
methods: {
add(n) {
// 在 methods 处理函数中,this 就是 new 出来的 vm 实例对象
// console.log(vm === this)
console.log(vm);
// vm.count += 1
this.count += n;
},
sub() {
// console.log('触发了 sub 处理函数')
this.count -= 1;
},
},
});
</script>
</body>
</html>
注意:原生 DOM 对象有 onclick、oninput、onkeyup 等原生事件,替换为 vue 的事件绑定形式后, 分别为:v-on:click、v-on:input、v-on:keyup
事件参数对象
在原生的 DOM 事件绑定中,可以在事件处理函数的形参处,接收事件参数对象 event。
同理,在 v-on 指令 (简写为 @ )所绑定的事件处理函数中,同样可以接收到事件参数对象 event,
绑定事件并传参
在使用 v-on 指令绑定事件时,可以使用 ( ) 进行传参。
事件对象$event
$event 是 vue 提供的特殊变量,用来表示原生的事件参数对象 event。$event 可以解决事件参数对象 event 被覆盖的问题。
$event
的应用场景:如果默认的事件对象 e 被覆盖了,则可以手动传递一个$event。
需求:单机button时候,count数值+1,当count数值为偶数时候,改变button的背景颜色。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>My test page</title>
<!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
<div id="app">
<p>count 的值是:{{ count }}</p>
<!-- 在绑定事件处理函数的时候,可以使用 () 传递参数 -->
<!-- v-on: 指令可以被简写为 @ -->
<!-- vue 提供了内置变量,名字叫做 $event,它就是原生 DOM 的事件对象 e -->
<button @click="add($event, 1)">+1</button>
<!-- 未传递参数时,默认接收到参数e -->
<button @click="sub">-1</button>
</div>
<!-- 2. 创建 Vue 的实例对象 -->
<script>
// 创建 Vue 的实例对象
const vm = new Vue({
// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
el: "#app",
// data 对象就是要渲染到页面上的数据
data: {
count: 0,
},
// methods 的作用,就是定义事件的处理函数
methods: {
add(e, n) {
// 在 methods 处理函数中,this 就是 new 出来的 vm 实例对象
// console.log(vm === this)
console.log(vm);
// vm.count += 1;
this.count += n;
e.target.style.backgroundColor = this.count % 2 === 0 ? "blue" : "";
},
sub(e) {
// console.log('触发了 sub 处理函数')
console.log(e);
this.count -= 1;
e.target.style.backgroundColor = this.count % 2 === 0 ? "red" : "";
},
},
});
</script>
</body>
</html>
事件修饰符
在事件处理函数中调用 event.preventDefault() 或 event.stopPropagation() 是非常常见的需求。
因此, vue 提供了事件修饰符的概念,来辅助程序员更方便的对事件的触发进行控制。
常用的 5 个事件修饰符如下:
事件修饰符 | 说明 |
---|---|
.prevent | 阻止默认行为(例如:阻止 a 连接的跳转、阻止表单的提交等) |
.stop | 阻止事件冒泡 |
.capture | 以捕获模式触发当前的事件处理函数 |
.once | 绑定的事件只触发1次 |
.self | 只有在 event.target 是当前元素自身时触发事件处理函数 |
语法格式如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>My test page</title>
<!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
<div id="app">
<a href="https://www.baidu.com" @click.prevent="show">跳转到百度首页</a>
<hr />
<div
style="
height: 150px;
background-color: orange;
padding-left: 100px;
line-height: 150px;
"
@click="divHandler"
>
<button @click.stop="btnHandler">按钮</button>
</div>
</div>
<!-- 2. 创建 Vue 的实例对象 -->
<script>
// 创建 Vue 的实例对象
const vm = new Vue({
// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
el: "#app",
// data 对象就是要渲染到页面上的数据
data: {},
// methods 的作用,就是定义事件的处理函数
methods: {
show() {
console.log("单击了a链接");
},
divHandler() {
console.log("divHandler");
},
btnHandler() {
console.log("btnHandler");
},
},
});
</script>
</body>
</html>
按键修饰符
在监听键盘事件时,我们经常需要判断详细的按键。此时,可以为键盘相关的事件添加按键修饰符,例如:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>My test page</title>
<!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
<div id="app">
<input type="text" @keyup.esc="clearInput" @keyup.enter="commitAjax">
</div>
<!-- 2. 创建 Vue 的实例对象 -->
<script>
// 创建 Vue 的实例对象
const vm = new Vue({
// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
el: "#app",
// data 对象就是要渲染到页面上的数据
data: {},
// methods 的作用,就是定义事件的处理函数
methods: {
clearInput(e) {
console.log('触发了clearInput方法');
e.target.value = '';
},
commitAjax() {
console.log('触发了commitAjax方法');
},
},
});
</script>
</body>
</html>
示例2(按键修饰符、系统修饰健、鼠标按钮修饰符)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>My test page</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<!-- 1、按键修饰符 -->
<!-- Vue 允许为 v-on 在监听键盘事件时添加按键修饰符 -->
<!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` -->
<input v-on:keyup.enter="submit1">
<!-- 可以直接将 KeyboardEvent.key 暴露的任意有效按键名转换为 kebab-case 来作为修饰符 -->
<!-- 示例中,处理函数只会在 $event.key 等于 PageDown 时被调用 -->
<input v-on:keyup.page-down="onPageDown">
<!-- **按键码,keyCode 的事件用法已经被废弃了并可能不会被最新的浏览器支持** -->
<input v-on:keyup.13="submit2">
<!-- 按键码 -->
<!-- .enter
.tab
.delete (捕获“删除”和“退格”键)
.esc
.space
.up
.down
.left
.right -->
<!-- 2、系统修饰键 -->
<!-- 2.1.0 新增 -->
<!-- .ctrl
.alt
.shift
.meta -->
<!-- Alt + C -->
<input @keyup.alt.67="clear">
<!-- Ctrl + Click -->
<div @click.ctrl="doSomething">Do something</div>
<!-- 请注意修饰键与常规按键不同,在和 keyup 事件一起用时,事件触发时修饰键必须处于按下状态。
换句话说,只有在按住 ctrl 的情况下释放其它按键,才能触发 keyup.ctrl。
而单单释放 ctrl 也不会触发事件。如果你想要这样的行为,请为 ctrl 换用 keyCode:keyup.17 -->
<!-- 2.5.0 新增 -->
<!-- .exact 修饰符允许你控制由精确的系统修饰符组合触发的事件。 -->
<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button @click.ctrl="onClick1">A</button>
<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>
<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button @click.exact="onClick2">A</button>
<!-- 3、鼠标按钮修饰符 -->
<!-- 2.2.0 新增 -->
<!-- .left
.right
.middle -->
<button @click.left="onClickLeft">Left</button>
<button @click.right="onClickRight">Right</button>
<button @click.middle="onClickMiddle">Middle</button>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
name: 'Vue.js',
},
computed: {
},
methods: {
submit1: function () {
alert("submit1");
},
onPageDown: function () {
alert("onPageDown");
},
submit2: function () {
alert("submit2");
},
clear: function () {
alert("clear");
},
doSomething: function () {
alert("doSomething");
},
onClick1: function () {
alert("onClick1");
},
onCtrlClick: function () {
alert("onCtrlClick");
},
onClick2: function () {
alert("onClick2");
},
onClickLeft: function () {
alert("onClickLeft");
},
onClickRight: function () {
alert("onClickRight");
},
onClickMiddle: function () {
alert("onClickMiddle");
},
}
})
</script>
</body>
</html>
双向绑定指令v-model
vue 提供了 v-model 双向数据绑定指令,用来辅助开发者在不操作 DOM 的前提下,快速获取表单的数据。
v-model其实是一个语法糖,通过props和emit组合而成。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>My test page</title>
<!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
<div id="app">
<p>用户的名字是:{{ username }}</p>
<input type="text" v-model="username" />
<hr />
<input type="text" :value="username" />
<hr />
<p>选中的城市是{{ city }}</p>
<select v-model="city">
<option value="">请选择城市</option>
<option value="1">北京</option>
<option value="2">上海</option>
<option value="3">广州</option>
</select>
</div>
<!-- 2. 创建 Vue 的实例对象 -->
<script>
// 创建 Vue 的实例对象
const vm = new Vue({
// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
el: "#app",
// data 对象就是要渲染到页面上的数据
data: {
username: "ZhangSan",
city: "2",
},
});
</script>
</body>
</html>
v-model指令的修饰符
为了方便对用户输入的内容进行处理,vue 为 v-model 指令提供了 3 个修饰符,分别是:
修饰符 | 作用 | 示例 |
---|---|---|
.number | 自动将用户的输入值转为数值类型。 | <input v-model.number="age" /> |
.trim | 自动过滤用户输入的首尾空白字符。 | <input v-model.trim="msg" /> |
.lazy | 在“change”时而非“input”时更新,例如在输入框中的行为初始不会同步到数据源,直到失去鼠标焦点,才会把数据源的数据同步到数据源。 | <input v-model.lazy="msg" /> |
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>My test page</title>
<!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
<div id="app">
<input type="text" v-model.number="n1" /> +
<input type="text" v-model.number="n2" /> =
<span>{{ n1 + n2 }}</span>
<hr />
<input type="text" v-model.trim="username" />
<button @click="getUserName">获取用户名</button>
<hr />
<input type="text" v-model.lazy="username">
</div>
<!-- 2. 创建 Vue 的实例对象 -->
<script>
// 创建 Vue 的实例对象
const vm = new Vue({
// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
el: "#app",
// data 对象就是要渲染到页面上的数据
data: {
username: "ZhangSan",
n1: 1,
n2: 2,
},
methods: {
getUserName() {
console.log("获取到的用户名为:'" + this.username + "'");
},
},
});
</script>
</body>
</html>
条件渲染指令
条件渲染指令用来辅助开发者按需控制 DOM 的显示与隐藏。
条件渲染指令有如下两个,分别是:
- v-if
- v-show
v-if 和 v-show 的区别
实现原理不同:
- v-if 指令会动态地创建或移除 DOM 元素,从而控制元素在页面上的显示与隐藏;
- v-show 指令会动态为元素添加或移除 style="display: none;" 样式,从而控制元素的显示与隐藏;
性能消耗不同:
v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此:
- 如果需要非常频繁地切换,则使用 v-show 较好
- 如果在运行时条件很少改变,则使用 v-if 较好
v-else
v-if 可以单独使用,或配合 v-else 指令一起使用:
注意:v-else 指令必须配合 v-if 指令一起使用,否则它将不会被识别!
v-else-if
v-else-if 指令,顾名思义,充当 v-if 的“else-if 块”,可以连续使用:
注意:v-else-if 指令必须配合 v-if 指令一起使用,否则它将不会被识别!
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>My test page</title>
<!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
<div id="app">
<button @click="toggleFlagValue">切换flag的值</button>
<p v-if="flag">这是被 v-if 控制的元素</p>
<p v-show="flag">这是被 v-show 控制的元素</p>
<hr />
<div v-if="type === 'A'">优秀</div>
<div v-else-if="type === 'B'">良好</div>
<div v-else-if="type === 'C'">一般</div>
<div v-else>差</div>
</div>
<!-- 2. 创建 Vue 的实例对象 -->
<script>
// 创建 Vue 的实例对象
const vm = new Vue({
// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
el: "#app",
// data 对象就是要渲染到页面上的数据
data: {
// 如果 flag 为 true,则显示被控制的元素;如果为 false 则隐藏被控制的元素
flag: true,
type: "A",
},
methods: {
toggleFlagValue() {
this.flag = !this.flag;
},
},
});
</script>
</body>
</html>
v-for列表渲染指令
vue 提供了 v-for 列表渲染指令,用来辅助开发者基于一个数组来循环渲染一个列表结构。
v-for 指令需要使 用 item in items 形式的特殊语法,其中:
- items 是待循环的数组
- item 是被循环的每一项
v-for中的索引
v-for 指令还支持一个可选的第二个参数,即当前项的索引。语法格式为 (item, index) in items。
注意:v-for指令中的item项和index索引都是形参,可以根据需要进行重命名。例如(user, i) in userlist。
使用key维护列表的状态
当列表的数据变化时,默认情况下,vue 会尽可能的复用已存在的 DOM 元素,从而提升渲染的性能。但这种默认的性能优化策略,会导致有状态的列表无法被正确更新。
为了给 vue 一个提示,以便它能跟踪每个节点的身份,从而在保证有状态的列表被正确更新的前提下,提升渲 染的性能。
此时,需要为每项提供一个唯一的key属性。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>My test page</title>
<!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
<div id="app">
<table class="table table-bordered table-hover table-striped">
<thead>
<th>索引</th>
<th>ID</th>
<th>姓名</th>
</thead>
<tbody>
<!-- 官方建议:只要用到了 v-for 指令,那么一定要绑定一个 :key 属性 -->
<!-- 而且,尽量把 id 作为 key 的值 -->
<!-- 官方对 key 的值类型,是有要求的:字符串或数字类型 -->
<!-- key 的值是千万不能重复的,否则会终端报错:Duplicate keys detected -->
<!-- <tr v-for="(item, index) in list" :key="item.id" :title="item.name + index"> -->
<tr v-for="(item, index) in list" :key="item.id">
<td>{{ index }}</td>
<td>{{ item.id }}</td>
<td>{{ item.name }}</td>
</tr>
</tbody>
</table>
</div>
<!-- 2. 创建 Vue 的实例对象 -->
<script>
// 创建 Vue 的实例对象
const vm = new Vue({
// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
el: "#app",
// data 对象就是要渲染到页面上的数据
data: {
list: [
{ id: 1, name: "张三" },
{ id: 2, name: "李四" },
{ id: 3, name: "王五" },
{ id: 4, name: "张三" },
],
},
});
</script>
</body>
</html>
key 的注意事项
- key 的值只能是字符串或数字类型。
- key 的值必须具有唯一性(即:key 的值不能重复)。
- 建议把数据项 id 属性的值作为 key 的值(因为 id 属性的值具有唯一性)。
- 使用 index 的值当作 key 的值没有任何意义(因为 index 的值不具有唯一性,例如在新增条目的状态下,索引不具有对应条目的唯一性)。
- 建议使用 v-for 指令时一定要指定 key 的值(既提升性能、又防止列表状态紊乱)。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>My test page</title>
<!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
<div id="app">
<input type="text" v-model="name" />
<button @click="addNewUser">添加用户</button>
<!-- 用户列表区域 -->
<ul>
<li v-for="(user, index) in userList" :id="user.id">
<input type="checkbox" name="" id="" />姓名:{{ user.name }}
</li>
</ul>
</div>
<!-- 2. 创建 Vue 的实例对象 -->
<script>
// 创建 Vue 的实例对象
const vm = new Vue({
// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
el: "#app",
// data 对象就是要渲染到页面上的数据
data: {
// 输入的用户名
name: "",
// 用户列表
userList: [
{ id: 1, name: "ZhangSan" },
{ id: 2, name: "LiSi" },
],
// 下一个可用的ID值
nextId: 3,
},
methods: {
addNewUser() {
this.userList.unshift({ id: this.nextId, name: this.name });
this.name = "";
this.nextId++;
},
},
});
</script>
</body>
</html>
Label的for属性
for 属性规定 label 与哪个表单元素绑定。
品牌列表案例
添加品牌
品牌名称:“请输入品牌名称” 【添加品牌】
# | 品牌名称 | 状态 | 创建时间 | 操作 |
---|---|---|---|---|
1 | 宝马 | 已启用 | 2020-11-03 11:03:56 | 删除 |
2 | 奥迪 | 已禁用 | 2020-11-03 11:03:56 | 删除 |
3 | 奔驰 | 已启用 | 2020-11-03 11:03:56 | 删除 |
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>My test page</title>
<!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
<!-- 新 Bootstrap 核心 CSS 文件 -->
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css"
/>
<link rel="stylesheet" href="./css/brandlist.css" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
<div id="app">
<!-- 卡片区域 -->
<div class="card">
<div class="card-header">添加品牌</div>
<div class="card-body">
<!-- 添加品牌的表单区域 -->
<!-- form 表单元素有 submit 事件 -->
<form @submit.prevent="add">
<div class="form-row align-items-center">
<div class="col-auto">
<div class="input-group mb-2">
<div class="input-group-prepend">
<div class="input-group-text">品牌名称</div>
</div>
<input
type="text"
class="form-control"
placeholder="请输入品牌名称"
v-model.trim="brand"
/>
</div>
</div>
<div class="col-auto">
<button type="submit" class="btn btn-primary mb-2">添加</button>
</div>
</div>
</form>
</div>
</div>
<!-- 表格区域 -->
<table class="table table-bordered table-hover table-striped">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">品牌名称</th>
<th scope="col">状态</th>
<th scope="col">创建时间</th>
<th scope="col">操作</th>
</tr>
</thead>
<tbody>
<tr v-for="item in list" :key="item.id">
<td>{{ item.id }}</td>
<td>{{ item.name }}</td>
<td>
<div class="custom-control custom-switch">
<!-- 使用 v-model 实现双向数据绑定 -->
<input
type="checkbox"
class="custom-control-input"
:id="'cb' + item.id"
v-model="item.status"
/>
<!-- 使用 v-if 结合 v-else 实现按需渲染 -->
<label
class="custom-control-label"
:for="'cb' + item.id"
v-if="item.status"
>
已启用
</label>
<label
class="custom-control-label"
:for="'cb' + item.id"
v-else
>
已禁用
</label>
</div>
</td>
<td>{{ item.time }}</td>
<td>
<a href="javascript:;" @click="remove(item.id)">删除</a>
</td>
</tr>
</tbody>
</table>
</div>
<!-- 2. 创建 Vue 的实例对象 -->
<script>
// 创建 Vue 的实例对象
const vm = new Vue({
// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
el: "#app",
// data 对象就是要渲染到页面上的数据
data: {
// 用户输入的品牌名称
brand: "",
// nextId 是下一个,可用的 id
nextId: 4,
// 品牌列表的数据
list: [
{ id: 1, name: "宝马", status: true, time: new Date() },
{ id: 2, name: "奔驰", status: false, time: new Date() },
{ id: 3, name: "奥迪", status: true, time: new Date() },
],
},
methods: {
// 点击链接,删除对应的品牌信息
remove(id) {
this.list = this.list.filter((item) => item.id !== id);
},
// 阻止表单的默认提交行为之后,触发 add 方法
add() {
// 如果判断到 brand 的值为空字符串,则 return 出去
if (this.brand === "") {
return alert("必须填写品牌名称!");
}
// 如果没有被 return 出去,应该执行添加的逻辑
// 1. 先把要添加的品牌对象,整理出来
const obj = {
id: this.nextId,
name: this.brand,
status: true,
time: new Date(),
};
// 2. 往 this.list 数组中 push 步骤 1 中得到的对象
this.list.push(obj);
// 3. 清空 this.brand;让 this.nextId 自增 +1
this.brand = "";
this.nextId++;
},
},
});
</script>
</body>
</html>
Vue过滤器
Vue过滤器适用于Vue2.x,而Vue3.x不再支持该功能。
过滤器定义
过滤器可以用在两个地方:插值表达式 和 v-bind 属性绑定。
过滤器应该被添加在 JavaScript 表达式的尾部,由“管道符”进行调用。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>My test page</title>
<!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
<div id="app">
<p>message的值是:{{ message | capi }}</p>
</div>
<!-- 2. 创建 Vue 的实例对象 -->
<script>
// 创建 Vue 的实例对象
const vm = new Vue({
// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
el: "#app",
// data 对象就是要渲染到页面上的数据
data: {
message: "hello, vue.js.",
},
// 过滤器函数,必须被定义到filters节点之下
// 过滤器本质上是函数
// 在过滤器的形参中就可以获取到管道符前面的值
filters: {
capi(val) {
console.log(val);
const firstChar = val.charAt(0).toUpperCase();
const otherChar = val.slice(1);
// 强调:过滤器中,一定要有返回值
// 过滤器函数形参中的val一定是过滤器前面的值
return firstChar + otherChar;
},
},
});
</script>
</body>
</html>
私有过滤器和全局过滤器
在 filters 节点下定义的过滤器,称为“私有过滤器”,因为它只能在当前 vm 实例所控制的 el 区域内使用。 如果希望在多个 vue 实例之间共享过滤器,则可以按照如下的格式定义全局过滤器:
注意:如果全局过滤器和私有过滤器名字一致,此时按照“就近原则”,调用的是“私有过滤器”。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>My test page</title>
<!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
<div id="app1">
<p>message的值是:{{ message | capi }}</p>
</div>
<div id="app2">
<p>message的值是:{{ message | capi }}</p>
</div>
<!-- 2. 创建 Vue 的实例对象 -->
<script>
// 使用Vue.filter()定义全局过滤器
Vue.filter("capi", (val) => {
console.log(val);
const firstChar = val.charAt(0).toUpperCase();
const otherChar = val.slice(1);
// 强调:过滤器中,一定要有返回值
// 过滤器函数形参中的val一定是过滤器前面的值
return firstChar + otherChar;
});
// 创建 Vue 的实例对象
const vm1 = new Vue({
// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
el: "#app1",
// data 对象就是要渲染到页面上的数据
data: {
message: "hello, vue.js.",
},
});
// 创建 Vue 的实例对象
const vm2 = new Vue({
// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
el: "#app2",
// data 对象就是要渲染到页面上的数据
data: {
message: "hello, world.",
},
});
</script>
</body>
</html>
连续调用多个过滤器
过滤器可以串联地进行调用。
过滤器传参
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>My test page</title>
<!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
<div id="app">
<p>
message的值是:{{ message | capitalize("参数1", "参数2") | maxLength }}
</p>
</div>
<!-- 2. 创建 Vue 的实例对象 -->
<script>
// 全局过滤器,使用Vue.filter()定义全局过滤器
Vue.filter("capitalize", (val, arg1, arg2) => {
console.log(
"filter of capitalize: " + val + ", arg1: " + arg1 + ", arg2: " + arg2
);
const firstChar = val.charAt(0).toUpperCase();
const otherChar = val.slice(1);
// 强调:过滤器中,一定要有返回值
// 过滤器函数形参中的val一定是过滤器前面的值
return firstChar + otherChar;
});
// 全局过滤器,使用Vue.filter()定义全局过滤器
// 控制文本的最大长度
Vue.filter("maxLength", (val) => {
console.log("filter of maxLength: " + val);
if (val.length <= 0) {
return val;
}
// 强调:过滤器中,一定要有返回值
// 过滤器函数形参中的val一定是过滤器前面的值
return val.slice(0, 11) + "...";
});
// 创建 Vue 的实例对象
const vm = new Vue({
// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
el: "#app",
// data 对象就是要渲染到页面上的数据
data: {
message: "hello, vue.js. hello, vue.js.",
},
});
</script>
</body>
</html>
过滤器的兼容性
过滤器仅在 vue 2.x 和 1.x 中受支持,在 vue 3.x 的版本中剔除了过滤器相关的功能。
在企业级项目开发中:
- 如果使用的是 2.x 版本的 vue,则依然可以使用过滤器相关的功能。
- 如果项目已经升级到了 3.x 版本的 vue,官方建议使用计算属性或方法代替被剔除的过滤器功能。
具体的迁移指南,请参考 vue 3.x 的官方文档给出的说明。
Vue侦听器
什么是侦听器
watch 侦听器允许开发者监视数据的变化,从而针对数据的变化做特定的操作。
- 方法格式的侦听器:
- 缺点1:无法在刚进入页面的时候,自动触发。
- 缺点2:如果侦听的是一个对象,如果对象中的属性发生了变化,不会触发侦听器
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>My test page</title>
<!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
<div id="app">
<input type="text" v-model="username" />
<p>用户名:{{username}}</p>
</div>
<!-- 2. 创建 Vue 的实例对象 -->
<script>
// 创建 Vue 的实例对象
const vm = new Vue({
// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
el: "#app",
// data 对象就是要渲染到页面上的数据
data: {
username: "",
},
// 所有的侦听器,都应该被定义到watch节点下
watch: {
// 侦听器本质上是一个函数,要监视那个数据的变化,就把数据名作为方法名即可
// 新值在前,旧值在后
username(newVal, oldVal) {
console.log(
`监听到了username的变化, newVal: ${newVal}, oldVal: ${oldVal}`
);
},
},
});
</script>
</body>
</html>
判断用户是否被占用
// 监听 username 值的变化,并使用 axios 发起 Ajax 请求,检测当前输入的用户名是否可用:
watch: {
// 监听 username 值的变化
async username(newVal) {
if (newVal === '') return;
// 使用 axios 发起请求,判断用户名是否可用
const { data: res } = await axios.get('https://www.escook.cn/api/finduser/' + newVal);
console.log(res);
}
}
侦听器-immediate选项
默认情况下,组件在初次加载完毕后不会调用 watch 侦听器。如果想让 watch 侦听器立即被调用,则需要使 用 immediate 选项。
对象格式的侦听器:可以通过immediate选项,在刚进入页面的时候,让侦听器自动触发一次。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>My test page</title>
<!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
<div id="app">
<input type="text" v-model="username" />
<p>用户名:{{username}}</p>
</div>
<!-- 2. 创建 Vue 的实例对象 -->
<script>
// 创建 Vue 的实例对象
const vm = new Vue({
// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
el: "#app",
// data 对象就是要渲染到页面上的数据
data: {
username: "ZhangSan",
},
// 所有的侦听器,都应该被定义到watch节点下
watch: {
// 侦听器本质上是一个函数,要监视那个数据的变化,就把数据名作为方法名即可
// 新值在前,旧值在后
username: {
handler(newVal, oldVal) {
console.log(
`监听到了username的变化, newVal: ${newVal}, oldVal: ${oldVal}`
);
},
// immediate选项的默认值是false
// immediate的作用是:控制侦听器是否自动触发一次
immediate: true,
},
},
});
</script>
</body>
</html>
侦听器-deep选项(深度监听,监听对象属性)
对象格式的侦听器:可以通过deep选项,让侦听器深度监听对象中每个属性的变化。
方式一:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>My test page</title>
<!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
<div id="app">
<input type="text" v-model="info.username" />
<p>用户名:{{info.username}}</p>
</div>
<!-- 2. 创建 Vue 的实例对象 -->
<script>
// 创建 Vue 的实例对象
const vm = new Vue({
// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
el: "#app",
// data 对象就是要渲染到页面上的数据
data: {
info: {
username: "ZhangSan",
},
},
// 所有的侦听器,都应该被定义到watch节点下
watch: {
info: {
handler(newVal) {
console.log(`监听到了username的变化, newVal: ${newVal.username}`);
},
// 开启深度监听,只要对象中任何一个属性变化了,都会触发对象侦听器
deep: true,
},
},
});
</script>
</body>
</html>
方式二:直接侦听子属性的变化
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>My test page</title>
<!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
<div id="app">
<input type="text" v-model="info.username" />
<p>用户名:{{info.username}}</p>
</div>
<!-- 2. 创建 Vue 的实例对象 -->
<script>
// 创建 Vue 的实例对象
const vm = new Vue({
// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
el: "#app",
// data 对象就是要渲染到页面上的数据
data: {
info: {
username: "ZhangSan",
},
},
// 所有的侦听器,都应该被定义到watch节点下
watch: {
// 如果要侦听的对象的子属性的变化,则必须包裹一层单引号
"info.username": {
handler(newVal, oldVal) {
console.log(
`监听到了username的变化, newVal: ${newVal}, oldVal: ${oldVal}`
);
},
},
},
});
</script>
</body>
</html>
Vue计算属性
计算属性指的是通过一系列运算之后,最终得到一个属性值。
这个动态计算出来的属性值可以被模板结构或 methods 方法使用。
计算属性的特点
- 虽然计算属性在声明的时候被定义为方法,但是计算属性的本质是一个属性。
- 计算属性会缓存计算的结果,只有计算属性依赖的数据变化时,才会重新进行运算。
优点
- 实现了代码的复用。
- 只要计算属性中依赖的数据源变化了,则计算属性会自动重新求值。
原始代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>My test page</title>
<!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<style>
.box {
width: 200px;
height: 200px;
border: 1px solid #ccc;
}
</style>
</head>
<body>
<!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
<div id="app">
<div>
<span>R:</span>
<input type="text" v-model.number="r" />
</div>
<div>
<span>G:</span>
<input type="text" v-model.number="g" />
</div>
<div>
<span>B:</span>
<input type="text" v-model.number="b" />
</div>
<hr />
<!-- 专门用户呈现颜色的 div 盒子 -->
<div class="box" :style="{ backgroundColor: `rgb(${r}, ${g}, ${b})` }">
{{ `rgb(${r}, ${g}, ${b})` }}
</div>
<button @click="show">按钮</button>
</div>
<!-- 2. 创建 Vue 的实例对象 -->
<script>
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: "#app",
data: {
// 红色
r: 0,
// 绿色
g: 0,
// 蓝色
b: 0,
},
methods: {
// 点击按钮,在终端显示最新的颜色
show() {
console.log(`rgb(${this.r}, ${this.g}, ${this.b})`);
},
},
});
</script>
</body>
</html>
计算属性改造案例,优化之后的代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>My test page</title>
<!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<style>
.box {
width: 200px;
height: 200px;
border: 1px solid #ccc;
}
</style>
</head>
<body>
<!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
<div id="app">
<div>
<span>R:</span>
<input type="text" v-model.number="r" />
</div>
<div>
<span>G:</span>
<input type="text" v-model.number="g" />
</div>
<div>
<span>B:</span>
<input type="text" v-model.number="b" />
</div>
<hr />
<!-- 专门用户呈现颜色的 div 盒子 -->
<div class="box" :style="{ backgroundColor: rgb }">{{ rgb }}</div>
<button @click="show">按钮</button>
</div>
<!-- 2. 创建 Vue 的实例对象 -->
<script>
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: "#app",
data: {
// 红色
r: 0,
// 绿色
g: 0,
// 蓝色
b: 0,
},
// 所有的计算属性都要定义到computed中
// computed属性要定义程方法格式
computed: {
// rgb作为一个计算属性,被定义成了方法格式
// 最终,在这个方法中,要返回一个生成好的rgb(x, x, x)的字符串
rgb() {
return `rgb(${this.r}, ${this.g}, ${this.b})`;
},
},
methods: {
// 点击按钮,在终端显示最新的颜色
show() {
console.log(this.rgb);
},
},
});
</script>
</body>
</html>
axios
说明:
axios专注于网络请求的库。
参考的请求URL,可以从F12找到一个网络请求即可(通过Open in new tab)。
axios基本使用
在Vue、React中都会用到axios来请求数据。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>My test page</title>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
<script>
// http://www.liulongbin.top:3006/api/getbooks
// 1. 调用 axios 方法得到的返回值是 Promise 对象
axios({
// 请求方式
method: "GET",
// 请求的地址
url: "http://www.liulongbin.top:3006/api/getbooks",
// URL 中的查询参数(GET请求)
params: {
id: 1,
},
// 请求体参数(PUT请求)
data: {},
}).then(function (result) {
console.log(result);
});
</script>
</body>
</html>
axios发起POST请求
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>My test page</title>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
<button id="btnPOST">发起POST请求</button>
<button id="btnGET">发起GET请求</button>
<script>
document
.querySelector("#btnPOST")
.addEventListener("click", async function () {
// 如果调用某个方法的返回值是 Promise 实例,则前面可以添加 await!
// await 只能用在被 async “修饰”的方法中
const { data } = await axios({
method: "POST",
url: "http://www.liulongbin.top:3006/api/post",
data: {
name: "zs",
age: 20,
},
});
console.log(data);
});
document
.querySelector("#btnGET")
.addEventListener("click", async function () {
// 解构赋值的时候,使用 : 进行重命名
// 1. 调用 axios 之后,使用 async/await 进行简化
// 2. 使用解构赋值,从 axios 封装的大对象中,把 data 属性解构出来
// 3. 把解构出来的 data 属性,使用 冒号 进行重命名,一般都重命名为 { data: res }
const { data: res } = await axios({
method: "GET",
url: "http://www.liulongbin.top:3006/api/getbooks",
});
console.log(res.data);
});
// $.ajax() $.get() $.post()
// axios() axios.get() axios.post() axios.delete() axios.put()
</script>
</body>
</html>
axios直接发起GET和POST请求(推荐)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>My test page</title>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
<button id="btnPOST">发起POST请求</button>
<button id="btnGET">发起GET请求</button>
<script>
document
.querySelector("#btnGET")
.addEventListener("click", async function () {
// axios.get("url地址", {
// // GET 参数
// params: {},
// });
const { data: res } = await axios.get(
"http://www.liulongbin.top:3006/api/getbooks",
{
params: { id: 1 },
}
);
console.log(res);
});
document
.querySelector("#btnPOST")
.addEventListener("click", async function () {
// axios.post('url', { /* POST 请求体数据 */ })
const { data: res } = await axios.post(
"http://www.liulongbin.top:3006/api/post",
{ name: "zs", gender: "女" }
);
console.log(res);
});
</script>
</body>
</html>
Vue CLI中axios的使用
建议先看完Vue CLI,再看本章节内容。
axios的基本用法
- 参考:“VUE CLI > 新建一个工程(基础工程)”
新建一个工程,包含一个App.vue根组件和CompLeft.vue、Compight.vue两个子组件。
- 安装axios
npm install axios --save
-
在CompLeft.vue组件中发起GET请求。
<template> <div class="left-container"> <h3>Left组件</h3> <button @click="getInfo">发起GET请求</button> </div> </template> <script> import axios from 'axios' export default { methods: { async getInfo() { const { data: res } = await axios.get( "http://liulongbin.top:3006/api/get" ); console.log("GET request res: " + JSON.stringify(res)); }, }, }; </script> <style lang="less" scoped> .left-container { background-color: lightgreen; min-height: 200px; flex: 1; } </style>
-
在Compight.vue组件中发起POST请求。
<template> <div class="right-container"> <h3>Right组件</h3> <button @click="postInfo">发起POST请求</button> </div> </template> <script> import axios from 'axios' export default { methods: { async postInfo() { const { data: res } = await axios.post( "http://liulongbin.top:3006/api/post" ); console.log("POST request res: " + JSON.stringify(res)); }, }, }; </script> <style lang="less" scoped> .right-container { background-color: lightcoral; min-height: 200px; flex: 1; } </style>
-
App.vue
<template> <div id="app"> <h1>App根组件</h1> <div class="box"> <Left></Left> <right></right> </div> </div> </template> <script> import Left from "@/components/CompLeft.vue"; import Right from "@/components/CompRight.vue"; export default { name: "App", components: { Left, Right, }, }; </script> <style lang="less" scoped> .box { display: flex; } </style>
把axios挂载到Vue原型上并配置请求根路径
改装以上工程。
-
在main.js中,把axios中挂载到Vue原型上,自定义一个属性:$http,并给axios配置一个默认的URL根路径。
import Vue from 'vue' import App from './App.vue' import axios from 'axios' Vue.config.productionTip = false // 全局配置axios的请求根路径 axios.defaults.baseURL = 'http://liulongbin.top:3006' // 把axios挂载到Vue.prototype上,供每个.vue组件的实例直接使用 // 后期在每个.vue组件要发起请求,直接调用this.$http.get / this.$http.post即可 // 但是,把axios挂载到Vue原型上,无法实现API接口的复用。 // 即如果某个API在多个组件中都有使用,需要在每个组件中都需要重新调用该API接口。 Vue.prototype.$http = axios new Vue({ render: h => h(App) }).$mount('#app')
-
CompLeft.vue组件修改
<template> <div class="left-container"> <h3>Left组件</h3> <button @click="getInfo">发起GET请求</button> </div> </template> <script> export default { methods: { async getInfo() { const { data: res } = await this.$http.get("/api/get"); console.log("GET request res: " + JSON.stringify(res)); }, }, }; </script> <style lang="less" scoped> .left-container { background-color: lightgreen; min-height: 200px; flex: 1; } </style>
-
CompRight.vue组件修改
<template> <div class="right-container"> <h3>Right组件</h3> <button @click="postInfo">发起POST请求</button> </div> </template> <script> export default { methods: { async postInfo() { const { data: res } = await this.$http.post("/api/post"); console.log("POST request res: " + JSON.stringify(res)); }, }, }; </script> <style lang="less" scoped> .right-container { background-color: lightcoral; min-height: 200px; flex: 1; } </style>
Vue CLI
单页面应用程序
Single Page Application,顾名思义,指的是一个Web网站中只有唯一一个HTML页面,所有的功能与交互都在这唯一的一个页面内完成。
什么是vue-cli
vue-cli 是 Vue.js 开发的标准工具。
它简化了程序员基于 webpack 创建工程化的 Vue 项目的过程。
引用自 vue-cli 官网上的一句话: 程序员可以专注在撰写应用上,而不必花好几天去纠结 webpack 配置的问题。
安装和使用
vue-cli 是 npm 上的一个全局包,使用 npm install 命令,即可方便的把它安装到自己的电脑上:
npm install -g @vue/cli
安装完成后,执行以下命令查看是否安装成功。
vue -V
# 返回结果示例
@vue/cli 5.0.1
在XX目录/文件夹中,基于 vue-cli 快速生成工程化的 Vue 项目:
vue create 项目名称
新建一个Vue CLI工程 *
新建Vue CLI工程(基础工程)【模板】
-
执行命令,新建一个工程
vue create demo-1
-
手动配置工程
# Manually select features # 新增勾选“CSS Pre-processors” # 以2.x为例,选择“2.x” # 选择“less”CSS预处理器 # 选择“ESLint with error prevention only”或者“ESLint + Standard config”,根据需要选择 # 最后咨询是否保存为模板,这里选择“N”
新建Vue CLI工程(带路由工程)【模板】
说明:建议看完路由章节内容,再关注本章节,带路由的工程创建。
-
执行命令,新建一个工程
vue create demo-1
-
手动配置工程
# Manually select features # 新增勾选“Router”和“CSS Pre-processors” # 以2.x为例,选择“2.x” # 选择“less”CSS预处理器 # 选择“ESLint with error prevention only”或者“ESLint + Standard config”,根据需要选择 # 最后咨询是否保存为模板,这里选择“N”
说明:与路由切换相关的组件,建议放到views文件夹中。反之,建议放到components文件夹中。
新建两个子组件
-
新建组件CompLeft.vue组件内容:
<template> <div class="left-container"> <h3>Left组件</h3> </div> </template> <script> export default {}; </script> <style lang="less" scoped> .left-container { background-color: lightgreen; min-height: 200px; flex: 1; } </style>
-
新建组件CompRight.vue组件内容:
<template> <div class="right-container"> <h3>Right组件</h3> </div> </template> <script> export default {}; </script> <style lang="less" scoped> .right-container { background-color: lightcoral; min-height: 200px; flex: 1; } </style>
-
删除components文件夹中的HelloWorld.vue组件。
-
修改App.vue组件内容:
<template> <div id="app"> <h1>App根组件</h1> <div class="box"> <Left></Left> <right></right> </div> </div> </template> <script> import Left from "@/components/CompLeft.vue"; import Right from "@/components/CompRight.vue"; export default { name: "App", components: { Left, Right, }, }; </script> <style lang="less" scoped> .box { display: flex; } </style>
说明:如需要关闭ESLint,可以在vue.config.js文件中增加以下一条语句:
lintOnSave: false // 关闭ESLint检查
Vue项目的运行流程
在工程化的项目中,vue 要做的事情很单纯:通过 main.js 把 App.vue 渲染到 index.html 的指定区域中。
其中:
- App.vue 用来编写待渲染的模板结构
- index.html 中需要预留一个 el 区域
- main.js 把 App.vue 渲染到了 index.html 所预留的区域中
目录结构
│ .gitignore
│ babel.config.js # Babel插件配置文件
│ jsconfig.json
│ package-lock.json
│ package.json
│ README.md
│ vue.config.js
│
├─node_modules # 依赖包目录
├─public
│ favicon.ico
│ index.html # 主页,生成的文件会被注入到此文件中
│
└─src # 源代码目录
│ App.vue # 项目的根组件
│ main.js # 项目的入口文件,整个项目的运行,要先执行main.js
│
├─assets # 静态资源目录,例如图片资源、CSS样式表
│ logo.png
│
└─components # 组件,封装的、可复用的组件
HelloWorld.vue
main.js文件解析
// 导入Vue包,得到Vue构造函数
import Vue from 'vue'
// 导入App.vue根组件,将来要把App.vue中的模板结构,渲染到HTML页面中
import App from './App.vue'
Vue.config.productionTip = false
// 创建Vue的实例对象
new Vue({
// 把render函数指定的组件,渲染到HTML页面中
render: h => h(App),
}).$mount('#app')
// 使用$mount方法指定到app区域,作用与el属性完全一样。两种方式选择其一即可。
测试$mount方法
等价于:el: '#app'
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>My test page</title>
<!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<style>
.box {
width: 200px;
height: 200px;
border: 1px solid #ccc;
}
</style>
</head>
<body>
<div id="app">
<input type="text" v-model="username" />
</div>
<!-- 2. 创建 Vue 的实例对象 -->
<script>
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
data: {
username: "ZhangSan",
},
});
vm.$mount("#app");
</script>
</body>
</html>
Vue组件
什么是组件化开发
组件化开发指的是:根据封装的思想,把页面上可重用的 UI 结构封装为组件,从而方便项目的开发和维护。
Vue是一个支持组件化开发的前端框架。
Vue中规定:组件的后缀名是 .vue。之前接触到的 App.vue 文件本质上就是一个 vue 的组件。
Vue组件的三个组成部分
每个 .vue 组件都由 3 部分构成,分别是:
- template:组件的模板结构。
- script:组件的 JavaScript 行为。
- style:组件的样式 其中,每个组件中必须包含 template 模板结构,而 script 行为和 style 样式是可选的组成部分。
template
vue 规定:每个组件对应的模板结构,需要定义到template
节点中。
注意:
- template 是 vue 提供的容器标签,只起到包裹性质的作用,它不会被渲染为真正的 DOM 元素
- template 中只能包含唯一的根节点
script
vue 规定:开发者可以在script节点中封装组件的JavaScript业务逻辑。
.vue 组件中的 data 必须是函数
vue 规定:.vue 组件中的 data 必须是一个函数,不能直接指向一个数据对象。
style
vue 规定:组件内的style节点是可选的,开发者可以在style节点中编写样式美化当前组件的 UI 结构。
新建文件Test.vue
<template>
<div class="text-box">
<h3>这是一个自定义组件--{{ username }}</h3>
</div>
</template>
<script>
// 默认导出,固定写法
export default {
// data数据源
// 注意:.vue组件中的data不能像之前一样,不能指向对象
// 注意:组件中的data必须是一个函数
data() {
// return出去的对象中,可以定义数据
return {
username: "admin",
};
},
};
</script>
<style>
</style>
在组件中定义methods方法
<template>
<div class="text-box">
<h3>这是一个自定义组件--{{ username }}</h3>
<button @click="changeName">修改名字</button>
</div>
</template>
<script>
// 默认导出,固定写法
export default {
// data数据源
// 注意:.vue组件中的data不能像之前一样,不能指向对象
// 注意:组件中的data必须是一个函数
data() {
// return出去的对象中,可以定义数据
return {
username: "admin",
};
},
methods: {
// 在组件中,this就表示当前组件的实例对象
changeName() {
this.username = "娃哈哈";
},
// 当前组件中的侦听器
watch: {},
// 当前组件中的计算属性
computed: {},
// 当前组件中的过滤器
filters: {},
},
};
</script>
<style>
.text-box {
background-color: pink;
}
</style>
让style中支持 less 语法
在style标签上添加 lang="less" 属性,即可使用 less 语法编写组件的样式。
<style lang="less">
.test-box {
background-color: pink;
h3 {
color: red;
}
}
</style>
使用组件的三个步骤
-
步骤1:使用 import 语法导入需要的组件。
import Left from '@/components/Left.vue'
-
步骤2:使用components节点注册组件。
export default { components: { Left } }
-
步骤3:以标签形式使用刚才注册的组件。
<div class="box"> <Left></Left> </div>
通过components注册的是私有子组件
在组件 A 的 components 节点下,注册了组件 F。
则组件 F 只能用在组件 A 中;不能被用在组件 C 中。
请大家思考两个问题:
① 为什么 F 不能用在组件 C 中?
② 怎样才能在组件 C 中使用 F?
注册全局组件
方式一(main.js中定义)
在 vue 项目的 main.js 入口文件中,通过 Vue.component() 方法,可以注册全局组件。
// 导入需要被全局注册的组件
import TestName from '@/components/TestName'
// 参数1:字符串格式,表示组件的“注册名称”
// 参数2:需要被全局注册的对应组件
Vue.component('MyTestName', TestName);
在使用时候,只需要将注册的全局组件当成一个常规组件使用即可。
例如在App.vue中需要使用该组件。
<template>
<div id="app">
<MyTestName></MyTestName>
</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>
方式二(App.vue中定义,推荐)
App.vue
<template>
<div id="app">
<MyTestName :init="9"></MyTestName>
</div>
</template>
<script>
// 1. 导入需要使用的.vue组件 import TestName from '@/components/TestName.vue'
import MyTestName from "@/components/TestName";
export default {
name: "App",
// 2. 注册组件
components: {
MyTestName,
},
};
</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>
组件的自定义属性props
props 是组件的自定义属性,在封装通用组件的时候,合理地使用 props 可以极大的提高组件的复用性!
语法使用如下:
export default {
// 组件的自定义属性
props: ['自定义属性A', '自定义属性B', '其他自定义属性...'],
// 组件的私有属性
data() {
return {},
}
}
举例说明。
TestName.vue
<template>
<div class="test-container">
<h3 id="myh3">Test.vue 组件 --- {{ books.length }} 本图书</h3>
<p id="pppp">message 的值是:{{ message }}</p>
<button @click="message += '~'">修改 message 的值</button>
</div>
</template>
<script>
export default {
// props是自定义属性,允许使用者通过自定义属性,为当前组件指定初始值
// 自定义属性的名字,是封装者自定义的(只要名称合法即可)
props: ["info"],
data() {
return {
message: "hello vue.js",
// 定义 books 数组,存储的是所有图书的列表数据。默认为空数组!
books: [],
};
},
};
</script>
<style></style>
调用的组件中,通过属性的方式传递参数值即可。
App.vue
<template>
<div id="app">
<MyTestName int="9"></MyTestName>
</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>
结合v-bind使用自定义属性
默认传递过去的是字符串数值。如需要传递数字值,使用v-bind属性即可。
<template>
<div id="app">
<MyTestName :int="9"></MyTestName>
</div>
</template>
props是只读的
vue 规定:组件中封装的自定义属性是只读的,程序员不能直接修改 props 的值。否则会直接报错。
要想修改 props 的值,可以把 props 的值转存到 data 中,因为 data 中的数据都是可读可写的!
export default {
// props是自定义属性,允许使用者通过自定义属性,为当前组件指定初始值
// 自定义属性的名字,是封装者自定义的(只要名称合法即可)
// props中的数据,可以直接在末班结构中被使用
// 注意:props中的数据是只读的,不要直接修改props的值,否则终端会报错!
props: ['init'],
// 组件的私有属性
data() {
return {
count: this.init, // 把this.init的值转存到count
},
}
}
TestName.vue
<template>
<div class="test-container">
<p id="pppp">count 的值是:{{ count }}</p>
<input type="text" v-model="count" />
<br />
<br />
<button @click="countPlusOne">Count+1</button>
</div>
</template>
<script>
export default {
props: ["init"],
data() {
return {
count: this.init,
};
},
methods: {
countPlusOne() {
console.log(this);
this.count++;
},
},
};
</script>
<style></style>
组件调用方,App.vue中,以属性形式传递参数。
<template>
<div id="app">
<MyTestName :init="9"></MyTestName>
</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>
props中default默认值
在声明自定义属性时,可以通过 default 来定义属性的默认值。
export default {
// props是自定义属性,允许使用者通过自定义属性,为当前组件指定初始值
// 自定义属性的名字,是封装者自定义的(只要名称合法即可)
// props中的数据,可以直接在末班结构中被使用
// 注意:props中的数据是只读的,不要直接修改props的值,否则终端会报错!
props: {
// 自定义属性A:{ /* 配置项 */ }
// 自定义属性B:{ /* 配置项 */ }
// 自定义属性C:{ /* 配置项 */ }
init: {
// 如果外界使用TestName组件的时候,没有传递init属性,则默认值生效
// 用default属性定义属性的默认值
default: 0,
},
},
// 组件的私有属性
data() {
return {
count: this.init, // 把this.init的值转存到count
},
}
}
props中type值类型
在声明自定义属性时,可以通过 type 来定义属性的值类型。
export default {
// props是自定义属性,允许使用者通过自定义属性,为当前组件指定初始值
// 自定义属性的名字,是封装者自定义的(只要名称合法即可)
// props中的数据,可以直接在末班结构中被使用
// 注意:props中的数据是只读的,不要直接修改props的值,否则终端会报错!
props: {
// 自定义属性A:{ /* 配置项 */ }
// 自定义属性B:{ /* 配置项 */ }
// 自定义属性C:{ /* 配置项 */ }
init: {
// 如果外界使用TestName组件的时候,没有传递init属性,则默认值生效
// 用default属性定义属性的默认值
default: 0,
// 用type属性定义属性的值类型
// 如果传递过来的值不符合此类型,则会在终端报错
type: Number,
},
},
// 组件的私有属性
data() {
return {
count: this.init, // 把this.init的值转存到count
},
}
}
props的required必填项
在声明自定义属性时,可以通过 required 选项,将属性设置为必填项,强制用户必须传递属性的值。
export default {
// props是自定义属性,允许使用者通过自定义属性,为当前组件指定初始值
// 自定义属性的名字,是封装者自定义的(只要名称合法即可)
// props中的数据,可以直接在末班结构中被使用
// 注意:props中的数据是只读的,不要直接修改props的值,否则终端会报错!
props: {
// 自定义属性A:{ /* 配置项 */ }
// 自定义属性B:{ /* 配置项 */ }
// 自定义属性C:{ /* 配置项 */ }
init: {
// 如果外界使用TestName组件的时候,没有传递init属性,则默认值生效
// 用default属性定义属性的默认值
default: 0,
// 用type属性定义属性的值类型
// 如果传递过来的值不符合此类型,则会在终端报错
type: Number,
// 必填项校验
required: true,
},
},
// 组件的私有属性
data() {
return {
count: this.init, // 把this.init的值转存到count
},
}
}
组件之间的样式冲突问题
默认情况下,写在 .vue 组件中的样式会全局生效,因此很容易造成多个组件之间的样式冲突问题。
导致组件之间样式冲突的根本原因是:
① 单页面应用程序中,所有组件的 DOM 结构,都是基于唯一的 index.html 页面进行呈现的
② 每个组件中的样式,都会影响整个 index.html 页面中的 DOM 元素
如何解决组件样式冲突的问题
为每个组件分配唯一的自定义属性,在编写组件样式时,通过属性选择器来控制样式的作用域。
为了提高开发效率和开发体验,vue 为 style 节点提供了 scoped 属性,从而防止组件之间的样式冲突问题
TestName.vue
<template>
<div class="test-container">
<p id="pppp">count 的值是:{{ count }}</p>
<input type="text" v-model="count" />
<br />
<br />
<button @click="countPlusOne">Count+1</button>
</div>
</template>
<script>
export default {
props: {
init: {
default: 15,
type: Number,
required: true,
},
},
data() {
return {
count: this.init,
};
},
methods: {
countPlusOne() {
console.log(this);
this.count++;
},
},
};
</script>
<style scoped>
/* style节点的scoped属性,用来自动为每个组件分配一个唯一的“自定义属性”
并自动为当前组件的DOM标签和style样式应用这个自定义属性,防止组件的样式冲突问题 */
p {
color: blue;
}
</style>
生成的代码:
<div data-v-7fcd5826="" class="test-container">
<p data-v-7fcd5826="" id="pppp">count 的值是:9</p>
<input data-v-7fcd5826="" type="text" /><br data-v-7fcd5826="" /><br
data-v-7fcd5826=""
/><button data-v-7fcd5826="">Count+1</button>
</div>
/deep/ 样式穿透
如果给当前组件的 style 节点添加了 scoped 属性,则当前组件的样式对其子组件是不生效的。如果想让某些样
式对子组件生效,可以使用 /deep/ 深度选择器。
Vue组件的实例对象
-
组件定义的是一个模板结构。
-
实际组件使用的场景,即为一个组件实例对象。
Vue生命周期
生命周期&生命周期函数
生命周期(Life Cycle)是指一个组件从创建 -> 运行 -> 销毁
的整个阶段,强调的是一个时间段。
生命周期函数:是由 vue 框架提供的内置函数,会伴随着组件的生命周期,自动按次序执行。
注意:生命周期强调的是时间段,生命周期函数强调的是时间点。
生命周期图示
新建一个组件:TestLife.vue
<template>
<div class="test-container">
<h3>Test.vue组件</h3>
</div>
</template>
<script>
export default {};
</script>
<style scoped>
.test-container {
background-color: aqua;
height: 100px;
}
</style>
在App.vue中注册全局组件,并使用。
<template>
<div id="app">
<MyTestLife></MyTestLife>
<MyTestName :init="9"></MyTestName>
</div>
</template>
<script>
// 1. 导入需要使用的.vue组件 import TestName from '@/components/TestName.vue'
import MyTestName from "@/components/TestName";
import MyTestLife from "@/components/TestLife";
export default {
name: "App",
// 2. 注册组件
components: {
MyTestName,
MyTestLife,
},
};
</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>
- 了解beforeCreate和created生命周期的特点
- 了解beforeMount和mounted生命周期函数
- 组件运行阶段的生命周期函数
- 组件销毁阶段的生命周期函数
新建一个测试生命周期的组件TestLife.vue。
<template>
<div class="test-container">
<h3 id="myh3">Test.vue组件——{{ books.length }}本图书</h3>
<p v-for="item in books" :key="item.id">{{ item.name }}</p>
<p id="myId">{{ message }}</p>
<button @click="changeMessage">改变message的值</button>
</div>
</template>
<script>
export default {
// 组件的自定义属性
props: ["info"],
// 组件的私有属性
data() {
return {
message: "hello, vue.js",
books: [
{ id: 1, name: "张三书" },
{ id: 2, name: "李四书" },
],
};
},
methods: {
show() {
console.log("调用了TestLife组件的show()方法");
},
initBookList() {},
changeMessage() {
this.message += "~";
},
},
/***** 1. 组件创建阶段 *****/
// -->>(过程)初始化事件和生命周期函数
// 组件的props/data/methods尚未被创建,都处于不可用状态
beforeCreate() {
console.log("--生命周期: beforeCreate()");
// console.log(this.info); // 不可用
// console.log(this.message); // 不可用
// this.show(); // 不可用
},
// -->>(过程)初始化props、data、methods
// 组件的props/data/methods已创建好,都处于可用状态
// 但是组件的模板结构尚未生成,不能操作DOM元素
// created生命周期函数,非常常用
// 经常在它里面,调用methods中的方法,气你跪求服务器的数据
// 并且,把请求到的数据,转存到data中,供template模板渲染的时候使用
created() {
console.log("--生命周期: create() **");
console.info(this.info); // 可用
console.info(this.message); // 可用
this.show(); // 可用
// const dom = document.querySelector('#myh3'); // 不可用
// console.log(dom); // 不可用
},
// -->>(过程)基于数据和模板,在内存中编译生成HTML结构
// 将要把内存中编译好的HTML结构渲染到浏览器中
// 此时浏览器中还没有当前组件的DOM结构
beforeMount() {
console.log("--生命周期: beforeMount()");
// const dom = document.querySelector('#myh3'); // 不可用
// console.log(dom); // 不可用
},
// -->>(过程)用内存中编译生成的HTML结构,替换掉el属性指定的DOM元素
// 已经把内存中的HTML结构,成功的渲染到了浏览器之中。此时浏览器中已然包含了当前组件的DOM结构
mounted() {
console.log("--生命周期: mounted() **"); // 可用
const dom = document.querySelector("#myh3"); // 可用
console.log(dom);
},
/***** 2. 组件运行阶段 *****/
/***** 此阶段数据最少执行0次(无数据变化) *****/
/***** 此阶段数据最多执行N次(数据变化N次) *****/
/***** 当数据变化之后,为了能够操作最新的DOM结构,必须把代码写到updated生命周期中 *****/
// 将要根据变化过后,最新的数据,重新渲染组件的模板结构
// 此时数据已经变化,但DOM元素内容未更新
beforeUpdate() {
console.log("--生命周期: beforeUpdate()");
console.log("数据值(已更新): " + this.message);
const domId = document.querySelector("#myId"); // 可用
console.log("DOM元素值(未更新): " + domId.innerHTML);
},
// -->>(过程)根据最新的数据,重新渲染组件的DOM结构
// 已经根据最新的数据,完成了组件DOM结构的重新渲染
updated() {
console.log("--生命周期: updated() **");
console.log("数据值(已更新): " + this.message);
const domId = document.querySelector("#myId");
console.log("DOM元素值(已更新): " + domId.innerHTML);
},
/***** 3. 组件销毁阶段 *****/
// 将要销毁此组件,此时尚未销毁,组件处于正常工作的状态
beforeDestroy() {
console.log("--生命周期: beforeDestroy()");
},
// -->>(过程)销毁当前组件的数据侦听器、子组件、事件监听
// 组件已经被销毁,此组件在浏览器中对应的DOM结构已被完全移除
destroyed() {
console.log("--生命周期: destroyed()");
},
};
</script>
<style scoped>
.test-container {
background-color: aqua;
height: 180px;
}
</style>
在App.vue组件中进行传参和测试组件的销毁阶段。
<template>
<div id="app">
<h1>App根组件</h1>
<button @click="flag = !flag">Toggle TestLife Flag</button>
<MyTestLife info="你好" v-if="flag"></MyTestLife>
<MyTestName :init="9"></MyTestName>
</div>
</template>
<script>
// 1. 导入需要使用的.vue组件 import TestName from '@/components/TestName.vue'
import MyTestName from "@/components/TestName";
import MyTestLife from "@/components/TestLife";
export default {
name: "App",
data() {
return {
flag: true,
};
},
// 2. 注册组件
components: {
MyTestName,
MyTestLife,
},
};
</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>
组件之间的数据共享
组件之间的关系
最常见的组件之间的关系
- 父子关系
- 兄弟关系
父组件向子组件共享数据(自定义属性)
父组件App.vue:
- 简单类型是传递值到子组件
- 复杂类型传递的是对象的引用到子组件
<template>
<div id="app">
<h1>App根组件</h1>
<MyTestName :msg="message" :user="userInfo"></MyTestName>
</div>
</template>
<script>
// 1. 导入需要使用的.vue组件 import TestName from '@/components/TestName.vue'
import MyTestName from "@/components/TestName";
export default {
name: "App",
// 组件的自定义属性
props: [],
// 组件的私有属性
data() {
return {
message: "hello, vue.js",
userInfo: { name: "张三", age: 20 },
};
},
// 2. 注册组件
components: {
MyTestName,
},
};
</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>
子组件TestName.vue:
子组件中自定义属性(props)接收数据
说明:不要直接修改props中的值。可以在子组件中定义私有属性进行接收和改变。
<template>
<div class="test-container">
<h5>Son组件</h5>
<p>父组件传递过来的msg值是: {{ msg }}</p>
<p>父组件传递过来的user值是: {{ user }}</p>
</div>
</template>
<script>
export default {
// 组件的自定义属性
props: ["msg", "user"],
// 组件的私有属性
data() {
return {
msgIn: this.msg,
userIn: this.user,
};
},
};
</script>
<style scoped>
p {
color: blue; /* 不加/deep/时,生成的选择器格式为 p[data-v-052242de] */
}
</style>
子组件向父组件传递数据(自定义事件)
子组件向父组件共享数据使用自定义事件。示例代码:
子组件触发XX自定义事件:
<script>
export default {
// 组件的自定义属性
props: [],
// 组件的私有属性
data() {
return {
count: 0, // 需要传递给父组件的值
};
},
methods() {
add() {
this.count += 1;
// 修改数据时,通过$emit()触发自定义事件
this.$emit('numChange', this.count);
}
}
};
</script>
父组件监听XX自定义事件:
<Son @numChange="getNewCount"></Son>
<script>
export default {
// 组件的自定义属性
props: [],
// 组件的私有属性
data() {
return {
countFromSon: 0,
};
},
methods() {
getNewCount(val) {
this.countFromSon = val;
}
}
};
</script>
举例说明
App.vue
<template>
<div id="app">
<h1>App根组件</h1>
<p >子组件传递过来的数据值:{{ countFromSon }}</p>
<MyTestName :msg="message" :user="userInfo" @numChange="getNewCount"></MyTestName>
</div>
</template>
<script>
// 1. 导入需要使用的.vue组件 import TestName from '@/components/TestName.vue'
import MyTestName from "@/components/TestName";
export default {
name: "App",
// 组件的自定义属性
props: [],
// 组件的私有属性
data() {
return {
message: "hello, vue.js",
userInfo: { name: "张三", age: 20 },
countFromSon: 0,
};
},
methods: {
getNewCount(val) {
this.countFromSon = val;
},
},
// 2. 注册组件
components: {
MyTestName,
},
};
</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>
TestName.vue
<template>
<div class="test-container">
<h5>Son组件</h5>
<p>父组件传递过来的msg值是: {{ msg }}</p>
<p>父组件传递过来的user值是: {{ user }}</p>
<p>需要传递给父组件的count值:{{ count }}</p>
<button @click="add()">count数值+1</button>
</div>
</template>
<script>
export default {
// 组件的自定义属性
props: ["msg", "user"],
// 组件的私有属性
data() {
return {
msgIn: this.msg,
userIn: this.user,
count: 0, // 需要传递给父组件的值
};
},
methods: {
add() {
this.count += 1;
// 修改数据时,通过$emit()触发自定义事件
this.$emit('numChange', this.count);
}
},
};
</script>
<style scoped>
p {
color: blue; /* 不加/deep/时,生成的选择器格式为 p[data-v-052242de] */
}
</style>
兄弟组件之间的数据共享
在 Vue2.x 中,兄弟组件之间数据共享的方案是 EventBus。
在components文件夹中,新建一个EventBus的JS文件(eventBus.js):
import Vue from 'vue'
// 向外共享 Vue 的实例对象
export default new Vue();
兄弟组件A(数据发送方):
// 1. 导入eventBus.js模块
import bus from './eventBus.js'
<script>
export default {
data() {
return {
msg: 'hello vue.js'
}
},
methods: {
sendMsg() {
// 2. 通过eventBus触发自定义事件,发送数据
bus.$emit('share', this.msg)
}
}
}
</script>
兄弟组件C(数据接收方):
// 1. 导入eventBus.js模块
import bus from './eventBus.js'
<script>
export default {
data() {
return {
msgFromLeft: '',
}
},
created() {
// 2. 通过eventBus监听自定义事件,接收数据
bus.$on('share', val => {
this.msgFromLeft = val;
})
}
}
</script>
EventBus 的使用步骤
① 创建 eventBus.js 模块,并向外共享一个 Vue 的实例对象
② 在数据发送方,调用 bus.$emit('事件名称', 要发送的数据) 方法触发自定义事件
③ 在数据接收方,调用 bus.$on('事件名称', 事件处理函数) 方法注册一个自定义事件
Ref引用
什么是ref引用
ref 用来辅助开发者在不依赖于 jQuery 的情况下,获取 DOM 元素或组件的引用。
每个 vue 的组件实例上,都包含一个 $refs 对象,里面存储着对应的 DOM 元素或组件的引用。默认情况下,
组件的 $refs 指向一个空对象。
使用ref引用DOM元素
如果想要使用 ref 引用页面上的 DOM 元素,则可以按照如下的方式进行操作:
App.vue
<template>
<div id="app">
<h1 ref="myh1">App根组件</h1>
<p>子组件传递过来的数据值:{{ countFromSon }}</p>
<button @click="getRef">切换H1的颜色</button>
</div>
</template>
<script>
// 1. 导入需要使用的.vue组件 import TestName from '@/components/TestName.vue'
export default {
name: "App",
// 组件的自定义属性
props: [],
// 组件的私有属性
data() {
return {
message: "hello, vue.js",
userInfo: { name: "张三", age: 20 },
countFromSon: 0,
};
},
methods: {
getNewCount(val) {
console.log("numChange事件被触发了");
this.countFromSon = val;
},
getRef() {
console.log(this);
this.$refs.myh1.style.color = "red";
},
},
// 2. 注册组件
components: {},
};
</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>
使用ref进行父组件调用子组件的方法
TestName.vue
<template>
<div class="test-container">
<h5>Son组件</h5>
<p>父组件传递过来的msg值是: {{ msg }}</p>
<p>父组件传递过来的user值是: {{ user }}</p>
<p>子组件的count值:{{ count }}</p>
<button @click="add">count数值+1</button>
<button @click="resetCount">count数值充值</button>
</div>
</template>
<script>
export default {
// 组件的自定义属性
props: ["msg", "user"],
// 组件的私有属性
data() {
return {
msgIn: this.msg,
userIn: this.user,
count: 0,
};
},
methods: {
add() {
this.count += 1;
// 修改数据时,通过$emit()触发自定义事件
this.$emit("numChange", this.count);
},
resetCount() {
this.count = 0;
},
},
};
</script>
<style scoped>
p {
color: blue; /* 不加/deep/时,生成的选择器格式为 p[data-v-052242de] */
}
</style>
App.vue,通过ref属性调用子组件的方法
<template>
<div id="app">
<h1 ref="myH1">App根组件</h1>
<button @click="toggleH1Color">切换H1标题颜色</button>
<button @click="resetSubComCount">重置子组件的count数值</button>
<MyTestName
:msg="message"
:user="userInfo"
ref="componentTestName"
></MyTestName>
</div>
</template>
<script>
// 1. 导入需要使用的.vue组件 import TestName from '@/components/TestName.vue'
import MyTestName from "@/components/TestName";
export default {
name: "App",
// 组件的自定义属性
props: [],
// 组件的私有属性
data() {
return {
message: "hello, vue.js",
userInfo: { name: "张三", age: 20 },
countFromSon: 0,
};
},
methods: {
toggleH1Color() {
console.log(this);
this.$refs.myH1.style.color = "red";
},
resetSubComCount() {
console.log(this);
this.$refs.componentTestName.resetCount();
},
},
// 2. 注册组件
components: {
MyTestName,
},
};
</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>
动态组件
什么是动态组件
动态组件指的是动态切换组件的显示与隐藏。
如何实现动态组件渲染
vue 提供了一个内置的<component>
组件(只是一个占位符),专门用来实现动态组件的渲染。示例代码如下:
data() {
// 1. 当前要渲染的组件名称
return { comName: 'Left' }
}
<!-- 2. 通过is属性,动态指定要渲染的组件 -->
<component :is="comName"></component>
<!-- 3. 点击按钮,动态切换组件的名称 -->
<button @click="comName = 'Left'">展示Left组件</button>
<button @click="comName = 'Right'">展示Right组件</button>
- component标签是Vue内置的,作用:组件的占位符
- is属性的值,表示要渲染的组件的名字
举例说明,前提子组件TestName.vue已经创建。App.vue组件:
<template>
<div id="app">
<h1 ref="myH1">App根组件</h1>
<button @click="componentToggle">展示组件TestName</button>
<component :is="componentId"></component>
</div>
</template>
<script>
// 1. 导入需要使用的.vue组件 import TestName from '@/components/TestName.vue'
import MyTestName from "@/components/TestName";
export default {
name: "App",
// 组件的自定义属性
props: [],
// 组件的私有属性
data() {
return {
componentId: "",
};
},
methods: {
componentToggle() {
console.log(this.componentId);
if (this.componentId !== "MyTestName") {
this.componentId = "MyTestName";
} else {
this.componentId = "";
}
},
},
// 2. 注册组件
components: {
MyTestName,
},
};
</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>
使用keep-alive保持状态
默认情况下,切换动态组件时无法保持组件的状态。
此时可以使用 vue 内置的 <keep-alive>
组件保持动态组件的状态。
<keep-alive>
可以把内部的组件进行缓存,而不是销毁组件。
示例代码如下:
<keep-alive>
<component :is="componentId"></component>
</keep-alive>
举例,App.vue
<template>
<div id="app">
<h1 ref="myH1">App根组件</h1>
<button @click="componentToggle">展示组件TestName</button>
<keep-alive>
<component :is="componentId"></component>
</keep-alive>
</div>
</template>
<script>
// 1. 导入需要使用的.vue组件 import TestName from '@/components/TestName.vue'
import MyTestName from "@/components/TestName";
export default {
name: "App",
// 组件的自定义属性
props: [],
// 组件的私有属性
data() {
return {
componentId: "",
};
},
methods: {
componentToggle() {
console.log(this.componentId);
if (this.componentId !== "MyTestName") {
this.componentId = "MyTestName";
} else {
this.componentId = "";
}
},
},
// 2. 注册组件
components: {
MyTestName,
},
};
</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>
keep-alive对应的生命周期函数
当组件被缓存时,会自动触发组件的deactivated生命周期函数。
当组件被激活时,会自动触发组件的activated生命周期函数。
<template>
<div id="app">
<h1 ref="myH1">App根组件</h1>
<button @click="componentToggle">展示组件TestName</button>
<keep-alive>
<component :is="componentId"></component>
</keep-alive>
</div>
</template>
<script>
// 1. 导入需要使用的.vue组件 import TestName from '@/components/TestName.vue'
import MyTestName from "@/components/TestName";
export default {
name: "App",
// 组件的自定义属性
props: [],
// 组件的私有属性
data() {
return {
componentId: "",
};
},
methods: {
componentToggle() {
console.log(this.componentId);
if (this.componentId !== "MyTestName") {
this.componentId = "MyTestName";
} else {
this.componentId = "";
}
},
},
// 2. 注册组件
components: {
MyTestName,
},
created() {
console.log("TestName组件被创建了");
},
destroyed() {
console.log("TestName组件被销毁了");
},
// 当组件第一次被创建的时候,既会执行created生命周期,也会执行activated生命周期
// 当组件被激活的时候,只会触发activated生命周期,不再触发created生命周期,因为组件没有被重新创建
activated() {
console.log("组件被激活了,activated");
},
deactivated() {
console.log("组件被去激活了,deactivated");
},
};
</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>
keep-alive的include/exclude属性
include 属性用来指定:只有名称匹配的组件会被缓存。多个组件名之间使用英文的逗号分隔:
<keep-alive include="Left,Right">
<component :is="componentId"></component>
</keep-alive>
exclude 属性用来指定:除了名称匹配的其他的组件会被缓存。多个组件名之间使用英文的逗号分隔:
<keep-alive exclude="Right">
<component :is="componentId"></component>
</keep-alive>
- 在使用keep-alive时候,可以通过include属性指定哪些组件需要被缓存。
- 在使用keep-alive时候,可以通过exclude属性指定哪些组件不需要被缓存。
- 但是,不要同时使用include和exclude这两个属性。
了解组件注册名称和组件声明时name的区别
- 组件的“注册名称”主要应用场景:以标签的形式,把注册好的组件,渲染和使用到页面结构之中。
- 组件声明时候的“name”名称的主要应用场景:结合
<keep-alive>
标签实现组件缓存功能;以及在调试工具中看到组件的name名称。
<script>
export default {
// 当提供了name属性之后,组件的名称,就是name属性的值
name: 'MyComponent',
// 组件的自定义属性
props: [],
// 组件的私有属性
data() {};
},
methods: {},
};
</script>
components属性中名称为组件的注册名称
<script>
export default {
// 组件的自定义属性
props: [],
// 组件的私有属性
data() {};
},
components: {
// 如果在声明组件的时候,没有为组件指定name名称,则组件的名称默认就是“注册时候的名称”
TestName,
}
methods: {},
};
</script>
插槽
什么是插槽
插槽(slot)是 vue 为组件的封装者提供的能力。允许开发者在封装组件时,把不确定的、希望由用户指定的
部分定义为插槽。
可以把插槽认为是组件封装期间,为用户预留的内容的占位符。以实现组件的复用。
体验插槽的基础用法
在封装组件时,可以通过 <slot>
元素定义插槽,从而为用户预留内容占位符。
父组件,App.vue:
<template>
<div id="app">
<h1>App根组件</h1>
<MyTestName>
<p>这是子组件的内容区域,声明在父组件的p标签中</p>
</MyTestName>
</div>
</template>
<script>
// 导入需要使用的.vue组件 import TestName from '@/components/TestName.vue'
import MyTestName from "@/components/TestName";
export default {
name: "App",
// 组件的自定义属性
props: [],
// 组件的私有属性
data() {
return {
componentId: "",
};
},
methods: {},
// 注册组件
components: {
MyTestName,
},
};
</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>
子组件,TestName.vue:
<template>
<div class="test-container">
<h5>Son组件</h5>
<!-- 声明一个插槽区域 -->
<!-- Vue官方规定:每一个slot插槽,都要有一个name名称 -->
<!-- 如果省略了slot的name属性,则有一个默认名称,叫做default -->
<slot name="default"></slot>
</div>
</template>
<script>
export default {
name: "MyComponent",
// 组件的自定义属性
props: [],
// 组件的私有属性
data() {
return {};
},
methods: {},
};
</script>
<style scoped>
p {
color: blue; /* 不加/deep/时,生成的选择器格式为 p[data-v-052242de] */
}
</style>
v-slot指令以及插槽的后备内容
在向具名插槽提供内容的时候,我们可以在一个 <template>
元素上使用 v-slot 指令,并以 v-slot 的参数的
形式提供其名称。
具名插槽的定义和使用
如果在封装组件时需要预留多个插槽节点,则需要为每个 <slot>
插槽指定具体的 name 名称。这种带有具体
名称的插槽叫做“具名插槽”。
父组件,App.vue:
<template>
<div id="app">
<h1>App根组件</h1>
<MyTestName>
<!-- 1. 如果要把内容填充到指定名称的插槽中,需要使用“v-slot:”这个指令 -->
<!-- 2. v-slot: 后面要跟上插槽的名称 -->
<!-- 3. v-slot: 指令不能直接用在元素身上,必须用在template标签上 -->
<!-- 4. template这个标签,它是一个虚拟的标签,只起到包裹性质的作用,但是,不会被渲染为任何实质性的html元素 -->
<!-- 5. v-slot: 指令的间歇形式是“#” -->
<template v-slot:header>
<h>滕王阁序</h>
</template>
<!-- v-slot: 指令的间歇形式是“#” -->
<template #default>
<p>豫章故郡,洪都新府。</p>
<p>星分翼轸,地接衡庐。</p>
<p>襟三江而带五湖,控蛮荆而引瓯越。</p>
</template>
<template v-slot:footer>
<p>落款:王勃</p>
</template>
</MyTestName>
</div>
</template>
<script>
// 导入需要使用的.vue组件 import TestName from '@/components/TestName.vue'
import MyTestName from "@/components/TestName";
export default {
name: "App",
// 组件的自定义属性
props: [],
// 组件的私有属性
data() {
return {
componentId: "",
};
},
methods: {},
// 注册组件
components: {
MyTestName,
},
};
</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>
子组件,TestName.vue,包含了插槽的后备内容。
<template>
<div class="test-container">
<h5>Son组件</h5>
<!-- 声明一个插槽区域 -->
<!-- Vue官方规定:每一个slot插槽,都要有一个name名称 -->
<!-- 如果省略了slot的name属性,则有一个默认名称,叫做default -->
<div class="header-box">
<slot name="header"></slot>
</div>
<div class="content-box">
<!-- 在声明插槽时候,提供了一个默认的内容 -->
<!-- 当用户没有指定内容时候,该后备内容即会生效 -->
<slot name="default">这是default插槽的默认内容</slot>
</div>
<div class="footer-box">
<slot name="footer"></slot>
</div>
</div>
</template>
<script>
export default {
name: "MyComponent",
// 组件的自定义属性
props: [],
// 组件的私有属性
data() {
return {};
},
methods: {},
};
</script>
<style lang="less" scoped>
.test-container {
> div {
min-height: 30px;
}
.header-box {
background-color: pink;
height: 10px;
}
.content-box {
background-color: lightblue;
height: 100px;
}
.footer-box {
background-color: lightgreen;
height: 10px;
}
}
</style>
作用域插槽的基本用法
在封装组件的过程中,可以为预留的 <slot>
插槽绑定 props 数据,这种带有 props 数据的 <slot>
叫做“作用域插槽”。
在
父组件,App.vue
<template>
<div id="app">
<h1>App根组件</h1>
<MyTestName>
<!-- 1. 如果要把内容填充到指定名称的插槽中,需要使用“v-slot:”这个指令 -->
<!-- 2. v-slot: 后面要跟上插槽的名称 -->
<!-- 3. v-slot: 指令不能直接用在元素身上,必须用在template标签上 -->
<!-- 4. template这个标签,它是一个虚拟的标签,只起到包裹性质的作用,但是,不会被渲染为任何实质性的html元素 -->
<!-- 5. v-slot: 指令的间歇形式是“#” -->
<template v-slot:header>
<h>滕王阁序</h>
</template>
<template #default="scope">
<p>豫章故郡,洪都新府。</p>
<p>星分翼轸,地接衡庐。</p>
<p>襟三江而带五湖,控蛮荆而引瓯越。</p>
<p>{{ scope.msg }}</p>
</template>
<template v-slot:footer>
<p>落款:王勃</p>
</template>
</MyTestName>
</div>
</template>
<script>
// 导入需要使用的.vue组件 import TestName from '@/components/TestName.vue'
import MyTestName from "@/components/TestName";
export default {
name: "App",
// 组件的自定义属性
props: [],
// 组件的私有属性
data() {
return {
componentId: "",
};
},
methods: {},
// 注册组件
components: {
MyTestName,
},
};
</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>
子组件,TestName.vue
<template>
<div class="test-container">
<h5>Son组件</h5>
<!-- 声明一个插槽区域 -->
<!-- Vue官方规定:每一个slot插槽,都要有一个name名称 -->
<!-- 如果省略了slot的name属性,则有一个默认名称,叫做default -->
<div class="header-box">
<slot name="header"></slot>
</div>
<div class="content-box">
<!-- 在封装组件时,为预留的<slot>提供属性对应的值,这种用法,叫作作用域插槽 -->
<slot name="default" msg="hello vue.js"></slot>
</div>
<div class="footer-box">
<slot name="footer"></slot>
</div>
</div>
</template>
<script>
export default {
name: "MyComponent",
// 组件的自定义属性
props: [],
// 组件的私有属性
data() {
return {};
},
methods: {},
};
</script>
<style lang="less" scoped>
.test-container {
> div {
min-height: 30px;
}
.header-box {
background-color: pink;
height: 10px;
}
.content-box {
background-color: lightblue;
height: 130px;
}
.footer-box {
background-color: lightgreen;
height: 10px;
}
}
</style>
作用域插槽的结构解析
作用域插槽对外提供的数据对象,可以使用解构赋值简化数据的接收过程。
父组件,App.vue
<template>
<div id="app">
<h1>App根组件</h1>
<MyTestName>
<!-- 1. 如果要把内容填充到指定名称的插槽中,需要使用“v-slot:”这个指令 -->
<!-- 2. v-slot: 后面要跟上插槽的名称 -->
<!-- 3. v-slot: 指令不能直接用在元素身上,必须用在template标签上 -->
<!-- 4. template这个标签,它是一个虚拟的标签,只起到包裹性质的作用,但是,不会被渲染为任何实质性的html元素 -->
<!-- 5. v-slot: 指令的间歇形式是“#” -->
<template v-slot:header>
<h>滕王阁序</h>
</template>
<!-- 作用域插槽对外提供的数据对象,可以通过“结构赋值”简化接收的过程 -->
<template #default="{ msg, user }">
<p>豫章故郡,洪都新府。</p>
<p>星分翼轸,地接衡庐。</p>
<p>襟三江而带五湖,控蛮荆而引瓯越。</p>
<p>{{ msg }}</p>
<p>{{ user }}</p>
</template>
<template v-slot:footer>
<p>落款:王勃</p>
</template>
</MyTestName>
</div>
</template>
<script>
// 导入需要使用的.vue组件 import TestName from '@/components/TestName.vue'
import MyTestName from "@/components/TestName";
export default {
name: "App",
// 组件的自定义属性
props: [],
// 组件的私有属性
data() {
return {
componentId: "",
};
},
methods: {},
// 注册组件
components: {
MyTestName,
},
};
</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>
子组件,TestName.vue
<template>
<div class="test-container">
<h5>Son组件</h5>
<!-- 声明一个插槽区域 -->
<!-- Vue官方规定:每一个slot插槽,都要有一个name名称 -->
<!-- 如果省略了slot的name属性,则有一个默认名称,叫做default -->
<div class="header-box">
<slot name="header"></slot>
</div>
<div class="content-box">
<!-- 在封装组件时,为预留的<slot>提供属性对应的值,这种用法,叫作作用域插槽 -->
<slot name="default" msg="hello vue.js" :user="userInfo"></slot>
</div>
<div class="footer-box">
<slot name="footer"></slot>
</div>
</div>
</template>
<script>
export default {
name: "MyComponent",
// 组件的自定义属性
props: [],
// 组件的私有属性
data() {
return {
userInfo: {
name: "张三",
age: 24,
},
};
},
methods: {},
};
</script>
<style lang="less" scoped>
.test-container {
> div {
min-height: 30px;
}
.header-box {
background-color: pink;
height: 10px;
}
.content-box {
background-color: lightblue;
height: 170px;
}
.footer-box {
background-color: lightgreen;
height: 10px;
}
}
</style>
自定义指令
什么是自定义指令
vue 官方提供了 v-text、v-for、v-model、v-if 等常用的指令。除此之外 vue 还允许开发者自定义指令。
vue 中的自定义指令分为两类,分别是:
-
私有自定义指令
-
全局自定义指令
私有自定义指令
在每个 vue 组件中,可以在 directives 节点下声明私有自定义指令。
App.vue
<template>
<div id="app">
<h1 v-color>App根组件</h1>
<p>测试内容</p>
</div>
</template>
<script>
export default {
name: "App",
// 组件的自定义属性
props: [],
// 组件的私有属性
data() {
return {};
},
// 私有自定义指令的节点
directives: {
// 定义名为color的指令,指向一个配置对象
color: {
// 当指令第一次被绑定到元素上的时候,会立即触发bind函数
// 形参中el表示当前指令所绑定到的哪个DOM对象
bind(el) {
console.log(el);
el.style.color = 'red';
}
},
},
methods: {},
// 注册组件
components: {},
};
</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>
使用binding.value获取指令绑定的值
App.vue
<template>
<div id="app">
<h1 v-color>App根组件</h1>
<p v-color="'red'">测试内容</p>
</div>
</template>
<script>
export default {
name: "App",
// 组件的自定义属性
props: [],
// 组件的私有属性
data() {
return {};
},
// 私有自定义指令的节点
directives: {
// 定义名为color的指令,指向一个配置对象
color: {
// 当指令第一次被绑定到元素上的时候,会立即触发bind函数
// 形参中el表示当前指令所绑定到的哪个DOM对象
bind(el, binding) {
console.log(el);
console.log(binding);
el.style.color = binding.value ? binding.value : "blue";
},
},
},
methods: {},
// 注册组件
components: {},
};
</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>
update函数
App.vue
<template>
<div id="app">
<h1 v-color="color">App根组件</h1>
<p v-color="'red'">测试内容</p>
<button @click="color = 'yellow'">改变color的颜色值</button>
</div>
</template>
<script>
export default {
name: "App",
// 组件的自定义属性
props: [],
// 组件的私有属性
data() {
return {
color: "blue",
};
},
// 私有自定义指令的节点
directives: {
// 定义名为color的指令,指向一个配置对象
color: {
// 当指令第一次被绑定到元素上的时候,会立即触发bind函数
// 形参中el表示当前指令所绑定到的哪个DOM对象
// binding函数只调用1次
// 当指令第一次绑定到元素时调用,当DOM更新时bind函数不会被触发
bind(el, binding) {
// console.log(el);
console.log(binding);
el.style.color = binding.value;
console.log("el.style.color: " + el.style.color);
},
// update函数会在每次DOM更新时被调用
update(el, binding) {
el.style.color = binding.value;
},
},
},
methods: {},
// 注册组件
components: {},
};
</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>
自定义指令-函数简写
如果 bind 和update 函数中的逻辑完全相同,则对象格式的自定义指令可以简写成函数格式:
<template>
<div id="app">
<h1 v-color="color">App根组件</h1>
<p v-color="'red'">测试内容</p>
<button @click="color = 'yellow'">改变color的颜色值</button>
</div>
</template>
<script>
export default {
name: "App",
// 组件的自定义属性
props: [],
// 组件的私有属性
data() {
return {
color: "blue",
};
},
// 私有自定义指令的节点
directives: {
// bind 和update 函数中的逻辑完全相同,可以缩写至一个方法中
color(el, binding) {
// console.log(el);
console.log(binding);
console.log("触发了v-color的bind函数");
el.style.color = binding.value;
},
},
methods: {},
// 注册组件
components: {},
};
</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>
全局自定义指令(推荐)
全局共享的自定义指令需要通过“Vue.directive()”进行声明。
在main.js中进行全局声明。main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
// 私有自定义指令的节点
Vue.directive('color', (el, binding) => {
el.style.color = binding.value;
});
new Vue({
render: h => h(App),
}).$mount('#app')
App.vue
<template>
<div id="app">
<h1 v-color="color">App根组件</h1>
<p v-color="'red'">测试内容</p>
<button @click="color = 'yellow'">改变color的颜色值</button>
</div>
</template>
<script>
export default {
name: "App",
// 组件的自定义属性
props: [],
// 组件的私有属性
data() {
return {
color: "blue",
};
},
methods: {},
// 注册组件
components: {},
};
</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>
路由
路由(英文:router)就是对应关系。
SPA 与前端路由
SPA 指的是一个 web 网站只有唯一的一个 HTML 页面,所有组件的展示与切换都在这唯一的一个页面内完成。
此时,不同组件之间的切换需要通过前端路由来实现。
结论:在 SPA 项目中,不同功能之间的切换,要依赖于前端路由来完成!
什么是前端路由
通俗易懂的概念:Hash 地址与组件之间的对应关系。
前端路由的工作方式
① 用户点击了页面上的路由链接
② 导致了 URL 地址栏中的 Hash 值发生了变化
③ 前端路由监听了到 Hash 地址的变化
④ 前端路由把当前 Hash 地址对应的组件渲染都浏览器中
结论:前端路由,指的是 Hash 地址与组件之间的对应关系!
实现简易的前端路由
-
通过
<component>
标签,结合 comName 动态渲染组件。示例代码如下:<!-- 通过is属性,指定要展示的组件的名称 --> <component :is="compName"></component> <script> export default { name: 'App', data() { return { // 要展示的组件的名称 compName: 'Home' } } } </script>
-
在 App.vue 组件中,为
<a>
链接添加对应的 hash 值<a href="#/home">Home</a> <a href="#/movie">Movie</a> <a href="#/about">About</a>
-
在 created 生命周期函数中,监听浏览器地址栏中 hash 地址的变化,动态切换要展示的组件的名称。
<script> export default { created() { window.onhashchange = () => { switch (location.hash) { case '#/home': // 单击了“首页”的链接 this.compName = 'Home'; break; case '#/movie': // 单击了“电影”的链接 this.compName = 'Movie'; break; case '#/about': // 单击了“关于”的链接 this.compName = 'About'; break; } } } } </script>
举例说明
-
在src/components文件夹中,新建子组件CompHome.vue
<template> <div class="home-container"> <h3>Home组件</h3> </div> </template> <script> export default { methods: {}, }; </script> <style lang="less" scoped> .home-container { background-color: skyblue; min-height: 200px; } </style>
-
在src/components文件夹中,新建子组件CompMovie.vue
<template> <div class="movie-container"> <h3>Movie组件</h3> </div> </template> <script> export default { methods: {}, }; </script> <style lang="less" scoped> .movie-container { background-color: lightgreen; min-height: 200px; } </style>
-
在src/components文件夹中,新建子组件CompAbout.vue
<template> <div class="about-container"> <h3>About组件</h3> </div> </template> <script> export default { methods: {}, }; </script> <style lang="less" scoped> .about-container { background-color: orange; min-height: 200px; } </style>
-
修改App.vue组件
<template> <div id="app"> <h1>App根组件</h1> <div class="app-container"> <a href="#/home">首页</a> <a href="#/movie">电影</a> <a href="#/about">关于</a> <hr /> <component :is="compName"></component> </div> </div> </template> <script> import Home from "@/components/CompHome.vue"; import Movie from "@/components/CompMovie.vue"; import About from "@/components/CompAbout.vue"; export default { name: "App", components: { Home, Movie, About, }, data() { return { compName: '', } }, created() { window.onhashchange = () => { switch (location.hash) { case "#/home": // 单击了“首页”的链接 this.compName = "Home"; break; case "#/movie": // 单击了“电影”的链接 this.compName = "Movie"; break; case "#/about": // 单击了“关于”的链接 this.compName = "About"; break; } }; }, }; </script> <style lang="less" scoped> .app-container { background-color: #efefef; overflow: hidden; margin: 10px; padding: 15px; > a { margin-right: 10px; } } </style>
vue-router的基本用法
vue-router 是 vue.js 官方给出的路由解决方案。它只能结合 vue 项目进行使用,能够轻松的管理 SPA 项目
中组件的切换。
vue-router 的官方文档地址:https://router.vuejs.org/zh/
vue-router安装和配置的步骤
① 安装 vue-router 包
② 创建路由模块
③ 导入并挂载路由模块
④ 声明路由链接和占位符
-
项目中安装vue-router。可以执行以下命令安装:
npm i vue-router -S
-
创建路由模块。在src源代码目录下,新建router/index.js 路由模块,并初始化如下的代码:
// src/router/index.js 就是当前项目的路由模块 // 1. 导入Vue和VueRouter的包 import Vue from 'vue' import VueRouter from 'vue-router' // 2. 把 VueRouter 安装为 Vue 项目的插件 // Vue.use() 函数的作用,就是来安装插件的 Vue.use(VueRouter) // 3. 创建路由的实例对象 const router = new VueRouter() // 4. 向外共享路由的实例对象 export default router
如果需要加上组件的路由,可以在创建路由的实例对象中,增加一个routes属性,定义hash地址和组件之间的对应关系
// src/router/index.js 就是当前项目的路由模块 // 导入Vue和VueRouter的包 import Vue from 'vue' import VueRouter from 'vue-router' // 导入需要路由的组件 import Home from '@/components/CompHome.vue' import Movie from '@/components/CompMovie.vue' import About from '@/components/CompAbout.vue' // 把 VueRouter 安装为 Vue 项目的插件 // Vue.use() 函数的作用,就是来安装插件的 Vue.use(VueRouter) // 创建路由的实例对象 const router = new VueRouter({ // routes是一个数组,作用:定义“hash地址”与“组件”之间的对应关系 routes: [ // 重定向的路由规则 { path: '/', component: Home }, // 路由规则 { path: '/home', component: Home }, { path: '/movie', component: Movie }, { path: '/about', component: About } ] }) // 向外共享路由的实例对象 export default router
-
导入并挂载路由模块。在src/main.js 入口文件中,导入并挂载路由模块。示例代码如下:
import Vue from 'vue' import App from './App2.vue' // 导入路由模块,目的:拿到路由的实例对象 // 在进行模块化导入的时候,如果给定的是文件夹,则默认导入这个文件夹下,名字叫做 index.js 的文件,因此此处可以直接引导到文件夹,不用引导到具体的文件中 import router from '@/router' Vue.config.productionTip = false new Vue({ render: h => h(App), // 在 Vue 项目中,要想把路由用起来,必须把路由实例对象,通过下面的方式进行挂载 // router: 路由的实例对象 router }).$mount('#app')
声明路由链接和占位符
在src/App.vue 组件中,使用vue-router 提供的<router-link>
和<router-view>
声明路由链接和占位符:
<template>
<div id="app">
<h1>App根组件</h1>
<div class="app-container">
<!-- <a href="#/home">首页</a>> -->
<router-link to="/home">首页</router-link>>
<router-link to="/movie">电影</router-link>>
<router-link to="/about">关于</router-link>>
<hr />
<!-- 只要在项目中安装和配置了vue-router,就可以使用router-view这个组件了 -->
<!-- 它的作用很单纯,就是占位符 -->
<router-view></router-view>
</div>
</div>
</template>
路由重定向
路由重定向指的是:用户在访问地址A 的时候,强制用户跳转到地址C ,从而展示特定的组件页面。
通过路由规则的redirect属性,指定一个新的路由地址,可以很方便地设置路由的重定向:
router/index.js中修改如下:
// src/router/index.js 就是当前项目的路由模块
// 导入Vue和VueRouter的包
import Vue from 'vue'
import VueRouter from 'vue-router'
// 导入需要路由的组件
import Home from '@/components/CompHome.vue'
import Movie from '@/components/CompMovie.vue'
import About from '@/components/CompAbout.vue'
// 把 VueRouter 安装为 Vue 项目的插件
// Vue.use() 函数的作用,就是来安装插件的
Vue.use(VueRouter)
// 创建路由的实例对象
const router = new VueRouter({
// routes是一个数组,作用:定义“hash地址”与“组件”之间的对应关系
routes: [
// 重定向的路由规则
{ path: '/', component: Home },
// 路由规则
{ path: '/home', component: Home },
{ path: '/movie', component: Movie },
{ path: '/about', component: About }
]
})
// 向外共享路由的实例对象
export default router
嵌套路由
通过路由实现组件的嵌套展示,叫作嵌套路由。
声明子路由链接和子路由占位符
在 About.vue 组件中,声明 tab1 和 tab2 的子路由链接以及子路由占位符。
<template>
<div class="ablut-container">
<h3>About组件</h3>
<!-- 1. 在关于页面中,声明两个子路由链接 -->
<router-link to="/about/tab1">tab1</router-link>
<router-link to="/about/tab2">tab2</router-link>
<hr />
<!-- 2. 在关于页面中,声明子路由的占位符 -->
<router-view></router-view>
</div>
</template>
通过 children 属性声明子路由规则
在 src/router/index.js 路由模块中,导入需要的组件,并使用 children 属性声明子路由规则:
import Tab1 from '@components/tabs.Tab1.vue'
import Tab2 from '@components/tabs.Tab2.vue'
const router = new VueRouter({
routes: [
{ // about页面的路由规则(父级路由规则)
path: '/about',
component: About,
children: [ // 1. 通过children属性,嵌套声明子级路由规则
{ path: 'tab1', components: Tab1 }, // 2. 访问/about/tab1时,展示Tab1组件
{ path: 'tab2', components: Tab2 }, // 2. 访问/about/tab2时,展示Tab2组件
]
}
]
})
完整示例 *
-
新建Vue CLI工程,参见[新建Vue CLI工程(带路由工程)【模板】](# 新建Vue CLI工程(带路由工程)【模板】)。
-
在src/views文件夹中,新建子组件CompHome.vue
<template> <div class="home-container"> <h3>Home组件</h3> </div> </template> <script> export default { methods: {}, }; </script> <style lang="less" scoped> .home-container { background-color: skyblue; min-height: 200px; } </style>
-
在src/views文件夹中,新建子组件CompMovie.vue
<template> <div class="movie-container"> <h3>Movie组件</h3> </div> </template> <script> export default { methods: {}, }; </script> <style lang="less" scoped> .movie-container { background-color: lightgreen; min-height: 200px; } </style>
-
在src/views文件夹中,新建子组件CompAbout.vue
<template> <div class="about-container"> <h3>About组件</h3> <!-- 子级路由链接 --> <router-link to="/about/tab1">Tab1</router-link> | <router-link to="/about/tab2">Tab2</router-link> <hr /> <!-- 子级路由占位符 --> <router-view></router-view> </div> </template> <script> export default { methods: {}, }; </script> <style lang="less" scoped> .about-container { background-color: orange; min-height: 200px; } </style>
-
在src/views文件夹中,新建文件夹tabs。
-
在tabs文件夹中新建子组件CompTab1.vue。
<template> <div class="tab1-container"> <h3>About中的Tab1组件</h3> </div> </template> <script> export default { methods: {}, }; </script> <style lang="less" scoped> .tab1-container { background-color: royalblue; min-height: 100px; } </style>
-
在tabs文件夹中新建子组件CompTab2.vue。
<template> <div class="about-container"> <h3>About中的Tab2组件</h3> </div> </template> <script> export default { methods: {}, }; </script> <style lang="less" scoped> .about-container { background-color: rebeccapurple; min-height: 100px; } </style>
-
在router/indes.js中,创建路由规则。
import Vue from 'vue' import VueRouter from 'vue-router' import CompHome from '@/views/CompHome.vue' import CompMovie from '@/views/CompMovie.vue' import CompAbout from '@/views/CompAbout.vue' import CompTab1 from '@/views/tabs/CompTab1' import CompTab2 from '@/views/tabs/CompTab2' Vue.use(VueRouter) const routes = [ // 重定向的路由规则 { path: '/', name: 'CompHome', component: CompHome }, // 路由规则 { path: '/home', name: 'CompHome', component: CompHome }, { path: '/movie', name: 'CompMovie', component: CompMovie }, { path: '/about', name: 'CompAbout', component: CompAbout, // redirect: '/about/tab1', // 默认子路由方式一:增加重定向功能 children: [ // 子路由规则,注意path不能加/前缀 // 默认子路由方式二:如果children数组中,某个路由规则的path值为空字符串,则这条路由规则路由为默认子路由 { path: '', name: 'CompTab1', component: CompTab1 }, { path: 'tab1', name: 'CompTab1', component: CompTab1 }, { path: 'tab2', name: 'CompTab2', component: CompTab2 }, ] }, ] const router = new VueRouter({ routes }) export default router
-
修改App.vue根组件内容。
<template> <div id="app"> <nav> <router-link to="/home">Home</router-link> | <router-link to="/movie">Movie</router-link> | <router-link to="/about">About</router-link> </nav> <router-view/> </div> </template> <style lang="less"> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; } nav { padding: 30px; a { font-weight: bold; color: #2c3e50; &.router-link-exact-active { color: #42b983; } } } </style>
动态路由
动态路由的概念
动态路由指的是:把 Hash 地址中可变的部分定义为参数项,从而提高路由规则的复用性。
在 vue-router 中使用英文的冒号(:)来定义路由的参数项。router/index.js示例代码如下:
// 路由中的动态参数以:进行声明,冒号后面的是动态参数的名称,id为自定义的一个参数
{ path: 'movie/:id', component: Movie }
// 将以下3个路由规则,合并成了一个,提高了路由规则的复用性
{ path: 'movie/1', component: Movie }
{ path: 'movie/2', component: Movie }
{ path: 'movie/3', component: Movie }
示例,以嵌套路由中的完整示例修改说明。
-
App.vue修改为如下所示。
<template> <div id="app"> <nav> <router-link to="/home">Home</router-link> | <!-- 注意1:在hash地址中,/后面的参数项,叫作路径参数 --> <!-- 在路由参数对象中,需要使用 this.$route.param来访问路径参数 --> <!-- 注意2:在hash地址中,?后面的参数项,叫作查询参数 --> <!-- 在查询参数对象中,需要使用 this.$route.query来访问查询参数 --> <!-- 注意3:在this.$route中,path只是路径部分,fullPath是完整的地址 --> <router-link to="/movie/1">Movie-洛神</router-link> | <router-link to="/movie/2?name=zs&age=24">Movie-雷神</router-link> | <router-link to="/movie/3">Movie-复联</router-link> | <router-link to="/about">About</router-link> </nav> <router-view/> </div> </template> <style lang="less"> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; } nav { padding: 30px; a { font-weight: bold; color: #2c3e50; &.router-link-exact-active { color: #42b983; } } } </style>
-
在router/indes.js中,修改为如下所示。
import Vue from 'vue' import VueRouter from 'vue-router' import CompHome from '@/views/CompHome.vue' import CompMovie from '@/views/CompMovie.vue' import CompAbout from '@/views/CompAbout.vue' import CompTab1 from '@/components/tabs/CompTab1' import CompTab2 from '@/components/tabs/CompTab2' Vue.use(VueRouter) const routes = [ // 重定向的路由规则 { path: '/', name: 'CompHome', component: CompHome }, // 路由规则 { path: '/home', name: 'CompHome', component: CompHome }, // 需求:在Movie组件中,希望根据mid的值,展示对应电影的详情信息 { path: '/movie/:mid', name: 'CompMovie', component: CompMovie }, { path: '/about', name: 'CompAbout', component: CompAbout, // redirect: '/about/tab1', // 默认子路由方式一:增加重定向功能 children: [ // 子路由规则,注意path不能加/前缀 // 默认子路由方式二:如果children数组中,某个路由规则的path值为空字符串,则这条路由规则路由为默认子路由 { path: '', name: 'CompTab1', component: CompTab1 }, { path: 'tab1', name: 'CompTab1', component: CompTab1 }, { path: 'tab2', name: 'CompTab2', component: CompTab2 }, ] }, ] const router = new VueRouter({ routes }) export default router
方式一:$route.params 参数对象
在动态路由渲染出来的组件中,可以使用 this.$route.params 对象访问到动态匹配的参数值。
在CompMovie.vue组件中接收参数。
<template>
<div class="movie-container">
<h3>Movie组件</h3>
<h3>this.$route.params.xx方式:路径参数 -- {{ this.$route.params.mid }}</h3>
<h3>this.$route.query.xx方式:路径参数 -- {{ this.$route.query }}</h3>
<button @click="showThis">打印this</button>
</div>
</template>
<script>
export default {
methods: {
showThis() {
console.log(this);
}
},
};
</script>
<style lang="less" scoped>
.movie-container {
background-color: lightgreen;
min-height: 200px;
}
</style>
方式二:使用props接收路由参数
-
修改router/index.js,为Movie组件开启props传参。
import Vue from 'vue' import VueRouter from 'vue-router' import CompHome from '@/views/CompHome.vue' import CompMovie from '@/views/CompMovie.vue' import CompAbout from '@/views/CompAbout.vue' import CompTab1 from '@/components/tabs/CompTab1' import CompTab2 from '@/components/tabs/CompTab2' Vue.use(VueRouter) const routes = [ // 重定向的路由规则 { path: '/', name: 'CompHome', component: CompHome }, // 路由规则 { path: '/home', name: 'CompHome', component: CompHome }, // 为Movie组件开启props传参,从而方便的拿到动态参数的值 { path: '/movie/:mid', name: 'CompMovie', component: CompMovie, props: true }, { path: '/about', name: 'CompAbout', component: CompAbout, // redirect: '/about/tab1', // 默认子路由方式一:增加重定向功能 children: [ // 子路由规则,注意path不能加/前缀 // 默认子路由方式二:如果children数组中,某个路由规则的path值为空字符串,则这条路由规则路由为默认子路由 { path: '', name: 'CompTab1', component: CompTab1 }, { path: 'tab1', name: 'CompTab1', component: CompTab1 }, { path: 'tab2', name: 'CompTab2', component: CompTab2 }, ] }, ] const router = new VueRouter({ routes }) export default router
-
在CompMovie.vue组件中通过props自定义属性接收参数。
<template> <div class="movie-container"> <h3>Movie组件 -- {{ mid }}</h3> <h3>this.$route.params.xx:路径参数 -- {{ this.$route.params.mid }}</h3> <h3>this.$route.query.xx:查询参数 -- {{ this.$route.query }}</h3> <h3>this.$route.fullPath:完整地址 -- {{ this.$route.fullPath }}</h3> <button @click="showThis">打印this</button> </div> </template> <script> export default { props: ["mid"], methods: { showThis() { console.log(this); } }, }; </script> <style lang="less" scoped> .movie-container { background-color: lightgreen; min-height: 200px; } </style>
声明式导航&编程式导航
-
在浏览器中,点击链接实现导航的方式,叫做声明式导航。例如:
普通网页中点击<a>
链接、vue 项目中点击<router-link>
都属于声明式导航。 -
在浏览器中,调用 API 方法实现导航的方式,叫做编程式导航。例如:
普通网页中调用 location.href 跳转到新页面的方式,属于编程式导航。
vue-router中的编程式导航API
vue-router 提供了许多编程式导航的 API,其中最常用的导航 API 分别是:
- this.$router.push('hash 地址')
- 跳转到指定 hash 地址,并增加一条历史记录
- this.$router.replace('hash 地址')
- 跳转到指定的 hash 地址,并替换掉当前的历史记录
- this.$router.go(数值 n)
- 实现导航历史前进、后退
$router.push跳转指定页面
调用 this.$router.push() 方法,可以跳转到指定的 hash 地址,从而展示对应的组件页面。
会增加一个历史记录,即可以返回上一个网址中。
修改上述示例的CompHome.vue组件为例:
<template>
<div class="home-container">
<h3>Home组件</h3>
<button @click="gotoMovie">跳转到“洛神”页面</button>
</div>
</template>
<script>
export default {
methods: {
gotoMovie() {
// 通过编程式导航跳转到指定页面
this.$router.push('/movie/1');
}
},
};
</script>
<style lang="less" scoped>
.home-container {
background-color: skyblue;
min-height: 200px;
}
</style>
$router.replace跳转指定页面
调用 this.$router.replace() 方法,可以跳转到指定的 hash 地址,从而展示对应的组件页面。
修改上述示例的CompHome.vue组件为例:
<template>
<div class="home-container">
<h3>Home组件</h3>
<button @click="gotoMovie">跳转到“洛神”页面</button>
</div>
</template>
<script>
export default {
methods: {
gotoMovie() {
// 通过编程式导航跳转到指定页面
this.$router.replace('/movie/1');
}
},
};
</script>
<style lang="less" scoped>
.home-container {
background-color: skyblue;
min-height: 200px;
}
</style>
$router.go()
调用 this.$router.go() 方法,可以在浏览历史中前进和后退。
<template>
<div class="home-container">
<h3>Home组件</h3>
<button @click="goBack">后退</button>
</div>
</template>
<script>
export default {
methods: {
gotoMovie() {
// 后退到之前的组件页面
// 如果后退的层数超过上限,则原地不动
this.$router.goBack('-1');
}
},
};
</script>
$router.go 的简化用法:
在实际开发中,一般只会前进和后退一层页面。因此 vue-router 提供了如下两个便捷方法:
- $router.back():在历史记录中,后退到上一个页面
- $router.forward():在历史记录中,前进到下一个页面
导航守卫
导航守卫可以控制路由的访问权限。
全局前置守卫
每次发生路由的导航跳转时,都会触发全局前置守卫。因此,在全局前置守卫中,程序员可以对每个路由进行访问权限的控制(router/index.js):
// 创建路由实例对象
const router = new VueRouter({ ... });
// 调用路由实例对象的beforeEach方法,即可声明“全局前置守卫”
// 每次发生路由导航跳转的时候,都会自动触发fn这个“回调函数”
router.beforeEach(fn);
守卫方法的3个形参(控制后台主页的访问权限)
router/index.js:
// 为 router 实例对象,声明全局前置导航守卫
// 只要发生了路由的跳转,必然会触发 beforeEach 指定的 function 回调函数
router.beforeEach(function(to, from, next) {
// to 表示将要访问的路由的信息对象
// from 表示将要离开的路由的信息对象
// next() 函数表示放行的意思
// 分析:
// 1. 要拿到用户将要访问的 hash 地址
// 2. 判断 hash 地址是否等于 /main。
// 2.1 如果等于 /main,证明需要登录之后,才能访问成功
// 2.2 如果不等于 /main,则不需要登录,直接放行 next()
// 3. 如果访问的地址是 /main。则需要读取 localStorage 中的 token 值
// 3.1 如果有 token,则放行
// 3.2 如果没有 token,则强制跳转到 /login 登录页
if (to.path === '/main') {
// 要访问后台主页,需要判断是否有 token
const token = localStorage.getItem('token')
if (token) {
next()
} else {
// 没有登录,强制跳转到登录页
next('/login')
}
} else {
next()
}
})
next函数的 3 种调用方式
- 当前用户拥有后台主页的访问权限,直接放行:next()
- 当前用户没有后台主页的访问权限,强制其跳转到登录页面:next('/login')
- 当前用户没有后台主页的访问权限,不允许跳转到后台主页:next(false)
Vant组件库
Vant组件库基本使用【模板】
- 参考:Vant组件库:轻量、可靠的移动端 Vue 组件库。
-
新建VUE CLI工程。
-
参考快速上手 - Vant 3。包含安装Vant、安装vite-plugin-style-import插件(自动按需引入组件的样式)、配置插件。
-
以Button按钮为例说明。
-
在main.js中注册全局组件,增加以下代码。
import { Button } from "vant"; Vue.use(Button);
-
在应用组件中直接使用该组件即可。例如在App.vue中使用Button组件。
<van-button type="primary">主要按钮</van-button>
-
重新编译代码
npm run serve
-
案例说明
-
新建VUE CLI工程,带路由和less功能。
-
配置初始工程。
- 删除components文件夹中的文件。
- 删除views文件夹中的文件。
- 删除router/index.js中的默认路由配置。
- F12切换浏览器为移动端的浏览效果。
-
安装和导入Vant组件以及vite-plugin-style-import插件及插件配置。
-
在views文件夹,新建Home文件夹和CompHome.vue文件。
<template> <div class="home-container"> <h3>Home组件</h3> </div> </template> <script> export default {}; </script> <style lang="less" scoped></style>
-
在views文件夹,新建User文件夹和CompUser.vue文件。
<template> <div class="user-container"> <h3>User组件</h3> </div> </template> <script> export default {}; </script> <style lang="less" scoped></style>
-
main.js中引入组件。
import Vue from 'vue' import App from './App.vue' import router from './router' import { Button } from 'vant'; import { Tabbar, TabbarItem } from 'vant'; Vue.use(Button); Vue.use(Tabbar); Vue.use(TabbarItem); Vue.config.productionTip = false new Vue({ router, render: h => h(App) }).$mount('#app')
-
使用Tabbar 标签栏组件,并开启路由模式。App.vue
<template> <div id="app"> <h1>App根组件</h1> <van-button type="primary">主要按钮</van-button> <van-button type="success">成功按钮</van-button> <router-view /> <van-tabbar route> <van-tabbar-item replace to="/home" icon="home-o">首页</van-tabbar-item> <van-tabbar-item replace to="/user" icon="user-o">我的</van-tabbar-item> </van-tabbar> </div> </template> <style lang="less" scoped> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; } </style>
-
配置路由规则。router/index.js。
import Vue from 'vue' import VueRouter from 'vue-router' import Home from '@/views/Home/CompHome.vue' import User from '@/views/User/CompUser.vue' Vue.use(VueRouter) // 路由规则配置 const routes = [ // 定义“首页”的路由规则 { path: '/', component: Home }, { path: '/home', component: Home }, // 定义“我的”的路由规则 { path: '/user', component: User }, ] const router = new VueRouter({ routes }) export default router
VueX使用
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。
- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
- 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
操作步骤:
-
新建VUE CLI工程,带路由、Vuex和less功能。
-
配置初始工程。
- components/HelloWorld.vue中,删除多余的展示内容,只保留msg的信息。
- views/HomeView.vue中,只保留
<HelloWorld msg="Welcome to Your Vue.js App"/>
的展示内容。
state
-
store/index.js中新增一个状态num。
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ // state相当于组件中的data,专门用来存放全局的数据 state: { // 用来存放数据,类似于组件中的data num: 0, }, getters: { // 计算属性,类似于组件中的computed }, mutations: { // 存放方法,类似于组件中的methods }, actions: { // Action类似于mutation,不同在于:Action提交的是mutation,而不是直接变更状态 }, modules: { } })
-
修改App.vue内容。
<template> <div id="app"> <h3>App根组件的数字:{{ num }}</h3> <nav> <router-link to="/">Home</router-link> | <router-link to="/about">About</router-link> </nav> <router-view /> </div> </template> <script> import store from "./store"; export default { computed: { num() { return store.state.num; } }, }; </script> <style lang="less"> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; } nav { padding: 30px; a { font-weight: bold; color: #2c3e50; &.router-link-exact-active { color: #42b983; } } } </style>
-
修改components/HomeView.vue内容。
<template> <div class="home"> <HelloWorld msg="Welcome to Your Vue.js App"/> <p>Home页面的数字:{{ $store.state.num }}</p> </div> </template> <script> // @ is an alias to /src import HelloWorld from '@/components/HelloWorld.vue' export default { name: 'HomeView', components: { HelloWorld } } </script>
-
修改components/AboutView.vue内容(通过计算属性的方式进行展示)。
<template> <div class="about"> <h1>This is an about page</h1> <p>About页面的数字:{{ num }}</p> </div> </template> <script> export default { data() { return {} }, computed: { num() { return this.$store.state.num; }, }, }; </script>
getters
将组件中统一使用的computed都放到getters里面来操作。
-
修改store/index.js内容。
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ // state相当于组件中的data,专门用来存放全局的数据 state: { num: 10, toDos: [ { id: 1, text: 'text1...', done: true }, { id: 2, text: 'text2...', done: false }, { id: 3, text: 'text3...', done: true }, ], }, // getters相当于组件中的computed,getters是全局的,computed是组件内部使用的 getters: { // 通过属性访问 getNum(state) { return state.num; }, // 通过属性访问 doneTodos(state) { return state.toDos.filter((todo) => todo.done); }, // 通过属性访问 // Getter 也可以接受其他 getter 作为第二个参数 getTodosCount(state, getters) { return getters.doneTodos.length; }, // 通过方法访问 getTodoById: (state) => (id) => { return state.toDos.filter((todo) => todo.id === id); } }, mutations: { }, actions: { }, modules: { } })
-
修改App.vue内容。
<template> <div id="app"> <h3>App根组件的数字:{{ $store.getters.getNum }}</h3> <nav> <router-link to="/">Home</router-link> | <router-link to="/about">About</router-link> </nav> <router-view /> </div> </template> <script> export default {}; </script> <style lang="less"> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; } nav { padding: 30px; a { font-weight: bold; color: #2c3e50; &.router-link-exact-active { color: #42b983; } } } </style>
-
修改components/HomeView.vue内容。
<template> <div class="home"> <HelloWorld msg="Welcome to Your Vue.js App" /> <p>Home页面的数字:{{ $store.getters.getNum }}</p> <p>doneTodos:{{ $store.getters.doneTodos }}</p> <p>getTodosCount:{{ $store.getters.getTodosCount }}</p> <p>getTodoById:{{ $store.getters.getTodoById(2) }}</p> </div> </template> <script> // @ is an alias to /src import HelloWorld from "@/components/HelloWorld.vue"; export default { name: "HomeView", components: { HelloWorld, }, }; </script>
-
修改components/AboutView.vue内容(通过计算属性的方式进行展示)。
<template> <div class="about"> <h1>This is an about page</h1> <p>About页面的数字:{{ num }}</p> <p>getTodosCount:{{ getTodosCount }}</p> </div> </template> <script> export default { data() { return {}; }, computed: { num() { return this.$store.getters.getNum; }, getTodosCount() { return this.$store.getters.getTodosCount; }, }, }; </script>
mutations
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type)和一个回调函数 (handler) 。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数。
一条重要的原则就是要记住 mutation 必须是同步函数。
提交负荷
-
修改store/index.js内容。
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ // state相当于组件中的data,专门用来存放全局的数据 state: { num: 10, toDos: [ { id: 1, text: 'text1...', done: true }, { id: 2, text: 'text2...', done: false }, { id: 3, text: 'text3...', done: true }, ], }, // getters相当于组件中的computed,getters是全局的,computed是组件内部使用的 getters: { // 通过属性访问 getNum(state) { return state.num; }, // 通过属性访问 doneTodos(state) { return state.toDos.filter((todo) => todo.done); }, // 通过属性访问 // Getter 也可以接受其他 getter 作为第二个参数 getTodosCount(state, getters) { return getters.doneTodos.length; }, // 通过方法访问 getTodoById: (state) => (id) => { return state.toDos.filter((todo) => todo.id === id); } }, mutations: { numPlusOne(state) { state.num++; }, // 可以向 store.commit 传入额外的参数,即 mutation 的载荷(payload) numPlusX(state, x) { // x (payload)是一个形参,如果组件在commit时,有传这个参数过来,就存在,如果没有传过来,就是undefined state.num += x ? x : 1; }, // 在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读 numPlusObjectX(state, payload) { state.num += payload.amount; } }, actions: { }, modules: { } })
-
修改views/HomeView.vue内容。
<template> <div class="home"> <HelloWorld msg="Welcome to Your Vue.js App" /> <p>Home页面的数字:{{ $store.getters.getNum }}</p> <p>doneTodos:{{ $store.getters.doneTodos }}</p> <p>getTodosCount:{{ $store.getters.getTodosCount }}</p> <p>getTodoById:{{ $store.getters.getTodoById(2) }}</p> <button @click="numPlusOne">num数值+1</button> <button @click="$store.commit('numPlusX', 2)">num数值+X</button> <button @click="$store.commit('numPlusObjectX', { amount: 3 })">num数值+ObjX</button> <button @click="$store.commit({type: 'numPlusObjectX', amount: 3 })">num数值+ObjX(对象风格提交方式)</button> </div> </template> <script> // @ is an alias to /src import HelloWorld from "@/components/HelloWorld.vue"; export default { name: "HomeView", components: { HelloWorld, }, methods: { numPlusOne() { this.$store.commit("numPlusOne"); }, }, }; </script>
actions
actions是store中专门用来处理异步的,实际修改状态值的,还是mutations。
Action类似于mutation,不同在于:
- Action提交的是mutation,而不是直接变更状态。
- Action可以包含任意异步操作。
Action 函数接受一个与 store 实例具有相同方法和属性的context对象,因此你可以调用context.commit
提交一个mutation,或者通过context.state
和context.getters
来获取state和getters。
-
修改store/index.js内容。
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ // state相当于组件中的data,专门用来存放全局的数据 state: { num: 10, toDos: [ { id: 1, text: 'text1...', done: true }, { id: 2, text: 'text2...', done: false }, { id: 3, text: 'text3...', done: true }, ], }, // getters相当于组件中的computed,getters是全局的,computed是组件内部使用的 getters: { // 通过属性访问 getNum(state) { return state.num; }, // 通过属性访问 doneTodos(state) { return state.toDos.filter((todo) => todo.done); }, // 通过属性访问 // Getter 也可以接受其他 getter 作为第二个参数 getTodosCount(state, getters) { return getters.doneTodos.length; }, // 通过方法访问 getTodoById: (state) => (id) => { return state.toDos.filter((todo) => todo.id === id); } }, mutations: { numPlusOne(state) { state.num++; }, // 可以向 store.commit 传入额外的参数,即 mutation 的载荷(payload) numPlusX(state, x) { // x (payload)是一个形参,如果组件在commit时,有传这个参数过来,就存在,如果没有传过来,就是undefined state.num += x ? x : 1; }, // 在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读 numPlusObjectX(state, payload) { state.num += payload.amount; }, }, actions: { // Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象 // 因此可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters numPlusOneDelayNSec(context, nSec) { console.log("numPlusOneDelayNSec ing."); setTimeout(() => { context.commit('numPlusOne'); console.log("numPlusOnesDelayNSec done."); }, nSec) }, numPlusObjectXDelayNSec(context, payload) { console.log("numPlusObjectXDelayNSec ing."); setTimeout(() => { console.log('numPlusObjectXDelayNSec ing payload: ' + JSON.stringify(payload)); console.log("numPlusObjectXDelayNSec done."); }, payload.nSec) } }, modules: { } })
-
修改views/HomeView.vue内容。
<template> <div class="home"> <HelloWorld msg="Welcome to Your Vue.js App" /> <p>Home页面的数字:{{ $store.getters.getNum }}</p> <p>doneTodos:{{ $store.getters.doneTodos }}</p> <p>getTodosCount:{{ $store.getters.getTodosCount }}</p> <p>getTodoById:{{ $store.getters.getTodoById(2) }}</p> <button @click="numPlusOne">num数值+1</button> <button @click="$store.commit('numPlusX', 2)">num数值+X</button> <button @click="$store.commit('numPlusObjectX', { amount: 3 })"> num数值+ObjX </button> <button @click="$store.commit({ type: 'numPlusObjectX', amount: 3 })"> num数值+ObjX(对象风格提交方式) </button> <button @click="$store.dispatch('numPlusOneDelayNSec', 2000)"> numPlusOneDelayNSec(actions异步实现) </button> <!-- 以载荷形式分发 --> <button @click=" $store.dispatch('numPlusObjectXDelayNSec', { nSec: 2000, amount: 10, }) " > numPlusObjectXDelayNSec(actions异步实现) </button> <!-- 以对象形式分发 --> <button @click=" $store.dispatch({ type: 'numPlusObjectXDelayNSec', nSec: 2000, amount: 10, }) " > numPlusObjectXDelayNSec(actions异步实现) </button> </div> </template> <script> // @ is an alias to /src import HelloWorld from "@/components/HelloWorld.vue"; export default { name: "HomeView", components: { HelloWorld, }, methods: { numPlusOne() { this.$store.commit("numPlusOne"); }, }, }; </script>
辅助函数 *
mapState和mapGetters在组件中都是写在computed里面。
mapMutations和mapActions在组件中都是写在methods里面。
-
store/index.js内容不变。
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ // state相当于组件中的data,专门用来存放全局的数据 state: { num: 10, toDos: [ { id: 1, text: 'text1...', done: true }, { id: 2, text: 'text2...', done: false }, { id: 3, text: 'text3...', done: true }, ], }, // getters相当于组件中的computed,getters是全局的,computed是组件内部使用的 getters: { // 通过属性访问 getNum(state) { return state.num; }, // 通过属性访问 doneTodos(state) { return state.toDos.filter((todo) => todo.done); }, // 通过属性访问 // Getter 也可以接受其他 getter 作为第二个参数 getTodosCount(state, getters) { return getters.doneTodos.length; }, // 通过方法访问 getTodoById: (state) => (id) => { return state.toDos.filter((todo) => todo.id === id); } }, mutations: { numPlusOne(state) { state.num++; }, // 可以向 store.commit 传入额外的参数,即 mutation 的载荷(payload) numPlusX(state, x) { // x (payload)是一个形参,如果组件在commit时,有传这个参数过来,就存在,如果没有传过来,就是undefined state.num += x ? x : 1; }, // 在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读 numPlusObjectX(state, payload) { state.num += payload.amount; }, }, actions: { // Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象 // 因此可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters numPlusOneDelayNSec(context, nSec) { console.log("numPlusOneDelayNSec ing."); setTimeout(() => { context.commit('numPlusOne'); console.log("numPlusOnesDelayNSec done."); }, nSec) }, numPlusObjectXDelayNSec(context, payload) { console.log("numPlusObjectXDelayNSec ing."); setTimeout(() => { console.log('numPlusObjectXDelayNSec ing payload: ' + JSON.stringify(payload)); console.log("numPlusObjectXDelayNSec done."); }, payload.nSec) } }, modules: { } })
-
修改views/HomeView.vue内容。
<template> <div class="home"> <HelloWorld msg="Welcome to Your Vue.js App" /> <p>Home页面的数字:{{ getNum }}</p> <p>doneTodos:{{ doneTodos }}</p> <p>getTodosCount:{{ getTodosCount }}</p> <p>getTodoById:{{ getTodoById(2) }}</p> <button @click="numPlusOne">num数值+1</button> <button @click="numPlusX(2)">num数值+X</button> <button @click="numPlusObjectX({ amount: 3 })">num数值+ObjX</button> <button @click="numPlusObjectX({ amount: 3 })"> num数值+ObjX(对象风格提交方式) </button> <button @click="numPlusOneDelayNSec(2000)"> numPlusOneDelayNSec(actions异步实现) </button> <button @click="numPlusOneDelayNSecAlias(2000)"> numPlusOneDelayNSecAlias(actions异步实现) </button> <button @click=" numPlusObjectXDelayNSec({ nSec: 2000, amount: 10, }) " > numPlusObjectXDelayNSec(actions异步实现) </button> </div> </template> <script> // @ is an alias to /src import HelloWorld from "@/components/HelloWorld.vue"; import { mapState, mapGetters } from "vuex"; import { mapMutations, mapActions } from "vuex"; export default { name: "HomeView", components: { HelloWorld, }, computed: { ...mapState(["num"]), ...mapGetters(["getNum", "doneTodos", "getTodosCount", "getTodoById"]), }, methods: { ...mapMutations(["numPlusOne", "numPlusX", "numPlusObjectX"]), ...mapActions([ "numPlusOneDelayNSec", // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)` "numPlusObjectXDelayNSec", ]), ...mapActions({ numPlusOneDelayNSecAlias: "numPlusOneDelayNSec", // 将 `this.add()` 映射为 `this.$store.dispatch('increment')` }), numPlusOne() { this.$store.commit("numPlusOne"); }, }, }; </script>
-
修改views/AboutView.vue内容。
<template> <div class="about"> <h1>This is an about page</h1> <p>About页面的数字:{{ num }}</p> <p>getTodosCount:{{ getTodosCount }}</p> </div> </template> <script> import { mapState, mapGetters } from "vuex"; import { mapMutations, mapActions } from "vuex"; export default { data() { return {}; }, computed: { ...mapState(["num"]), ...mapGetters(["getNum", "doneTodos", "getTodosCount", "getTodoById"]), }, methods: { ...mapMutations(["numPlusOne", "numPlusX", "numPlusObjectX"]), ...mapActions(["numPlusOneDelayNSec", "numPlusObjectXDelayNSec"]), }, }; </script>
拆分写法以及使用常量替代Mutation事件类型*
-
在store目录下新建文件mutations_type.js。
export const MUTATIONS_TYPE = { NUM_PLUS_ONE: 'NUM_PLUS_ONE', NUM_PLUS_X: 'NUM_PLUS_X', NUM_PLUS_OBJECT_X: 'NUM_PLUS_OBJECT_X', } export default { [MUTATIONS_TYPE.NUM_PLUS_ONE](state) { state.num++; }, // 可以向 store.commit 传入额外的参数,即 mutation 的载荷(payload) [MUTATIONS_TYPE.NUM_PLUS_X](state, x) { // x (payload)是一个形参,如果组件在commit时,有传这个参数过来,就存在,如果没有传过来,就是undefined state.num += x ? x : 1; }, // 在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读 [MUTATIONS_TYPE.NUM_PLUS_OBJECT_X](state, payload) { state.num += payload.amount; } }
-
在store目录下新建文件actions.js。
/* eslint-disable */ const actions = { // Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象 // 因此可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters numPlusOneDelayNSec(context, nSec) { console.log("numPlusOneDelayNSec ing."); setTimeout(() => { context.commit('NUM_PLUS_ONE'); console.log("numPlusOnesDelayNSec done."); }, nSec) }, numPlusObjectXDelayNSec(context, payload) { console.log("NUM_PLUS_OBJECT_X ing."); setTimeout(() => { console.log('NUM_PLUS_OBJECT_X ing payload: ' + JSON.stringify(payload)); console.log("NUM_PLUS_OBJECT_X done."); }, payload.nSec) }, // 组合 Action // store.dispatch 可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch makeUpActionA({ commit }) { // 返回一个 promise 对象 return new Promise((resolve, reject) => { setTimeout(() => { commit('NUM_PLUS_ONE'); resolve(); // 跟一般 promise 的使用差别不大 }, 1000); }) }, makeUpActionB({ commit, dispatch }) { console.log("actions of makeUpActionA ing.") return dispatch('makeUpActionA').then(() => { // commit('someOtherMutation'); console.log("actions of makeUpActionA done.") }) }, // 如果我们利用 async / await,我们可以如下组合 action // 一个 store.dispatch 在不同模块中可以触发多个 action 函数。 // 在这种情况下,只有当所有触发函数完成后,返回的 Promise 才会执行。 async actionA({ commit }) { commit('gotData', await getData()); }, async actionB({ dispatch, commit }) { await dispatch('actionA'); // 等待 actionA 完成 commit('gotOtherData', await getOtherData()); } }; export default actions;
-
修改store/index.js内容。
import Vue from 'vue' import Vuex from 'vuex' import mutations from '@/store/mutation-types' import actions from '@/store/actions' Vue.use(Vuex) export default new Vuex.Store({ // state相当于组件中的data,专门用来存放全局的数据 state: { num: 10, toDos: [ { id: 1, text: 'text1...', done: true }, { id: 2, text: 'text2...', done: false }, { id: 3, text: 'text3...', done: true }, ], }, // getters相当于组件中的computed,getters是全局的,computed是组件内部使用的 getters: { // 通过属性访问 getNum(state) { return state.num; }, // 通过属性访问 doneTodos(state) { return state.toDos.filter((todo) => todo.done); }, // 通过属性访问 // Getter 也可以接受其他 getter 作为第二个参数 getTodosCount(state, getters) { return getters.doneTodos.length; }, // 通过方法访问 getTodoById: (state) => (id) => { return state.toDos.filter((todo) => todo.id === id); } }, mutations, actions, modules: { } })
-
修改App.vue文件。
<template> <div id="app"> <h3>App根组件的数字:{{ getNum }}</h3> <nav> <router-link to="/">Home</router-link> | <router-link to="/about">About</router-link> </nav> <router-view /> </div> </template> <script> import { mapState, mapGetters } from "vuex"; export default { computed: { ...mapState(['num']), ...mapGetters(['getNum', 'doneTodos', 'getTodosCount', 'getTodoById']), }, }; </script> <style lang="less"> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; } nav { padding: 30px; a { font-weight: bold; color: #2c3e50; &.router-link-exact-active { color: #42b983; } } } </style>
-
修改views/HomeView.vue文件。
<template> <div class="home"> <HelloWorld msg="Welcome to Your Vue.js App" /> <p>Home页面的数字:{{ getNum }}</p> <p>doneTodos:{{ doneTodos }}</p> <p>getTodosCount:{{ getTodosCount }}</p> <p>getTodoById:{{ getTodoById(2) }}</p> <button @click="NUM_PLUS_ONE">num数值+1</button> <button @click="NUM_PLUS_X(2)">num数值+X</button> <button @click="NUM_PLUS_OBJECT_X({ amount: 3 })">num数值+ObjX</button> <button @click="NUM_PLUS_OBJECT_X({ amount: 3 })"> num数值+ObjX(对象风格提交方式) </button> <button @click="numPlusOneDelayNSec(2000)"> numPlusOneDelayNSec(actions异步实现) </button> <button @click="numPlusOneDelayNSecAlias(2000)"> numPlusOneDelayNSecAlias(actions异步实现) </button> <button @click=" numPlusObjectXDelayNSec({ nSec: 2000, amount: 10, }) " > numPlusObjectXDelayNSec(actions异步实现) </button> <button @click="makeUpActionB">组合Action</button> </div> </template> <script> // @ is an alias to /src import HelloWorld from "@/components/HelloWorld.vue"; import { mapState, mapGetters } from "vuex"; import { mapMutations, mapActions } from "vuex"; export default { name: "HomeView", components: { HelloWorld, }, computed: { ...mapState(['num']), ...mapGetters(['getNum', 'doneTodos', 'getTodosCount', 'getTodoById']), }, methods: { ...mapMutations(['NUM_PLUS_ONE', 'NUM_PLUS_X', 'NUM_PLUS_OBJECT_X']), ...mapActions([ 'numPlusOneDelayNSec', // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)` 'numPlusObjectXDelayNSec', 'makeUpActionB' ]), ...mapActions({ numPlusOneDelayNSecAlias: 'numPlusOneDelayNSec', // 将 `this.add()` 映射为 `this.$store.dispatch('increment')` }), numPlusOne() { this.$store.commit('numPlusOne'); }, }, }; </script>
-
修改views/AboutView.vue文件。
<template> <div class="about"> <h1>This is an about page</h1> <p>About页面的数字:{{ num }}</p> <p>getTodosCount:{{ getTodosCount }}</p> </div> </template> <script> import { mapState, mapGetters } from "vuex"; import { mapMutations, mapActions } from "vuex"; export default { data() { return {}; }, computed: { ...mapState(['num']), ...mapGetters(['getNum', 'doneTodos', 'getTodosCount', 'getTodoById']), }, methods: { ...mapMutations(['NUM_PLUS_ONE', 'NUM_PLUS_X', 'NUM_PLUS_OBJECT_X']), ...mapActions(['numPlusOneDelayNSec', 'numPlusObjectXDelayNSec']), }, }; </script>
module
默认情况下,模块内部的 action 和 mutation 仍然是注册在全局命名空间的——这样使得多个模块能够对同一个 action 或 mutation 作出响应。Getter 同样也默认注册在全局命名空间,但是目前这并非出于功能上的目的(仅仅是维持现状来避免非兼容性变更)。必须注意,不要在不同的、无命名空间的模块中定义两个相同的 getter 从而导致错误。
如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true
的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。
操作步骤:
-
新建VUE CLI工程,带路由、Vuex和less功能。
-
配置初始工程。
- components/HelloWorld.vue中,删除多余的展示内容,只保留msg的信息。
- views/HomeView.vue中,只保留
<HelloWorld msg="Welcome to Your Vue.js App"/>
的展示内容。
-
在store目录下新建文件moduleA.js。
const moduleA = { namespaced: true, state: { goods: [1, 2, 3, 4] }, getters: { }, mutations: { ADD_GOODS(state) { state.goods.push(state.goods.length + 1); } }, actions: { addGoodsDelay(context) { setTimeout(() => { context.commit('ADD_GOODS'); }, 1000); } } }; export default moduleA;
-
在store目录下新建文件moduleB.js。
const moduleB = { namespaced: true, state: { list: ['A', 'B', 'C'] }, getters: { }, mutations: { ADD_LIST(state) { state.list.push(state.list.length + 1); } }, actions: { addListDelay(context) { setTimeout(() => { context.commit('ADD_LIST'); }, 1000); } } }; export default moduleB;
-
修改store/index.js内容。
import Vue from 'vue' import Vuex from 'vuex' import moduleA from '@/store/moduleA' import moduleB from '@/store/moduleB' Vue.use(Vuex) export default new Vuex.Store({ state: { }, getters: { }, mutations: { }, actions: { }, modules: { moduleA, moduleB } })
-
修改views/HomeView.vue内容。
<template> <div class="home"> <HelloWorld msg="Welcome to Your Vue.js App" /> <p v-for="item in goods" :key="item">{{ item }}</p> <button @click="ADD_GOODS">moduleA ADD_GOODS</button> <button @click="addGoodsDelay">moduleA addGoodsDelay</button> </div> </template> <script> // @ is an alias to /src import HelloWorld from "@/components/HelloWorld.vue"; import { mapState } from "vuex"; import { mapMutations, mapActions } from "vuex"; export default { name: "HomeView", components: { HelloWorld, }, computed: { ...mapState("moduleA", ["goods"]), }, methods: { ...mapMutations("moduleA", ["ADD_GOODS"]), ...mapActions("moduleA", ["addGoodsDelay"]), }, }; </script>
-
修改views/AboutView.vue内容。
<template> <div class="about"> <h1>This is an about page</h1> <p v-for="item in list" :key="item">{{ item }}</p> <button @click="ADD_LIST">moduleA ADD_LIST</button> <button @click="addListDelay">moduleA addListDelay</button> </div> </template> <script> import { mapState } from "vuex"; import { mapMutations, mapActions } from "vuex"; export default { name: "AboutView", computed: { ...mapState("moduleB", ["list"]), }, methods: { ...mapMutations("moduleB", ["ADD_LIST"]), ...mapActions({ addListDelay: "moduleB/addListDelay", // 对象格式 }), }, }; </script>
mutations结合axios示例
需求背景:
初始加载Home页面发起一个请求,并将请求的结果保存下来。
进入About页面时候,展示在Home页面请求数据的结果内容。
操作步骤:
-
新建VUE CLI工程,带路由、Vuex和less功能。
-
配置初始工程。
- components/HelloWorld.vue中,删除多余的展示内容,只保留msg的信息。
- views/HomeView.vue中,只保留
<HelloWorld msg="Welcome to Your Vue.js App"/>
的展示内容。
-
安装axios(
npm run axios
)。 -
在views/HomeView.vue中导入axios请求。
... <script> import axios from 'axios' ... </script>
-
修改store/index.js内容。
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { a: 1, axiosRes: {}, }, getters: { }, mutations: { INIT_DATA(state, payload) { state.axiosRes = payload; }, CHANGE_A2B(state) { state.a = 'BCD'; }, }, actions: { }, modules: { } })
-
修改views/HomeView.vue内容。
<template> <div class="home"> <HelloWorld msg="Welcome to Your Vue.js App" /> <p>a: {{ a }}</p> <p>axiosRes: {{ axiosRes }}</p> <button @click="CHANGE_A2B">CHANGE_A2B</button> </div> </template> <script> // @ is an alias to /src import HelloWorld from "@/components/HelloWorld.vue"; import axios from "axios"; import { mapState } from "vuex"; import { mapMutations } from "vuex"; export default { name: "HomeView", components: { HelloWorld, }, created() { this.getData(); }, computed: { ...mapState(["a", "axiosRes"]), }, methods: { ...mapMutations(["INIT_DATA", "CHANGE_A2B"]), getData() { axios .post("http://liulongbin.top:3006/api/post", { page: 1, sie: 3 }) .then((res) => { console.log(res); this.INIT_DATA(res); }); }, }, }; </script>
-
修改views/AboutView.vue内容。
<template> <div class="about"> <h1>This is an about page</h1> <p>axiosRes: {{ axiosRes }}</p> </div> </template> <script> import { mapState } from "vuex"; export default { computed: { ...mapState(["axiosRes"]), }, }; </script>
actions实现全选示例
操作步骤:
-
新建VUE CLI工程,带Vuex和less功能。
-
配置初始工程。
- components/HelloWorld.vue中,删除多余的展示内容,只保留msg的信息。
- App.vue中,只保留
<HelloWorld msg="Welcome to Your Vue.js App"/>
的展示内容。
-
修改store/index.js内容。
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { list: [ { checked: false }, { checked: false }, { checked: false }, { checked: false }, { checked: false }, { checked: false }, { checked: false }, ], selectedList: [], }, getters: { isCheckedAll(state) { return state.list.length === state.selectedList.length; } }, mutations: { CHECK_ALL(state) { state.selectedList = state.list.map(v => v.checked = true // 全部赋值为true ) }, UNCHECK_ALL(state) { state.selectedList = state.list.map(v => v.checked = false // 全部赋值为false ); state.selectedList = []; // 恢复默认值 }, }, actions: { toggleCheckedAllFn({ commit, getters }) { getters.isCheckedAll ? commit('UNCHECK_ALL') : commit('CHECK_ALL'); } }, modules: { } })
-
修改components/HelloWorld.vue内容。
<template> <div class="hello"> <h1>{{ msg }}</h1> <ul> <li v-for="(item, index) in list" :key="index"> <input type="radio" :checked="item.checked" /> {{ index }} </li> </ul> <label @click="toggleCheckedAllFn"> <input type="radio" :checked="isCheckedAll" /> </label> </div> </template> <script> import { mapState, mapGetters } from "vuex"; import { mapMutations, mapActions } from "vuex"; export default { name: "HelloWorld", props: { msg: String, }, computed: { ...mapState(["list", "selectedList"]), ...mapGetters(["isCheckedAll"]), }, methods: { ...mapMutations(["CHECK_ALL", "UNCHECK_ALL"]), ...mapActions(["toggleCheckedAllFn"]), }, }; </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped lang="less"> h3 { margin: 40px 0 0; } ul { list-style-type: none; padding: 0; } li { display: inline-block; margin: 0 10px; } a { color: #42b983; } </style>
/---- Vue3.x ----/
Vue3.x和Vue2.x版本的对比
Vue2.x 中绝大多数的 API 与特性,在 Vue3.x 中同样支持。同时,Vue3.x 中还新增了 3.x 所特有的功能、并
废弃了某些 2.x 中的旧功能:
新增的功能例如:
组合式 API、多根节点组件、更好的 TypeScript 支持等
废弃的旧功能如下:
过滤器、不再支持 $on,$off 和 $once 实例方法等
详细的变更信息,请参考官方文档给出的迁移指南:
https://v3.vuejs.org/guide/migration/introduction.html
Vue3.x新特性介绍
新特性介绍
- 重写双向数据绑定
- VDOM性能瓶颈
- Fragments
- Tree-Shaking的支持
- Composition API
Vite
备注:目前只支持Vue3.x的项目。
Vite创建Vue3.x项目【模板】
Vite特征特点
- Vite冷服务启动,ES6 import。
- 开发中热更新。
- 按需进行编译,不会刷新全部DOM。
操作步骤
-
新建项目文件夹ViteDemo(Vite项目工程存放的位置)。
-
执行以下命令,创建Vite工程项目。
# 方式一 npm create vite@latest <project-name> # 方式二(vite2.0版本) npm init @vitejs/app <project-name> # 方式三(vite1.0版本) npm init vite-app <project-name> # 项目名称不写时,默认为vite # 直接回车,按照提示操作即可
-
使用VS Code打开工程目录。
-
执行以下命令安装相关依赖。
npm install
-
执行以下命令运行代码。
npm run dev
Vite对Typescript、CSS和JSON的支持
-
使用Vite搭建Vue3开发环境,参见[Vite创建Vue3.x项目【模板】](# Vite创建Vue3.x项目【模板】)。
-
修改文件App.vue。验证Vite对Typescript的支持。
<script lang="ts"> // This starter template is using Vue 3 <script setup> SFCs // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup import HelloWorld from "./components/HelloWorld.vue"; const tsVal: string = "Hello TS"; export default { name: 'app', components: { HelloWorld, }, mounted() { console.log(tsVal); }, }; </script> <template> <img alt="Vue logo" src="./assets/logo.png" /> <HelloWorld msg="Hello Vue 3 + Vite" /> </template> <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>
-
在src\assets\目录中,新建文件app.css。验证Vite对CSS文件的支持。
body { background-color: aqua; }
-
修改文件App.vue。
<script lang="ts"> // This starter template is using Vue 3 <script setup> SFCs // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup import HelloWorld from "./components/HelloWorld.vue"; import "./assets/app.css"; const tsVal: string = "Hello TS"; export default { name: "app", components: { HelloWorld, }, mounted() { console.log(tsVal); }, }; </script> <template> <img alt="Vue logo" src="./assets/logo.png" /> <HelloWorld msg="Hello Vue 3 + Vite" /> </template> <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>
-
在src\assets\目录中,新建文件config.json。验证Vite对JSON文件的支持。
{ "name": "张三", "website": "www.baidu.com" }
-
修改文件App.vue。
<script lang="ts"> // This starter template is using Vue 3 <script setup> SFCs // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup import HelloWorld from "./components/HelloWorld.vue"; import "./assets/app.css"; import data from "./assets/config.jso n"; const tsVal: string = "Hello TS"; export default { name: "app", components: { HelloWorld, }, mounted() { console.log(tsVal); console.log(`${data.name} | ${data.website}`); }, }; </script> <template> <img alt="Vue logo" src="./assets/logo.png" /> <HelloWorld msg="Hello Vue 3 + Vite" /> </template> <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>
Vite配置文件和别名设置
基于上述的工程进行适配修改。
在项目根目录中新建配置文件vite.config.js。
const { resolve } = require('path');
export default {
alias: {
'/@/': resolve(__dirname, 'src'),
}
}
Vite安装和配置less【模板】
-
搭建完成Vite项目。
-
安装依赖包。
npm i -D less-loader less
-
使用示例,新建XX.vue。
<template> <div class="content-home"> <h3>我是Home组件</h3> </div> </template> <script setup lang="ts"> </script> <style lang="less" scoped> .content-home { width: 200px; height: 200px; background-color: aqua; } </style>
Vue3.x组合式API
创建Vue3.x项目
Vue CLI创建Vue3.x项目【模板】
-
执行命令,新建一个工程
vue create vue3-project-1
-
手动配置工程
# Manually select features # 新增勾选“Typescript”、“Router”、“Vuex”和“CSS Pre-processors” # 以3.x为例,选择“3.x” # Use Babel alongside TypeScript,选择“Yes” # 选择“less”CSS预处理器 # 选择“ESLint with error prevention only”或者“ESLint + Standard config”,根据需要选择 # 最后咨询是否保存为模板,这里选择“N”
Vite创建Vue3.x项目【模板】
参见[Vite创建Vue3.x项目【模板】](# Vite创建Vue3.x项目【模板】)。
setup函数
新的 setup
选项在组件被创建之前执行,一旦 props
被解析完成,它就将被作为组合式 API 的入口。
说明:在
setup
中你应该避免使用this
,因为它不会找到组件实例。setup
的调用发生在data
property、computed
property 或methods
被解析之前,所以它们无法在setup
中被获取。
带ref的响应式变量
在 Vue 3.0 中,我们可以通过一个新的 ref
函数使任何响应式变量在任何地方起作用,如下所示:
import { ref } from 'vue'
const counter = ref(0)
ref
接收参数并将其包裹在一个带有 value
property 的对象中返回,然后可以使用该 property 访问或更改响应式变量的值:
import { ref } from 'vue'
const counter = ref(0)
console.log(counter) // { value: 0 }
console.log(counter.value) // 0
counter.value++
console.log(counter.value) // 1
操作步骤
-
创建Vue3.x项目,参见[Vite创建Vue3.x项目【模板】](# Vite创建Vue3.x项目【模板】)。
-
修改文件src\views\AboutView.vue。
<template> <div class="about"> <h1>This is an about page</h1> <h3>01. setup函数介绍和使用</h3> <h3> Vue2.x中的数据:{{ title01 }}---- <button @click="getVue3Data">获取一下setup中的数据</button> </h3> <h3> Vue3.x中的数据:{{ title02 }} <button @click="getVue2Data">获取一下data中的数据</button> </h3> <h3> <button @click="addCount">count++ == {{ count }}</button> </h3> </div> </template> <script> import { ref } from "vue"; export default { // 1. setup执行的时期:在beforeCreate和created之前执行 // 2. setup和options api对比 // 3. 在Vue3.x中仍然可以使用Vue2.x相关语法 // 4. 在Vue2.x中可以获取到setup中的数据 // 5. 在setup中无法获取到Vue2.x中的数据,没有this,this的值是undefined // 6. 如果setup中的方法或者数据和Vue2.x中的方法和数据冲突了,优先使用setup中的数据或方法 data() { return { title01: "我是Vue2中的data中的数据!", // count: 1, }; }, methods: { // addCount() { // console.log("methods--addCount"); // this.count++; // }, getVue3Data() { console.log("methods--getVue3Data, title02: " + this.title02); }, }, beforeCreate() { console.log("Life cycle--beforeCreate"); }, created() { console.log("Life cycle--created"); }, // setup是在created和beforeCreate生命周期之前触发 setup(props) { // props是一个对象,组件外部传递过来的值,并且这个值是在组件内接收了的 console.log("setup, props: ", props); let title02 = ref("我是setup中的数据!"); // setup中无法获取到Vue2.x中的数据 // setup中打印this也会是undefined function getVue2Data() { console.log("setup, function--getVue2Data, title01: " + this.title01); } let count = ref(0); function addCount() { count.value++; } // 这里返回的任何内容都可以用于组件的其余部分 return { title02, getVue2Data, addCount, count, }; }, }; </script>
说明:
-
在Vue2.x中可以获取到setup中的数据。
-
在setup中无法获取到Vue2.x中的数据(在setup中获取this,打印出来是undefined)。
-
Vue2.x中的方法如果跟setup中的方法冲突,优先选择setup中的方法。
-
上述的方式可以使用setup的语法糖形式。不需要进行导出操作。
<script setup> // 使用了setup的语法糖 import { ref, reactive } from "vue"; const coffee = ref("卡布奇洛"); const movie = reactive({ name: "<strong>蝙蝠侠</strong>", price: 55, }); const params = reactive({ username: "admin", password: "123", }); const gender = ref("Female"); const isAgree = ref(false); </script>
-
-
验证setup中props参数使用场景。
-
在components目录下,新建文件TestSetUpProps.vue。
<template> <div> <h1>测试子组件</h1> </div> </template> <script lang="ts"> export default { } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped lang="less"></style>
-
修改文件src\views\AboutView.vue。补充两个父组件传递给子组件的内容。
<template> <div class="about"> <h1>This is an about page</h1> <h3>01. setup函数介绍和使用</h3> <h3> Vue2.x中的数据:{{ title01 }} ---- <button @click="getVue3Data">获取一下setup中的数据</button> </h3> <h3> Vue3.x中的数据:{{ title02 }} <button @click="getVue2Data">获取一下data中的数据</button> </h3> <h3> <button @click="addCount">count++ == {{ count }}</button> </h3> <hr /> <TestSetUpProps :title03="title03" :cookie="cookie"></TestSetUpProps> </div> </template> <script> import { ref } from "vue"; import TestSetUpProps from "../components/TestSetUpProps.vue"; export default { // 1. setup执行的时期:在beforeCreate和created之前执行 // 2. setup和options api对比 // 3. 在Vue3.x中仍然可以使用Vue2.x相关语法 // 4. 在Vue2.x中可以获取到setup中的数据 // 5. 在setup中无法获取到Vue2.x中的数据,没有this,this的值是undefined // 6. 如果setup中的方法或者数据和Vue2.x中的方法和数据冲突了,优先使用setup中的数据或方法 data() { return { title01: "我是Vue2中的data中的数据!", // count: 1, }; }, components: { TestSetUpProps, }, methods: { // addCount() { // console.log("methods--addCount"); // this.count++; // }, getVue3Data() { console.log("methods--getVue3Data, title02: " + this.title02); }, }, beforeCreate() { console.log("Life cycle--beforeCreate"); }, created() { console.log("Life cycle--created"); }, // setup是在created和beforeCreate生命周期之前触发 setup(props) { // props是一个对象,组件外部传递过来的值,并且这个值是在组件内接收了的 console.log("setup, props: ", props); let title02 = ref("我是setup中的数据!"); // setup中无法获取到Vue2.x中的数据 // setup中打印this也会是undefined function getVue2Data() { console.log("setup, function--getVue2Data, title01: " + this.title01); } let count = ref(0); function addCount() { count.value++; } let title03 = ref("给子组件传递的数据!"); let cookie = ref("奥利奥!"); // 这里返回的任何内容都可以用于组件的其余部分 return { title02, getVue2Data, addCount, count, title03, cookie, }; }, }; </script>
-
修改文件components/TestSetUpProps.vue,接收和显示父组件传递过来的数据内容。
<template> <div> <h1>测试子组件</h1> <h3>接收来自父组件传递过来的参数 ---- {{ newVal }}</h3> </div> </template> <script lang="ts"> import { ref } from "vue"; export default { // 子组件接收父组件传递值,一定要用props自定义属性进行接收 props: { title03: { type: String, require: true, }, }, setup(props) { console.log(props); const newVal = ref(props.title03 + " + 经过setup处理之后的值!"); return { newVal, }; }, }; </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped lang="less"></style>
-
-
验证setup中context参数使用场景,先说明context参数的attrs和slots属性。
-
修改文件components/TestSetUpProps.vue。
<template> <div> <h1>测试子组件</h1> <h3>接收来自父组件传递过来的参数 ---- {{ newVal }}</h3> <slot name="one"></slot> </div> </template> <script lang="ts"> import { ref } from "vue"; export default { // 子组件接收父组件传递值,一定要用props自定义属性进行接收 props: { title03: { type: String, require: true, }, }, setup(props, context) { // context表示上下文对象,包含attrs, slots, emit // attrs,外部传递过来的属性,并且没有在props中定义 // slots,插槽 // emit,分发自定义的事件,相当于$emit console.log("setup ing.."); console.log(props); console.log(context); console.log(JSON.stringify(context.attrs)); // {"cookie":"奥利奥!"} console.log(context.slots); // const newVal = ref(props.title03 + " + 经过setup处理之后的值!"); return { newVal, }; }, }; </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped lang="less"></style>
-
修改文件src\views\AboutView.vue。
<template> <div class="about"> <h1>This is an about page</h1> <h3>01. setup函数介绍和使用</h3> <h3> Vue2.x中的数据:{{ title01 }} ---- <button @click="getVue3Data">获取一下setup中的数据</button> </h3> <h3> Vue3.x中的数据:{{ title02 }} <button @click="getVue2Data">获取一下data中的数据</button> </h3> <h3> <button @click="addCount">count++ == {{ count }}</button> </h3> <hr /> <TestSetUpProps :title03="title03" :cookie="cookie"> <template v-slot:one> <h3>Hello demo, 自定义插槽!!</h3> </template> </TestSetUpProps> </div> </template> <script> import { ref } from "vue"; import TestSetUpProps from "../components/TestSetUpProps.vue"; export default { // 1. setup执行的时期:在beforeCreate和created之前执行 // 2. setup和options api对比 // 3. 在Vue3.x中仍然可以使用Vue2.x相关语法 // 4. 在Vue2.x中可以获取到setup中的数据 // 5. 在setup中无法获取到Vue2.x中的数据,没有this,this的值是undefined // 6. 如果setup中的方法或者数据和Vue2.x中的方法和数据冲突了,优先使用setup中的数据或方法 data() { return { title01: "我是Vue2中的data中的数据!", // count: 1, }; }, components: { TestSetUpProps, }, methods: { // addCount() { // console.log("methods--addCount"); // this.count++; // }, getVue3Data() { console.log("methods--getVue3Data, title02: " + this.title02); }, }, beforeCreate() { console.log("Life cycle--beforeCreate"); }, created() { console.log("Life cycle--created"); }, // setup是在created和beforeCreate生命周期之前触发 setup(props) { // props是一个对象,组件外部传递过来的值,并且这个值是在组件内接收了的 console.log("setup, props: ", props); let title02 = ref("我是setup中的数据!"); // setup中无法获取到Vue2.x中的数据 // setup中打印this也会是undefined function getVue2Data() { console.log("setup, function--getVue2Data, title01: " + this.title01); } let count = ref(0); function addCount() { count.value++; } let title03 = ref("给子组件传递的数据!"); let cookie = ref("奥利奥!"); // 这里返回的任何内容都可以用于组件的其余部分 return { title02, getVue2Data, addCount, count, title03, cookie, }; }, }; </script>
-
-
验证setup中context参数使用场景,说明context参数的emit属性。
-
修改文件components/TestSetUpProps.vue。
<template> <div> <h1>测试子组件</h1> <h3>接收来自父组件传递过来的参数 ---- {{ newVal }}</h3> <slot name="one"></slot> <h3><button @click="send">子向父传值</button></h3> </div> </template> <script lang="ts"> import { ref } from "vue"; export default { // 子组件接收父组件传递值,一定要用props自定义属性进行接收 props: { title03: { type: String, require: true, }, }, setup(props, context) { // context表示上下文对象,包含attrs, slots, emit // attrs,外部传递过来的属性,并且没有在props中定义 // slots,插槽 // emit,分发自定义的事件,相当于$emit console.log("setup ing.."); console.log(props); console.log(context); console.log(JSON.stringify(context.attrs)); // {"cookie":"奥利奥!"} console.log(context.slots); // const newVal = ref(props.title03 + " + 经过setup处理之后的值!"); const price = ref("100元"); // 传递给父组件的值内容 function send() { context.emit("sendPrice", price); } return { newVal, price, send, }; }, }; </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped lang="less"></style>
-
修改文件src\views\AboutView.vue。
<template> <div class="about"> <h1>This is an about page</h1> <h3>01. setup函数介绍和使用</h3> <h3> Vue2.x中的数据:{{ title01 }} ---- <button @click="getVue3Data">获取一下setup中的数据</button> </h3> <h3> Vue3.x中的数据:{{ title02 }} <button @click="getVue2Data">获取一下data中的数据</button> </h3> <h3> <button @click="addCount">count++ == {{ count }}</button> </h3> <br /> <h3>接收子组件传递出来的数据 ---- {{ gift }}</h3> <hr /> <TestSetUpProps :title03="title03" :cookie="cookie" @sendPrice="receiveFromSubComp" > <template v-slot:one> <h3>Hello demo, 自定义插槽!!</h3> </template> </TestSetUpProps> </div> </template> <script> import { ref } from "vue"; import TestSetUpProps from "../components/TestSetUpProps.vue"; export default { // 1. setup执行的时期:在beforeCreate和created之前执行 // 2. setup和options api对比 // 3. 在Vue3.x中仍然可以使用Vue2.x相关语法 // 4. 在Vue2.x中可以获取到setup中的数据 // 5. 在setup中无法获取到Vue2.x中的数据,没有this,this的值是undefined // 6. 如果setup中的方法或者数据和Vue2.x中的方法和数据冲突了,优先使用setup中的数据或方法 data() { return { title01: "我是Vue2中的data中的数据!", // count: 1, }; }, components: { TestSetUpProps, }, methods: { // addCount() { // console.log("methods--addCount"); // this.count++; // }, getVue3Data() { console.log("methods--getVue3Data, title02: " + this.title02); }, }, beforeCreate() { console.log("Life cycle--beforeCreate"); }, created() { console.log("Life cycle--created"); }, // setup是在created和beforeCreate生命周期之前触发 setup(props) { // props是一个对象,组件外部传递过来的值,并且这个值是在组件内接收了的 console.log("setup, props: ", props); let title02 = ref("我是setup中的数据!"); // setup中无法获取到Vue2.x中的数据 // setup中打印this也会是undefined function getVue2Data() { console.log("setup, function--getVue2Data, title01: " + this.title01); } let count = ref(0); function addCount() { count.value++; } let title03 = ref("给子组件传递的数据!"); let cookie = ref("奥利奥!"); let gift = ref("0元"); function receiveFromSubComp(val) { console.log("子组件传递过来的数据, val", val); console.log("子组件传递过来的数据, val.value", val.value); gift.value = val.value; console.log("gift", gift); } // 这里返回的任何内容都可以用于组件的其余部分 return { title02, getVue2Data, addCount, count, title03, cookie, receiveFromSubComp, gift, }; }, }; </script>
-
ref
说明:处理基本数据类型的数据。
接受一个内部值并返回一个响应式且可变的ref对象。ref对象仅有一个.value property,指向该内部值。
-
创建Vue3.x项目,参见[Vite创建Vue3.x项目【模板】](# Vite创建Vue3.x项目【模板】)。
-
修改文件src\views\AboutView.vue。
<template> <div class="about"> <h1>This is an about page</h1> <h3>02. ref</h3> <p>当前的电影是:{{ title }}</p> <p>当前电影的价格是:{{ price }}</p> <p>电影的详情是: {{ info }}</p> <button @click="getMovieInfo">单击查看详情</button> <br /><br /> <input type="text" v-model="mobile" /> <p>mobile: {{ mobile }}</p> <br /> <p>当前咖啡商品的名字是:{{ coffee.name }}</p> <p>当前咖啡商品的价格是:{{ coffee.price }}</p> </div> </template> <script> import { ref } from "vue"; export default { // 1. setup执行的时期:在beforeCreate和created之前执行 // 2. setup和options api对比 // 3. 在Vue3.x中仍然可以使用Vue2.x相关语法 // 4. 在Vue2.x中可以获取到setup中的数据 // 5. 在setup中无法获取到Vue2.x中的数据,没有this,this的值是undefined // 6. 如果setup中的方法或者数据和Vue2.x中的方法和数据冲突了,优先使用setup中的数据或方法 data() { return {}; }, components: {}, methods: {}, // setup是在created和beforeCreate生命周期之前触发 setup(props, context) { // props是一个对象,组件外部传递过来的值,并且这个值是在组件内接收了的 // context表示上下文对象,包含attrs, slots, emit // attrs,外部传递过来的属性,并且没有在props中定义 // slots,插槽 // emit,分发自定义的事件,相当于$emit // 这里返回的任何内容都可以用于组件的其余部分 console.log(props); console.log(context); let title = "长津湖"; let price = 56; let info = ref("抗美援朝的故事"); function getMovieInfo() { info.value = `当前电影的名字是:${title},当前电影的价格是:${price},详情是:${info.value}`; } // ref实现数据的双向绑定,实际上是通过set和get的方法来实现的 let mobile = ref("123"); // 对象类型 const coffee = ref({ name: "瑞纳冰拿铁", price: 48, }); console.log("coffee.value.name", coffee.value.name); return { title, price, info, getMovieInfo, mobile, coffee, }; }, }; </script>
-
模拟登录注册的效果。
-
修改文件src\views\AboutView.vue。编写登录框架内容。
<template> <div class="about"> <h1>This is an about page</h1> <h3>02. ref</h3> <p>当前的电影是:{{ title }}</p> <p>当前电影的价格是:{{ price }}</p> <p>电影的详情是: {{ info }}</p> <button @click="getMovieInfo">单击查看详情</button> <br /><br /> <input type="text" v-model="mobile" /> <p>mobile: {{ mobile }}</p> <br /> <p>当前咖啡商品的名字是:{{ coffee.name }}</p> <p>当前咖啡商品的价格是:{{ coffee.price }}</p> <br /> <p>用户名:<input type="text" v-model="loginParams.username" /></p> <p>密码:<input type="text" v-model="loginParams.password" /></p> <button @click="doLogin">登录</button> <p>login info: {{ loginParams }}</p> </div> </template> <script> import { ref } from "vue"; export default { // 1. setup执行的时期:在beforeCreate和created之前执行 // 2. setup和options api对比 // 3. 在Vue3.x中仍然可以使用Vue2.x相关语法 // 4. 在Vue2.x中可以获取到setup中的数据 // 5. 在setup中无法获取到Vue2.x中的数据,没有this,this的值是undefined // 6. 如果setup中的方法或者数据和Vue2.x中的方法和数据冲突了,优先使用setup中的数据或方法 data() { return {}; }, components: {}, methods: {}, // setup是在created和beforeCreate生命周期之前触发 setup(props, context) { // props是一个对象,组件外部传递过来的值,并且这个值是在组件内接收了的 // context表示上下文对象,包含attrs, slots, emit // attrs,外部传递过来的属性,并且没有在props中定义 // slots,插槽 // emit,分发自定义的事件,相当于$emit // 这里返回的任何内容都可以用于组件的其余部分 console.log(props); console.log(context); let title = "长津湖"; let price = 56; let info = ref("抗美援朝的故事"); function getMovieInfo() { info.value = `当前电影的名字是:${title},当前电影的价格是:${price},详情是:${info.value}`; } // ref实现数据的双向绑定,实际上是通过set和get的方法来实现的 let mobile = ref("123"); // 对象类型 const coffee = ref({ name: "瑞纳冰拿铁", price: 48, }); console.log("coffee.value.name", coffee.value.name); const loginParams = ref({ username: "", password: "", }); function doLogin() { console.log(loginParams.value); } return { title, price, info, getMovieInfo, mobile, coffee, loginParams, doLogin, }; }, }; </script>
-
在根目录中执行以下命令,安装axios和qs。
npm i -D axios qs
-
修改main.ts,挂在全局对象。
import { createApp } from "vue"; import App from "./App.vue"; import router from "./router"; import store from "./store"; import axios from "axios"; import qs from "qs"; const app = createApp(App); // 挂载全局对象 app.config.globalProperties.foo = "Hello world!"; app.config.globalProperties.$http = axios; axios.defaults.baseURL = "http://bufantec.com/api/"; // 请求拦截器 axios.interceptors.request.use( (config) => { if (config.method?.toLocaleLowerCase() === "post") { config.data = qs.stringify(config.data); } return config; }, (err) => { return Promise.reject(err); } ); app.use(store).use(router).mount("#app");
-
修改文件src\views\AboutView.vue。编写请求内容。
<template> <div class="about"> <h1>This is an about page</h1> <h3>02. ref</h3> <p>当前的电影是:{{ title }}</p> <p>当前电影的价格是:{{ price }}</p> <p>电影的详情是: {{ info }}</p> <button @click="getMovieInfo">单击查看详情</button> <br /><br /> <input type="text" v-model="mobile" /> <p>mobile: {{ mobile }}</p> <br /> <p>当前咖啡商品的名字是:{{ coffee.name }}</p> <p>当前咖啡商品的价格是:{{ coffee.price }}</p> <br /> <p>用户名:<input type="text" v-model="loginParams.username" /></p> <p>密码:<input type="text" v-model="loginParams.password" /></p> <button @click="doLogin">登录</button> <p>login info: {{ loginParams }}</p> </div> </template> <script> // ref:创建一个包含响应式数据的引用对象,xxx.value // ref可以接收基本数据类型和对象类型 import { getCurrentInstance, ref } from "vue"; export default { // 1. setup执行的时期:在beforeCreate和created之前执行 // 2. setup和options api对比 // 3. 在Vue3.x中仍然可以使用Vue2.x相关语法 // 4. 在Vue2.x中可以获取到setup中的数据 // 5. 在setup中无法获取到Vue2.x中的数据,没有this,this的值是undefined // 6. 如果setup中的方法或者数据和Vue2.x中的方法和数据冲突了,优先使用setup中的数据或方法 data() { return {}; }, components: {}, methods: {}, // setup是在created和beforeCreate生命周期之前触发 setup(props, context) { // props是一个对象,组件外部传递过来的值,并且这个值是在组件内接收了的 // context表示上下文对象,包含attrs, slots, emit // attrs,外部传递过来的属性,并且没有在props中定义 // slots,插槽 // emit,分发自定义的事件,相当于$emit // 这里返回的任何内容都可以用于组件的其余部分 console.log(props); console.log(context); // 接收挂载的全局变量 // 例如proxy.poo就表示"Hello world!" // 例如proxy.$http就表示axios请求 const { proxy } = getCurrentInstance(); let title = "长津湖"; let price = 56; let info = ref("抗美援朝的故事"); function getMovieInfo() { info.value = `当前电影的名字是:${title},当前电影的价格是:${price},详情是:${info.value}`; } // ref实现数据的双向绑定,实际上是通过set和get的方法来实现的 let mobile = ref("123"); // 对象类型 const coffee = ref({ name: "瑞纳冰拿铁", price: 48, }); console.log("coffee.value.name", coffee.value.name); const loginParams = ref({ username: "", password: "", }); function doLogin() { console.log("setup doLogin ing.."); console.log(loginParams.value); proxy.$http.post("test/user/doLogin", loginParams.value).then((res) => { console.log(res); }); } return { title, price, info, getMovieInfo, mobile, coffee, loginParams, doLogin, }; }, }; </script>
-
reactive
说明:处理对象数据类型的数据。
修改文件src\views\AboutView.vue。修改对象的处理内容。修改咖啡的信息为reactive方式进行处理。
<template>
<div class="about">
<h1>This is an about page</h1>
<h3>02. ref</h3>
<p>当前的电影是:{{ title }}</p>
<p>当前电影的价格是:{{ price }}</p>
<p>电影的详情是: {{ info }}</p>
<button @click="getMovieInfo">单击查看详情</button>
<br /><br />
<input type="text" v-model="mobile" />
<p>mobile: {{ mobile }}</p>
<br />
<p>当前咖啡商品的名字是:{{ coffee.name }}</p>
<p>当前咖啡商品的价格是:{{ coffee.price }}</p>
<br />
<p>用户名:<input type="text" v-model="loginParams.username" /></p>
<p>密码:<input type="text" v-model="loginParams.password" /></p>
<button @click="doLogin">登录</button>
<p>login info: {{ loginParams }}</p>
</div>
</template>
<script>
// ref:创建一个包含响应式数据的引用对象,xxx.value
// ref可以接收基本数据类型和对象类型
import { getCurrentInstance, ref, reactive } from "vue";
export default {
// 1. setup执行的时期:在beforeCreate和created之前执行
// 2. setup和options api对比
// 3. 在Vue3.x中仍然可以使用Vue2.x相关语法
// 4. 在Vue2.x中可以获取到setup中的数据
// 5. 在setup中无法获取到Vue2.x中的数据,没有this,this的值是undefined
// 6. 如果setup中的方法或者数据和Vue2.x中的方法和数据冲突了,优先使用setup中的数据或方法
data() {
return {};
},
components: {},
methods: {},
// setup是在created和beforeCreate生命周期之前触发
setup(props, context) {
// props是一个对象,组件外部传递过来的值,并且这个值是在组件内接收了的
// context表示上下文对象,包含attrs, slots, emit
// attrs,外部传递过来的属性,并且没有在props中定义
// slots,插槽
// emit,分发自定义的事件,相当于$emit
// 这里返回的任何内容都可以用于组件的其余部分
console.log(props);
console.log(context);
// 接收挂载的全局变量
// 例如proxy.poo就表示"Hello world!"
// 例如proxy.$http就表示axios请求
const { proxy } = getCurrentInstance();
let title = "长津湖";
let price = 56;
let info = ref("抗美援朝的故事");
function getMovieInfo() {
info.value = `当前电影的名字是:${title},当前电影的价格是:${price},详情是:${info.value}`;
}
// ref实现数据的双向绑定,实际上是通过set和get的方法来实现的
let mobile = ref("123");
// 对象类型
const coffee = reactive({
name: "瑞纳冰拿铁",
price: 48,
});
console.log(coffee); // 直接就是Proxy的处理方式
console.log("coffee.name", coffee.name, "coffee.price", coffee.price);
const loginParams = reactive({
username: "",
password: "",
});
function doLogin() {
console.log("setup doLogin ing..");
console.log(loginParams);
proxy.$http.post("test/user/doLogin", loginParams).then((res) => {
console.log(res);
});
}
return {
title,
price,
info,
getMovieInfo,
mobile,
coffee,
loginParams,
doLogin,
};
},
};
</script>
options-api和composition-api
Vue2.x实现一个成员的添加和删减方式
options-api
修改文件AboutView.vue。
<template>
<div class="about">
<h1>This is an about page</h1>
<h3>Vue2.x实现成员添加和删减</h3>
<p><input type="text" v-model="user.name" /></p>
<p><input type="text" v-model="user.age" /></p>
<p><button @click="addItem">添加一个成员</button></p>
<ul>
<li v-for="(item, index) in list" :key="index" @click="removeItem(index)">
No.{{ index + 1 }} ---- 姓名:{{ item.name }} ---- 年龄:{{ item.age }}
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
list: [
{
name: "张三",
age: 17,
},
{
name: "李四",
age: 19,
},
{
name: "王五",
age: 18,
},
],
user: {
name: "LeMo",
age: 20,
},
};
},
components: {},
methods: {
addItem() {
if (
this.user.name &&
this.user.name !== "" &&
this.user.age &&
this.user.age !== ""
) {
this.list.push(Object.assign({}, this.user));
this.user.name = "";
this.user.age = "";
}
},
removeItem(i) {
this.list = this.list.filter((val, index) => index !== i);
},
},
};
</script>
Vue3.x实现一个成员的添加和删减方式
composition-api
修改文件AboutView.vue。
<template>
<div class="about">
<h1>This is an about page</h1>
<h3>Vue3.x实现成员添加和删减</h3>
<p><input type="text" v-model="params.item.name" /></p>
<p><input type="text" v-model="params.item.age" /></p>
<p><button @click="addItem">添加一个成员</button></p>
<ul>
<li
v-for="(item, index) in list.items"
:key="index"
@click="removeItem(index)"
>
No.{{ index + 1 }} ---- 姓名:{{ item.name }} ---- 年龄:{{ item.age }}
</li>
</ul>
</div>
</template>
<script>
import { reactive } from "vue";
export default {
data() {
return {};
},
components: {},
methods: {},
setup(props) {
console.log(props);
let list = reactive({
items: [
{
name: "张三",
age: 17,
},
{
name: "李四",
age: 19,
},
{
name: "王五",
age: 18,
},
],
});
let params = reactive({
item: {
name: "阿三",
age: 20,
},
});
function addItem() {
if (
params.item.name &&
params.item.name !== "" &&
params.item.age &&
params.item.age !== ""
) {
list.items.push(Object.assign({}, params.item));
params.item.name = "";
params.item.age = "";
}
}
function removeItem(i) {
list.items = list.items.filter((val, index) => index !== i);
}
return {
list,
params,
addItem,
removeItem,
};
},
};
</script>
Vue3.x实现一个成员的添加和删减方式优化1
修改文件AboutView.vue。
<template>
<div class="about">
<h1>This is an about page</h1>
<h3>Vue3.x实现成员添加和删减</h3>
<p><input type="text" v-model="params.item.name" /></p>
<p><input type="text" v-model="params.item.age" /></p>
<p><button @click="addItem">添加一个成员</button></p>
<ul>
<li
v-for="(item, index) in list.items"
:key="index"
@click="removeItem(index)"
>
No.{{ index + 1 }} ---- 姓名:{{ item.name }} ---- 年龄:{{ item.age }}
</li>
</ul>
</div>
</template>
<script>
import { reactive } from "vue";
export default {
data() {
return {};
},
components: {},
methods: {},
setup(props) {
console.log(props);
let { list, removeItem } = originRemoveItem();
let { params, addItem } = originAddItem(list);
return {
params,
addItem,
list,
removeItem,
};
},
};
// 原始数据和删除方法
function originRemoveItem() {
// 原始数据
let list = reactive({
items: [
{
name: "张三",
age: 17,
},
{
name: "李四",
age: 19,
},
{
name: "王五",
age: 18,
},
],
});
// 删除一条数据
function removeItem(i) {
list.items = list.items.filter((val, index) => index !== i);
}
return {
list,
removeItem,
};
}
// 默认数据和添加方法
function originAddItem(list) {
let params = reactive({
item: {
name: "阿三",
age: 20,
},
});
function addItem() {
if (
params.item.name &&
params.item.name !== "" &&
params.item.age &&
params.item.age !== ""
) {
list.items.push(Object.assign({}, params.item));
params.item.name = "";
params.item.age = "";
}
}
return {
params,
addItem,
};
}
</script>
Vue3.x实现一个成员的添加和删减方式优化2
将原始的数据删除和数据添加的方法,分别挪到独立的JS文件中。
-
在src目录中,新建一个目录hooks。
-
在hooks目录中,新建文件del.js。
import { reactive } from 'vue' // 按需导出 // 原始数据和删除方法 export function originRemoveItem() { // 原始数据 let list = reactive({ items: [ { name: "张三", age: 17, }, { name: "李四", age: 19, }, { name: "王五", age: 18, }, ], }); // 删除一条数据 function removeItem(i) { list.items = list.items.filter((val, index) => index !== i); } return { list, removeItem, }; }
-
在hooks目录中,新建文件add.js。
import { reactive } from 'vue' // 按需导出 // 默认数据和添加方法 export function originAddItem(list) { let params = reactive({ item: { name: "阿三", age: 20, }, }); function addItem() { if ( params.item.name && params.item.name !== "" && params.item.age && params.item.age !== "" ) { list.items.push(Object.assign({}, params.item)); params.item.name = ""; params.item.age = ""; } } return { params, addItem, }; }
-
修改文件AboutView.vue。
<template> <div class="about"> <h1>This is an about page</h1> <h3>Vue3.x实现成员添加和删减</h3> <p><input type="text" v-model="params.item.name" /></p> <p><input type="text" v-model="params.item.age" /></p> <p><button @click="addItem">添加一个成员</button></p> <ul> <li v-for="(item, index) in list.items" :key="index" @click="removeItem(index)" > No.{{ index + 1 }} ---- 姓名:{{ item.name }} ---- 年龄:{{ item.age }} </li> </ul> </div> </template> <script> import { originRemoveItem } from "../hooks/del.js"; import { originAddItem } from "../hooks/add.js"; export default { data() { return {}; }, components: {}, methods: {}, setup(props) { console.log(props); console.log(originRemoveItem); console.log(originAddItem); let { list, removeItem } = originRemoveItem(); let { params, addItem } = originAddItem(list); return { params, addItem, list, removeItem, }; }, }; </script>
Vue3.x原理
-
在Vue2.x中,如果属性在data()方法中未定义,随后在相应的方法中,新增属性,此时新增的属性实际上是生效的,但不会渲染到页面中(非响应式)。Vue2.x中的双向数据绑定会存在一些缺陷。
Vue2.x使用的是 Object.prototype 方式。<script> var VM = new Vue({ el: '#app', data: { person: { name: "张三", hobby: ["打篮球", "游泳", "健身"], }, }, methods: { changePerson() { // this.person.age = 18; // 不生效 // this.$set(this.person, "age", 19); // 生效 this.person = Object.assign({}, this.person, {age: 20}); // 生效 // 数组下标的方式修改元素也是不生效的 this.person.habby[0] = "踢足球"; // 不生效 this.$set(this.person.hobby, 0, "吃饭"); // 生效 } }, }) </script>
-
Vue3.x绑定原理。不存在该问题。
Vue3.x使用的是Proxy方式。<template> <div class="about"> <h1>This is an about page</h1> <h3>Vue3.x的绑定原理</h3> <p><button @click="changeObj">修改当前对象</button></p> <p>{{ person }}</p> </div> </template> <script> import { reactive } from "vue"; export default { setup(props) { console.log(props); var person = reactive({ name: "张三", hobby: ["打篮球", "游泳", "健身"], }); function changeObj() { person.age = 20; // 生效 person.hobby[0] = "读书"; // 生效 } return { person, changeObj, }; }, }; </script>
computed
示例:
<template>
<div class="about">
<h1>This is an about page</h1>
<h3>Vue3.x的computed</h3>
<table border="1" cellpadding="0" cellspacing="0">
<tr>
<th><input type="checkbox" name="" id="" v-model="isAllChecked" /></th>
<th>商品名字</th>
<th>商品价格</th>
<th>购买数量</th>
</tr>
<tr v-for="item in iceCream" :key="item.id">
<td>
<input type="checkbox" name="" id="" v-model="item.isChecked" />
</td>
<td>{{ item.name }}</td>
<td>{{ item.price }}</td>
<td>{{ item.num }}</td>
</tr>
<tr>
<td>总计:</td>
<td colspan="3">{{ totalPrice }}</td>
</tr>
</table>
</div>
</template>
<script>
import { computed, reactive } from "vue";
export default {
setup(props) {
console.log(props);
var { iceCream } = reactive({
iceCream: [
{
id: 101,
name: "哈根达斯",
price: 98,
num: 10,
isChecked: true,
},
{
id: 100,
name: "DQ",
price: 67,
num: 15,
isChecked: false,
},
{
id: 112,
name: "八喜",
price: 48,
num: 28,
isChecked: false,
},
{
id: 117,
name: "蒙牛",
price: 3,
num: 34,
isChecked: false,
},
],
});
var isAllChecked = computed({
get() {
return iceCream.every((el) => el.isChecked);
},
set(value) {
iceCream.forEach((el) => (el.isChecked = value));
},
});
var totalPrice = computed(() => {
return iceCream.reduce((cur, val) => {
if (val.isChecked) {
return cur + val.num * val.price;
} else {
return cur;
}
}, 0);
});
return {
iceCream,
isAllChecked,
totalPrice,
};
},
};
</script>
<style lang="less" scoped>
table {
width: 500px;
margin: 0 auto;
td {
text-align: center;
}
}
</style>
watch
<template>
<div class="about">
<h1>This is an about page</h1>
<h3>Vue3.x的watch函数</h3>
<h3>1. 监听ref的响应式数据</h3>
<p>
当前的年龄是:{{ ageRef }}
<br />
出生日期:<input type="text" v-model.lazy="birth" />
</p>
<br />
<h3>2. 监听多个ref的响应式数据</h3>
<p>
当前的姓名是:{{ fullName }}
<input type="text" v-model.lazy="firstName" />
<input type="text" v-model.lazy="lastName" />
</p>
<br />
<h3>3. 监听reactive的响应式数据 ---- {{ film }}</h3>
<p>当前的电影票价是:<input type="number" v-model.lazy="movie.price" /></p>
<p>当前的电影是:<input type="text" v-model.lazy="movie.title" /></p>
<p>movie: {{ movie }}</p>
<br />
<h3>4. 监听reactive数据中的某个属性</h3>
<p>当前的学生是:<input type="text" v-model.lazy="student.name" /></p>
<p>student: {{ student }}</p>
</div>
</template>
<script>
import { watch, ref, reactive } from "vue";
export default {
setup(props) {
console.log(props);
// 1. 监听ref的响应式数据,newVal和oldVal都可以正常使用
var birth = ref("2000-01-04");
var ageRef = ref(0);
watch(
birth, // 被监听的数据
(newVal, oldVal) => {
console.log(newVal, oldVal);
ageRef.value =
new Date().getFullYear() - new Date(birth.value).getFullYear();
},
{
immediate: true, // 初始状态也生效
}
);
// 2. 监听多个ref的响应式数据
var firstName = ref("张");
var lastName = ref("三");
var fullName = ref("");
watch(
[firstName, lastName],
(newVal, oldVal) => {
console.log(newVal, oldVal);
fullName.value = newVal[0] + newVal[1];
},
{
immediate: true, // 初始状态也生效
}
);
// 3. 监听reactive的响应式数据
// watch监听reactive响应式数据,无法正确获取oldVal
// 默认开启的是深度监听
var movie = reactive({
title: "长津湖",
price: 48,
});
var film = reactive({});
watch(
movie,
(newVal, oldVal) => {
console.log(newVal, oldVal);
film.title = newVal.title;
film.price = newVal.price;
},
{ immediate: true }
// { deep: false }
);
// 4. 监听reactive数据中的某个属性
// watch监听reactive响应式数据的某个属性,可以正确获取oldVal
var student = reactive({
name: "李晓华",
age: 12,
});
watch(
// () => student.name,
student.name,
(newVal, oldVal) => {
console.log(newVal, oldVal);
}
);
return {
birth,
ageRef,
firstName,
lastName,
fullName,
movie,
film,
student,
};
},
};
</script>
<style lang="less" scoped></style>
数据绑定指令(举例说明)
指令说明
-
v-text指令:向其所在的节点中渲染文本内容,放入标签则也会被当成文本解析。
-
{{}}插值表达式:基本上和v-text的作用差不多,可以理解为v-text的简写。
v-text与差值表达式的区别:v-text会替换掉节点中的内容,原来的内容会被代替,无法与原来的内容一起出现。{{}}插值表达式则可以。
在Vue3.x中直接在包含内容的标签上使用v-text会报错。
-
v-html指令:向指定节点中渲染包含html结构的内容,更新元素的innerHTML。
内容按普通HTML插入,不会作为Vue模板进行编译。如果试图使用v-html组合模板,可以重新考虑通过使用组件来替代。
操作步骤
-
创建Vue3.x项目,参见[Vite创建Vue3.x项目【模板】](# Vite创建Vue3.x项目【模板】)。
-
在src\views目录下,新建文件InstructionView.vue。
<template> <div> <h3>1. 数据绑定指令</h3> <h3>今日份咖啡:{{ coffee }}</h3> </div> </template> <script setup> // 使用了setup的语法糖 import { ref, reactive } from "vue"; const coffee = ref("卡布奇洛"); const movie = reactive({ name: "蝙蝠侠", price: 55, }); const params = reactive({ username: "admin", password: "123", }); </script>
-
修改文件src\router\index.ts。
import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router"; import HomeView from "../views/HomeView.vue"; import InstructionView from "../views/InstructionView.vue"; const routes: Array<RouteRecordRaw> = [ { path: "/", name: "home", component: HomeView, }, { path: "/about", name: "about", // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import(/* webpackChunkName: "about" */ "../views/AboutView.vue"), }, { path: "/instruction", name: "instruction", component: InstructionView, }, ]; const router = createRouter({ history: createWebHashHistory(), routes, }); export default router;
-
修改文件src\App.vue。
<template> <nav> <router-link to="/">Home</router-link> | <router-link to="/about">About</router-link> | <router-link to="/instruction">instruction</router-link> </nav> <router-view /> </template> <style lang="less"> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; } nav { padding: 30px; a { font-weight: bold; color: #2c3e50; &.router-link-exact-active { color: #42b983; } } } </style>
-
在src\components目录下,新建子组件InstructionComp.vue。
<template> <div> <h3>1. 数据绑定指令</h3> <!-- {{}}插值表达式指令,计算表达式等均可 --> <p>今日份咖啡:{{ coffee }}</p> <hr /> <!-- v-text指令,只能渲染文本 --> <p v-text="coffee"></p> <!-- v-text指令,<strong>蝙蝠侠</strong>,不会解析strong元素 --> <p v-text="movie.name"></p> <hr /> <!-- v-html指令,蝙蝠侠,会解析strong元素 --> <!-- v-html指令,不建议使用,存在安全风险,容易造成XSS攻击 --> <p v-html="movie.name"></p> <hr /> <!-- v-model指令,双向数据绑定,作用于表单元素 --> <p>用户名:<input type="text" v-model="params.username" /></p> <p>密码:<input type="text" v-model="params.password" /></p> <p>params: {{ params }}</p> <!-- v-model指令,语法糖解析 --> <input type="text" :value="params.username" @input="params.username = $event.target.value" /> <hr /> <!-- v-model指令,双向数据绑定,作用于表单元素,单选框举例 --> <p>单选按钮:</p> <label for="">Male</label> <input type="radio" name="" id="" value="Male" v-model="gender" /> | <label for="">Female</label> <input type="radio" name="" id="" value="Female" v-model="gender" /> | <label for="">Other</label> <input type="radio" name="" id="" value="Other" v-model="gender" /> <p>当前选择的性别是:{{ gender }}</p> <hr /> <!-- v-model指令,双向数据绑定,作用于表单元素,单选框举例 --> <p>单选框:</p> <input type="checkbox" v-model="isAgree" />大熊协议 <p>您当前的选择是:{{ isAgree ? "同意" : "不同意" }}</p> <hr /> <!-- v-model指令,双向数据绑定,作用于表单元素,多选框举例 --> <!-- 多选框时候,存储数据的变量要以数组的形成呈现 --> <p>多选框:</p> <input type="checkbox" value="足球" v-model="hobbit" />足球 <input type="checkbox" value="游泳" v-model="hobbit" />游泳 <input type="checkbox" value="乒乓球" v-model="hobbit" />乒乓球 <input type="checkbox" value="篮球" v-model="hobbit" /> 篮球 <p>您当前的爱好是:{{ hobbit }}</p> <hr /> <!-- v-model指令,双向数据绑定,作用于表单元素,下拉框举例 --> <p>select下拉框</p> <select name="" id="" v-model="level"> <option value="黑铁">黑铁</option> <option value="青铜">青铜</option> <option value="白银">白银</option> <option value="黄金">黄金</option> <option value="铂金">铂金</option> <option value="钻石">钻石</option> <option value="荣耀">荣耀</option> <option value="王者">王者</option> </select> <p>您当前的段位是:{{ level }}</p> <hr /> <!-- v-model指令,双向数据绑定,作用于表单元素,xx.number修饰符,数值类型字符串转换为数值 --> <input type="text" v-model.number="num1" /> + <input type="text" v-model.number="num2" /> = <input type="text" :value="num1 + num2" /> <p>typeof num1: {{ typeof num1 }}</p> <hr /> <!-- v-model指令,双向数据绑定,作用于表单元素,xx.lazy修饰符,鼠标失去焦点的时候才会触发数据的同步 --> <input type="text" v-model.lazy="mobile" /> <p>mobile: {{ mobile }}</p> <hr /> <!-- v-model指令,双向数据绑定,作用于表单元素,xx.trim修饰符,自动去掉字符串前后的空格 --> <input type="text" v-model.trim="params.username" /> <p>params.username.length: {{ params.username.length }}</p> </div> </template> <script setup> // 使用了setup的语法糖 import { ref, reactive } from "vue"; const coffee = ref("卡布奇洛"); const movie = reactive({ name: "<strong>蝙蝠侠</strong>", price: 55, }); const params = reactive({ username: "admin", password: "123", }); const gender = ref("Female"); const isAgree = ref(false); const hobbit = ref([]); const level = ref("黑铁"); const num1 = ref(0); const num2 = ref(0); const mobile = ref(0); </script>
属性绑定指令
参见[属性绑定指令(v-bind/:)](# 属性绑定指令(v-bind/:))内容进行改造成Vue3.x版本的内容。
条件判断指令
参见[条件渲染指令](# 条件渲染指令)内容进行改造成Vue3.x版本的内容。
<template>
<div>
<h3>条件判断指令</h3>
<div>
<p v-if="cartList.length !== 0">
您当前加入购物车的商品一共有:{{ cartList.length }}件
</p>
<p v-else>您的购物车空空如也,快去购物吧</p>
</div>
<hr />
<div>
多分支条件语句,根据分数判断当前考试等级
<p>您当前的考试等级是:</p>
<p v-if="score >= 90">A</p>
<p v-else-if="score >= 80">B</p>
<p v-else-if="score >= 60">C</p>
<p v-else>D</p>
</div>
</div>
</template>
<script setup>
// 使用了setup的语法糖
import { ref, reactive } from "vue";
const { cartList } = reactive({
// cartList: [],
cartList: [
{
id: 1001,
name: "苹果",
},
],
});
const score = ref(10);
</script>
v-for列表渲染指令
参见[v-for列表渲染指令](# v-for列表渲染指令)内容进行改造成Vue3.x版本的内容。
<template>
<div>
<h3>v-for列表渲染指令</h3>
<!-- 在不做增删改查的时候,key绑定下标没问题 -->
<!-- -->
<p v-for="(item, index) in books" :key="index">
{{ index + 1 }} ---- {{ item }}
</p>
<hr />
<!-- 遍历对象内容 -->
<!-- value表示对象的值 -->
<!-- attr表示对象的属性名(key值) -->
<p v-for="(value, attr) in actor" :key="attr">
{{ attr }} ---- {{ value }}
</p>
</div>
<hr />
<!-- 循环嵌套 -->
<div v-for="item in beverages" :key="item.id">
<h3>{{ item.category }}</h3>
<ul>
<li v-for="(good, index) in item.commodity" :key="index">{{ good }}</li>
</ul>
</div>
<hr />
<h3>key绑定index和id的情况</h3>
<p><button @click="addItem">添加一项数据</button></p>
<ul>
<!-- 不推荐如此使用,这种情况是存在问题的 -->
<li v-for="(item, index) in todos" :key="index">
当前的内容是:{{ item.text }} <input type="text" />
</li>
</ul>
<!--
分析:
绑定index的旧虚拟DOM:
<li :key="0">当前的内容是:吃饭 <input type=""></li>
<li :key="1">当前的内容是:睡觉 <input type=""></li>
<li :key="2">当前的内容是:打豆豆 <input type=""></li>
绑定index的新虚拟DOM:
index在头部添加一项数据的虚拟DOM:
<li :key="0">当前的内容是:打游戏 <input type=""></li> ————key没有变化,input内容认为没有变化,实际上input内容需要跟随变化
<li :key="1">当前的内容是:吃饭 <input type=""></li>
<li :key="2">当前的内容是:睡觉 <input type=""></li>
<li :key="3">当前的内容是:打豆豆 <input type=""></li>
-->
<!--
绑定id的旧虚拟DOM:
<li :key="1001">当前的内容是:吃饭 <input type=""></li>
<li :key="1002">当前的内容是:睡觉 <input type=""></li>
<li :key="1003">当前的内容是:打豆豆 <input type=""></li>
绑定index的新虚拟DOM:
<li :key="1004">当前的内容是:打游戏 <input type=""></li> ————key新增DOM
<li :key="1001">当前的内容是:吃饭 <input type=""></li> ————key无变化
<li :key="1002">当前的内容是:睡觉 <input type=""></li> ————key无变化
<li :key="1003">当前的内容是:打豆豆 <input type=""></li> ————key无变化
-->
<hr />
<h3>key绑定index和id的情况</h3>
<p><button @click="addItem">添加一项数据</button></p>
<ul>
<!-- 推荐 -->
<li v-for="item in todos" :key="item.id">
当前的内容是:{{ item.text }} <input type="text" />
</li>
</ul>
</template>
<script setup>
// 使用了setup的语法糖
import { reactive } from "vue";
// v-for:用于循环列表数据
// 可遍历:数组、对象、字符串、指定次数
// v-for指令:"item in data",data为数据源
// v-for还可以接收第二个参数,指的是当前项的索引
// 语法:v-for="(item, index) in data"
// key的作用:
// key是虚拟DOM的标识,当数据发生变化的时候,Vue会根据新数据生成新的虚拟DOM节点
// 随后Vue进行新虚拟DOM和旧虚拟DOM的对比,对比规则:
// 1)旧虚拟DOM找到新虚拟DOM的key值
// 如果虚拟DOM内容没有变化,直接使用之前的真实DOM
// 如果虚拟DOM内容发生了变化,则生成新的真实DOM,随后替换掉页面之前的真实DOM
// 2)旧虚拟DOM未找到与新的虚拟DOM相同的key,会创建真实的DOM,随后渲染到页面
const { books, actor, beverages } = reactive({
books: [
"西游记",
"水浒传",
"三国演义",
"人性的弱点",
"晚熟的人",
"人类简史",
"今日简史",
"未来简史",
"三体",
],
actor: {
name: "沈腾",
age: 40,
work: "演员",
},
beverages: [
{
id: 10011,
category: "咖啡",
commodity: ["拿铁", "厚乳", "瑞纳水", "卡布奇诺", "美式"],
},
{
id: 10015,
category: "茶叶",
commodity: ["西湖龙井", "太平猴魁", "黄山毛峰", "庐山雨雾", "银针白毫"],
},
{
id: 10019,
category: "奶茶",
commodity: ["珍珠奶茶", "燕麦奶茶", "波霸奶茶", "蓝莓奶茶", "椰果奶茶"],
},
],
});
// 验证key绑定index和id的区别
const { todos } = reactive({
todos: [
{
id: 1001,
text: "吃饭",
done: false,
},
{
id: 1002,
text: "睡觉",
done: true,
},
{
id: 1003,
text: "打豆豆",
done: false,
},
],
});
function addItem() {
todos.unshift({
id: 1004,
text: "打游戏",
done: false,
});
}
</script>
/---- Vue3.x+TS ----/
Vue+TS
模板语法&Vue指令
模板插值语法
-
搭建Vue3.x+TS环境。
-
修改文件AblutView.vue。验证模板插值语法。
<template> <div class="about"> <h1>This is an about page</h1> <p>{{ msg }}</p> <p>{{ msg.split(",") }}</p> </div> </template> <script setup lang="ts"> let msg: string = "Hello, TS."; </script>
指令
-
v-text:显示文本。
<template> <div class="about"> <h1>This is an about page</h1> <p v-text="msg"></p> </div> </template> <script setup lang="ts"> let msg: string = "Hello, TS."; </script>
-
v-html:展示富文本。
<template> <div class="about"> <h1>This is an about page</h1> <p v-html="msg"></p> </div> </template> <script setup lang="ts"> let msg: string = "<div>Hello, TS.</div>"; </script>
-
v-if: 用来控制元素的显示隐藏(切换真假DOM)。
v-else-if:表示
v-if
的“else if 块”。可以链式调用。v-else:v-if条件收尾语句。
<template> <div class="about"> <h1>This is an about page</h1> <p v-if="flag === 'A'">A</p> <p v-else-if="flag === 'B'">B</p> <p v-else-if="flag === 'C'">C</p> <p v-else>D</p> </div> </template> <script setup lang="ts"> let flag: string = "B"; </script>
-
v-show:用来控制元素的显示隐藏(display none block ccss切换)。
<template> <div class="about"> <h1>This is an about page</h1> <p v-show="flag">A</p> <p>B</p> </div> </template> <script setup lang="ts"> let flag: boolean = false; </script>
-
v-on:简写
@
用来给元素添加事件。<template> <div class="about"> <h1>This is an about page</h1> <button @click="clickEvent">按键事件</button> </div> </template> <script setup lang="ts"> const clickEvent = () => { console.log("触发了点击事件"); }; </script>
-
v-on修饰符:冒泡举例。
<template> <div class="about"> <h1>This is an about page</h1> <div @click="parent"> <button @click.stop="clickEvent">按键事件</button> </div> </div> </template> <script setup lang="ts"> const parent = () => { console.log("我是父级的内容"); }; const clickEvent = () => { console.log("我是子级的内容"); }; </script>
说明:如果子级没有添加
.stop
的话,会同时触发子级和父级的点击事件。 -
v-on修饰符:阻止表单提交案例。
<template> <div class="about"> <h1>This is an about page</h1> <form action="/"> <button @click.prevent="clickEvent" type="submit">提交</button> </form> </div> </template> <script setup lang="ts"> const clickEvent = () => { console.log("我是子级的内容"); }; </script>
-
-
v-bind:简写
:
用来绑定元素的属性Attr。-
绑定样式举例,对象格式。
<template> <div class="about"> <h1>This is an about page</h1> <div :style="style">我是一个测试数据</div> </div> </template> <script setup lang="ts"> type Style = { color: string; height: string; }; const style: Style = { color: "blue", height: "200px", }; </script>
-
绑定样式举例,数组格式。
<template> <div class="about"> <h1>This is an about page</h1> <div :class="['a', 'b']">我是一个测试数据</div> </div> </template> <script setup lang="ts"></script> <style> .a { color: red; } .b { height: 300px; } </style>
-
绑定样式举例,数组格式,增加条件表达式。
<template> <div class="about"> <h1>This is an about page</h1> <div :class="[flag ? 'a' : 'b']">我是一个测试数据</div> </div> </template> <script setup lang="ts"> const flag: boolean = true; </script> <style> .a { color: red; } .b { color: blue; } </style>
-
绑定样式举例,对象格式。
<template> <div class="about"> <h1>This is an about page</h1> <div :class="cls">我是一个测试数据</div> </div> </template> <script setup lang="ts"> type Cls = { a: boolean; b: boolean; }; const cls = { a: true, b: true, }; </script> <style> .a { color: red; } .b { height: 200px; } </style>
-
-
v-for:用来遍历元素。
-
遍历数组格式。
<template> <div class="about"> <h1>This is an about page</h1> <div v-for="item in arr" :key="item">{{ item }}</div> </div> </template> <script setup lang="ts"> const arr: Array<number> = [1, 2, 3, 4, 5]; </script> <style></style>
-
遍历对象格式。
<template> <div class="about"> <h1>This is an about page</h1> <div v-for="item in arr" :key="item.id">{{ item.name }}</div> </div> </template> <script setup lang="ts"> const arr: Array<any> = [ { id: 1, name: "张三", }, { id: 2, name: "李四", }, ]; </script> <style></style>
-
-
v-model:数据的双向绑定。
<template> <div class="about"> <h1>This is an about page</h1> <input v-model="coffee" type="text" /> <p>{{ coffee }}</p> </div> </template> <script setup lang="ts"> import { ref } from "vue"; const coffee = ref("卡布奇洛"); </script> <style></style>
ref全家桶
-
ref使用,方式一
<template> <div class="about"> <h1>This is an about page</h1> <p>{{ msg }}</p> <button @click="changeMsg">修改msg值</button> </div> </template> <script setup lang="ts"> import { ref, Ref } from "vue"; let msg: Ref<string> = ref("卡布奇洛"); function changeMsg(): void { msg.value = "Hello TS"; } </script> <style> </style>
说明:注意被ref包装之后需要.value 来进行赋值。
-
ref使用,方式二
<template> <div class="about"> <h1>This is an about page</h1> <p>{{ msg }}</p> <button @click="changeMsg">修改msg值</button> </div> </template> <script setup lang="ts"> import { ref } from "vue"; let msg = ref<string | number>("卡布奇洛"); function changeMsg(): void { msg.value = "Hello TS"; } </script> <style> </style>
-
isRef:判断是不是一个Ref对象。
<template> <div class="about"> <h1>This is an about page</h1> <p>{{ msg }}</p> <button @click="changeMsg">修改msg值</button> </div> </template> <script setup lang="ts"> import { ref, Ref, isRef } from "vue"; let msg: Ref<string> = ref("卡布奇洛"); let notRef: number = 10; function changeMsg(): void { msg.value = "Hello TS"; console.log(isRef(msg)); // true console.log(isRef(notRef)); // false } </script> <style> </style>
-
shallowRef:创建一个跟踪自身
.value
变化的ref,但不会使其值也变成响应式的。<template> <div class="about"> <h1>This is an about page</h1> <p>{{ msg }}</p> <button @click="changeMsg">修改msg值</button> </div> </template> <script setup lang="ts"> import { ref, Ref, isRef, shallowRef } from "vue"; type Obj = { name: string } let msg: Ref<Obj> = shallowRef({ name: "张三" }); function changeMsg(): void { // msg.value.name = "Hello TS"; // 不会生效 msg.value = { name: "Hello Ts" }; // 生效 } </script> <style> </style>
-
triggerRef:强制刷新页面DOM。
<template> <div class="about"> <h1>This is an about page</h1> <p>{{ msg }}</p> <button @click="changeMsg">修改msg值</button> </div> </template> <script setup lang="ts"> import { ref, Ref, isRef, shallowRef, triggerRef } from "vue"; type Obj = { name: string } let msg: Ref<Obj> = shallowRef({ name: "张三" }); function changeMsg(): void { msg.value.name = "Hello TS"; // 不会生效 triggerRef(msg); // 强制刷新页面DOM } </script> <style> </style>
示例2:triggerRef存在视图更新的问题。
<template> <div class="about"> <h1>This is an about page</h1> <p>{{ r }}</p> <p>{{ msg }}</p> <button @click="changeMsg">修改msg值1</button> <button @click="changeMsg2">修改msg值2</button> </div> </template> <script setup lang="ts"> import { ref, Ref, shallowRef } from "vue"; type Obj = { name: string } let msg: Ref<Obj> = shallowRef({ name: "张三", bar: "哈哈" }); let r = ref<string>("前缀"); function changeMsg(): void { console.log("changeMsg ing."); msg.value.name = "Hello TS"; // 不会生效 } function changeMsg2(): void { console.log("changeMsg2 ing."); r.value = "前缀被修改"; msg.value.name = "Hello TS"; // 上行代码的修改会触发triggerRef方法导致shallowRef失效 } </script> <style> </style>
-
customerRef:自定义ref。
customRef是个工厂函数要求我们返回一个对象,并且实现get和set。<template> <div class="about"> <h1>This is an about page</h1> <p>{{ msg }}</p> <button @click="changeMsg">修改msg值</button> </div> </template> <script setup lang="ts"> import { Ref, shallowRef, triggerRef, customRef } from 'vue' function MyRef<T>(value: T) { return customRef((track, trigger) => { return { get() { track(); return value; }, set(newVal: T) { console.log('set'); value = newVal; trigger(); } } }) } let msg = MyRef('张三'); const changeMsg = () => { msg.value = '李四'; } </script> <style> </style>
reacive全家桶
用来绑定复杂的数据类型,例如对象和数组。
-
reactive
<template> <div class="about"> <h1>This is an about page</h1> <p>{{ obj }}</p> <p>{{ msg1 }}</p> <p>{{ msg2 }}</p> </div> </template> <script setup lang="ts"> import { reactive } from 'vue'; type Obj = { name: string, age: number } let obj = reactive<Obj>({ name: "张三", age: 15 }); obj.name = "李四"; let msg1 = reactive<number[]>([]); let arr1: Array<number> = [1, 2, 3, 4, 5]; msg1 = arr1; let msg2 = reactive<number[]>([]); let arr2: Array<number> = [2, 3, 4, 5, 6]; msg2.push(...arr2); </script> <style> </style>
-
readonly:拷贝一份proxy对象将其设置为只读。
<template> <div class="about"> <h1>This is an about page</h1> <p>{{ obj }}</p> </div> </template> <script setup lang="ts"> import { reactive, readonly } from 'vue'; type Obj = { name: string, age: number, count: number } let obj = reactive<Obj>({ name: "张三", age: 15, count: 0 }); const copy = readonly(obj); // copy.count++; // 报错,无法分配到 "count" ,因为它是只读属性 obj.count++; // 没问题 obj.name = "李四"; // 没问题 </script> <style> </style>
-
shallowReactive
-
直接代码触发方法,会导致页面的渲染修改。
在DOM挂载阶段,是会生效的。如果挂载之后再去修改数据,是不会被改变的。<template> <div class="about"> <h1>This is an about page</h1> <p>{{ msg }}</p> </div> </template> <script setup lang="ts"> import { shallowReactive } from 'vue'; let msg = shallowReactive({ name: "张三", age: 15, count: 0, nav: { bar: { text: "Hello" } } }); const change1 = () => { msg.name = "张三改成了李四"; console.log(msg); } const change2 = () => { msg.nav.bar.text = "我被改啦"; console.log(msg); } change1(); change2(); // 会触发页面的内容渲染修改 </script> <style> </style>
-
如果挂载之后再去修改数据,是不会被改变的。
<template> <div> <div>{{ state }}</div> <button @click="change1">test1</button> <button @click="change2">test2</button> </div> </template> <script setup lang="ts"> import { shallowReactive } from 'vue' const obj = { a: 1, first: { b: 2, second: { c: 3 } } } const state = shallowReactive(obj) function change1() { state.a = 7 } function change2() { state.first.b = 8 state.first.second.c = 9 console.log(state); } </script> <style> </style>
-
to系列全家桶(toRef/toRefs/toRaw)
-
toRef:针对原始对象是否是响应式的对象使用效果有所差异。
-
如果原始对象是非响应式的就不会更新视图,数据是会变的。toRef引用对象,会对自身造成影响,也会对原始对象造成影响,同时页面上的视图是不会发生变化的。
<template> <div class="about"> <h1>This is an about page</h1> <button @click="change">修改</button> <p>{{ msg }}</p> </div> </template> <script setup lang="ts"> import { toRef } from 'vue'; let msg = { foo: 1, bar: 1 }; let state = toRef(msg, "foo"); const change = () => { state.value++; console.log("原始对象:", msg); console.log("引用对象:", state); } </script> <style> </style>
-
如果原始对象是一个proxy代理的响应式对象。引用对象、原始对象以及页面上的视图都是会发生变化。
<template> <div class="about"> <h1>This is an about page</h1> <button @click="change">修改</button> <p>{{ msg }}</p> </div> </template> <script setup lang="ts"> import { toRef, reactive } from 'vue'; let msg = reactive({ foo: 1, bar: 1 }); let state = toRef(msg, "foo"); const change = () => { state.value++; console.log("原始对象:", msg); console.log("引用对象:", state); } </script> <style> </style>
-
-
toRefs:可以帮我们批量创建ref对象主要是方便我们解构使用。
非响应式举例:<template> <div class="about"> <h1>This is an about page</h1> <button @click="change">修改</button> <p>{{ msg }}</p> </div> </template> <script setup lang="ts"> import { toRefs, reactive } from 'vue'; let msg = reactive({ foo: 1, bar: 1 }); let { foo, bar } = msg; const change = () => { console.log(foo, bar); // 非响应式 } </script> <style> </style>
使用toRefs后,响应式举例:
<template> <div class="about"> <h1>This is an about page</h1> <button @click="change">修改</button> <p>{{ msg }}</p> </div> </template> <script setup lang="ts"> import { toRefs, reactive } from 'vue'; let msg = reactive({ foo: 1, bar: 1 }); let { foo, bar } = toRefs(msg); const change = () => { foo.value++; bar.value++; } </script> <style> </style>
-
toRaw:将响应式对象转换为普通对象(节约内存)。
<template> <div class="about"> <h1>This is an about page</h1> <button @click="change1">修改msg</button> <p>msg -- {{ msg }}</p> <button @click="change2">修改raw</button> <p>raw -- {{ raw }}</p> </div> </template> <script setup lang="ts"> import { toRaw, reactive } from 'vue'; let msg = reactive({ foo: 1, bar: 1 }); let raw = toRaw(msg); const change1 = () => { console.log("msg", msg); // msg Proxy {foo: 7, bar: 7},视图也会变化 msg.foo++; msg.bar++; } const change2 = () => { console.log("raw", raw); // raw {foo: 8, bar: 8},视图不会变化 raw.foo = 15; raw.bar++; } </script> <style> </style>
computed计算属性
-
以姓名为例说明(常规的计算方式)
<template> <div class="about"> <h1>This is an about page</h1> <input type="text" v-model="firstName"> <input type="text" v-model="lastName"> <p>{{ firstName }} -- {{ lastName }}</p> </div> </template> <script setup lang="ts"> import { computed, ref } from 'vue'; let firstName = ref<string>(""); let lastName = ref<string>(""); </script> <style> </style>
-
计算属性(方式一,函数形式),以计算属性的方式计算姓名全称。
<template> <div class="about"> <h1>This is an about page</h1> <input type="text" v-model="firstName"> <input type="text" v-model="lastName"> <p>{{ name }}</p> </div> </template> <script setup lang="ts"> import { computed, ref } from 'vue'; let firstName = ref<string>(""); let lastName = ref<string>(""); const name = computed(() => { return `${firstName.value} -- ${lastName.value}`; }); </script> <style> </style>
-
计算属性(方式二,对象形式),以计算属性的方式计算姓名全称。
<template> <div class="about"> <h1>This is an about page</h1> <input type="text" v-model="firstName"> <input type="text" v-model="lastName"> <p>{{ name }}</p> </div> </template> <script setup lang="ts"> import { computed, ref } from 'vue'; let firstName = ref<string>(""); let lastName = ref<string>(""); const name = computed({ get() { return `${firstName.value} -- ${lastName.value}`; }, set() { `${firstName.value} -- ${lastName.value}`; } }); </script> <style> </style>
computed购物车案例
<template>
<div>
<table style="width:800px" border>
<thead>
<tr>
<th>名称</th>
<th>数量</th>
<th>价格</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr :key="index" v-for="(item, index) in data">
<td align="center">{{ item.name }}</td>
<td align="center">
<button @click="AddAnbSub(item, false)">-</button>
{{ item.num }}
<button @click="AddAnbSub(item, true)">+</button>
</td>
<td align="center">{{ item.num * item.price }}</td>
<td align="center">
<button @click="del(index)">删除</button>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td></td>
<td></td>
<td></td>
<td align="center">总价:{{ $total }}</td>
</tr>
</tfoot>
</table>
</div>
</template>
<script setup lang="ts">
import { computed, reactive, ref } from 'vue'
type Shop = {
name: string,
num: number,
price: number
}
let $total = ref<number>(0)
const data = reactive<Shop[]>([
{
name: "XX的袜子",
num: 1,
price: 100
},
{
name: "XX的裤子",
num: 1,
price: 200
},
{
name: "XX的衣服",
num: 1,
price: 300
},
{
name: "XX的毛巾",
num: 1,
price: 400
}
])
const AddAnbSub = (item: Shop, type: boolean = false): void => {
if (item.num > 1 && !type) {
item.num--
}
if (item.num <= 99 && type) {
item.num++
}
}
const del = (index: number) => {
data.splice(index, 1)
}
$total = computed<number>(() => {
return data.reduce((prev, next) => {
return prev + (next.num * next.price)
}, 0)
})
</script>
<style>
</style>
watch侦听器
<template>
<div class="about">
<h1>This is an about page</h1>
<h3>Vue3.x的watch函数</h3>
<h3>1. 监听ref的响应式数据</h3>
<p>
当前的年龄是:{{ ageRef }}
<br />
出生日期:<input type="text" v-model.lazy="birth" />
</p>
<br />
<h3>2. 监听多个ref的响应式数据</h3>
<p>
当前的姓名是:{{ fullName }}
<input type="text" v-model.lazy="firstName" />
<input type="text" v-model.lazy="lastName" />
</p>
<br />
<h3>3. 监听reactive的响应式数据 ---- {{ movie }}</h3>
<p>当前的电影票价是:<input type="number" v-model.lazy="movie.price" /></p>
<p>当前的电影是:<input type="text" v-model.lazy="movie.title" /></p>
<p>movie: {{ movie }}</p>
<br />
<h3>4. 监听reactive数据中的某个属性</h3>
<p>当前的学生是:<input type="text" v-model.lazy="student.name" /></p>
<p>student: {{ student }}</p>
</div>
</template>
<script setup lang="ts">
import { watch, ref, reactive } from "vue";
// 1. 监听ref的响应式数据,newVal和oldVal都可以正常使用
var birth = ref<string>("2000-01-04");
var ageRef = ref<number>(0);
// 被监听的数据
watch(birth, (newVal, oldVal) => {
console.log(newVal, oldVal);
ageRef.value =
new Date().getFullYear() - new Date(birth.value).getFullYear();
}, {
immediate: true, // 初始状态也生效
});
// 2. 监听多个ref的响应式数据
var firstName = ref<string>("张");
var lastName = ref<string>("三");
var fullName = ref<string>("");
watch([firstName, lastName], (newVal, oldVal) => {
console.log(newVal, oldVal);
fullName.value = newVal[0] + newVal[1];
}, {
immediate: true, // 初始状态也生效
});
// 3. 监听reactive的响应式数据
// watch监听reactive响应式数据,无法正确获取oldVal
// 默认开启的是深度监听
var movie = reactive({
title: "长津湖",
price: 48,
});
watch(movie, (newVal, oldVal) => {
console.log(newVal, oldVal);
}, { immediate: true }
// { deep: false }
);
// 4. 监听reactive数据中的某个属性
// watch监听reactive响应式数据的某个属性,可以正确获取oldVal
var student = reactive({
name: "李晓华",
age: 12,
});
watch(() => student.name, (newVal, oldVal) => {
console.log(newVal, oldVal);
});
</script>
<style lang="less" scoped>
</style>
watchEffect高级侦听器
立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。
如果用到message就只会监听messag,就是用到几个监听几个,而且是非惰性 会默认调用一次。
-
watchEffect使用
<template> <div class="about"> <h1>This is an about page</h1> <input type="text" v-model="msg1"> <input type="text" v-model="msg2"> </div> </template> <script setup lang="ts"> import { watchEffect, ref } from "vue"; let msg1 = ref<string | number>(""); let msg2 = ref<string | number>(""); watchEffect(() => { console.log("----"); console.log("msg1: ", msg1.value); console.log("msg2: ", msg2.value); }); </script> <style lang="less" scoped> </style>
-
消除副作用:就是在触发监听之前会调用一个函数可以处理你的逻辑例如防抖。
<template> <div class="about"> <h1>This is an about page</h1> <input type="text" v-model.lazy="msg1"> </div> </template> <script setup lang="ts"> import { watchEffect, ref } from "vue"; let msg1 = ref<string | number>(""); watchEffect((before) => { before(()=> { console.log("before"); }) console.log("----"); console.log("msg1: ", msg1.value); }); </script> <style lang="less" scoped> </style>
-
停止跟踪:watchEffect返回一个函数,调用后停止更新
<template> <div class="about"> <h1>This is an about page</h1> <input type="text" v-model.lazy="msg"> <button @click="stop">停止侦听</button> </div> </template> <script setup lang="ts"> import { watchEffect, ref } from "vue"; let msg = ref<string | number>(""); const stop = watchEffect((before) => { before(()=> { console.log("before"); }) console.log("----"); console.log("msg: ", msg.value); }); </script> <style lang="less" scoped> </style>
-
消除副作用的刷新机制。副作用刷新时机flush一般使用post。
pre sync post 更新时机 组件更新前执行 强制效果始终同步触发 组件更新后执行 <template> <div class="about"> <h1>This is an about page</h1> <input type="text" v-model.lazy="msg"> <button @click="stop">停止侦听</button> </div> </template> <script setup lang="ts"> import { watchEffect, ref } from "vue"; let msg = ref<string | number>(""); const stop = watchEffect((before) => { before(() => { console.log("before"); }) console.log("----"); console.log("msg: ", msg.value); }, { flush: "post" }); </script> <style lang="less" scoped> </style>
-
onTrigger可以帮我们调试watchEffect。
<template> <div class="about"> <h1>This is an about page</h1> <input type="text" v-model.lazy="msg"> <button @click="stop">停止侦听</button> </div> </template> <script setup lang="ts"> import { watchEffect, ref } from "vue"; let msg = ref<string | number>(""); const stop = watchEffect((before) => { before(() => { console.log("before"); }) console.log("----"); console.log("msg: ", msg.value); }, { flush: "post", onTrigger(e) { debugger } }); </script> <style lang="less" scoped> </style>
认识组件和生命周期
<template>
<div class="about">
<h1>This is an about page</h1>
<div id="hello">我是Hello组件</div>
<br />
<p>count: {{ count }}</p>
<button @click="count++">更新Count值</button>
</div>
</template>
<script setup lang="ts">
import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, ref } from "vue";
let count = ref<number>(1);
onBeforeMount(() => {
let div = document.querySelector("#hello"); // 获取到的是null
console.log("创建之前 -->> onBeforeMount", div);
});
onMounted(() => {
let div = document.querySelector("#hello"); // 可以获取到元素值
console.log("创建完成 -->> onMounted", div);
});
// onBeforeUpdate和onUpdated生命周期
// 例如组件发生了更新/变化,会进入这两个生命周期
onBeforeUpdate(() => {
console.log("更新之前 -->> onBeforeUpdate");
});
onUpdated(() => {
console.log("更新完成 -->> onUpdated");
});
// 组件卸载之前和卸载完成
// 可以在组件被调用的位置加上一个v-if属性进行模拟组件卸载的效果
onBeforeUnmount(() => {
console.log("卸载之前 -->> onBeforeUnmount");
});
onUnmounted(() => {
console.log("卸载完成 -->> onUnmounted");
});
</script>
<style lang="less" scoped>
</style>
父子组件传参
以views/HomeView.vue组件和components/HelloWorld.vue组件的传值为例说明。
父组件给子组件传参
通过自定义属性进行传值。
-
src\views\HomeView.vue父组件传递参数值。
传递字符串类型可以不用加v-bind。
传递非字符串类型,需要加v-bind。<template> <div class="home"> <!-- 父组件传子组件,文本类型的参数直接传参即可,其他类型参数需要在传参前面加上一个v-bind --> <HelloWorld msg="Welcome to Your Vue.js + TypeScript App" :dataArr="dataArr" /> </div> </template> <script setup lang="ts"> import { reactive } from 'vue'; import HelloWorld from '../components/HelloWorld.vue'; // @ is an alias to /src let dataArr = reactive<number[]>([1, 2, 3, 4]); </script>
-
src\components\HelloWorld.vue子组件接收传值。
通过defineProps来接收。defineProps是无须引入,直接使用即可。
如果使用的是TypeScript,可以使用传递字面量类型的纯类型语法作为参数。
TypeScript接收方式。<template> <div class="hello"> <h1>{{ msg }}</h1> <p>{{ dataArr }}</p> </div> </template> <script setup lang="ts"> type Props = { msg: string, dataArr: number[] } defineProps<Props>(); </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped lang="less"> </style>
TypeScript特有的默认值方式。
withDefault是个函数也是无须引入开箱即用,接收参数中,第一个参数props函数,第二个参数是一个对象,设置默认值。<template> <div class="hello"> <h1>{{ msg }}</h1> <p>{{ dataArr }}</p> </div> </template> <script setup lang="ts"> type Props = { msg?: string, dataArr?: number[] } withDefaults(defineProps<Props>(), { msg: "张三", // 配置默认值 dataArr: () => [6, 6, 6] // 复杂函数需要使用函数返回值得方式使用 }); </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped lang="less"> </style>
子组件给父组件传参
通过自定义事件进行传值。
-
修改文件src\components\HelloWorld.vue。
<template> <div class="hello"> <h1>{{ msg }}</h1> <p>{{ dataArr }}</p> <div> <button @click="clickTip1"> 子组件派发值到父组件1</button> <button @click="clickTip2"> 子组件派发值到父组件2</button> </div> </div> </template> <script setup lang="ts"> import { reactive } from "@vue/reactivity"; // 子组件接收父组件的传值 type Props = { msg?: string, dataArr?: number[] } withDefaults(defineProps<Props>(), { msg: "张三", // 配置默认值 dataArr: () => [6, 6, 6] // 复杂函数需要使用函数返回值得方式使用 }); // 子组件向父组件传值 let list = reactive<number[]>([8, 8, 8]); let flag = true; const emit = defineEmits(['sunToFather1', 'sunToFather2']); // 自定义属性 const clickTip1 = () => { emit('sunToFather1', list, flag); } const clickTip2 = () => { emit('sunToFather2', list); } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped lang="less"> </style>
-
src\views\HomeView.vue接收子组件的传参。
<template> <div class="home"> <!-- 父组件传子组件,自定义属性,文本类型的参数直接传参即可,其他类型参数需要在传参前面加上一个v-bind --> <!-- 父组件接收子组件传值,自定义事件 --> <HelloWorld msg="Welcome to Your Vue.js + TypeScript App" :dataArr="dataArr" @sunToFather1="getList1" @sunToFather2="getList2" /> </div> </template> <script setup lang="ts"> import { reactive } from 'vue'; import HelloWorld from '../components/HelloWorld.vue'; // @ is an alias to /src let dataArr = reactive<number[]>([1, 2, 3, 4]); const getList1 = (list: number[], flag: boolean) => { console.log(list, flag, "我是子组件传递过来的值"); } const getList2 = (list: number[]) => { console.log(list, "我是子组件传递过来的值"); } </script>
子组件暴露给父组件的内部属性
通过defineExpress。
-
子组件通过defineExpress暴露属性给父组件。
<script setup lang="ts"> const list = reactive<number[]>([4, 5, 6]); defineExpress({ list, }); </script>
-
父组件获取子组件实例通过ref。
<template> <div class="home"> <HelloWorld ref="helloWorld" /> </div> </template> <script setup lang="ts"> import { reactive, ref } from 'vue'; import HelloWorld from '../components/HelloWorld.vue'; // @ is an alias to /src const helloWorld = ref(null); </script>
举例说明。
-
修改文件src\components\HelloWorld.vue。
<template> <div class="hello"> <h1>{{ msg }}</h1> <p>{{ dataArr }}</p> <div> <button @click="clickTip1"> 子组件派发值到父组件1</button> </div> </div> </template> <script setup lang="ts"> import { reactive } from "@vue/reactivity"; import { ref } from "vue"; // 子组件接收父组件的传值 type Props = { msg?: string, dataArr?: number[] } withDefaults(defineProps<Props>(), { msg: "张三", // 配置默认值 dataArr: () => [6, 6, 6] // 复杂函数需要使用函数返回值得方式使用 }); // 子组件向父组件传值 let list = reactive<number[]>([8, 8, 8]); let flag = ref(true); const emit = defineEmits(['sunToFather1']); // 自定义属性 const clickTip1 = () => { emit('sunToFather1', list, flag); } // 子组件暴露给父组件内部属性 defineExpose({ list, }) </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped lang="less"> </style>
-
修改文件。
<template> <div class="home"> <!-- 父组件传子组件,自定义属性,文本类型的参数直接传参即可,其他类型参数需要在传参前面加上一个v-bind --> <!-- 父组件接收子组件传值,自定义事件 --> <HelloWorld ref="helloWorld" @sunToFather1="getList1" /> </div> </template> <script setup lang="ts"> import { reactive, ref } from 'vue'; import HelloWorld from '../components/HelloWorld.vue'; // @ is an alias to /src const helloWorld = ref(null); const getList1 = () => { console.log(helloWorld.value); // 在Proxy > Target > Target中能拿到list的值 } </script>
v-model实现父子组件的双向绑定
直接使用默认值
-
新建一个Vite项目。
-
修改文件App.vue。
<template> <h3>我是父组件</h3> <button @click="flag = !flag">修改Flag值</button> <p>{{ flag }}</p> <HelloWorld v-model="flag" /> </template> <script setup lang="ts"> import { ref } from '@vue/reactivity' import HelloWorld from './components/HelloWorld.vue' let flag = ref<boolean>(true); let title = ref<string>("我是一条狗"); </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>
-
修改文件HelloWorld.vue。
<template> <div v-if="modelValue" class="hello"> <h3>我是子组件</h3> <p>{{ modelValue }}</p> </div> </template> <script setup lang="ts"> import { ref } from 'vue' type Props = { modelValue: boolean, } defineProps<Props>(); </script> <style scoped> .hello { background-color: aqua; width: 200px; height: 200px; } </style>
子组件修改父组件值
修改子组件HelloWorld.vue。
<template>
<div v-if="modelValue" class="hello">
<h3>我是子组件</h3>
<p>{{ modelValue }}</p>
<button @click="close">Close</button>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
type Props = {
modelValue: boolean,
}
defineProps<Props>();
const emit = defineEmits(['update:modelValue']); // 自定义属性
const close = () => {
emit('update:modelValue', false);
}
</script>
<style scoped>
.hello {
background-color: aqua;
width: 200px;
height: 200px;
}
</style>
自定义名称及实现双向绑定 *
-
修改父组件文件src\App.vue。
<template> <h3>我是父组件</h3> <button @click="flag = !flag">修改Flag值</button> <p>{{ flag }}</p> <p>{{ title }}</p> <!-- 支持传递绑定多个v-model参数,以及支持自定义名称 --> <HelloWorld v-model:title="title" v-model="flag" /> </template> <script setup lang="ts"> import { ref } from '@vue/reactivity' import HelloWorld from './components/HelloWorld.vue' let flag = ref<boolean>(true); let title = ref<string>("我是一条狗"); </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>
-
修改子组件文件src\components\HelloWorld.vue。
<template> <div v-if="modelValue" class="hello"> <h3>我是子组件</h3> <p>Flag: {{ modelValue }}</p> <p>Title: {{ title }}</p> <button @click="close">CloseAndChangeTitle</button> </div> </template> <script setup lang="ts"> import { ref } from 'vue' type Props = { modelValue: boolean, title: string, } defineProps<Props>(); const emit = defineEmits(['update:modelValue', 'update:title']); // 自定义属性 const close = () => { emit('update:modelValue', false); emit('update:title', '我是一只猫'); } </script> <style scoped> .hello { background-color: aqua; width: 200px; height: 200px; } </style>
支持自定义修饰符
-
修改父组件文件src\App.vue。
<template> <h3>我是父组件</h3> <button @click="flag = !flag">修改Flag值</button> <p>{{ flag }}</p> <p>{{ title }}</p> <!-- 支持传递绑定多个v-model参数,以及支持自定义名称 --> <HelloWorld v-model:title.selfAA="title" v-model.bb="flag" /> </template> <script setup lang="ts"> import { ref } from '@vue/reactivity' import HelloWorld from './components/HelloWorld.vue' let flag = ref<boolean>(true); let title = ref<string>("我是一条狗"); </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>
-
修改子组件文件src\components\HelloWorld.vue。
<template> <div v-if="modelValue" class="hello"> <h3>我是子组件</h3> <p>Flag: {{ modelValue }}</p> <p>Title: {{ title }}</p> <button @click="close">CloseAndChangeTitle</button> </div> </template> <script setup lang="ts"> import { ref } from 'vue' type Props = { modelValue: boolean, title: string, modelModifiers?: { bb: boolean, }, titleModifiers?: { selfAA: boolean } } const PropsData = defineProps<Props>(); const emit = defineEmits(['update:modelValue', 'update:title']); // 自定义属性 const close = () => { console.log("自定义修饰符", PropsData); if (PropsData.modelModifiers?.bb) { emit('update:title', '我是一只猫'); } else { emit('update:title', '我是一只狗'); } emit('update:modelValue', false); } </script> <style scoped> .hello { background-color: aqua; width: 200px; height: 200px; } </style>
全局组件、局部组件和递归组件
全局组件
例如组件使用频率非常高(table,Input,button,等)这些组件,几乎每个页面都在使用便可以封装成全局组件。
-
新建组件src\components\Cart.vue。
<template> <div class="card"> <div class="card-header"> <div>标题</div> <div>副标题</div> </div> <div v-if='content' class="card-content"> {{ content }} </div> </div> </template> <script setup lang="ts"> type Props = { content: string } defineProps<Props>() </script> <style scoped lang='less'> @border: #ccc; .card { width: 300px; border: 1px solid @border; border-radius: 3px; &:hover { box-shadow: 0 0 10px @border; } &-content { padding: 10px; } &-header { display: flex; justify-content: space-between; padding: 10px; border-bottom: 1px solid @border; } } </style>
-
在src\main.ts中引入组件,跟随在createApp(App)后面,切记不能放到mount后面。
import { createApp } from "vue"; import App from "./App.vue"; import router from "./router"; import store from "./store"; import Cart from "./components/Cart.vue"; createApp(App).component("Cart", Cart).use(store).use(router).mount("#app");
-
使用时直接引用该组件即可。
<template> <div class="home"> <Cart /> </div> </template>
局部组件
就是在组件A通过import引入别的组件B,称之为局部组件。
-
新建组件src\components\Cart.vue。
同上。 -
在src\views\HomeView.vue中引入Cart.vue组件。
<template> <div class="home"> <Cart content=""/> </div> </template> <script setup lang="ts"> import { reactive, ref } from 'vue'; import HelloWorld from '../components/HelloWorld.vue'; // @ is an alias to /src import Cart from '../components/Cart.vue' const helloWorld = ref(null); const getList1 = () => { console.log(helloWorld.value); // 在Proxy > Target > Target中能拿到list的值 } </script>
递归组件(未完成)
原理跟我们写JS递归是一样的,自己调用自己,通过一个条件来结束递归,否则导致内存泄漏。
-
新建文件src\components\Tree.vue。
<template> <div class="tree"> <h3>我是Tree组件</h3> </div> </template> <script setup lang="ts"> </script> <style scoped lang='less'> </style>
-
在文件src\views\HomeView.vue中,
<template> <div class="home"> <!-- 将参数传递给子组件 --> <Tree :data="data"></Tree> </div> </template> <script setup lang="ts"> import Tree from '../components/Tree.vue' import { reactive } from "vue"; type TreeList = { name: string; icon?: string; children?: TreeList[] | []; }; const data = reactive<TreeList[]>([ { name: "no.1", children: [ { name: "no.1-1", children: [ { name: "no.1-1-1", }, ], }, ], }, { name: "no.2", children: [ { name: "no.2-1", }, ], }, { name: "no.3", }, ]); </script>
-
子组件接收参数。修改文件src\components\Tree.vue。
动态组件
-
新建组件src\components\A.vue。
<template> <div class="a"> <h3>我是组件A</h3> </div> </template> <script setup lang="ts"> </script> <style scoped lang='less'> .a { background-color: orangered; height: 100px; width: 200px; margin: auto; }
-
新建组件src\components\B.vue。
<template> <div class="b"> <h3>我是组件B</h3> </div> </template> <script setup lang="ts"> </script> <style scoped lang='less'> .b { background-color: blue; height: 100px; width: 200px; margin: auto; } </style>
-
新建组件src\components\C.vue。
<template> <div class="c"> <h3>我是组件C</h3> </div> </template> <script setup lang="ts"> </script> <style scoped lang='less'> .c { background-color: green; height: 100px; width: 200px; margin: auto; } </style>
-
修改文件src\views\HomeView.vue。
<template> <div class="home"> <button v-for="item in data" :key="item.name" @click="switchComp(item)">{{ item.name }}</button> <component :is="cur.compName"></component> </div> </template> <script setup lang="ts"> import { markRaw, reactive } from '@vue/reactivity' import A from '../components/A.vue' import B from '../components/B.vue' import C from '../components/C.vue' type Tabs = { name: string, compName: any } const data = reactive<Tabs[]>([ { name: "我是A组件", compName: markRaw(A), }, { name: "我是B组件", compName: markRaw(B), }, { name: "我是C组件", compName: markRaw(C), }, ]); let cur = reactive({ compName: data[0].compName, // 定义一个默认值 }) const switchComp = (item: Tabs) => { cur.compName = item.compName; } </script>
插槽
匿名插槽
-
子组件放置一个插槽。新建组件src\components\A.vue。
<template> <div class="a"> <h3>我是组件A</h3> <slot></slot> </div> </template> <script setup lang="ts"> </script> <style scoped lang='less'> .a { background-color: orangered; height: 100px; width: 200px; margin: auto; } </style>
-
父组件使用插槽。在父组件给这个插槽填充内容。修改文件src\views\HomeView.vue。
<template> <div class="home"> <h1>我是HomeView页面</h1> <A> <template v-slot> <h>滕王阁序</h> </template> </A> </div> </template> <script setup lang="ts"> import { reactive } from '@vue/reactivity' import A from '../components/A.vue' </script>
具名插槽
具名插槽其实就是给插槽取个名字。一个子组件可以放多个插槽,而且可以放在不同的地方,而父组件填充内容时,可以根据这个名字把内容填充到对应插槽中。
-
新建组件src\components\A.vue。
<template> <div class="a"> <h3>我是组件A</h3> <slot name="header"></slot> </div> </template> <script setup lang="ts"> </script> <style scoped lang='less'> .a { background-color: orangered; height: 100px; width: 200px; margin: auto; } </style>
-
新建组件src\components\B.vue。
<template> <div class="b"> <h3>我是组件B</h3> <slot name="middle"></slot> </div> </template> <script setup lang="ts"> </script> <style scoped lang='less'> .b { background-color: blue; height: 100px; width: 200px; margin: auto; } </style>
-
新建组件src\components\C.vue。
<template> <div class="c"> <h3>我是组件C</h3> <slot name="footer"></slot> </div> </template> <script setup lang="ts"> </script> <style scoped lang='less'> .c { background-color: green; height: 100px; width: 200px; margin: auto; } </style>
-
修改文件src\views\HomeView.vue。
<template> <div class="home"> <h1>我是HomeView页面</h1> <A> <template #header> <h>滕王阁序</h> </template> </A> <B> <template #middle> <h>正文,哈哈</h> </template> </B> <C> <template #footer> <h>结尾了哦</h> </template> </C> </div> </template> <script setup lang="ts"> import { reactive } from '@vue/reactivity' import A from '../components/A.vue' import B from '../components/B.vue' import C from '../components/C.vue' </script>
作用域插槽
-
子组件放置一个插槽。新建组件src\components\A.vue。
<template> <div class="a"> <h3>我是组件A</h3> <div v-for="(item, index) in data" :key="item.name"> <slot :data="item" :index="index"></slot> </div> </div> </template> <script setup lang="ts"> import { reactive } from "vue"; type Names = { name: string, age: number, } const data = reactive<Names[]>([ { name: "张三", age: 16, }, { name: "李四", age: 15, }, { name: "王五", age: 18, }, ]); </script> <style scoped lang='less'> .a { background-color: orangered; height: 280px; width: 200px; margin: auto; } </style>
-
父组件使用插槽。在父组件给这个插槽填充内容。修改文件src\views\HomeView.vue。
<template> <div class="home"> <h1>我是HomeView页面</h1> <A> <template #default="scoop"> <h>滕王阁序</h> <p>{{ scoop.data.name }} -- {{ scoop.index }}</p> </template> </A> <!-- 结构解析 --> <A> <template #default="{ data, index }"> <h>滕王阁序</h> <p>{{ data.name }} -- {{ index }}</p> </template> </A> </div> </template> <script setup lang="ts"> import { reactive } from '@vue/reactivity' import A from '../components/A.vue' </script>
动态插槽
-
新建组件src\components\A.vue。
<template> <div class="a"> <h3>我是组件A</h3> <slot name="header"></slot> <p>header -- default</p> <slot></slot> <!-- name="default" --> <p>default -- footer</p> <slot name="footer"></slot> </div> </template> <script setup lang="ts"> </script> <style scoped lang='less'> .a { background-color: orangered; height: 200px; width: 200px; margin: auto; } </style>
-
修改文件src\views\HomeView.vue。
<template> <div class="home"> <h1>我是HomeView页面</h1> <A> <template #[name]> <h>滕王阁序</h> </template> </A> </div> </template> <script setup lang="ts"> import { reactive, ref } from '@vue/reactivity' import A from '../components/A.vue' const name = ref("footer"); </script>
异步组件&代码分包&suspense
异步组件
在大型应用中,我们可能需要将应用分割成小一些的代码块,并且减少主包的体积。
这时候就可以使用异步组件。
Teleport传送组件
Teleport Vue 3.0新特性之一。
Teleport 是一种能够将我们的模板渲染至指定DOM节点,不受父级style、v-show等属性影响,但data、prop数据依旧能够共用的技术;类似于 React 的 Portal。
主要解决的问题 因为Teleport节点挂载在其他指定的DOM节点下,完全不受父级style样式影响
使用方法
-
通过to属性 插入指定元素位置 to="body" 便可以将Teleport 内容传送到指定位置。
<template> <div class="home"> <h1>我是HomeView页面</h1> <Teleport to='body'> Hello teleport </Teleport> </div> </template> <script setup lang="ts"> </script>
-
可以自定义传送位置,支持class id等选择器。可以使用多个。
<template>
<div class="home">
<h1>我是HomeView页面</h1>
<Teleport to='#app'>
Hello teleport
</Teleport>
<Teleport to='#app'>
Hello teleport
</Teleport>
</div>
</template>
<script setup lang="ts">
</script>
keep-alive缓存组件
有时候我们不希望组件被重新渲染影响使用体验;或者处于性能考虑,避免多次重复渲染降低性能。而是希望组件可以缓存下来,维持当前的状态。这时候就需要用到keep-alive组件。
开启keep-alive生命周期的变化
- 初次进入时:onMounted> onActivated
- 退出后触发deactivated
- 再次进入:
- 只会触发onActivated
- 事件挂载的方法等,只执行一次的放在 onMounted中;组件每次进去执行的方法放在onActivated中。
操作步骤
-
新建文件src\components\A.vue。
<template> <div class="a"> <h3>我是组件A</h3> <table> <tr> <td>帐号</td> <td><input type="text" v-model="form.login"></td> </tr> <tr> <td>密码</td> <td><input type="text" v-model="form.psd"></td> </tr> <tr> <td>验证码</td> <td><input type="text" v-model="form.code"></td> </tr> </table> <button @click="submit">登录</button> </div> </template> <script setup lang="ts"> import { reactive } from "@vue/reactivity"; import { onActivated, onDeactivated, onMounted, onUnmounted } from "@vue/runtime-core"; const form = reactive({ login: "", psd: "", code: "", }); const submit = () => { console.log(form); }; onMounted(() => { console.log("Register onMounted."); }); onUnmounted(() => { console.log("Register onUnmounted."); }); onActivated(() => { console.log("Register onActivated."); }); onDeactivated(() => { console.log("Register onDeactivated."); }); </script> <style scoped lang='less'> .a { background-color: olive; height: 200px; width: 300px; margin: auto; } </style>
-
新建文件src\components\B.vue。
<template> <div class="a"> <h3>我是组件B</h3> <table> <tr> <td>帐号</td> <td><input type="text" v-model="form.login"></td> </tr> <tr> <td>密码</td> <td><input type="text" v-model="form.psd"></td> </tr> </table> <button @click="register">注册</button> </div> </template> <script setup lang="ts"> import { reactive } from "@vue/reactivity"; const form = reactive({ login: "", psd: "", }); const register = () => { console.log(form); } </script> <style scoped lang='less'> .a { background-color: green; height: 200px; width: 300px; margin: auto; } </style>
-
修改文件src\views\HomeView.vue。
<template> <div class="home"> <h1>我是HomeView页面</h1> <button @click="switchComp">切换</button> <keep-alive> <A v-if="flag"></A> <B v-else></B> </keep-alive> </div> </template> <script setup lang="ts"> import { ref } from 'vue'; import A from '../components/A.vue' import B from '../components/B.vue' let flag = ref<boolean>(true); const switchComp = () => { flag.value = !flag.value; } </script>
参数介绍
include和exclude prop允许组件有条件地缓存。二者都可以用逗号分隔字符串、正则表达式或一个数组来表示。
<keep-alive :include="" :exclude="" :max=""></keep-alive>
-
src\components\B.vue新建文件src\components\A.vue。
... <script lang="ts"> export default { name: 'A' } </script>
-
修改文件src\components\B.vue。
... <script lang="ts"> export default { name: 'B' } </script>
-
修改文件src\views\HomeView.vue。
<template> <div class="home"> <h1>我是HomeView页面</h1> <button @click="switchComp">切换</button> <keep-alive :include="['A', 'B']"> <A v-if="flag"></A> <B v-else></B> </keep-alive> </div> </template> <script setup lang="ts"> import { ref } from 'vue'; import A from '../components/A.vue' import B from '../components/B.vue' let flag = ref<boolean>(true); const switchComp = () => { flag.value = !flag.value; } </script>
-
transition动画组件
基本使用
修改文件src\views\HomeView.vue。
<template>
<div class="home">
<h1>我是HomeView页面</h1>
<button @click="flag = !flag">切换</button>
<transition name="fade">
<div class="box" v-if="flag"></div>
</transition>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
let flag = ref<boolean>(true);
</script>
<style scoped>
.box {
width: 200px;
height: 200px;
background-color: red;
}
.fade-enter-from {
background: red;
width: 0px;
height: 0px;
transform: rotate(360deg)
}
.fade-enter-active {
transition: all 2.5s linear;
}
.fade-enter-to {
background: yellow;
width: 200px;
height: 200px;
}
.fade-leave-from {
width: 200px;
height: 200px;
transform: rotate(360deg)
}
.fade-leave-active {
transition: all 1s linear;
}
.fade-leave-to {
width: 0px;
height: 0px;
}
</style>
自定义过渡类名并结合Animate三方库使用
transition props
- enter-from-class
- enter-active-class
- enter-to-class
- leave-from-class
- leave-active-class
- leave-to-class
自定义过渡时间(单位:毫秒),或者分别指定进入和离开的持续时间。
<transition :duration="1000">...</transition>
<transition :duration="{ enter: 1000, leave: 800 }">...</transition>
通过自定义class结合CSS动画库Animate CSS。
-
安装依赖库。
npm i -D animate.css
-
引入
import "animate.css"
,并使用动画库。
修改文件src\views\HomeView.vue。<template> <div class="home"> <h1>我是HomeView页面</h1> <button @click="flag = !flag">切换</button> <transition leave-active-class="animate__animated animate__fadeInDownBig" enter-active-class="animate__animated animate__bounceInRight" > <div v-if="flag" class="box"></div> </transition> </div> </template> <script setup lang="ts"> import { ref } from "vue"; import "animate.css"; let flag = ref<boolean>(true); </script> <style scoped> .box { width: 200px; height: 200px; background-color: red; } </style>
transition的8个生命周期结合GSAP三方库使用
@before-enter="beforeEnter" // 对应enter-from
@enter="enter" // 对应enter-active
@after-enter="afterEnter" // 对应enter-to
@enter-cancelled="enterCancelled" // 显示过度打断
@before-leave="beforeLeave" // 对应leave-from
@leave="leave" // 对应enter-active
@after-leave="afterLeave" // 对应leave-to
@leave-cancelled="leaveCancelled" // 离开过度打断
修改文件src\views\HomeView.vue。
<template>
<div class="home">
<h1>我是HomeView页面</h1>
<button @click="flag = !flag">切换</button>
<transition
leave-active-class="animate__animated animate__fadeInDownBig"
enter-active-class="animate__animated animate__bounceInRight"
@before-enter="enterFrom"
@enter="enterActive"
@after-enter="enterTo"
@enter-cancelled="enterCancelled"
@before-leave="leaveFrom"
@leave="leaveActive"
@after-leave="leaveTo"
@leave-cancelled="leaveCancelled"
>
<div v-if="flag" class="box"></div>
</transition>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import "animate.css";
let flag = ref<boolean>(true);
const enterFrom = (el: Element) => {
console.log("进入之前", el);
};
const enterActive = (el: Element, done: Function) => {
console.log("过渡曲线", el);
setTimeout(() => {
done();
}, 3000);
};
const enterTo = (el: Element) => {
console.log("过渡完成", el);
};
const enterCancelled = (el: Element) => {
console.log("过渡被打断", el);
};
const leaveFrom = (el: Element) => {
console.log("过渡完成", el);
};
const leaveActive = (el: Element, done: Function) => {
console.log("离开之前", el);
setTimeout(() => {
done();
}, 5000);
};
const leaveTo = (el: Element) => {
console.log("过渡曲线", el);
};
const leaveCancelled = (el: Element) => {
console.log("离开被打断", el);
};
</script>
<style scoped>
.box {
width: 200px;
height: 200px;
background-color: red;
}
</style>
结合GSAP三方库使用
-
安装依赖库。
npm i -D gsap
-
引入
import gsap "gsap"
,并使用动画库。
修改文件src\views\HomeView.vue。<template> <div class="home"> <h1>我是HomeView页面</h1> <button @click="flag = !flag">切换</button> <transition leave-active-class="animate__animated animate__fadeInDownBig" enter-active-class="animate__animated animate__bounceInRight" @before-enter="enterFrom" @enter="enterActive" @leave="leaveActive" > <div v-if="flag" class="box"></div> </transition> </div> </template> <script setup lang="ts"> import { ref } from "vue"; import gsap from "gsap"; let flag = ref<boolean>(true); const enterFrom = (el: Element) => { console.log("进入之前", el); gsap.set(el, { width: 0, height: 0, }); }; const enterActive = (el: Element, done: gsap.Callback) => { console.log("过渡曲线", el); gsap.to(el, { width: 200, height: 200, onComplete: done, }); }; const leaveActive = (el: Element, done: gsap.Callback) => { console.log("离开之前", el); gsap.to(el, { width: 0, height: 0, onComplete: done, }); }; </script> <style scoped> .box { width: 200px; height: 200px; background-color: red; } </style>
appear属性页面加载完成的开始动画
通过这个属性可以设置初始节点过度,就是页面加载完成就开始动画,对应三个状态。
只是针对加载完成的开始动画。
appear
appear-from-class=""
appear-active-class=""
appear-to-class=""
修改文件src\views\HomeView.vue。
<template>
<div class="home">
<h1>我是HomeView页面</h1>
<button @click="flag = !flag">切换</button>
<transition
appear
appear-from-class="from"
appear-active-class="active"
appear-to-class="to"
>
<div v-if="flag" class="box"></div>
</transition>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
let flag = ref<boolean>(true);
</script>
<style scoped>
.box {
width: 200px;
height: 200px;
background-color: red;
}
.from {
width: 0;
height: 0;
}
.active {
transition: all 2s ease;
}
.to {
width: 200px;
height: 200px;
}
</style>
也可以结合Animate三方库一起使用。
修改文件src\views\HomeView.vue。
<template>
<div class="home">
<h1>我是HomeView页面</h1>
<button @click="flag = !flag">切换</button>
<transition
appear
appear-active-class="animate__animated animate__fadeInDownBig"
>
<div v-if="flag" class="box"></div>
</transition>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import "animate.css";
let flag = ref<boolean>(true);
</script>
<style scoped>
.box {
width: 200px;
height: 200px;
background-color: red;
}
</style>
transition-group过渡列表
过渡列表
<template>
<div class="home">
<h1>我是HomeView页面</h1>
<button @click="addItem">ADD</button>
<button @click="deleteItem">POP</button>
<div class="wraps">
<!-- tag="section"为可选配置,配置之后,会多包裹一层div -->
<transition-group
enter-active-class="animate__animated animate__fadeInDownBig"
leave-active-class="animate__animated animate__hinge"
>
<div class="item" v-for="(item, index) in list" :key="index">
{{ item }}
</div>
</transition-group>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive } from "vue";
import "animate.css";
let list = reactive<number[]>([1, 2, 3, 5, 6, 6, 4]);
const addItem = () => {
list.push(list.length + 1);
};
const deleteItem = () => {
list.pop();
};
</script>
<style lang="less" scoped>
.wraps {
display: flex;
flex-wrap: wrap;
word-break: break-all;
border: 1px solid #ccc;
.item {
margin: 10px;
font-size: 30px;
}
}
</style>
列表的移动过渡
参见参考内容。
列表的状态过渡
参见参考内容。
依赖注入(Provide/Inject)
通常,当我们需要从父组件向子组件传递数据时,我们使用 props。想象一下这样的结构:有一些深度嵌套的组件,而深层的子组件只需要父组件的部分内容。在这种情况下,如果仍然将 prop 沿着组件链逐级传递下去,可能会很麻烦。
官网的解释很让人疑惑,那我翻译下这几句话:
provide 可以在祖先组件中指定我们想要提供给后代组件的数据或方法,而在任何后代组件中,我们都可以使用 inject 来接收 provide 提供的数据或方法。
-
修改文件src\views\HomeView.vue。
<template lang=""> <div class="content-home"> <h3>我是HomeView组件</h3> <p>{{ flag }}</p> <A></A> </div> </template> <script setup lang="ts"> import { ref } from '@vue/reactivity'; import { provide } from '@vue/runtime-core'; import A from '../components/A.vue'; // 使用ref或者reactive才能将传递的参数变成响应式的参数 let flag = ref<number>(1); provide('flag', flag); </script> <style deep lang="less"> .content-home { background: blue; color: #fff; width: 200px; height: 200px; } </style>
-
新建文件src\components\A.vue。
<template lang=""> <div class="content-a"> <h3>我是A组件</h3> <p>{{ flag }}</p> <button @click="change">Change</button> </div> </template> <script setup lang="ts"> import { inject, ref } from "@vue/runtime-core"; import { Ref } from "vue"; const flag = inject<Ref<number>>('flag', ref(1)); const change = () => { flag.value += 1; }; </script> <style deep lang="less"> .content-a { background: green; color: #fff; width: 200px; height: 100px; } </style>
兄弟组件传参(Mitt)
结合Mitt库使用Event Bus实现兄弟组件的传参。
-
安装依赖库。
npm i -D mitt
-
修改文件src\main.ts。
全局总线,vue入口文件main.js中挂载全局属性。import { createApp } from "vue"; import App from "./App.vue"; import router from "./router"; import store from "./store"; import mitt from "mitt"; const Mit = mitt(); // TypeScript注册 // 由于必须要拓展ComponentCustomProperties类型才能获得类型提示 declare module "vue" { export interface ComponentCustomProperties { $Bus: typeof Mit; } } const app = createApp(App); // Vue3挂载全局API app.config.globalProperties.$Bus = Mit; app.use(store).use(router).mount("#app");
-
修改文件src\components\A.vue。使用方法通过emit派发。
<template> <div class="a"> <h3>我是组件A</h3> <button @click="emit1">emit1</button> <button @click="emit2">emit2</button> </div> </template> <script setup lang="ts"> import { getCurrentInstance } from 'vue'; const instance = getCurrentInstance(); const emit1 = () => { instance?.proxy?.$Bus.emit("on-num1", 100); }; const emit2 = () => { instance?.proxy?.$Bus.emit("on-num2", 500); }; </script> <style scoped lang='less'> .a { background-color: orange; height: 200px; width: 300px; margin: auto; } </style> <script lang="ts"> export default { name: 'A' } </script>
-
修改文件src\components\B.vue。注册监听事件。
<template> <div class="b"> <h3>我是组件B</h3> </div> </template> <script setup lang="ts"> import { getCurrentInstance } from 'vue'; const instance = getCurrentInstance(); instance?.proxy?.$Bus.on('on-num1', (num) => { console.log("组件B on-num1", num); }); instance?.proxy?.$Bus.on('on-num2', (num) => { console.log("组件B on-num2", num); }); </script> <style scoped lang='less'> .b { background-color: green; height: 200px; width: 300px; margin: auto; } </style> <script lang="ts"> export default { name: 'B' } </script>
拓展
-
监听所有事件。
<script> ... // 监听所有事件 instance?.proxy?.$Bus.on('*', (type, num) => { console.log("组件B", type, num); }); </script>
-
移除监听事件。
<script> ... const Fn = (num: any) => { console.log(num, '===========>B') } instance?.proxy?.$Bus.on('on-num',Fn); // listen instance?.proxy?.$Bus.off('on-num',Fn); // unListen </script>
-
清空所有监听。
<script> ... instance?.proxy?.$Bus.all.clear(); </script>
TSX
unplugin-auto-import/vite无须import
Vite项目使用。
-
安装依赖包。
npm i -D unplugin-auto-import
-
在main.ts中导入。
import AutoImport from 'unplugin-auto-import/vite'
自定义指令directive
Vue3指令的钩子函数
- created:元素初始化的时候
- beforeMount:指令绑定到元素后调用,只调用一次
- mounted:元素插入父级dom调用
- beforeUpdate:元素被更新之前调用
- update:这个周期方法被移除 改用updated
- beforeUnmount:在元素被移除前调用
- unmounted:指令被移除后调用 只调用一次
setup中定义局部指令
这里有一个需要注意的限制:必须以 vNameOfDirective
的形式来命名本地自定义指令,以使得它们可以直接在模板中使用。
-
修改文件src\App.vue。
<template> <h3>我是App组件</h3> <button @click="show = !show">开关{{ show }} ----- {{ title }}</button> <!-- 可以自定义参数aaa和增加修饰符bcd --> <HelloWorld v-if="show" v-move:aaa.bcd="{ background: 'green', flag: show }"></HelloWorld> </template> <script setup lang="ts"> import { Directive, DirectiveBinding, ref } from "vue"; import HelloWorld from "./components/HelloWorld.vue"; let show = ref<boolean>(true); let title = ref<string>("Hello Vue+TS"); type Dir = { background: string, } const vMove: Directive = { created: () => { console.log("生命周期created:初始化"); }, beforeMount(...args: Array<any>) { // 在元素上做些操作 console.log("生命周期beforeMount:初始化一次", args); }, // 可以增加一个类型推导(可选)<Dir> mounted(el: any, dir: DirectiveBinding<Dir>) { el.style.background = dir.value.background; console.log("生命周期mounted:初始化"); }, beforeUpdate() { console.log("生命周期beforeUpdate:更新之前"); }, updated() { console.log("生命周期updated:更新结束"); }, beforeUnmount(...args: Array<any>) { console.log("生命周期beforeUnmount:卸载之前", args); }, unmounted(...args: Array<any>) { console.log("生命周期unmounted:卸载完成", args); }, }; </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>
-
修改文件src\components\HelloWorld.vue。
<template> <div class="hello"> <h3>我是A组件</h3> </div> </template> <script setup lang="ts"> </script> <style scoped> .hello { background-color: aqua; width: 200px; height: 200px; } </style>
函数简写
修改文件src\App.vue。
<template>
<div>
<h3>我是App组件</h3>
<input type="text" v-model="val">
<HelloWorld v-move="{ background: val }"></HelloWorld>
</div>
</template>
<script setup lang="ts">
import { Directive, DirectiveBinding, ref } from "vue";
import HelloWorld from "./components/HelloWorld.vue";
let val = ref<string>("");
type Dir = {
background: string,
}
const vMove: Directive = (el: any, dir: DirectiveBinding<Dir>) => {
el.style.background = dir.value.background;
};
</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>
自定义指令案例(拖拽窗口)
参见参考内容。
自定义Hooks(VueUse开源库)
自定义全局函数和变量
操作步骤
-
修改文件src\main.ts。
import { createApp } from 'vue' import App from './App.vue' let app = createApp(App); // 自定义了一个全局函数$filter app.config.globalProperties.$filters = { format<T>(str: T): string { return `真-${str}`; } } // 自定义了一个全局变量$env app.config.globalProperties.$env = "dev"; // 声明定义的全局函数和变量 // 声明要扩充@vue/runtime-core包的声明. // 这里扩充"ComponentCustomProperties"接口, 因为他是Vue3中实例的属性的类型 type Filter = { format: <T extends any>(str: T) => T }; declare module '@vue/runtime-core' { export interface ComponentCustomProperties { $filters: any, $env: string } } app.mount('#app')
-
修改文件src\App.vue。
<template> <div> <h3>我是App组件</h3> <div>{{ $filters.format("张三") }}</div> <div>{{ $env }}</div> </div> </template> <script setup lang="ts"></script> <style lang="less"></style>
自定义Vue3插件
UI库ElementUI【模板】
开箱即用。
以表格举例说明。
-
安装依赖库。
npm i -D element-plus
-
修改文件src\main.ts。
import { createApp } from 'vue' import App from './App.vue' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' const app = createApp(App); app.use(ElementPlus); app.mount('#app')
-
复制代码使用即可。
UI库AntDesign【模板】
操作步骤
-
安装依赖库。
npm i -D ant-design-vue
-
修改文件src\main.ts。
import { createApp } from 'vue' import App from './App.vue' import DatePicker from 'ant-design-vue'; import 'ant-design-vue/dist/antd.css'; const app = createApp(App); app.use(DatePicker); app.mount('#app')
-
复制代码使用即可。
<template> <a-timeline> <a-timeline-item>Create a services site 2015-09-01</a-timeline-item> <a-timeline-item>Solve initial network problems 2015-09-01</a-timeline-item> <a-timeline-item>Technical testing 2015-09-01</a-timeline-item> <a-timeline-item>Network problems being solved 2015-09-01</a-timeline-item> </a-timeline> </template>
详解Scoped和样式穿透
修改文件App.vue。
<template>
<div style="margin: 200px">
<el-input class="ipt"></el-input>
</div>
</template>
<script setup lang="ts"></script>
<style scoped lang="less">
.ipt {
:deep(input) {
background-color: red;
}
}
</style>
CSS完整新特性
插槽选择器
-
修改文件src\App.vue。
<template lang=""> <div> <h3>我是App组件</h3> <HelloWorld> <p class="helloSlot">我是父组件给子组件的内容</p> </HelloWorld> </div> </template> <script setup lang="ts"> import HelloWorld from './components/HelloWorld.vue' </script> <style lang="less"> </style>
-
修改文件src\components\HelloWorld.vue。
在子组件中添加插槽选择器即可控制修改父组件的插槽内容的样式。<template> <div class="hello"> <h3>我是HelloWorld组件</h3> <slot></slot> </div> </template> <script setup lang="ts"> </script> <style scoped> .hello { background-color: aqua; width: 200px; height: 200px; } :slotted(.helloSlot) { color: red; } </style>
全局选择器
之前我们想加入全局样式,通常都是新建一个style标签,不加scoped。
现在有更优雅的解决方案。
<style scoped lang="less">
.hello {
background-color: aqua;
width: 200px;
height: 200px;
}
:global(h3) {
color: red;
}
</style>
动态CSS
- 单文件组件的
<style>
标签可以通过v-bind这一CSS函数将CSS的值关联到动态的组件状态上。 - 如果是对象参数,v-bind参数中需要加上引号。
修改文件src\App.vue。
<template lang="">
<div>
<h3>我是App组件</h3>
<p><input v-model.lazy="fontColor" /></p>
<p><input v-model.lazy="fontBackground.color" /></p>
<p class="change">动态变化</p>
</div>
</template>
<script setup lang="ts">
import { reactive, ref } from "@vue/reactivity";
type BG = {
color: string
}
let fontColor = ref<string>('red');
let fontBackground = reactive<BG>({
color: "blue",
})
</script>
<style scoped lang="less">
.change {
color: v-bind('fontColor');
background-color: v-bind('fontBackground.color');
}
</style>
CSS Module
-
<style module>
标签会编译为CSS Modules,并且将生成的CSS类作为$style对象的键暴露给组件。<template lang=""> <div> <h3>我是App组件</h3> <p :class="$style.aaa">我是测试样式数据</p> </div> </template> <script setup lang="ts"> </script> <style module> .aaa { color: red; font-size: 20px; } </style>
-
自定义注入名称(多个情况下可以用数组)。
示例1:<template lang=""> <div> <h3>我是App组件</h3> <p :class="zs.aaa">我是测试样式数据</p> </div> </template> <script setup lang="ts"> </script> <style module="zs"> .aaa { color: red; font-size: 20px; } </style>
示例2:
<template lang=""> <div> <h3>我是App组件</h3> <p :class="[zs.aaa, zs.bbb]">我是测试样式数据</p> </div> </template> <script setup lang="ts"> </script> <style module="zs"> .aaa { color: red; font-size: 20px; } .bbb { border: 1px solid #ccc; } </style>
Vue3集成Tailwind CSS *
- 参考:CSDN 学习Vue3 第三十四章(Vue3集成Tailwind CSS)
- 参考:在 Vue 3 和 Vite 安装 Tailwind CSS - Tailwind CSS 中文文档
- 参考:VS Code中可以结合
Tailwind CSS IntelliSense
插件使用
操作步骤
-
安装依赖包。
npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
-
创建配置文件。
生成tailwind.config.js
和postcss.config.js
文件。npx tailwindcss init -p
-
修改配置文件
tailwind.config.js
。-
V2.6版本。
module.exports = { purge: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'], theme: { extend: {}, }, plugins: [], }
-
V3.0版本。
module.exports = { content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'], theme: { extend: {}, }, plugins: [], }
-
-
创建文件src\assets\tailwind.css。
@tailwind base; @tailwind components; @tailwind utilities;
-
在文件src\main.ts中引入CSS文件。
import { createApp } from 'vue' import App from './App.vue' import './assets/tailwind.css' createApp(App).mount('#app')
-
在XX.vue组件中直接使用该样式即可。
-
运行项目。
npm run dev
Pinia(适用Vue2.x和Vue3.x)
- 参考:bilibili 2022最新1小时Pinia快速入门教程
- 参考:bilibili Vue3 + vite + Ts + pinia + 实战 + 源码 +全栈
- 参考:CSDN 小满的博客 Pinia
- 参考:Pinia 中文文档: Home
安装Pinia(Vue3.x为例)
-
创建Vue3.x项目工程。
-
安装依赖包。
npm i -D pinia
-
修改文件src\main.ts。引入注册。
import { createApp } from 'vue' import App from './App.vue' import { createPinia } from 'pinia' const pinia = createPinia(); const app = createApp(App); app.use(pinia); app.mount('#app')
初始化仓库Store
-
新建文件夹src\store。
-
新建文件src\store\store-name.ts。
export const enum Names { TEST = 'TEST', }
-
修建文件src\store\index.ts。
import { defineStore } from "pinia"; import { Names } from "./store-name"; // 当前仓库名称为Names.TEST export const userStore = defineStore(Names.TEST, { state: () => { return { count: 10, name: '张三', } }, // computed计算属性 getters: { }, // methods,可以使用同步/异步方法,提交state actions: { }, });
-
修改文件src\App.vue。
<template> <div> pinia -- {{ TEST.count }} -- {{ TEST.name }} </div> </template> <script setup lang="ts"> // 可以根据需要导入需要的数据仓库 import { userStore } from './store' const TEST = userStore(); </script> <style scoped lang=""> </style>
-
State的修改方式。修改文件src\App.vue。
-
方式一:获取后直接修改。
<template> <div> pinia -- {{ TEST.count }} -- {{ TEST.name }} </div> <button @click="countAddOne">Count+1</button> </template> <script setup lang="ts"> // 可以根据需要导入需要的数据仓库 import { userStore } from './store' const TEST = userStore(); function countAddOne() { TEST.count++; } </script> <style scoped lang=""> </style>
-
方式二:数据结构后结合响应式的声明进行修改。
<template> <div> pinia -- {{ TEST.count }} -- {{ TEST.name }} </div> <button @click="countAddOne">Count+1</button> </template> <script setup lang="ts"> // 可以根据需要导入需要的数据仓库 import { storeToRefs } from 'pinia'; import { userStore } from './store' const TEST = userStore(); let { count } = storeToRefs(TEST); function countAddOne() { // TEST.count++; count.value++; } </script> <style scoped lang=""> </style>
-
方式三:使用$patch进行修改。
举例1:<template> <div> pinia -- {{ TEST.count }} -- {{ TEST.name }} </div> <button @click="countAddOne">Count+1</button> <button @click="patchClick">Count+5</button> </template> <script setup lang="ts"> // 可以根据需要导入需要的数据仓库 import { storeToRefs } from 'pinia'; import { userStore } from './store' const TEST = userStore(); let { count } = storeToRefs(TEST); function countAddOne() { // TEST.count++; count.value++; } // $patch的方式修改 function patchClick() { TEST.$patch({ count: TEST.count + 5, }) } </script> <style scoped lang=""> </style>
举例2:
修改文件src\store\index.ts。
import { defineStore } from "pinia"; import { Names } from "./store-name"; // 当前仓库名称为Names.TEST export const userStore = defineStore(Names.TEST, { state: () => { return { count: 10, name: '张三', list: [{ name: "iPhone", price: 5888, }, { name: "Mate40 Pro", price: 6888, },] } }, // computed计算属性 getters: { }, // methods,可以使用同步/异步方法,提交state actions: { }, });
修改文件src\App.vue。
<template> <div> pinia -- {{ TEST.count }} -- {{ TEST.name }} </div> <button @click="countAddOne">Count+1</button> <button @click="patchClick">Count+5</button> <br> <h3>商品列表:</h3> <ul> <li v-for="(item, index) in TEST.list" :key="index">{{ item }}</li> </ul> <button @click="listAddOne">ListAddOne</button> </template> <script setup lang="ts"> // 可以根据需要导入需要的数据仓库 import { storeToRefs } from 'pinia'; import { userStore } from './store' const TEST = userStore(); let { count } = storeToRefs(TEST); function countAddOne() { // TEST.count++; count.value++; } // $patch的方式修改 function patchClick() { TEST.$patch({ count: TEST.count + 5, }) } // $patch的方式修改 function listAddOne() { TEST.list.push({ name: "Oppo", price: 2000 }) TEST.$patch({ list: TEST.list }) } </script> <style scoped lang=""> </style>
-
方式四(推荐):使用$patch进行修改。
<template> <div> pinia -- {{ TEST.count }} -- {{ TEST.name }} </div> <button @click="countAddOne">Count+1</button> <button @click="patchClick">Count+5</button> <br> <h3>商品列表:</h3> <ul> <li v-for="(item, index) in TEST.list" :key="index">{{ item }}</li> </ul> <button @click="listAddOne">ListAddOne</button> <button @click="recommendPatch">RecommendPatch</button> </template> <script setup lang="ts"> // 可以根据需要导入需要的数据仓库 import { storeToRefs } from 'pinia'; import { userStore } from './store' const TEST = userStore(); let { count } = storeToRefs(TEST); function countAddOne() { // TEST.count++; count.value++; } // $patch的方式修改 function patchClick() { TEST.$patch({ count: TEST.count + 5, }) } // $patch的方式修改 function listAddOne() { TEST.list.push({ name: "Oppo", price: 2000 }) TEST.$patch({ list: TEST.list }) } // $patch推荐的修改方式 function recommendPatch() { TEST.$patch((state) => { state.list.push({ name: "Xiaomi", price: 2001, }); state.count += 10; } } </script> <style scoped lang=""> </style> 重置State。
-
替换State值
修改文件src\App.vue。
<template>
<div>
pinia -- {{ TEST.count }} -- {{ TEST.name }}
</div>
<button @click="countAddOne">Count+1</button>
<button @click="patchClick">Count+5</button>
<br>
<h3>商品列表:</h3>
<ul>
<li v-for="(item, index) in TEST.list" :key="index">{{ item }}</li>
</ul>
<button @click="listAddOne">ListAddOne</button>
<button @click="recommendPatch">RecommendPatch</button>
<button @click="toggleState">重置State状态</button>
</template>
<script setup lang="ts">
// 可以根据需要导入需要的数据仓库
import { storeToRefs } from 'pinia';
import { userStore } from './store'
const TEST = userStore();
let { count } = storeToRefs(TEST);
function countAddOne() {
// TEST.count++;
count.value++;
}
// $patch的方式修改
function patchClick() {
TEST.$patch({
count: TEST.count + 5,
})
}
// $patch的方式修改
function listAddOne() {
TEST.list.push({
name: "Oppo",
price: 2000
})
TEST.$patch({
list: TEST.list
})
}
// $patch推荐的修改方式
function recommendPatch() {
TEST.$patch((state) => {
state.list.push({
name: "Xiaomi",
price: 2001,
});
state.count += 10;
})
}
// 替换State值
function toggleState() {
TEST.$state = {
count: 100,
name: "李四",
list: [
{
name: "Oppo",
price: 2000
},
{
name: "Xiaomi",
price: 2001,
}
],
};
}
</script>
<style scoped lang="">
</style>
重置State状态
修改文件src\App.vue。
<template>
<div>
pinia -- {{ TEST.count }} -- {{ TEST.name }}
</div>
<button @click="countAddOne">Count+1</button>
<button @click="patchClick">Count+5</button>
<br>
<h3>商品列表:</h3>
<ul>
<li v-for="(item, index) in TEST.list" :key="index">{{ item }}</li>
</ul>
<button @click="listAddOne">ListAddOne</button>
<button @click="recommendPatch">RecommendPatch</button>
<button @click="toggleState">重置State状态</button>
<button @click="resetState">重置State状态</button>
</template>
<script setup lang="ts">
// 可以根据需要导入需要的数据仓库
import { storeToRefs } from 'pinia';
import { userStore } from './store'
const TEST = userStore();
let { count } = storeToRefs(TEST);
function countAddOne() {
// TEST.count++;
count.value++;
}
// $patch的方式修改
function patchClick() {
TEST.$patch({
count: TEST.count + 5,
})
}
// $patch的方式修改
function listAddOne() {
TEST.list.push({
name: "Oppo",
price: 2000
})
TEST.$patch({
list: TEST.list
})
}
// $patch推荐的修改方式
function recommendPatch() {
TEST.$patch((state) => {
state.list.push({
name: "Xiaomi",
price: 2001,
});
state.count += 10;
})
}
// 替换State值
function toggleState() {
TEST.$state = {
count: 100,
name: "李四",
list: [
{
name: "Oppo",
price: 2000
},
{
name: "Xiaomi",
price: 2001,
}
],
};
}
// 重置State状态
function resetState() {
TEST.$reset();
}
</script>
<style scoped lang="">
</style>
监听整个仓库变化
修改文件src\App.vue。
<template>
<div>
pinia -- {{ TEST.count }} -- {{ TEST.name }}
</div>
<button @click="countAddOne">Count+1</button>
<button @click="patchClick">Count+5</button>
<br>
<h3>商品列表:</h3>
<ul>
<li v-for="(item, index) in TEST.list" :key="index">{{ item }}</li>
</ul>
<button @click="listAddOne">ListAddOne</button>
<button @click="recommendPatch">RecommendPatch</button>
<button @click="toggleState">替换State值</button>
<button @click="resetState">重置State状态</button>
</template>
<script setup lang="ts">
// 可以根据需要导入需要的数据仓库
import { storeToRefs } from 'pinia';
import { userStore } from './store'
const TEST = userStore();
let { count } = storeToRefs(TEST);
function countAddOne() {
// TEST.count++;
count.value++;
}
// $patch的方式修改
function patchClick() {
TEST.$patch({
count: TEST.count + 5,
})
}
// $patch的方式修改
function listAddOne() {
TEST.list.push({
name: "Oppo",
price: 2000
})
TEST.$patch({
list: TEST.list
})
}
// $patch推荐的修改方式
function recommendPatch() {
TEST.$patch((state) => {
state.list.push({
name: "Xiaomi",
price: 2001,
});
state.count += 10;
})
}
// 替换State值
function toggleState() {
TEST.$state = {
count: 100,
name: "李四",
list: [
{
name: "Oppo",
price: 2000
},
{
name: "Xiaomi",
price: 2001,
}
],
};
}
// 重置State状态
function resetState() {
TEST.$reset();
}
// 监听整个仓库变化
TEST.$subscribe((mutations, state) => {
console.log("mutations", mutations);
console.log("state", state);
})
</script>
<style scoped lang="">
</style>
计算属性
例如需要计算总的价格。
修改文件src\App.vue。
-
修改文件src\store\index.ts。
import { defineStore } from "pinia"; import { Names } from "./store-name"; // 当前仓库名称为Names.TEST export const userStore = defineStore(Names.TEST, { state: () => { return { count: 10, name: '张三', list: [{ name: "iPhone", price: 5888, num: 1, }, { name: "Mate40 Pro", price: 6888, num: 1, },] } }, // computed计算属性 getters: { sumPrice: (state) => { return state.list.reduce((pre, item) => { return pre + (item.price * item.num); }, 0); } }, // methods,可以使用同步/异步方法,提交state actions: { }, });
-
修改文件src\App.vue。
<template> <div> pinia -- {{ TEST.count }} -- {{ TEST.name }} </div> <button @click="countAddOne">Count+1</button> <button @click="patchClick">Count+5</button> <br> <h3>商品列表:</h3> <ul> <li v-for="(item, index) in TEST.list" :key="index">{{ item }}</li> </ul> <button @click="listAddOne">ListAddOne</button> <button @click="recommendPatch">RecommendPatch</button> <button @click="toggleState">替换State值</button> <button @click="resetState">重置State状态</button> <br> <p>方式一:</p> <p>总的价格为:{{ TEST.sumPrice }}</p> <p>方式二:结构后直接使用</p> <p>总的价格为:{{ sumPrice }}</p> </template> <script setup lang="ts"> // 可以根据需要导入需要的数据仓库 import { storeToRefs } from 'pinia'; import { userStore } from './store' const TEST = userStore(); // 计算属性sumPrice也可以直接结构后使用 let { count, sumPrice } = storeToRefs(TEST); function countAddOne() { // TEST.count++; count.value++; } // $patch的方式修改 function patchClick() { TEST.$patch({ count: TEST.count + 5, }) } // $patch的方式修改 function listAddOne() { TEST.list.push({ name: "Oppo", price: 2000, num: 1, }) TEST.$patch({ list: TEST.list }) } // $patch推荐的修改方式 function recommendPatch() { TEST.$patch((state) => { state.list.push({ name: "Xiaomi", price: 2001, num: 1, }); state.count += 10; }) } // 替换State值 function toggleState() { TEST.$state = { count: 100, name: "李四", list: [ { name: "Oppo", price: 2000, num: 1, }, { name: "Xiaomi", price: 2001, num: 1, } ], }; } // 重置State状态 function resetState() { TEST.$reset(); } // 监听整个仓库变化 TEST.$subscribe((mutations, state) => { console.log("mutations", mutations); console.log("state", state); }) </script> <style scoped lang=""> </style>
Actions
同步和异步皆可以。
以XX网络请求为例说明。
-
修改文件src\store\index.ts。
import { defineStore } from "pinia"; import { Names } from "./store-name"; import axios from 'axios'; // 当前仓库名称为Names.TEST export const userStore = defineStore(Names.TEST, { state: () => { return { count: 10, name: '张三', list: [{ name: "iPhone", price: 5888, num: 1, }, { name: "Mate40 Pro", price: 6888, num: 1, },] } }, // computed计算属性 getters: { sumPrice: (state) => { return state.list.reduce((pre, item) => { return pre + (item.price * item.num); }, 0); } }, // methods,可以使用同步/异步方法,提交state actions: { countAddN(n: number) { this.count += n; }, async getExams() { let res = await axios.post( "http://www.liulongbin.top:3006/api/post", { name: "zs", gender: "女" } ); console.log(res); }, }, });
-
修改文件src\App.vue。
<template> <div> pinia -- {{ TEST.count }} -- {{ TEST.name }} </div> <button @click="countAddOne">Count+1</button> <button @click="patchClick">Count+5</button> <br> <h3>商品列表:</h3> <ul> <li v-for="(item, index) in TEST.list" :key="index">{{ item }}</li> </ul> <button @click="listAddOne">ListAddOne</button> <button @click="recommendPatch">RecommendPatch</button> <button @click="toggleState">替换State值</button> <button @click="resetState">重置State状态</button> <br> <p>方式一:</p> <p>总的价格为:{{ TEST.sumPrice }}</p> <p>方式二:结构后直接使用</p> <p>总的价格为:{{ sumPrice }}</p> <hr> <button @click="TEST.countAddN(20)">Actions countAddN</button> <button @click="TEST.getExams">获取请求</button> </template> <script setup lang="ts"> // 可以根据需要导入需要的数据仓库 import { storeToRefs } from 'pinia'; import { userStore } from './store' const TEST = userStore(); // 计算属性sumPrice也可以直接结构后使用 let { count, sumPrice } = storeToRefs(TEST); function countAddOne() { // TEST.count++; count.value++; } // $patch的方式修改 function patchClick() { TEST.$patch({ count: TEST.count + 5, }) } // $patch的方式修改 function listAddOne() { TEST.list.push({ name: "Oppo", price: 2000, num: 1, }) TEST.$patch({ list: TEST.list }) } // $patch推荐的修改方式 function recommendPatch() { TEST.$patch((state) => { state.list.push({ name: "Xiaomi", price: 2001, num: 1, }); state.count += 10; }) } // 替换State值 function toggleState() { TEST.$state = { count: 100, name: "李四", list: [ { name: "Oppo", price: 2000, num: 1, }, { name: "Xiaomi", price: 2001, num: 1, } ], }; } // 重置State状态 function resetState() { TEST.$reset(); } // 监听整个仓库变化 TEST.$subscribe((mutations, state) => { console.log("mutations", mutations); console.log("state", state); }) </script> <style scoped lang=""> </style>
Pinia插件
Pinia和Vuex都有一个通病,页面刷新状态会丢失。
Router
路由模式
-
vue2 mode hash vue3 createWebHashHistory
hash是URL中hash (#) 及后面的那部分,常用作锚点在页面内进行导航,改变URL中的hash部分,不会引起页面刷新。-
基于H5的
location.hash = 'xx'
实现原理。 -
通过以下方式监听hash值的变化。
window.addEventListener('hashchange', (e) => { console.log('hashchange', e); })
-
-
vue2 mode abstract vue3 createMemoryHistory
history提供pushState和replaceState两个方法,这两个方法改变URL的path部分,不会引起页面刷新。-
基于H5的history实现原理。
-
通过以下方法监听值得变化。
window.addEventListener('popstate', (e) => { console.log('popstate', e); })
-
通过以下方式实现跳转。
history.pushState({ state: 1 }, '', '/ccc')
-
Vite安装和配置Router【模板】
-
使用Vite搭建Vue3开发环境,参见[Vite创建Vue3.x项目【模板】](# Vite创建Vue3.x项目【模板】)。
-
安装和配置less。参见[Vite安装和配置less【模板】](# Vite安装和配置less【模板】)。
-
安装和配置Router。参见[Vite安装和配置Router【模板】](# Vite安装和配置Router【模板】)。
-
修改文件src\router\index.ts。
//引入路由对象 import { createRouter, createWebHistory, createWebHashHistory, createMemoryHistory, RouteRecordRaw } from 'vue-router' // vue2 mode history vue3 createWebHistory // vue2 mode hash vue3 createWebHashHistory // vue2 mode abstract vue3 createMemoryHistory // 路由数组的类型 RouteRecordRaw // 定义一些路由 // 每个路由都需要映射到一个组件。 const routes: Array<RouteRecordRaw> = [{ path: '/', component: () => import('../views/Home.vue') }, { path: '/about', component: () => import('../views/About.vue') }] const router = createRouter({ history: createWebHistory(), routes }) // 导出router export default router;
-
新建文件夹src\views和修建文件src\views\Home.vue。
<template> <div class="content-home"> <h3>我是Home组件</h3> </div> </template> <script setup lang="ts"> </script> <style lang="less" scoped> .content-home { width: 200px; height: 200px; background-color: aqua; } </style>
-
新建文件src\views\About.vue。
<template> <div class="content-about"> <h3>我是About组件</h3> </div> </template> <script setup lang="ts"> </script> <style lang="less" scoped> .content-about { width: 200px; height: 200px; background-color: green; } </style>
-
新建文件夹src\router和新建文件src\router\index.ts。
//引入路由对象 import { createRouter, createWebHistory, createWebHashHistory, createMemoryHistory, RouteRecordRaw } from 'vue-router' // vue2 mode history vue3 createWebHistory // vue2 mode hash vue3 createWebHashHistory // vue2 mode abstract vue3 createMemoryHistory // 路由数组的类型 RouteRecordRaw // 定义一些路由 // 每个路由都需要映射到一个组件。 const routes: Array<RouteRecordRaw> = [{ path: '/', component: () => import('../views/Home.vue') }, { path: '/about', component: () => import('../views/About.vue') }] const router = createRouter({ history: createWebHistory(), routes }) // 导出router export default router;
-
修改文件src\App.vue。声明路由链接和占位符。
<script setup lang="ts"> // This starter template is using Vue 3 <script setup> SFCs // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup </script> <template> <div> <!-- <router-link to="/home">首页</router-link> --> <router-link to="/">首页</router-link> | <router-link to="/about">关于</router-link> <hr /> <!-- 只要在项目中安装和配置了vue-router,就可以使用router-view这个组件了 --> <!-- 它的作用很单纯,就是占位符 --> <router-view></router-view> </div> </template> <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>
命名路由
-
修改文件src\router\index.ts。增加name属性。
//引入路由对象 import { createRouter, createWebHistory, createWebHashHistory, createMemoryHistory, RouteRecordRaw } from 'vue-router' // vue2 mode history vue3 createWebHistory // vue2 mode hash vue3 createWebHashHistory // vue2 mode abstract vue3 createMemoryHistory // 路由数组的类型 RouteRecordRaw // 定义一些路由 // 每个路由都需要映射到一个组件。 const routes: Array<RouteRecordRaw> = [{ path: '/', name: 'Home', component: () => import('../views/Home.vue') }, { path: '/about', name: 'About', component: () => import('../views/About.vue') }] const router = createRouter({ history: createWebHistory(), routes }) // 导出router export default router;
-
修改文件src\App.vue。
<script setup lang="ts"> // This starter template is using Vue 3 <script setup> SFCs // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup </script> <template> <div> <!-- <router-link to="/home">首页</router-link> --> <router-link :to="{ name: 'Home' }">首页</router-link> | <router-link :to="{ name: 'About' }">关于</router-link> <hr /> <!-- 只要在项目中安装和配置了vue-router,就可以使用router-view这个组件了 --> <!-- 它的作用很单纯,就是占位符 --> <router-view></router-view> </div> </template> <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>
使用
a
标签也可以实现跳转,会存在页面刷新的现象,不推荐使用。<script setup lang="ts"> // This starter template is using Vue 3 <script setup> SFCs // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup </script> <template> <div> <!-- <router-link to="/home">首页</router-link> --> <!-- <router-link :to="{ name: 'Home' }">首页</router-link> | --> <!-- <router-link :to="{ name: 'About' }">关于</router-link> --> <a href="/">首页</a> | <a href="/about">关于</a> <hr /> <!-- 只要在项目中安装和配置了vue-router,就可以使用router-view这个组件了 --> <!-- 它的作用很单纯,就是占位符 --> <router-view></router-view> </div> </template> <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>
编程式导航
修改文件src\App.vue。
-
字符串模式
<script setup lang="ts"> // This starter template is using Vue 3 <script setup> SFCs // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup import { useRouter } from 'vue-router' const router = useRouter(); const gotoPage = (url: string) => { router.push(url); } </script> <template> <div> <button @click="gotoPage('/')">首页</button> | <button @click="gotoPage('/about')">关于</button> </div> <hr> <router-view></router-view> </template> <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>
-
对象模式
<script setup lang="ts"> // This starter template is using Vue 3 <script setup> SFCs // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup import { useRouter } from 'vue-router' const router = useRouter(); const gotoPage = (url: string) => { router.push({ path: url, }); } </script> <template> <div> <button @click="gotoPage('/')">首页</button> | <button @click="gotoPage('/about')">关于</button> </div> <hr> <router-view></router-view> </template> <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>
-
命名式路由模式
<script setup lang="ts"> // This starter template is using Vue 3 <script setup> SFCs // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup import { useRouter } from 'vue-router' const router = useRouter(); const gotoPage = (urlName: string) => { router.push({ name: urlName, }); } </script> <template> <div> <button @click="gotoPage('Home')">首页</button> | <button @click="gotoPage('About')">关于</button> </div> <hr> <router-view></router-view> </template> <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>
历史记录
屏蔽历史记录
修改文件src\App.vue。
-
场景1
<script setup lang="ts"> // This starter template is using Vue 3 <script setup> SFCs // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup </script> <template> <div> <!-- <router-link to="/home">首页</router-link> --> <router-link replace :to="{ name: 'Home' }">首页</router-link> | <router-link replace :to="{ name: 'About' }">关于</router-link> <hr /> <!-- 只要在项目中安装和配置了vue-router,就可以使用router-view这个组件了 --> <!-- 它的作用很单纯,就是占位符 --> <router-view></router-view> </div> </template> <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>
-
场景2
<script setup lang="ts"> // This starter template is using Vue 3 <script setup> SFCs // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup import { useRouter } from 'vue-router' const router = useRouter(); const gotoPage = (urlName: string) => { router.replace({ name: urlName, }); } </script> <template> <div> <button @click="gotoPage('Home')">首页</button> | <button @click="gotoPage('About')">关于</button> </div> <hr> <router-view></router-view> </template> <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>
主动触发前进和后退
<script setup lang="ts">
// This starter template is using Vue 3 <script setup> SFCs
// Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
import { useRouter } from 'vue-router'
const router = useRouter();
const gotoPage = (urlName: string) => {
router.push({
name: urlName,
});
}
const next = () => {
router.go(1); // router.go(2);
}
const back = () => {
// 方式一
router.back();
// 方式二(不推荐)
// router.go(-1);
}
</script>
<template>
<div>
<!-- <router-link to="/home">首页</router-link> -->
<router-link :to="{ name: 'Home' }">首页</router-link> |
<router-link :to="{ name: 'About' }">关于</router-link> |
<button @click="gotoPage('Home')">首页</button> |
<button @click="gotoPage('About')">关于</button> |
<button @click="next">Next</button> |
<button @click="back">Back</button>
<hr />
<!-- 只要在项目中安装和配置了vue-router,就可以使用router-view这个组件了 -->
<!-- 它的作用很单纯,就是占位符 -->
<router-view></router-view>
</div>
</template>
<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>
路由传参
query方式传参
说明:query方式传参,会直接展示在URL的地址上。
-
修改文件src\router\index.ts。
//引入路由对象 import { createRouter, createWebHistory, createWebHashHistory, createMemoryHistory, RouteRecordRaw } from 'vue-router' // vue2 mode history vue3 createWebHistory // vue2 mode hash vue3 createWebHashHistory // vue2 mode abstract vue3 createMemoryHistory // 路由数组的类型 RouteRecordRaw // 定义一些路由 // 每个路由都需要映射到一个组件。 const routes: Array<RouteRecordRaw> = [{ path: '/', name: 'Home', component: () => import('../views/Home.vue') }, { path: '/about', name: 'About', component: () => import('../views/About.vue') }] const router = createRouter({ history: createWebHistory(), routes }) // 导出router export default router;
-
修改文件src\App.vue。
<script setup lang="ts"> // This starter template is using Vue 3 <script setup> SFCs // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup import { useRouter } from 'vue-router' const router = useRouter(); type RootObject = { data: Datum[]; } type Datum = { name: string; price: number; id: number; } let list: RootObject = { "data": [ { name: "红烧牛肉面", price: 50, id: 1, }, { name: "火腿肠", price: 35, id: 2, }, { name: "牛肉干", price: 24, id: 3, }, ] } const gotoPage = (url: string, dataNo: number) => { router.push({ path: url, query: list.data[dataNo], }); } </script> <template> <div> <button @click="gotoPage('/', 0)">首页</button> | <button @click="gotoPage('/about', 1)">关于</button> <hr /> <!-- 只要在项目中安装和配置了vue-router,就可以使用router-view这个组件了 --> <!-- 它的作用很单纯,就是占位符 --> <router-view></router-view> </div> </template> <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>
-
修改文件src\views\Home.vue。
<template> <div class="content-home"> <h3>我是Home组件</h3> <p>品牌:{{ route.query.name }}</p> <p>价格:{{ route.query.price }}</p> <p>ID:{{ route.query.id }}</p> </div> </template> <script setup lang="ts"> import { useRoute } from 'vue-router'; // 接收传参数据 import { useRouter } from 'vue-router'; const route = useRoute(); const router = useRouter(); </script> <style lang="less" scoped> .content-home { width: 200px; height: 200px; background-color: aqua; } </style>
-
修改文件src\views\About.vue。
<template> <div class="content-about"> <h3>我是About组件</h3> <p>品牌:{{ route.query.name }}</p> <p>价格:{{ route.query.price }}</p> <p>ID:{{ route.query.id }}</p> </div> </template> <script setup lang="ts"> import { useRoute } from 'vue-router'; // 接收传参数据 import { useRouter } from 'vue-router'; const route = useRoute(); const router = useRouter(); </script> <style lang="less" scoped> .content-about { width: 200px; height: 200px; background-color: green; } </style>
params方式传参
说明:
- params需要结合name的方式进行使用。
- 存储在内存中,刷新页面,参数会丢失。
-
修改文件src\router\index.ts。
//引入路由对象 import { createRouter, createWebHistory, createWebHashHistory, createMemoryHistory, RouteRecordRaw } from 'vue-router' // vue2 mode history vue3 createWebHistory // vue2 mode hash vue3 createWebHashHistory // vue2 mode abstract vue3 createMemoryHistory // 路由数组的类型 RouteRecordRaw // 定义一些路由 // 每个路由都需要映射到一个组件。 const routes: Array<RouteRecordRaw> = [{ path: '/', name: 'Home', component: () => import('../views/Home.vue') }, { path: '/about', name: 'About', component: () => import('../views/About.vue') }] const router = createRouter({ history: createWebHistory(), routes }) // 导出router export default router;
-
修改文件src\App.vue。
<script setup lang="ts"> // This starter template is using Vue 3 <script setup> SFCs // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup import { useRouter } from 'vue-router' const router = useRouter(); type RootObject = { data: Datum[]; } type Datum = { name: string; price: number; id: number; } let list: RootObject = { "data": [ { name: "红烧牛肉面", price: 50, id: 1, }, { name: "火腿肠", price: 35, id: 2, }, { name: "牛肉干", price: 24, id: 3, }, ] } const gotoPage = (urlName: string, dataNo: number) => { router.push({ name: urlName, params: list.data[dataNo], }); } </script> <template> <div> <button @click="gotoPage('Home', 0)">首页</button> | <button @click="gotoPage('About', 1)">关于</button> <hr /> <!-- 只要在项目中安装和配置了vue-router,就可以使用router-view这个组件了 --> <!-- 它的作用很单纯,就是占位符 --> <router-view></router-view> </div> </template> <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>
-
修改文件src\views\Home.vue。
<template> <div class="content-home"> <h3>我是Home组件</h3> <p>品牌:{{ route.params.name }}</p> <p>价格:{{ route.params.price }}</p> <p>ID:{{ route.params.id }}</p> </div> </template> <script setup lang="ts"> import { useRoute } from 'vue-router'; // 接收传参数据 import { useRouter } from 'vue-router'; const route = useRoute(); const router = useRouter(); </script> <style lang="less" scoped> .content-home { width: 200px; height: 200px; background-color: aqua; } </style>
-
修改文件src\views\About.vue。
<template> <div class="content-about"> <h3>我是About组件</h3> <p>品牌:{{ route.params.name }}</p> <p>价格:{{ route.params.price }}</p> <p>ID:{{ route.params.id }}</p> </div> </template> <script setup lang="ts"> import { useRoute } from 'vue-router'; // 接收传参数据 import { useRouter } from 'vue-router'; const route = useRoute(); const router = useRouter(); </script> <style lang="less" scoped> .content-about { width: 200px; height: 200px; background-color: green; } </style>
params方式结合动态路由参数
-
修改文件src\router\index.ts。
//引入路由对象 import { createRouter, createWebHistory, createWebHashHistory, createMemoryHistory, RouteRecordRaw } from 'vue-router' // vue2 mode history vue3 createWebHistory // vue2 mode hash vue3 createWebHashHistory // vue2 mode abstract vue3 createMemoryHistory // 路由数组的类型 RouteRecordRaw // 定义一些路由 // 定义一些路由 // 每个路由都需要映射到一个组件。 const routes: Array<RouteRecordRaw> = [{ path: '/', name: 'Home', component: () => import('../views/Home.vue') }, { // 动态路由传参 path: '/about/:id', name: 'About', component: () => import('../views/About.vue') }] const router = createRouter({ history: createWebHistory(), routes }) // 导出router export default router;
-
修改文件src\App.vue。
<script setup lang="ts"> // This starter template is using Vue 3 <script setup> SFCs // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup import { useRouter } from 'vue-router' const router = useRouter(); type RootObject = { data: Datum[]; } type Datum = { name: string; price: number; id: number; } let list: RootObject = { "data": [ { name: "红烧牛肉面", price: 50, id: 1, }, { name: "火腿肠", price: 35, id: 2, }, { name: "牛肉干", price: 24, id: 3, }, ] } const gotoPage = (urlName: string, dataNo: number) => { router.push({ name: urlName, params: { id: list.data[dataNo].id }, }); } </script> <template> <div> <button @click="gotoPage('Home', 0)">首页</button> | <button @click="gotoPage('About', 1)">关于</button> <hr /> <!-- 只要在项目中安装和配置了vue-router,就可以使用router-view这个组件了 --> <!-- 它的作用很单纯,就是占位符 --> <router-view></router-view> </div> </template> <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>
-
修改文件src\views\Home.vue。
<template> <div class="content-home"> <h3>我是Home组件</h3> <p>品牌:{{ route.params.name }}</p> <p>价格:{{ route.params.price }}</p> <p>ID:{{ route.params.id }}</p> </div> </template> <script setup lang="ts"> import { useRoute } from 'vue-router'; // 接收传参数据 import { useRouter } from 'vue-router'; const route = useRoute(); const router = useRouter(); </script> <style lang="less" scoped> .content-home { width: 200px; height: 200px; background-color: aqua; } </style>
-
修改文件src\views\About.vue。
<template> <div class="content-about"> <h3>我是About组件</h3> <p>品牌:{{ route.params.name }}</p> <p>价格:{{ route.params.price }}</p> <p>ID:{{ route.params.id }}</p> </div> </template> <script setup lang="ts"> import { useRoute } from 'vue-router'; // 接收传参数据 import { useRouter } from 'vue-router'; const route = useRoute(); const router = useRouter(); </script> <style lang="less" scoped> .content-about { width: 200px; height: 200px; background-color: green; } </style>
路由嵌套
-
使用Vite搭建Vue3开发环境,参见[Vite创建Vue3.x项目【模板】](# Vite创建Vue3.x项目【模板】)。
-
安装和配置less。参见[Vite安装和配置less【模板】](# Vite安装和配置less【模板】)。
-
安装和配置Router。参见[Vite安装和配置Router【模板】](# Vite安装和配置Router【模板】)。
-
修改文件src\router\index.ts。
import { createRouter, createWebHistory } from 'vue-router' import Users from '../views/Users.vue' import Home from '../views/Home.vue' import Tab1 from '../views/tabs/Tab1.vue' import Tab2 from '../views/tabs/Tab2.vue' const router = createRouter({ history: createWebHistory(), routes: [ { path: '/users/:username', component: Users, children: [ // UserHome will be rendered inside User's <router-view> // when /users/:username is matched { path: '', component: Home }, // UserProfile will be rendered inside User's <router-view> // when /users/:username/profile is matched { path: 'tab1', component: Tab1 }, // UserPosts will be rendered inside User's <router-view> // when /users/:username/posts is matched { path: 'tab2', component: Tab2 }, ], }, ], }) export default router;
-
新建文件src\views\Users.vue。
<template> <div class="content-users"> <h3>Top -- 我是Users组件</h3> <router-view></router-view> </div> </template> <script setup lang="ts"> </script> <style lang="less" scoped> .content-users { width: 400px; height: 400px; background-color: aqua; } </style>
-
新建文件src\views\Home.vue。
<template> <div class="content-home"> <h3>Second -- 我是Home组件</h3> </div> </template> <script setup lang="ts"> </script> <style lang="less" scoped> .content-home { width: 200px; height: 200px; background-color: green; } </style>
-
新建文件src\views\tabs\Tab1.vue。
<template> <div class="content-tab1"> <h3>Second -- 我是嵌套子组件Tab1组件</h3> </div> </template> <script setup lang="ts </script> <style lang="less" scoped> .content-tab1 { width: 200px; height: 200px; background-color: bisque; } </style>
-
新建文件src\views\tabs\Tab2.vue。
<template> <div class="content-tab2"> <h3>Second -- 我是嵌套子组件Tab2组件</h3> </div> </template> <script setup lang="ts"> </script> <style lang="less" scoped> .content-tab2 { width: 200px; height: 200px; background-color: orangered; } </style>
-
修改文件src\App.vue。
<template> <h1>Nested Views</h1> <p> <!-- 名称"/eduardo"为可选 --> <router-link to="/users/eduardo">/users/eduardo</router-link> <br /> <router-link to="/users/eduardo/tab1">/users/eduardo/tab1</router-link> <br /> <router-link to="/users/eduardo/tab2">/users/eduardo/tab2</router-link> <br /> </p> <router-view></router-view> </template> <script setup lang="ts"> </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; color: #2c3e50; } .router-link-active { color: orange; } .router-link-exact-active { color: crimson; } </style> <style scoped> ul { display: flex; list-style: none; padding: 0; margin: 0; } li:not(:last-of-type) { margin-right: 1rem; } </style><template> <h1>Nested Views</h1> <p> <!-- 名称"/eduardo"为可选 --> <router-link to="/users/eduardo">/users/eduardo</router-link> <br /> <router-link to="/users/eduardo/tab1">/users/eduardo/tab1</router-link> <br /> <router-link to="/users/eduardo/tab2">/users/eduardo/tab2</router-link> <br /> </p> <router-view></router-view> </template> <script setup lang="ts"> </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; color: #2c3e50; } .router-link-active { color: orange; } .router-link-exact-active { color: crimson; } </style> <style scoped> ul { display: flex; list-style: none; padding: 0; margin: 0; } li:not(:last-of-type) { margin-right: 1rem; } </style>
命名视图
命名视图可以在同一级(同一个组件)中展示更多的路由视图,而不是嵌套显示。 命名视图可以让一个组件中具有多个路由渲染出口,这对于一些特定的布局组件非常有用。 命名视图的概念非常类似于“具名插槽”,并且视图的默认名称也是default。
命名视图
-
使用Vite搭建Vue3开发环境,参见[Vite创建Vue3.x项目【模板】](# Vite创建Vue3.x项目【模板】)。
-
安装对应的Router版本。
使用Vue3安装对应的Router4版本。
使用Vue2安装对应的Router3版本。npm i -D vue-router@4
-
安装和配置less。参见[Vite安装和配置less【模板】](# Vite安装和配置less【模板】)。
-
修改文件src\main.ts。
import { createApp } from 'vue' import App from './App.vue' import router from './router'; const app = createApp(App); app.use(router); app.mount('#app')
-
新建文件夹src\views,新建文件src\views\Menu.vue。
<template> <div class="content-menu"> <h3>我是Menu组件</h3> </div> </template> <script setup lang="ts"> </script> <style lang="less" scoped> .content-menu { width: 200px; height: 200px; background-color: aqua; } </style>
-
新建文件src\views\Header.vue。
<template> <div class="content-header"> <h3>我是Header组件</h3> </div> </template> <script setup lang="ts"> </script> <style lang="less" scoped> .content-header { width: 200px; height: 200px; background-color: orangered; } </style>
-
新建文件src\views\Content.vue。
<template> <div class="content-content"> <h3>我是Content组件</h3> </div> </template> <script setup lang="ts"> </script> <style lang="less" scoped> .content-content { width: 200px; height: 200px; background-color: orange; } </style>
-
新建文件夹src\router和新建文件src\router\index.ts。
import { createRouter, createWebHistory } from 'vue-router' import Menu from '../views/Menu.vue' import Header from '../views/Header.vue' import Content from '../views/Content.vue' const router = createRouter({ history: createWebHistory(), routes: [ { path: '/', components: { default: Menu, aa: Header, bb: Content, }, }, { path: '/header', components: { default: Header, aa: Menu, bb: Content, }, }, { path: '/content', components: { default: Content, aa: Menu, bb: Header, }, }, ], }) export default router;
-
修改文件src\App.vue。
<template> <h1>我是App组件</h1> <p> <!-- 单击不同的路由链接,展示几个路由视图 --> <router-link to="/">Menu</router-link> | <router-link to="/header">Header</router-link> | <router-link to="/content">Content</router-link> </p> <hr> <router-view></router-view> <router-view name="aa"></router-view> <router-view name="bb"></router-view> </template> <script setup lang="ts"> // This starter template is using Vue 3 <script setup> SFCs // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup </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>
嵌套命名视图
-
基于[命名视图](# 命名视图)的基础上进行修改使用。
-
新建文件src\views\Root.vue。
<template> <div class="content-menu"> <h3>Top -- 我是Root组件</h3> <p> <!-- 名称"/eduardo"为可选 --> <router-link to="/">Menu</router-link> | <router-link to="/header">Header</router-link> | <router-link to="/content">Content</router-link> </p> <hr> <router-view></router-view> <router-view name="aa"></router-view> <router-view name="bb"></router-view> </div> </template> <script setup lang="ts"> </script> <style lang="less" scoped> .content-menu { width: 400px; height: 400px; background-color: yellow; } </style>
-
修改文件src\router\index.ts。
import { createRouter, createWebHistory } from 'vue-router' import Root from '../views/Root.vue' import Menu from '../views/Menu.vue' import Header from '../views/Header.vue' import Content from '../views/Content.vue' const router = createRouter({ history: createWebHistory(), routes: [ { path: '/', component: Root, children: [ { // path: '',子路由中如果路径填写为空,则默认展示的是此路径中的内容 path: '/header', components: { default: Header, aa: Menu, bb: Content, }, }, { path: '/content', components: { default: Content, aa: Menu, bb: Header, }, }, ] }, ], }) export default router;
-
修改文件src\App.vue。
<template> <h1>我是App组件</h1> <router-view></router-view> </template> <script setup lang="ts"> // This starter template is using Vue 3 <script setup> SFCs // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup </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>
重定向-别名
说明:基于上述嵌套命名视图的示例说明。修改文件src\router\index.ts内容。
重定向-redirect
-
字符串形式配置,访问
/
重定向到/header
。import { createRouter, createWebHistory } from 'vue-router' import Root from '../views/Root.vue' import Menu from '../views/Menu.vue' import Header from '../views/Header.vue' import Content from '../views/Content.vue' const router = createRouter({ history: createWebHistory(), routes: [ { path: '/', component: Root, // 默认展示/header子组件中的内容 redirect: '/header', children: [ { path: '/header', components: { default: Header, aa: Menu, bb: Content, }, }, { path: '/content', components: { default: Content, aa: Menu, bb: Header, }, }, ] }, ], }) export default router;
-
对象形式配置。
import { createRouter, createWebHistory } from 'vue-router' import Root from '../views/Root.vue' import Menu from '../views/Menu.vue' import Header from '../views/Header.vue' import Content from '../views/Content.vue' const router = createRouter({ history: createWebHistory(), routes: [ { path: '/', component: Root, redirect: { path: '/header', }, children: [ { path: '/header', components: { default: Header, aa: Menu, bb: Content, }, }, { path: '/content', components: { default: Content, aa: Menu, bb: Header, }, }, ] }, ], }) export default router;
-
函数形式(可以传参)。
import { createRouter, createWebHistory } from 'vue-router' import Root from '../views/Root.vue' import Menu from '../views/Menu.vue' import Header from '../views/Header.vue' import Content from '../views/Content.vue' const router = createRouter({ history: createWebHistory(), routes: [ { path: '/', component: Root, redirect(to) { // 模板 // return { // path: '/header', // query: to.query, // 可选的传参 // } // 示例 return { path: '/header', query: { // 可选的传参 name: '张三', }, } }, children: [ { path: '/header', components: { default: Header, aa: Menu, bb: Content, }, }, { path: '/content', components: { default: Content, aa: Menu, bb: Header, }, }, ] }, ], }) export default router;
别名-alias
将/
别名为/root
、/root1
、/root2
,意味着当用户访问/root
、/root1
、/root2
时候,会被匹配为用户正在访问/
。
import { createRouter, createWebHistory } from 'vue-router'
import Root from '../views/Root.vue'
import Menu from '../views/Menu.vue'
import Header from '../views/Header.vue'
import Content from '../views/Content.vue'
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
component: Root,
alias: ['/root', '/root1', '/root2'],
children: [
{
path: '/header',
components: {
default: Header,
aa: Menu,
bb: Content,
},
},
{
path: '/content',
components: {
default: Content,
aa: Menu,
bb: Header,
},
},
]
},
],
})
export default router;
导航守卫
前置守卫(结合ElementUI使用)
- 使用Vite搭建Vue3开发环境,参见[Vite创建Vue3.x项目【模板】](# Vite创建Vue3.x项目【模板】)。
- 安装和配置less。参见[Vite安装和配置less【模板】](# Vite安装和配置less【模板】)。
- 安装和配置Router。参见[Vite安装和配置Router【模板】](# Vite安装和配置Router【模板】)。
- 安装和配置ElementUI。参见[UI库ElementUI【模板】](# UI库ElementUI【模板】)。
后置守卫
posted on 2022-05-15 20:43 zyjhandsome 阅读(185) 评论(0) 编辑 收藏 举报