## 介绍 > 已经了解 Vue2,只想了解 Vue3 新功能可以参阅[vue3新功能](https://v3.cn.vuejs.org/guide/migration/introduction.html#%E6%A6%82%E8%A7%88)! > > 已经有 Vue2 项目,需要适配 Vue3 的可参阅[vue2 项目迁移 vue3](https://uniapp.dcloud.io/tutorial/migration-to-vue3)! **Vue.js 是什么** Vue (读音 /vjuː/,类似于 `view`) 是一套用于构建用户界面的**渐进式框架**。与其它大型框架不同的是,`Vue` 被设计为可以自底向上逐层应用。 `Vue.js` 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 `DOM` 的系统,**只关注视图层,易于上手**。所有东西都是响应式的。 **致谢** 本文大部分内容来源于[vue3中文文档官网](https://v3.cn.vuejs.org/guide/introduction.html#vue-js-%E6%98%AF%E4%BB%80%E4%B9%88),但结合 `uni-app` 做了部分调整,以更有利于开发者快速上手。感谢Vue团队! ## vue的优势 在传统开发中,用原生的 JavaScript DOM 操作函数对 DOM 进行频繁操作的时候,浏览器要不停的渲染新的 DOM 树,导致页面看起来非常卡顿。 vue 是单页面应用,使页面局部刷新,不用每次跳转页面都要请求所有数据和 DOM ,这样大大加快了访问速度和提升用户体验。 **vue的优势:** - 轻量级:几十K的体积 - 界面与逻辑分离,与html接近的概念和写法 - 响应式双向数据绑定,更新数据时无需再写代码更新界面,反之亦然。 - 组件化,可方便协作。方便造轮子,也就自然有大量轮子可用 - 虚拟DOM,比大多数手写操作dom的代码都更高效 - 易于上手,设计直观、文档丰富 **vue3相比vue2的优势:** - 响应式系统提升 - 更快,性能比Vue2快1.2~2倍(diff方法优化、静态提升、时间侦听器缓存、[ssr渲染](https://uniapp.dcloud.io/tutorial/ssr)) - 更小,按需编译,体积比Vue2更小 - 组合式API,提供更灵活的写法,也易于吸引react开发者 - 加强TypeScript支持 ## 白话uni-app 如果你了解html、js,那么本章节将让你快速了解uni-app和它们的差异。 ### 文件类型变化 - 以前是.html文件,开发也是html,运行也是html。 - 现在每个页面是一个.vue文件,开发是vue,但经过编译后,运行时已经变成了js文件(如果是uts则可能编译成kotlin、swift)。 - 现代前端开发,很少直接使用HTML,基本都是开发、编译、运行。所以 `uni-app` 有编译器、运行时的概念。 ### 文件内代码架构的变化 - 以前一个 `html` 大节点,里面有 `script` 和 `style` 节点; ```html ``` - 现在 `template` 是一级节点,用于写tag组件, `script` 和 `style` 是并列的一级节点,也就是有3个一级节点。这个叫[vue单文件组件规范sfc](vue3-components.md)。 ```html ``` ### 外部文件引用方式变化 - 以前通过script src、link href引入外部的js和css; ```html ``` - 现在是es6的写法, `import` 引入外部的js模块(注意不是文件)或css; **js要require进来,变成了对象**。 在hello uni-app的 `common` 目录有一个工具类 `util.js` ,可以在hello uni-app中搜索这个例子查看。hello uni-app示例代码可从 [github](https://github.com/dcloudio/hello-uniapp) 获取。 ```html ``` 而在这个 `util.js` 里,要把之前的 `function` 封装为模块(module)的方法并导出(exports)。只有被导出的方法和属性才能被外部调用,不导出的属于模块内部函数和变量。这是es6的模块规范。 ```js function formatTime(time) { return time;//这里没写逻辑 } module.exports = { formatTime: formatTime } ``` 当然还有一些高级的用法,比如在导出时可以重命名 ```js // 直接使用js模块的属性。在hello uni-app有示例 var dateUtils = require('../../../common/util.js').dateUtils; // 将js导入并重命名为echarts,然后使用echarts.来继续执行方法。在hello uni-app有示例 import * as echarts from '/components/echarts/echarts.simple.min.js'; ``` **css外部文件导入**。全局样式,在根目录下的 `app.vue` 里写入,每个页面都会加载 `app.vue` 里的样式。 ```html ``` **另外,vue支持组件导入,可以更方便的封装一个包括界面、js、样式的库**。[详见](vue3-components.md) ### 组件/标签的变化 以前是html标签,比如 `
` ,现在是小程序组件,比如 `` 。 那么标签和组件有什么区别,不都是用尖括号包围起来一段英文吗? - 其实标签是老的概念,标签属于浏览器内置的东西。 - 但组件,是可以自由扩展的。类似你可以把一段js封装成函数或模块,你也可以把一个ui控件封装成一个组件。 `uni-app` 参考小程序规范,提供了一批[内置组件](https://uniapp.dcloud.io/component/README)。 ### js的变化 - 以前script里随便写js变量和function ```html ``` - 现在script里默认有export default,在里面写data、事件和method * 写在 `export default {` 前面的变量,是页面内部的全局变量,可以在各种方法里使用。 * `export default {}` 里是一个大json,data、生命周期、method都需要用逗号分隔。 * data -> return 里,编写可以绑定在页面template模板里的变量,页面组件的text里绑定data数据使用{{}},比如下面例子中的`textvalue`。而下面的globalvar就不能在模板里绑定使用。在HBuilderX中,敲vdata代码块,可以快捷生成data的代码结构。 * 页面的生命周期/事件,如下面的`onLoad`,和data平级。 * 模板里要调用的方法,都需要写在`methods`下面。每个方法也需要用逗号分隔。不需要再使用`function`声明,只要写在`methods`下的函数,都可以在template里调用。同样,HBuilderX里敲`vmethods`代码块,也可以生成相应结构。 ```html ``` 在上述例子中,传统写法的定义的变量globalvar和函数globalfun,可以在`export default { }`里使用,但无法在模板里直接绑定和调用。模板里只能绑定data里的变量、调用methods里的方法。 - 以前的 DOM 操作,如果你想改变某个 DOM 元素的显示内容,比如一个view的显示文字:给view设id,然后js里通过选择器获取 DOM 元素,进一步通过js进行赋值操作,修改 DOM 元素的属性或值。 ```html 123 ``` - 现在的做法,是vue的绑定模式,给这个 DOM 元素绑定一个js变量,在script中修改js变量的值,DOM 会自动变化,页面会自动更新渲染。 - 前端改用 [MVVM](https://baike.baidu.com/item/MVVM/96310?fr=aladdin) (Model-View-ViewModel的简写)模式,简单来说,Model:代表数据模型,View:只专注视图UI处理,ViewModel:只处理业务和数据。它的核心是 MVVM 中的 VM,也就是 ViewModel。 ViewModel负责连接 View 和 Model,保证视图和数据的一致性,这种轻量级的架构让前端开发更加高效、便捷,大幅减少代码行数,同时差量渲染性能更好。 - `uni-app` 使用vue的数据绑定方式解决js和 DOM 界面交互的问题。 ```html ``` - 以前在是html的tag里用一个属性`onclick`来写点击事件 - 现在是使用`@click`(其实是`v-on:click`的缩写,在uni-app里基本都使用缩写)调用methods里的方法。 ## 在 uni-app 中使用vue的差异 `uni-app` 在发布到H5时支持所有vue的语法;发布到App和小程序时,由于平台限制,无法实现全部vue语法,但 `uni-app` 仍是对vue语法支持度最高的跨端框架。 相比Web平台, Vue.js 在 `uni-app` 中使用差异主要集中在两个方面: - 新增:`uni-app` 除了支持 Vue 实例的组件生命周期,还拥有[应用生命周期](/collocation/App.html#applifecycle)及[页面的生命周期](/tutorial/page.html#lifecycle)。 - 受限:相比 Web 平台,在小程序和 App 端部分功能支持不完善,具体见[兼容性列表](/vue3-api)。 [uni-app 项目支持 vue 3.0介绍,及升级指南](https://ask.dcloud.net.cn/article/37834) `uni-app` 项目对 vue 3.0 的支持版本情况如下: - Web平台:支持。 - 小程序平台:`HBuilderX 3.3.3+` 编译器改为 `vite`,之前版本的编译器为`webpack`。 - App 平台:`uni-app 3.2.5+`支持。`HBuilderX 3.3.13` 起 `nvue`编译器升级为`vite`。 **注意事项** - vue3 响应式基于 `Proxy` 实现,不支持`iOS9`和`ie11`。 - 暂不支持新增的 `Teleport`,`Suspense` 组件。 - 目前 `HBuilderX 3.2` 起已预置,之前的版本只能使用cli方式。 ## 模板语法 `Vue.js` 使用了基于 `HTML` 的模板语法,允许开发者声明式地将 `DOM` 绑定至底层组件实例的数据。 所有 `Vue.js` 的模板都是合法的 `HTML`,所以能被遵循规范的浏览器和 `HTML` 解析器解析。 在底层的实现上,`Vue` 将模板编译成虚拟 `DOM` 渲染函数。结合响应性系统,`Vue` 能够智能地计算出最少需要重新渲染多少组件,并把 `DOM` 操作次数减到最少。 ### 插值 #### 文本 数据绑定最常见的形式就是文本插值: ```html ``` {{msg}}里的内容将会被替代为对应数据对象上msg的值。无论何时,绑定的数据对象上msg发生了改变,插值处的内容都会更新。 #### 使用 JavaScript 表达式 迄今为止,在我们的模板中,我们一直都只绑定简单的 `property` 键值。但实际上,对于所有的数据绑定,Vue.js 都提供了完全的 `JavaScript` 表达式支持。 **示例一** ```html ``` **示例二** ```html ``` 这些表达式会在所属 Vue 实例的数据作用域下作为 `JavaScript` 被解析。有个限制就是,每个绑定都只能包含单个表达式,所以下面的例子都不会生效。 **错误示例** ```html ``` > 模板表达式都被放在沙盒中,只能访问**全局变量的一个白名单**: > - `Infinity` > - `undefined` > - `NaN` > - `isFinite` > - `isNaN` > - `parseFloat` > - `parseInt` > - `decodeURI` > - `decodeURIComponent` > - `encodeURI` > - `encodeURIComponent` > - `Math` > - `Number` > - `Date` > - `Array` > - `Object` > - `Boolean` > - `String` > - `RegExp` > - `Map` > - `Set` > - `JSON` > - `Intl` > - `BigInt` > > 你不应该在模板表达式中试图访问用户定义的全局变量。 ### 指令 指令是带有 v- 前缀的特殊属性。 - 指令属性的值预期是**单个 `JavaScript` 表达式** (`v-for` 和 `v-on` 是例外情况,稍后我们再讨论)。 - 指令的作用是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 `DOM` 。 - 一些指令能够接收一个“参数”,在指令名称之后以冒号( : )表示。 #### v-bind 动态地绑定一个或多个属性,`v-bind`缩写为‘ : ’,可以用于响应式地更新 `HTML attribute`: ```html ``` 在这里 `src` 是参数,告知 `v-bind` 指令将该元素的 `src` attribute 与表达式 `imgUrl` 的值绑定。 如果 `isButtonDisabled` 的值是 `null` 或 `undefined`,则 `disabled` attribute 甚至不会被包含在渲染出来的 `button` 元素中。 #### v-on v-on 指令,它用于监听 `DOM` 事件。v-on缩写为‘ @ ’,下文简称为 `@事件` ```html 点击 点击 ``` **注意:`uni-app x` 中函数 `event` 参数需要显式指定类型** ```html event must has type event must has type ``` #### v-once 只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。 和前端框架中的理解不同,客户端里要实现复用的逻辑,会标记模板节点的状态,添加了 `v-once` 能保证节点只渲染一次,但是并不一定能优化渲染性能,反而可能会拖慢客户端复用节点时的比对效率。 > h5、微信小程序均不支持 ```html This will never change: {{msg}} comment {{msg}} ``` **注意:`uni-app x` 暂不支持** #### v-html 更新元素的 [`innerHTML`](https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML) 。 - 注意:**内容按普通 HTML 插入 - 不会作为 Vue 模板进行编译。** - 如果试图使用 v-html 组合模板,可以重新考虑是否通过使用组件来替代。 - App端和H5端支持 `v-html` ,微信小程序会被转为 `rich-text`,其他端不支持 `v-html` 。 跨端的富文本处理方案详见:[https://ask.dcloud.net.cn/article/35772](https://ask.dcloud.net.cn/article/35772) ```html ``` **注意:`uni-app x` 暂不支持** ## Data 选项 `data` 选项已标准化为只接受**返回一个初始数据对象的函数**(注意函数内返回的数据对象不要直接引用函数外的对象);否则页面关闭时,数据不会自动销毁,再次打开该页面时,会显示上次数据。 ```js //正确用法,使用函数返回对象 data() { return { title: 'Hello' } } //错误写法,会导致再次打开页面时,显示上次数据 data: { title: 'Hello' } //错误写法,同样会导致多个组件实例对象数据相互影响 const obj = { title: 'Hello' } data() { return { obj } } ``` ## Class 与 Style 绑定 ### 绑定 HTML Class **对象语法** 我们可以传给 `:class` (`v-bind:class` 的简写) 一个对象,实现动态地切换 `class`。 也可以在对象中传入更多字段来动态切换多个 `class`。此外,`v-bind:class` 指令也可以与普通的 `class` 共存。 ```html ``` 渲染结果为 ```html ``` 当 `isActive` 或者 `hasError` 变化时,class 列表将相应地更新。例如,如果 `hasError` 的值为 `true` ,class 列表将变为 `static active text-danger`。 **数组语法** 可以把一个数组传给 `v-bind:class`,以应用一个 `class` 列表。 ```html ``` 渲染的结果为: ```html ``` 如果你想根据条件切换列表中的 class,可以使用三元表达式: ```html ``` 这样写将始终添加 `errorClass`,但是只有在 `isActive` 为 `truthy` 时才添加 `activeClass`。 > 在 `JavaScript` 中,`truthy`(真值)指的是在布尔值上下文中,转换后的值为真的值。所有值都是真值,除非它们被定义为 假值(即除 `false`、0、""、`null`、`undefined` 和 `NaN` 以外皆为真值)。 不过,当有多个条件 class 时这样写有些繁琐。所以在数组语法中也可以使用对象语法: ```html ``` **注意:以:style=""这样的方式设置px像素值,其值为实际像素,不会被编译器转换。** 此外还可以用计算属性 `computed` 方法生成 `class` 或者 `style` 字符串,插入到页面中,举例说明: ```html ``` ### 绑定内联样式 **对象语法** :style 的对象语法十分直观——看着非常像 CSS,但其实是一个 `JavaScript` 对象。CSS property 名可以用驼峰式 (`camelCase`) 或短横线分隔 (`kebab-case`,记得用引号括起来) 来命名: ```html ``` 直接绑定到一个样式对象通常更好,这会让模板更清晰: ```html ``` 同样的,对象语法常常结合返回对象的计算属性使用。 **数组语法** `:style` 的数组语法可以将多个样式对象应用到同一个元素上: ```html ``` **自动添加前缀** 在 `:style` 中使用需要 (浏览器引擎前缀) [vendor prefixesa](https://developer.mozilla.org/en-US/docs/Glossary/Vendor_Prefix) 的 `CSS property` 时,如 `transform`,`Vue` 将自动侦测并添加相应的前缀。 **多重值** 可以为 `style` 绑定中的 `property` 提供一个包含多个值的数组,常用于提供多个带前缀的值,例如: ```html ``` 这样写只会渲染数组中最后一个被浏览器支持的值。在本例中,如果浏览器支持不带浏览器前缀的 `flexbox`,那么就只会渲染 `display: flex`。 ## 条件渲染 ### v-if和v-else `v-if` 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 `truthy` 值的时候被渲染。 使用 `v-else` 指令来表示 `v-if` 的“else 块”。 `v-else` 元素必须紧跟在带 `v-if` 或者 `v-else-if` 的元素的后面,否则它将不会被识别。 > 在 `JavaScript` 中,`truthy`(真值)指的是在布尔值上下文中,转换后的值为真的值。所有值都是真值,除非它们被定义为 假值(即除 `false`、0、""、`null`、`undefined` 和 `NaN` 以外皆为真值)。 ```html ``` `v-else-if`,顾名思义,充当 v-if 的“else-if 块”,可以连续使用: ```html ``` 类似于 `v-else` ,`v-else-if` 也必须紧跟在带 `v-if` 或者 `v-else-if` 的元素之后。 ### 条件渲染分组 因为 `v-if` 是一个指令,所以必须将它添加到一个元素上。但是如果想切换多个元素呢? 此时可以把一个 `template` 元素当做不可见的包裹元素,并在上面使用 `v-if`。最终的渲染结果将不包含 `template` 元素。 ```html ``` ### v-show 另一个用于根据条件展示元素的选项是 `v-show` 指令。用法大致一样: ```html Hello! ``` 不同的是带有 `v-show` 的元素始终会被渲染并保留在 `DOM` 中。`v-show` 只是简单地切换元素的 `CSS` 属性的 `display` 。 > 注意,v-show 不支持 template 元素,也不支持 v-else。nvue 页面不支持 v-show。 ### v-if 和 v-show 区别 `v-if` 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。 `v-if` 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。 相比之下,`v-show` 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换,来控制元素的显示和隐藏。 **根据应用场景选择** - `v-if` 有更高的切换开销,如果在运行时条件很少改变,则使用 v-if 较好。 - `v-show` 有更高的初始渲染开销。如果需要非常频繁地切换,则使用 v-show 较好。 **注意** - 不推荐同时使用 `v-if` 和 `v-for`。 - 当 `v-if` 与 `v-for` 一起使用时,**`v-if` 具有比 `v-for` 更高的优先级**。 ## 列表渲染 ### 在 v-for 里使用数组 v-for 指令可以实现基于一个数组来渲染一个列表。 - `v-for` 指令需要使用 `item in items` 形式的特殊语法,其中 `items` 是源数据数组,而 `item` 则是被迭代的数组元素的别名。 - 在 v-for 块中,我们可以访问所有父作用域的 `property` - 第一个参数 `item` 则是被迭代的数组元素的别名。 - 第二个参数,即当前项的索引 `index` ,是可选的。 ```html ``` 结果: ```html Parent - 0 - Foo Parent - 1 - Bar ``` ### 在 v-for 里使用对象 你也可以用 v-for 来遍历一个对象的 `property`。 - 第一个参数 `value` 是被迭代的对象元素的属性值。 - 第二个参数为 `property` 名称 (也就是键名)。 - 第三个参数作为索引。 ```html ``` 结果: ```html 0.title: How to do lists in Vue, 1.author: Jane Doe, 2.publishedAt: 2021-05-10 ``` > 在遍历对象时,会按 `Object.keys()` 的结果遍历,但是不能保证它在不同 `JavaScript` 引擎下的结果都一致。 ### 列表渲染分组 类似于 `v-if`,你也可以利用带有 `v-for` 的 `template` 来循环渲染一段包含多个元素的内容。比如: ```html ``` ### 维护状态 当 `Vue` 正在更新使用 `v-for` 渲染的元素列表时,它默认使用“就地更新”的策略。如果数据项的顺序被改变,`Vue` 将不会移动 `DOM` 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。 这个默认的模式是高效的,但是**只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出**。 为了给 `Vue` 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 `key` attribute: ```html ``` 建议尽可能在使用 `v-for` 时提供 `key` attribute,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升。 - 如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。 - 而使用 key 时,它会基于 key 的变化重新排列元素顺序,并且会移除/销毁 key 不存在的元素。 - 有相同父元素的子元素必须有独特的 key。重复的 key 会造成渲染错误。 > 不要使用对象或数组之类的非基本类型值作为 v-for 的 key。请用字符串或数值类型的值。 > > 如不提供 :key,会报一个 `warning`, 如果明确知道该列表是静态,或者不必关注其顺序,可以选择忽略。 示例: ```html ``` ### 注意事项 - 在H5平台 使用 v-for 循环整数时和其他平台存在差异,如 `v-for="(item, index) in 10"` 中,在H5平台 item 从 1 开始,其他平台 item 从 0 开始,可使用第二个参数 index 来保持一致。 - 在非H5平台 循环对象时不支持第三个参数,如 `v-for="(value, name, index) in object"` 中,index 参数是不支持的。 - 小程序端数据为差量更新方式,由于小程序不支持删除对象属性,使用的设置值为 null 的方式替代,导致遍历时可能出现不符合预期的情况,需要自行过滤一下值为 null 的数据([相关反馈](https://ask.dcloud.net.cn/question/103269))。 ### 结合 `