vuejs3 第7页
目前为止,关于过渡我们已经讲到: 单个节点 同一时间渲染多个节点中的一个 那么怎么同时渲染整个列表,比如使用 v-for?在这种场景中,使用 <transition-group> 组件。在我们深入例子之前,先了解关于这个组件的几个特点: 不同于 <transition>,它会以一个真实元素渲染:默认为一个 <span>。你也可以通过 tag attribute 更换为其他元素。 过渡模式不可用,因为我们不再相互切换特有的元素。 内部元素总是需要提供唯一的 key attribute 值。 CSS 过渡的类将会应用在内部的元素中,而不是这个组/容器本身。 #列表的进入/离开过渡 现在让我们由一个简单的例子深入,进入和离开的过渡使用之前一样的 CSS class 名。 <div id="list-demo"> <button @click="add">Add</button> <button @click="remove">Remove</button> <transition-group name="list" tag="p"> <span v-for="item in items" :key="item" class="list-item"> {{ item }} </span> </transition-group> </div> const Demo = { data() { return { items: [1, 2, 3, 4, 5, 6, 7, 8, 9], nextNum: 10 } }, methods: { randomIndex() { return Math.floor(Math.random() * this.items.length) }, add() { this.items.splice(this.randomIndex(), 0, this.nextNum++) }, remove() { this.items.splice(this.randomIndex(), 1) } } } Vue.createApp(Demo).mount('#list-demo') .list-item { display: inline-block; margin-right: 10px; } .list-enter-active, .list-leave-active { transition: all 1s ease; } .list-enter-from, .list-leave-to { opacity: 0; transform: translateY(30px); } 点击此处实现 这个例子有个问题,当添加和移除元素的时候,周围的元素会瞬间移动到他们的新布局的位置,而不是平滑的过渡,我们下面会解决这个问题。 #列表的排序过渡 <transition-group> 组件还有一个特殊之处。不仅可以进入和离开动画,还可以改变定位。要使用这个新功能只需了解新增的 v-move...
在插入、更新或从 DOM 中移除项时,Vue 提供了多种应用转换效果的方法。这包括以下工具: 自动为 CSS 转换和动画应用 class; 集成第三方 CSS 动画库,例如 animate.css ; 在过渡钩子期间使用 JavaScript 直接操作 DOM; 集成第三方 JavaScript 动画库。 在这里,我们只会讲到进入、离开和列表的过渡,你也可以看下一节的管理过渡状态 。 #单元素/组件的过渡 Vue 提供了 transition 的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡 条件渲染 (使用 v-if) 条件展示 (使用 v-show) 动态组件 组件根节点 这里是一个典型的例子: <div id="demo"> <button @click="show = !show"> Toggle </button> <transition name="fade"> <p v-if="show">hello</p> </transition> </div> const Demo = { data() { return { show: true } } } Vue.createApp(Demo).mount('#demo') .fade-enter-active, .fade-leave-active { transition: opacity 0.5s ease; } .fade-enter-from, .fade-leave-to { opacity: 0; } 点击此处实现 当插入或删除包含在 transition 组件中的元素时,Vue 将会做以下处理: 自动嗅探目标元素是否应用了 CSS 过渡或动画,如果是,在恰当的时机添加/删除 CSS 类名。 如果过渡组件提供了 JavaScript 钩子函数 ,这些钩子函数将在恰当的时机被调用。 如果没有找到 JavaScript 钩子并且也没有检测到 CSS 过渡/动画,DOM 操作 (插入/删除) 在下一帧中立即执行。(注意:此指浏览器逐帧动画机制,和 Vue 的 nextTick 概念不同) #过渡class 在进入/离开的过渡中,会有 6 个 class 切换。 v-enter-from:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。 v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。 v-enter-to:定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter-from 被移除),在过渡/动画完成之后移除。 v-leave-from:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。 v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。 v-leave-to:离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时...
Vue 提供了一些抽象概念,可以帮助处理过渡和动画,特别是在响应某些变化时。这些抽象的概念包括: 在 CSS 和 JS 中,使用内置的 <transition> 组件来钩住组件中进入和离开 DOM。 过渡模式,以便你在过渡期间编排顺序。 在处理多个元素位置更新时,使用 <transition-group> 组件,通过 FLIP 技术来提高性能。 使用 watchers 来处理应用中不同状态的过渡。 我们将在本指南接下来的三个部分中介绍所有这些以及更多内容。然而,除了提供这些有用的 API 之外,值得一提的是,我们前面介绍的 class 和 style 声明也可以应用于动画和过渡,用于更简单的用例。 在下一节中,我们将回顾一些 web 动画和过渡的基础知识,并提供一些资源链接以进行进一步的研究。如果你已经熟悉 web 动画,并且了解这些原理如何与 Vue 的某些指令配合使用,可以跳过这一节。对于希望在开始学习之前进一步了解网络动画基础知识的其他人,请继续阅读。 #基于 class 的动画和过渡 尽管 <transition> 组件对于组件的进入和离开非常有用,但你也可以通过添加一个条件 class 来激活动画,而无需挂载组件。 <div id="demo"> Push this button to do something you shouldn't be doing:<br /> <div :class="{ shake: noActivated }"> <button @click="noActivated = true">Click me</button> <span v-if="noActivated">Oh no!</span> </div> </div> .shake { animation: shake 0.82s cubic-bezier(0.36, 0.07, 0.19, 0.97) both; transform: translate3d(0, 0, 0); backface-visibility: hidden; perspective: 1000px; } @keyframes shake { 10%, 90% { transform: translate3d(-1px, 0, 0); } 20%, 80% { transform: translate3d(2px, 0, 0); } 30%, 50%, 70% { transform: translate3d(-4px, 0, 0); } 40%, 60% { transform:...
该页面假设你已经阅读过了组件基础。如果你还对组件不太了解,推荐你先阅读它。 提示 这里记录的都是和处理边界情况有关的功能,即一些需要对 Vue 的规则做一些小调整的特殊情况。不过注意这些功能都是有劣势或危险的场景的。我们会在每个案例中注明,所以当你使用每个功能的时候请稍加留意。 #控制更新 得益于其响应性系统,Vue 总是知道何时更新 (如果你使用正确的话)。但是,在某些边缘情况下,你可能希望强制更新,尽管事实上没有任何响应式数据发生更改。还有一些情况下,你可能希望防止不必要的更新。 #强制更新 如果你发现自己需要在 Vue 中强制更新,在 99.99%的情况下,你在某个地方犯了错误。例如,你可能依赖于 Vue 响应性系统未跟踪的状态,例如,在组件创建之后添加了 data 属性。 但是,如果你已经排除了上述情况,并且发现自己处于这种非常罕见的情况下,必须手动强制更新,那么你可以使用 $forceUpdate。 #低级静态组件与 v-once 在 Vue 中渲染纯 HTML 元素的速度非常快,但有时你可能有一个包含很多静态内容的组件。在这些情况下,可以通过向根元素添加 v-once 指令来确保只对其求值一次,然后进行缓存,如下所示: app.component('terms-of-service', { template: ` <div v-once> <h1>Terms of Service</h1> ... a lot of static content ... </div> ` }) TIP 再次提醒,不要过度使用这种模式。虽然在极少数情况下需要渲染大量静态内容时很方便,但除非你注意到渲染速度——慢,否则就没有必要这样做—另外,这可能会在以后引起很多混乱。例如,假设另一个开发人员不熟悉 v-once 或者只是在模板中遗漏了它。他们可能会花上几个小时来弄清楚为什么模板没有正确更新。
该页面假设你已经阅读过了组件基础。如果你还对组件不太了解,推荐你先阅读它。 #在动态组件上使用 keep-alive 我们之前曾经在一个多标签的界面中使用 is attribute 来切换不同的组件: <component :is="currentTabComponent"></component> 当在这些组件之间切换的时候,你有时会想保持这些组件的状态,以避免反复重渲染导致的性能问题。例如我们来展开说一说这个多标签界面: 点击此处实现 你会注意到,如果你选择了一篇文章,切换到 Archive 标签,然后再切换回 Posts,是不会继续展示你之前选择的文章的。这是因为你每次切换新标签的时候,Vue 都创建了一个新的 currentTabComponent 实例。 重新创建动态组件的行为通常是非常有用的,但是在这个案例中,我们更希望那些标签的组件实例能够被在它们第一次被创建的时候缓存下来。为了解决这个问题,我们可以用一个 <keep-alive> 元素将其动态组件包裹起来。 <!-- 失活的组件将会被缓存!--> <keep-alive> <component :is="currentTabComponent"></component> </keep-alive> 来看看修改后的结果: 点击此处实现 现在这个 Posts 标签保持了它的状态 (被选中的文章) 甚至当它未被渲染时也是如此。你可以在这个示例查阅到完整的代码。 你可以在 API 参考文档查阅更多关于 <keep-alive> 的细节。 #异步组件 在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。为了简化,Vue 有一个 defineAsyncComponent 方法: const app = Vue.createApp({}) const AsyncComp = Vue.defineAsyncComponent( () => new Promise((resolve, reject) => { resolve({ template: '<div>I am async!</div>' }) }) ) app.component('async-example', AsyncComp) 如你所见,此方法接受返回 Promise 的工厂函数。从服务器检索组件定义后,应调用 Promise 的 resolve 回调。你也可以调用 reject(reason),以指示加载失败。 你也可以在工厂函数中返回一个 Promise,所以把 webpack 2 和 ES2015 语法加在一起,我们可以这样使用动态导入: import { defineAsyncComponent } from 'vue' const AsyncComp = defineAsyncComponent(() => import('./components/AsyncComponent.vue') ) app.component('async-component', AsyncComp) 当在本地注册组件时,你也可以使用 defineAsyncComponent import { createApp, defineAsyncComponent } from 'vue' createApp({ // ... components: { AsyncComponent: defineAsyncComponent(() => import('./components/AsyncComponent.vue') ) } })...
该页面假设你已经阅读过了组件基础。如果你还对组件不太了解,推荐你先阅读它。 通常,当我们需要将数据从父组件传递到子组件时,我们使用 props。想象一下这样的结构:你有一些深嵌套的组件,而你只需要来自深嵌套子组件中父组件的某些内容。在这种情况下,你仍然需要将 prop 传递到整个组件链中,这可能会很烦人。 对于这种情况,我们可以使用 provide 和 inject 对。父组件可以作为其所有子组件的依赖项提供程序,而不管组件层次结构有多深。这个特性有两个部分:父组件有一个 provide 选项来提供数据,子组件有一个 inject 选项来开始使用这个数据。 例如,如果我们有这样的层次结构: Root └─ TodoList ├─ TodoItem └─ TodoListFooter ├─ ClearTodosButton └─ TodoListStatistics 如果要将 todo-items 的长度直接传递给 TodoListStatistics,我们将把这个属性向下传递到层次结构:TodoList -> TodoListFooter -> TodoListStatistics。通过 provide/inject 方法,我们可以直接执行以下操作: const app = Vue.createApp({}) app.component('todo-list', { data() { return { todos: ['Feed a cat', 'Buy tickets'] } }, provide: { user: 'John Doe' }, template: ` <div> {{ todos.length }} <!-- 模板的其余部分 --> </div> ` }) app.component('todo-list-statistics', { inject: ['user'], created() { console.log(`Injected property: ${this.user}`) // > 注入 property: John Doe } }) 但是,如果我们尝试在此处提供一些组件实例 property,则这将不起作用: app.component('todo-list', { data() { return { todos: ['Feed a cat', 'Buy tickets'] } }, provide: { todoLength: this.todos.length // 将会导致错误 'Cannot read property 'length' of undefined` }, template: `...
该页面假设你已经阅读过了组件基础。如果你还对组件不太了解,推荐你先阅读它。 #事件名 不同于组件和 prop,事件名不存在任何自动化的大小写转换。而是触发的事件名需要完全匹配监听这个事件所用的名称。举个例子,如果触发一个 camelCase 名字的事件: this.$emit('myEvent') 则监听这个名字的 kebab-case 版本是不会有任何效果的: <!-- 没有效果 --> <my-component @my-event="doSomething"></my-component> 不同于组件和 prop,事件名不会被用作一个 JavaScript 变量名或 property 名,所以就没有理由使用 camelCase 或 PascalCase 了。并且 v-on 事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以 @myEvent 将会变成 @myevent——导致 myEvent 不可能被监听到。 因此,我们推荐你始终使用 kebab-case 的事件名。 #定义自定义事件 在 Vue School 上观看关于定义自定义事件的免费视频。 可以通过 emits 选项在组件上定义已发出的事件。 app.component('custom-form', { emits: ['in-focus', 'submit'] }) 当在 emits 选项中定义了原生事件 (如 click) 时,将使用组件中的事件替代原生事件侦听器。 TIP 建议定义所有发出的事件,以便更好地记录组件应该如何工作。 #验证抛出的事件 与 prop 类型验证类似,如果使用对象语法而不是数组语法定义发出的事件,则可以验证它。 要添加验证,将为事件分配一个函数,该函数接收传递给 $emit 调用的参数,并返回一个布尔值以指示事件是否有效。 app.component('custom-form', { emits: { // 没有验证 click: null, // 验证submit 事件 submit: ({ email, password }) => { if (email && password) { return true } else { console.warn('Invalid submit event payload!') return false } } }, methods: { submitForm() { this.$emit('submit', { email, password }) } } }) #v-model 参数...
该页面假设你已经阅读过了组件基础。如果你还对组件不太了解,推荐你先阅读它。 一个非 prop 的 attribute 是指传向一个组件,但是该组件并没有相应 props 或 emits 定义的 attribute。常见的示例包括 class、style 和 id 属性。 #Attribute 继承 当组件返回单个根节点时,非 prop attribute 将自动添加到根节点的 attribute 中。例如,在 <date-picker> 组件的实例中: app.component('date-picker', { template: ` <div class="date-picker"> <input type="datetime" /> </div> ` }) 如果我们需要通过 data status property 定义 <date-picker> 组件的状态,它将应用于根节点 (即 div.date-picker)。 <!-- 具有非prop attribute的Date-picker组件--> <date-picker data-status="activated"></date-picker> <!-- 渲染 date-picker 组件 --> <div class="date-picker" data-status="activated"> <input type="datetime" /> </div> 同样的规则适用于事件监听器: <date-picker @change="submitChange"></date-picker> app.component('date-picker', { created() { console.log(this.$attrs) // { onChange: () => {} } } }) 当有一个 HTML 元素将 change 事件作为 date-picker 的根元素时,这可能会有帮助。 app.component('date-picker', { template: ` <select> <option value="1">Yesterday</option> <option value="2">Today</option> <option value="3">Tomorrow</option> </select> ` }) 在这种情况下,change 事件监听器从父组件传递到子组件,它将在原生 select 的 change 事件上触发。我们不需要显式地从 date-picker 发出事件: <div id="date-picker" class="demo"> <date-picker @change="showChange"></date-picker> </div> const app = Vue.createApp({ methods:...
该页面假设你已经阅读过了组件基础。如果你还对组件不太了解,推荐你先阅读它。 #Prop 类型 到这里,我们只看到了以字符串数组形式列出的 prop: props: ['title', 'likes', 'isPublished', 'commentIds', 'author'] 但是,通常你希望每个 prop 都有指定的值类型。这时,你可以以对象形式列出 prop,这些 property 的名称和值分别是 prop 各自的名称和类型: props: { title: String, likes: Number, isPublished: Boolean, commentIds: Array, author: Object, callback: Function, contactsPromise: Promise // 或任何其他构造函数 } 这不仅为你的组件提供了文档,还会在它们遇到错误的类型时从浏览器的 JavaScript 控制台提示用户。你会在这个页面接下来的部分看到类型检查和其它 prop 验证。 #传递静态或动态的 Prop 这样,你已经知道了可以像这样给 prop 传入一个静态的值: <blog-post title="My journey with Vue"></blog-post> 你也知道 prop 可以通过 v-bind 或简写 : 动态赋值,例如: <!-- 动态赋予一个变量的值 --> <blog-post :title="post.title"></blog-post> <!-- 动态赋予一个复杂表达式的值 --> <blog-post :title="post.title + ' by ' + post.author.name"></blog-post> 在上述两个示例中,我们传入的值都是字符串类型的,但实际上任何类型的值都可以传给一个 prop。 #传入一个数字 <!-- 即便 `42` 是静态的,我们仍然需要 `v-bind` 来告诉 Vue --> <!-- 这是一个 JavaScript 表达式而不是一个字符串。 --> <blog-post :likes="42"></blog-post> <!-- 用一个变量进行动态赋值。--> <blog-post :likes="post.likes"></blog-post> #传入一个布尔值 <!-- 包含该 prop 没有值的情况在内,都意味着 `true`。 --> <blog-post is-published></blog-post> <!-- 即便 `false` 是静态的,我们仍然需要 `v-bind` 来告诉 Vue --> <!-- 这是一个 JavaScript 表达式而不是一个字符串。 -->...
该页面假设你已经阅读过了组件基础。如果你还对组件不太了解,推荐你先阅读它。 #组件名 在注册一个组件的时候,我们始终需要给它一个名字。比如在全局注册的时候我们已经看到了: const app = Vue.createApp({...}) app.component('my-component-name', { /* ... */ }) 该组件名就是 app.component 的第一个参数,在上面的例子中,组件的名称是“my-component-name”。 你给予组件的名字可能依赖于你打算拿它来做什么。当直接在 DOM 中使用一个组件 (而不是在字符串模板或单文件组件) 的时候,我们强烈推荐遵循 W3C 规范中的自定义组件名 (字母全小写且必须包含一个连字符)。这会帮助你避免和当前以及未来的 HTML 元素相冲突。 全部小写 包含连字符 (及:即有多个单词与连字符符号连接) 这样会帮助你避免与当前以及未来的 HTML 元素发生冲突。 你可以在风格指南中查阅到关于组件名的其它建议。 #组件名大小写 在字符串模板或单个文件组件中定义组件时,定义组件名的方式有两种: #使用 kebab-case app.component('my-component-name', { /* ... */ }) 当使用 kebab-case (短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用 kebab-case,例如 <my-component-name>。 #使用 PascalCase app.component('MyComponentName', { /* ... */ }) 当使用 PascalCase (首字母大写命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也就是说 <my-component-name> 和 <MyComponentName> 都是可接受的。注意,尽管如此,直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的。 #全局注册 到目前为止,我们只用过 app.component 来创建组件: Vue.createApp({...}).component('my-component-name', { // ... 选项 ... }) 这些组件是全局注册的。也就是说它们在注册之后可以用在任何新创建的组件实例的模板中。比如: const app = Vue.createApp({}) app.component('component-a', { /* ... */ }) app.component('component-b', { /* ... */ }) app.component('component-c', { /* ... */ }) app.mount('#app') <div id="app"> <component-a></component-a> <component-b></component-b> <component-c></component-c> </div> 在所有子组件中也是如此,也就是说这三个组件在各自内部也都可以相互使用。 #局部注册 全局注册往往是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。 在这些情况下,你可以通过一个普通的 JavaScript...