# vue3.0 Vue3.0 在北京时间2020年9月19 日凌晨,发布了 3.0 版本,代号:*One Piece* ## 1.新特性 Vue 3 中一些需要关注的新功能包括: - [组合式 API](https://v3.cn.vuejs.org/guide/composition-api-introduction.html) - [Teleport 将组件内容传递到父组件以外的任何区域](https://v3.cn.vuejs.org/guide/teleport.html) - [允许存在多个节点](https://v3.cn.vuejs.org/guide/migration/fragments.html) - [触发组件选项 可以驼峰传递事件 发送事件前需要声明 可以对事件进行验证](https://v3.cn.vuejs.org/guide/component-custom-events.html) - createRenderer可以跨平台渲染 - 单文件组件组合式 API 语法糖<script setup> - [单文件组件状态驱动的 CSS 变量 (` style中的 `v-bind`)](https://v3.cn.vuejs.org/api/sfc-style.html#状态驱动的动态-css) - [SFC <style scoped> 现在可以包含全局规则或只针对插槽内容的规则 - [Suspense](https://v3.cn.vuejs.org/guide/migration/suspense.html) 实验性 - 通过proxy进行数据劫持 vue2则是Object.defineProperty() - 自定义指令的钩子函数有 7个 - vue3中移除了过滤器 - vue3移除了eventBus ## 2.vue3和vue2有什么区别?(背诵) 1. 底层数据劫持的api发生了变化 在vue2里面使用的是Object.defineProperty,缺点是不能够劫持数组,vue3里面使用的是ES6的proxy,reflect可以完美响应数组变化,解决了vue2不能劫持数组的问题 2. 使用层面上来说,在vue3里面有'应用的概念',使用新的api createApp 用于解决配置项污染的问题 3. 一些新的特性,增加了组合式的api,组合式(composition)的api可以解决vue2中`选项式`代码量多的时候,焦点丢失,上下反复横跳的问题,是vue3里面非常非常大的革新 4. 可以有多个节点了,vue2里面只能有一个根节点,vue3里面可以有多个节点 5. 除了对props可以校验以外,vue3里面的事件也可以校验了 6. 新增了setup钩子,新增语法糖 script setup 编写逻辑更加简单方便,直接省略了选项 7. 移除了过滤器 移除了eventBus 8. style中也可以使用v-bind绑定数据了.... ## 3.准备 安装最新版的调试工具 `vetur`不支持vue3 暂时禁用 替换成 `Volar` Vue Language Features 或者 修改 `vetur`的配置 取消`vetur>validation>template`的勾选 ## 4.vue3.0体验 ✌🏻 ### 3.1.一个船新 API > `createApp` Vue 2 没有“app”的概念,我们定义的应用只是通过 `new Vue()` 创建的根 Vue 实例。从同一个 Vue 构造函数创建的每个根实例**共享相同的全局配置**,因此局配置很容易意外地污染其他测试用例.可以调用 `createApp` 返回一个*应用实例*,一个 Vue 3 中的新概念,解决全局污染`配置`的问题 ```js import { createApp } from 'vue' const app = createApp({}) ``` ### 3,2. vue2到vue3配置的变化 这块内容是对vue配置的迁移,可以回头过来再看 | 2.x 全局 API | 3.x 实例 API (`app`) | | -------------------------- | ------------------------------------------------------------ | | Vue.config | app.config | | Vue.config.productionTip | *移除* ([见下方](https://v3.cn.vuejs.org/guide/migration/global-api.html#config-productiontip-移除)) | | Vue.config.ignoredElements | app.config.compilerOptions.isCustomElement ([见下方](https://v3.cn.vuejs.org/guide/migration/global-api.html#config-ignoredelements-替换为-config-iscustomelement)) | | Vue.component | app.component | | Vue.directive | app.directive | | Vue.mixin | app.mixin | | Vue.use | app.use ([见下方](https://v3.cn.vuejs.org/guide/migration/global-api.html#插件开发者须知)) | | Vue.prototype | app.config.globalProperties ([见下方](https://v3.cn.vuejs.org/guide/migration/global-api.html#vue-prototype-替换为-config-globalproperties)) | | Vue.extend | *移除* ([见下方](https://v3.cn.vuejs.org/guide/migration/global-api.html#vue-extend-移除)) | ### 3.3.开始一个vue3项目 我们依旧采用最原始的html方式来体验一下vue3 在html中采用cdm的方式引入vue3 ```html <script src="https://unpkg.com/vue@next"></script> ``` ```html <div id="counter"> name: {{ name }} </div> ``` ```js const Counter = { data() { return { name: '李雷' } } } Vue.createApp(Counter).mount('#counter') ``` 到目前为止,我们依旧觉得,和我们原来的vue2是差别不大的,但其实已经有变化了,现在在vue3.0中,必须明确的声明`data属性必须是一个函数`,另外就是不在需要去实例化Vue这个操作了,而是引入了一3个叫做`createApp`的方法,创建了一个子实例 ## 5.过滤器 从 Vue 3.0 开始,过滤器已移除,且不再支持。取而代之的是,我们建议用方法调用或计算属性来替换它们。 ## 6.组件 ### 5.1.组件的定义 我们的一个vue3项目就已经跑起来啦,但是有一个感觉就是,就这? 多数人心中肯定有疑惑,就这??????和vue2的区别也不大嘛?的确,vue3本质上保留了vue2的编写特性,别着急,下面我们再来看看组件的定义 ```html <div id="app"> <h1>{{name}}</h1> <test></test> </div> <template id="test"> <fieldset> <legend>子组件</legend> <h1>{{msg}}</h1> </fieldset> </template> ``` 发现不同了吗?现在在`vue3.0中已经开始支持多节点了`,再也不同担心vue2.0单节点烦人的警告⚠️啦 ```js let app = Vue.createApp({ data() { return { name: "李雷" } }, mounted() { console.log(this.a) console.log(this) } }) app.component("test", { template: "#test", data() { return { msg: "子组件" } } }) //注意这里的调用顺序 app.mount("#app") ``` 好了 定义完了,这里要注意的一个问题就是`app.mount`这个方法一定要在`组件注册完毕之后`调用 ### 5.2.组件之间的传参 - 父子传参 - 子父传参 发现一个问题就是,和原来的vue2.0也没啥区别,感觉学了个寂寞,还有一个问题就是,兄弟传参哪儿去了? #### bus总线 很遗憾的一个问题就是在vue3中`$on`,`$off` 和 `$once` 实例方法已被移除,组件实例不再实现事件触发接口。 处理方案可以被替换为使用外部的、实现了事件触发器接口的库,例如 [mitt](https://github.com/developit/mitt) 或 [tiny-emitter](https://github.com/scottcorgan/tiny-emitter)。 ```js // eventBus.js import emitter from 'tiny-emitter/instance' export default { $on: (...args) => emitter.on(...args), $once: (...args) => emitter.once(...args), $off: (...args) => emitter.off(...args), $emit: (...args) => emitter.emit(...args), } ``` ### 5.3.异步组件 #### 5.4.1.传统HTML基本使用 import(/*webpackChunkName:''*/'xxxx'') 在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。为了实现这个效果,Vue 有一个 `defineAsyncComponent` 方法: ```js const { createApp, defineAsyncComponent } = Vue const app = createApp({}) const AsyncComp = defineAsyncComponent( () => new Promise((resolve, reject) => { resolve({ template: '<div>I am async!</div>' }) }) ) app.component('async-example', AsyncComp) ``` #### 2.使用模块化 如你所见,此方法接受一个返回 `Promise` 的工厂函数。从服务器检索组件定义后,应调用 Promise 的 `resolve` 回调。你也可以调用 `reject(reason)`,来表示加载失败。 你也可以在工厂函数中返回一个 `Promise`,把 webpack 2 及以上版本和 ES2015 语法相结合后,我们就可以这样使用动态地导入: ```js import { defineAsyncComponent } from 'vue' const AsyncComp = defineAsyncComponent(() => import('./components/AsyncComponent.vue') ) app.component('async-component', AsyncComp) ``` 当[在局部注册组件](https://v3.cn.vuejs.org/guide/component-registration.html#局部注册)时,你也可以使用 `defineAsyncComponent`: ```js import { createApp, defineAsyncComponent } from 'vue' createApp({ // ... components: { AsyncComponent: defineAsyncComponent(() => import('./components/AsyncComponent.vue') ) } }) ``` #### 3.与 Suspense 一起使用 异步组件在默认情况下是*可挂起*的。这意味着如果它在父链中有一个 ``,它将被视为该 `` 的异步依赖。在这种情况下,加载状态将由 `` 控制,组件自身的加载、错误、延迟和超时选项都将被忽略。 通过在其选项中指定 `suspensible: false`,异步组件可以退出 `Suspense` 控制,并始终控制自己的加载状态。 在正确渲染组件之前进行一些异步请求是很常见的事。组件通常会在本地处理这种逻辑,绝大多数情况下这是非常完美的做法。 该 `` 组件提供了另一个方案,允许将等待过程提升到组件树中处理,而不是在单个组件中。 一个常见的[异步组件](https://v3.cn.vuejs.org/guide/component-dynamic-async.html#异步组件)用例: ```vue <template> <div id="app"> {{msg}} <Suspense> <template #fallback> <h1>正在加载...</h1> </template> <template #default> <async-com></async-com> </template> </Suspense> </div> </template> <script> const { createApp, defineAsyncComponent } = Vue; let com = defineAsyncComponent(() => new Promise(resolve => { setTimeout(() => { resolve({ template: '<h1 >异步组件</h1>' }) }, 2000) })) let app = createApp({ data() { return { msg: "vue3" } }, components: { asyncCom: com } }) app.mount('#app') </script> ``` `` 组件有两个插槽。它们都只接收一个直接子节点。`default` 插槽里的节点会尽可能展示出来。如果不能,则展示 `fallback` 插槽里的节点。 ## 7.使用vue-cli开启一个vue3项目 1. 升级最新vue-cli 升级到5 ```bash npm i @vue/cli -g ``` 2. 创建一个项目 ```bash vue create 项目 ``` 3.兼容vue-cli3.0 ## 8.指令 vue3的指令几乎和vue2.0保持是一致的,但是增加了以下新特性 ### 1.组件上可以绑定多个model了 - `组件可以绑定多个model` - `v-model` prop 和事件默认名称已更改: - We can prop:`value` -> `modelValue`; - 事件:`input` -> `update:modelValue` - 可以自定义 `v-model` 修饰符 v-model在元素上的写法没有变化,但是在组件中的写法有了新特性,写法就是在指令后面跟上指令参数,指令参数就是绑定的数据名称 **下面是一个例子** ```html <body> <div id="app"> <h1>{{name}}</h1> <h1>{{name2}}</h1> <child v-model:msg="name" v-model:msg2="name2"></child> </div> </body> </html> <template id="child"> <fieldset> <legend>子组件</legend> <input type="text" :value="msg" @input="trans"> <input type="text" :value="msg2" @input="trans2"> </fieldset> </template> <script> let app = Vue.createApp({ data() { return { name: "李雷", name2:"韩梅梅" } } }) app.component("child", { props:['msg','msg2'], template: "#child", methods:{ trans(e){ //update后面的参数要和model绑定的一直 this.$emit("update:msg",e.target.value) console.log(this.msg2) }, trans2(e){ this.$emit("update:msg2",e.target.value) console.log(this.msg2) } } }) app.mount("#app") </script> ``` > 对于所有不带参数的 `v-model`,请确保分别将 prop 和 event 命名更改为 `modelValue` 和 `update:modelValue` ### 2.指令优先级的变化 Vue.js 中使用最多的两个指令就是 `v-if` 和 `v-for`,因此开发者们可能会想要同时使用它们。虽然不建议这样做,但有时确实是必须的2.x 版本中在一个元素上同时使用 `v-if` 和 `v-for` 时,`v-for` 会优先作用。 3.x 版本中 `v-if` 总是优先于 `v-for` 生效 ```vue <ul> <!-- vue3 v-if和v-for同时使用的时候,可以把v-for迁移到template标签上 --> <template v-for="item of arr" :key="item.id"> <li v-if="!item.isComplete">{{ item.todo }}</li> </template> </ul> ``` ### 3.v-on.native修饰符 `v-on` 的 `.native` 修饰符已被移除。 - 2.x 语法 默认情况下,传递给带有 `v-on` 的组件的事件监听器只能通过 `this.$emit` 触发。要将原生 DOM 监听器添加到子组件的根元素中,可以使用 `.native` 修饰符: ```html <my-component v-on:close="handleComponentEvent" v-on:click.native="handleNativeClickEvent" /> ``` - 3.x 语法 `v-on` 的 `.native` 修饰符已被移除。同时,[新增的 `emits` 选项](https://v3.cn.vuejs.org/guide/migration/emits-option.html)允许子组件定义真正会被触发的事件。 因此,对于子组件中*未*被定义为组件触发的所有事件监听器,Vue 现在将把它们作为原生事件监听器添加到子组件的根元素中 (除非在子组件的选项中设置了 `inheritAttrs: false`)。 ```html <my-component v-on:close="handleComponentEvent" v-on:click="handleNativeClickEvent" /> ``` ```vue MyComponent.vue <script> export default { emits: ['close'] } </script> ``` ### 4.自定义指令 自定义指令基本和vue2.0是保持一致的,但是有不同的地方就是,指令钩子函数增加到了7个,下面是一个基本用法 ```js const app = Vue.createApp({}) // 注册一个全局自定义指令 `v-focus` app.directive('focus', { // 当被绑定的元素挂载到 DOM 中时…… mounted(el) { // 聚焦元素 el.focus() } }) ``` 局部注册的方式是在app中 ```js directives: { focus: { // 指令的定义 mounted(el) { el.focus() } } } ``` 不同的是vue3.0的指令钩子函数增加到了7个分别是 - `created`:在绑定元素的 attribute 或事件监听器被应用之前调用 - `beforeMount`:当指令第一次绑定到元素并且在挂载父组件之前调用。 - `mounted`:在绑定元素的父组件被挂载后调用。 - `beforeUpdate`:在更新包含组件的 VNode 之前调用。 提示 - `updated`:在包含组件的 VNode **及其子组件的 VNode** 更新后调用。 - `beforeUnmount`:在卸载绑定元素的父组件之前调用 - `unmounted`:当指令与元素解除绑定且父组件已卸载时,只调用一次。 ### 7.小结 1. `vue3.0中已经开始支持多节点了` 2. 在vue3中`data属性必须是一个函数` 3. `vue3里面有app的概念,可以防止染全局配置` 4. `属性的劫持,使用了proxy` 5. `移除了过滤器` 6. `移除了eventBus` 7. `vue3.0中组件上可以绑定多个v-model了 而且参数和事件有变化` 8. `在vue3.0中 v-if的优先级要高于v-for` 9. `vue3.0中移除了native修饰符` ## 9.使用构建工具vite [Vite](https://cn.vitejs.dev/) 是一个 web 开发构建工具,由于其原生 ES 模块导入方式,可以实现闪电般的冷服务器启动。 通过在终端中运行以下命令,可以使用 Vite 快速构建 Vue 项目。 使用 npm: ```bash # npm 6.x $ npm init vite@latest <project-name> --template vue # npm 7+,需要加上额外的双短横线 $ npm init vite@latest <project-name> -- --template vue $ cd <project-name> $ npm install $ npm run dev ``` 或者 yarn: ```bash $ yarn create vite <project-name> --template vue $ cd <project-name> $ yarn $ yarn dev ``` ## 模块化样式 ### 1.选择器支持插槽了 在单文件组件范围的样式中提供更一致的自定义CSS扩展。 ```html <style scoped> /* 深选择器 */ ::v-deep(.foo) {} /* 简写 */ :deep(.foo) {} /* 针对槽内容 */ ::v-slotted(.foo) {} /* 简写 */ :slotted(.foo) {} /* 一个全局规则 */ ::v-global(.foo) {} /* 简写 */ :global(.foo) {} </style> ``` ### 2.深度选择器的改进 - `>>>` 和/deep/` 支持被弃用 - `::v-deep` 组合被弃用: ```css /* qiyong */ ::v-deep .bar {} ``` 使用这种方式来替代上面的写法 ```css ::v-deep(.bar) {} ``` 上述将编译为 ```css [v-data-xxxxxxx] .bar {} ``` ## ref 在 Vue 2 中,在 `v-for` 中使用的 `ref` attribute 会用 ref 数组填充相应的 `$refs` property。当存在嵌套的 `v-for` 时,这种行为会变得不明确且效率低下。 在 Vue 3 中,此类用法将不再自动创建 `$ref` 数组。要从单个绑定获取多个 ref,请将 `ref` 绑定到一个更灵活的函数上 (这是一个新特性): ```html <div v-for="item in list" :ref="setItemRef"></div> ``` ### 结合选项式 API: ```333 js export default { data() { return { itemRefs: [] } }, methods: { setItemRef(el) { if (el) { this.itemRefs.push(el) } } }, beforeUpdate() { this.itemRefs = [] }, updated() { console.log(this.itemRefs) } } ``` ### 结合组合式 API: ```js import { onBeforeUpdate, onUpdated } from 'vue' export default { setup() { let itemRefs = [] const setItemRef = el => { if (el) { itemRefs.push(el) } } onBeforeUpdate(() => { itemRefs = [] }) onUpdated(() => { console.log(itemRefs) }) return { setItemRef } } } ``` > 注意: - `itemRefs` 不必是数组:它也可以是一个对象,其 ref 可以通过迭代的 key 被设置。 - 如有需要,`itemRef` 也可以是响应式的,且可以被侦听。 ## emits选项**新增** ### 概述 Vue 3 现在提供一个 `emits` 选项,和现有的 `props` 选项类似。这个选项可以用来定义一个组件可以向其父组件触发的事件。 ### 2.x 的行为 在 Vue 2 中,你可以定义一个组件可接收的 prop,但是你无法声明它可以触发哪些事件: ```vue <template> <div> <p>{{ text }}</p> <button v-on:click="$emit('accepted')">OK</button> </div> </template> <script> export default { props: ['text'] } </script> ``` ### 3.x 的行为 和 prop 类似,现在可以通过 `emits` 选项来定义组件可触发的事件: ```vue <template> <div> <p>{{ text }}</p> <button v-on:click="$emit('accepted')">OK</button> </div> </template> <script> export default { props: ['text'], emits: ['accepted'] } </script> ``` 该选项也可以接收一个对象,该对象允许开发者定义传入事件参数的验证器,和 `props` 定义里的验证器类似。 ### 迁移策略 强烈建议使用 `emits` 记录每个组件所触发的所有事件。 这尤为重要,因为我们[移除了 `.native` 修饰符](https://v3.cn.vuejs.org/guide/migration/v-on-native-modifier-removed.html)。任何未在 `emits` 中声明的事件监听器都会被算入组件的 `$attrs`,并将默认绑定到组件的根节点上。 ### [#](https://v3.cn.vuejs.org/guide/migration/emits-option.html#示例)示例 对于向其父组件透传原生事件的组件来说,这会导致有两个事件被触发: ```vue <template> <button v-on:click="$emit('click', $event)">OK</button> </template> <script> export default { emits: [] // 不声明事件 } </script> ``` 当一个父级组件拥有 `click` 事件的监听器时: ```html <my-button v-on:click="handleClick"></my-button> ``` 该事件现在会被触发*两次*: - 一次来自 `$emit()`。 - 另一次来自应用在根元素上的原生事件监听器。 现在你有两个选项: 1. 正确地声明 `click` 事件。当你真的在 `` 的事件处理器上加入了一些逻辑时,这会很有用。 2. 移除透传的事件,因为现在父组件可以很容易地监听原生事件,而不需要添加 `.native`。适用于你只想透传这个事件。 ### emit验证 Emit也可以像props那样进行验证了 ```vue const app = createApp({}) // 数组语法 app.component('todo-item', { emits: ['check'], created() { this.$emit('check') } }) // 对象语法 app.component('reply-form', { emits: { // 没有验证函数 click: null, // 带有验证函数 submit: payload => { if (payload.email && payload.password) { return true } else { console.warn(`Invalid submit event payload!`) return false } } } }) ```
右侧赞助一下 代码改变世界一块二块也是爱