美文网首页
Vue3 选项式api

Vue3 选项式api

作者: 仰望天空的人 | 来源:发表于2023-03-05 10:49 被阅读0次

    DOM 中的根组件模板#

    当在未采用构建流程的情况下使用 Vue 时,我们可以在挂载容器中直接书写根组件模板:

    html

    <div id="app">
      <button @click="count++">{{ count }}</button>
    </div>
    
    

    js

    import { createApp } from 'vue'
    
    const app = createApp({
      data() {
        return {
          count: 0
        }
      }
    })
    
    app.mount('#app')
    

    当根组件没有设置 template 选项时,Vue 将自动使用容器的 innerHTML 作为模板。

    应用配置#

    应用实例会暴露一个 .config 对象允许我们配置一些应用级的选项,例如定义一个应用级的错误处理器,它将捕获所有由子组件上抛而未被处理的错误:

    js

    app.config.errorHandler = (err) => {
      /* 处理错误 */
    }
    
    

    应用实例还提供了一些方法来注册应用范围内可用的资源,例如注册一个组件:

    js

    app.component('TodoDeleteButton', TodoDeleteButton)
    
    

    这使得 TodoDeleteButton 在应用的任何地方都是可用的。我们会在指南的后续章节中讨论关于组件和其他资源的注册。你也可以在 API 参考中浏览应用实例 API 的完整列表。

    确保在挂载应用实例之前完成所有应用配置!

    如果你有像这样的一个包含多个 attribute 的 JavaScript 对象:

    js

    data() {
      return {
        objectOfAttrs: {
          id: 'container',
          class: 'wrapper'
        }
      }
    }
    

    通过不带参数的 v-bind,你可以将它们绑定到单个元素上:

    template

    <div v-bind="objectOfAttrs"></div>
    

    动态参数#

    同样在指令参数上也可以使用一个 JavaScript 表达式,需要包含在一对方括号内:

    template

    <!--
    注意,参数表达式有一些约束,
    参见下面“动态参数值的限制”与“动态参数语法的限制”章节的解释
    -->
    <a v-bind:[attributeName]="url"> ... </a>
    
    <!-- 简写 -->
    <a :[attributeName]="url"> ... </a>
    
    

    这里的 attributeName 会作为一个 JavaScript 表达式被动态执行,计算得到的值会被用作最终的参数。举例来说,如果你的组件实例有一个数据属性 attributeName,其值为 "href",那么这个绑定就等价于 v-bind:href

    相似地,你还可以将一个函数绑定到动态的事件名称上:

    template

    <a v-on:[eventName]="doSomething"> ... </a>
    
    <!-- 简写 -->
    <a @[eventName]="doSomething">
    
    

    在此示例中,当 eventName 的值是 "focus" 时,v-on:[eventName] 就等价于 v-on:focus

    DOM 更新时机#

    当你更改响应式状态后,DOM 会自动更新。然而,你得注意 DOM 的更新并不是同步的。相反,Vue 将缓冲它们直到更新周期的 “下个时机” 以确保无论你进行了多少次状态更改,每个组件都只更新一次。

    若要等待一个状态改变后的 DOM 更新完成,你可以使用 nextTick() 这个全局 API:

    js

    import { nextTick } from 'vue'
    
    export default {
      methods: {
        increment() {
          this.count++
          nextTick(() => {
            // 访问更新后的 DOM
          })
        }
      }
    }
    

    如果你的组件有多个根元素,你将需要指定哪个根元素来接收这个 class。你可以通过组件的 $attrs 属性来实现指定:

    template

    <p :class="$attrs.class">Hi!</p>
    <span>This is a child component</span>
    template
    <MyComponent class="baz" />

    这将被渲染为:

    html
    <p class="baz">Hi!</p>
    <span>This is a child component</span>

    v-ifv-for#

    警告

    同时使用 v-ifv-for不推荐的,因为这样二者的优先级不明显。请查看风格指南获得更多信息。

    v-ifv-for 同时存在于一个元素上的时候,v-if 会首先被执行。请查看列表渲染指南获取更多细节。

    组件上使用 v-for#

    这一小节假设你已了解组件的相关知识,或者你也可以先跳过这里,之后再回来看。

    我们可以直接在组件上使用 v-for,和在一般的元素上使用没有区别 (别忘记提供一个 key):

    template

    <MyComponent v-for="item in items" :key="item.id" />
    
    

    但是,这不会自动将任何数据传递给组件,因为组件有自己独立的作用域。为了将迭代后的数据传递到组件中,我们还需要传递 props:

    template

    <MyComponent
      v-for="(item, index) in items"
      :item="item"
      :index="index"
      :key="item.id"
    />
    
    

    不自动将 item 注入组件的原因是,这会使组件与 v-for 的工作方式紧密耦合。明确其数据的来源可以使组件在其他情况下重用。

    在计算属性中使用 reverse() 和 sort() 的时候务必小心!这两个方法将变更原始数组,计算函数中不应该这么做。请在调用这些方法之前创建一个原数组的副本:

    diff

    • return numbers.reverse()
    • return [...numbers].reverse()

    在内联事件处理器中访问事件参数#

    有时我们需要在内联事件处理器中访问原生 DOM 事件。你可以向该处理器方法传入一个特殊的 $event 变量,或者使用内联箭头函数:

    template

    <!-- 使用特殊的 $event 变量 -->
    <button @click="warn('Form cannot be submitted yet.', $event)">
      Submit
    </button>
    
    <!-- 使用内联箭头函数 -->
    <button @click="(event) => warn('Form cannot be submitted yet.', event)">
      Submit
    </button>
    
    

    js

    methods: {
      warn(message, event) {
        // 这里可以访问 DOM 原生事件
        if (event) {
          event.preventDefault()
        }
        alert(message)
      }
    }
    

    事件修饰符#

    在处理事件时调用 event.preventDefault()event.stopPropagation() 是很常见的。尽管我们可以直接在方法内调用,但如果方法能更专注于数据逻辑而不用去处理 DOM 事件的细节会更好。

    为解决这一问题,Vue 为 v-on 提供了事件修饰符。修饰符是用 . 表示的指令后缀,包含以下这些:

    • .stop
    • .prevent
    • .self
    • .capture
    • .once
    • .passive

    template

    <!-- 单击事件将停止传递 -->
    <a @click.stop="doThis"></a>
    
    <!-- 提交事件将不再重新加载页面 -->
    <form @submit.prevent="onSubmit"></form>
    
    <!-- 修饰语可以使用链式书写 -->
    <a @click.stop.prevent="doThat"></a>
    
    <!-- 也可以只有修饰符 -->
    <form @submit.prevent></form>
    
    <!-- 仅当 event.target 是元素本身时才会触发事件处理器 -->
    <!-- 例如:事件处理器不来自子元素 -->
    <div @click.self="doThat">...</div>
    
    

    TIP

    使用修饰符时需要注意调用顺序,因为相关代码是以相同的顺序生成的。因此使用 @click.prevent.self 会阻止元素及其子元素的所有点击事件的默认行为@click.self.prevent 则只会阻止对元素本身的点击事件的默认行为。

    .exact 修饰符#

    .exact 修饰符允许控制触发一个事件所需的确定组合的系统按键修饰符。

    template

    <!-- 当按下 Ctrl 时,即使同时按下 Alt 或 Shift 也会触发 -->
    <button @click.ctrl="onClick">A</button>
    
    <!-- 仅当按下 Ctrl 且未按任何其他键时才会触发 -->
    <button @click.ctrl.exact="onCtrlClick">A</button>
    
    <!-- 仅当没有按下任何系统按键时触发 -->
    <button @click.exact="onClick">A</button>
    

    鼠标按键修饰符#

    • .left
    • .right
    • .middle

    这些修饰符将处理程序限定为由特定鼠标按键触发的事件。

    修饰符#

    .lazy#

    默认情况下,v-model 会在每次 input 事件后更新数据 (IME 拼字阶段的状态例外)。你可以添加 lazy 修饰符来改为在每次 change 事件后更新数据:

    template

    <!-- 在 "change" 事件后同步更新而不是 "input" -->
    <input v-model.lazy="msg" />
    

    .number#

    如果你想让用户输入自动转换为数字,你可以在 v-model 后添加 .number 修饰符来管理输入:

    template

    <input v-model.number="age" />
    
    

    如果该值无法被 parseFloat() 处理,那么将返回原始值。

    number 修饰符会在输入框有 type="number" 时自动启用。

    this.$watch()#

    我们也可以使用组件实例的 $watch() 方法来命令式地创建一个侦听器:

    js

    export default {
      created() {
        this.$watch('question', (newQuestion) => {
          // ...
        })
      }
    }
    
    

    如果要在特定条件下设置一个侦听器,或者只侦听响应用户交互的内容,这方法很有用。它还允许你提前停止该侦听器。

    停止侦听器#

    watch 选项或者 $watch() 实例方法声明的侦听器,会在宿主组件卸载时自动停止。因此,在大多数场景下,你无需关心怎么停止它。

    在少数情况下,你的确需要在组件卸载之前就停止一个侦听器,这时可以调用 $watch() API 返回的函数:

    js

    const unwatch = this.$watch('foo', callback)
    
    // ...当该侦听器不再需要时
    unwatch()
    

    expose 选项可以用于限制对子组件实例的访问:

    js

    export default {
      expose: ['publicData', 'publicMethod'],
      data() {
        return {
          publicData: 'foo',
          privateData: 'bar'
        }
      },
      methods: {
        publicMethod() {
          /* ... */
        },
        privateMethod() {
          /* ... */
        }
      }
    }
    

    在上面这个例子中,父组件通过模板引用访问到子组件实例后,仅能访问 publicData 和 publicMethod。

    包含/排除#

    <KeepAlive> 默认会缓存内部的所有组件实例,但我们可以通过 includeexclude prop 来定制该行为。这两个 prop 的值都可以是一个以英文逗号分隔的字符串、一个正则表达式,或是包含这两种类型的一个数组:

    template

    <!-- 以英文逗号分隔的字符串 -->
    <KeepAlive include="a,b">
      <component :is="view" />
    </KeepAlive>
    
    <!-- 正则表达式 (需使用 `v-bind`) -->
    <KeepAlive :include="/a|b/">
      <component :is="view" />
    </KeepAlive>
    
    <!-- 数组 (需使用 `v-bind`) -->
    <KeepAlive :include="['a', 'b']">
      <component :is="view" />
    </KeepAlive>
    
    

    它会根据组件的 name 选项进行匹配,所以组件如果想要条件性地被 KeepAlive 缓存,就必须显式声明一个 name 选项。

    TIP

    在 3.2.34 或以上的版本中,使用 <script setup> 的单文件组件会自动根据文件名生成对应的 name 选项,无需再手动声明。

    最大缓存实例数#

    我们可以通过传入 max prop 来限制可被缓存的最大组件实例数。<KeepAlive> 的行为在指定了 max 后类似一个 LRU 缓存:如果缓存的实例数量即将超过指定的那个最大数量,则最久没有被访问的缓存实例将被销毁,以便为新的实例腾出空间。

    template

    <KeepAlive :max="10">
      <component :is="activeComponent" />
    </KeepAlive>
    
    

    缓存实例的生命周期#

    当一个组件实例从 DOM 上移除但因为被 <KeepAlive> 缓存而仍作为组件树的一部分时,它将变为不活跃状态而不是被卸载。当一个组件实例作为缓存树的一部分插入到 DOM 中时,它将重新被激活

    一个持续存在的组件可以通过 activateddeactivated 选项来注册相应的两个状态的生命周期钩子:

    js

    export default {
      activated() {
        // 在首次挂载、
        // 以及每次从缓存中被重新插入的时候调用
      },
      deactivated() {
        // 在从 DOM 上移除、进入缓存
        // 以及组件卸载时调用
      }
    }
    
    

    请注意:

    • activated 在组件挂载时也会调用,并且 deactivated 在组件卸载时也会调用。

    • 这两个钩子不仅适用于 <KeepAlive> 缓存的根组件,也适用于缓存树中的后代组件。

    自定义的组件 <blog-post-row> 将作为无效的内容被忽略,因而在最终呈现的输出中造成错误。我们可以使用特殊的 is attribute 作为一种解决方案:

    template

    <table>
      <tr is="vue:blog-post-row"></tr>
    </table>
    
    

    TIP

    当使用在原生 HTML 元素上时,is 的值必须加上前缀 vue: 才可以被解析为一个 Vue 组件。这一点是必要的,为了避免和原生的自定义内置元素相混淆。

    全局注册#

    我们可以使用 Vue 应用实例app.component() 方法,让组件在当前 Vue 应用中全局可用。

    js

    import { createApp } from 'vue'
    
    const app = createApp({})
    
    app.component(
      // 注册的名字
      'MyComponent',
      // 组件的实现
      {
        /* ... */
      }
    )
    
    

    如果使用单文件组件,你可以注册被导入的 .vue 文件:

    js

    import MyComponent from './App.vue'
    
    app.component('MyComponent', MyComponent)
    
    

    app.component() 方法可以被链式调用:

    js

    app
      .component('ComponentA', ComponentA)
      .component('ComponentB', ComponentB)
      .component('ComponentC', ComponentC)
    

    使用一个对象绑定多个 prop#

    如果你想要将一个对象的所有属性都当作 props 传入,你可以使用没有参数的 v-bind,即只使用 v-bind 而非 :prop-name。例如,这里有一个 post 对象:

    js

    export default {
      data() {
        return {
          post: {
            id: 1,
            title: 'My Journey with Vue'
          }
        }
      }
    }
    
    

    以及下面的模板:

    template

    <BlogPost v-bind="post" />
    
    

    而这实际上等价于:

    template

    <BlogPost :id="post.id" :title="post.title" />
    

    Prop 校验#

    Vue 组件可以更细致地声明对传入的 props 的校验要求。比如我们上面已经看到过的类型声明,如果传入的值不满足类型要求,Vue 会在浏览器控制台中抛出警告来提醒使用者。这在开发给其他开发者使用的组件时非常有用。

    要声明对 props 的校验,你可以向 props 选项提供一个带有 props 校验选项的对象,例如:

    js

    export default {
      props: {
        // 基础类型检查
        //(给出 `null` 和 `undefined` 值则会跳过任何类型检查)
        propA: Number,
        // 多种可能的类型
        propB: [String, Number],
        // 必传,且为 String 类型
        propC: {
          type: String,
          required: true
        },
        // Number 类型的默认值
        propD: {
          type: Number,
          default: 100
        },
        // 对象类型的默认值
        propE: {
          type: Object,
          // 对象或者数组应当用工厂函数返回。
          // 工厂函数会收到组件所接收的原始 props
          // 作为参数
          default(rawProps) {
            return { message: 'hello' }
          }
        },
        // 自定义类型校验函数
        propF: {
          validator(value) {
            // The value must match one of these strings
            return ['success', 'warning', 'danger'].includes(value)
          }
        },
        // 函数类型的默认值
        propG: {
          type: Function,
          // 不像对象或数组的默认,这不是一个工厂函数。这会是一个用来作为默认值的函数
          default() {
            return 'Default function'
          }
        }
      }
    }
    

    多根节点的 Attributes 继承#

    和单根节点组件有所不同,有着多个根节点的组件没有自动 attribute 透传行为。如果 $attrs 没有被显式绑定,将会抛出一个运行时警告。

    template

    <CustomLayout id="custom-layout" @click="changeValue" />
    
    

    如果 <CustomLayout> 有下面这样的多根节点模板,由于 Vue 不知道要将 attribute 透传到哪里,所以会抛出一个警告。

    template

    <header>...</header>
    <main>...</main>
    <footer>...</footer>
    
    

    如果 $attrs 被显式绑定,则不会有警告:

    template

    <header>...</header>
    <main v-bind="$attrs">...</main>
    <footer>...</footer>
    

    在 JavaScript 中访问透传 Attributes#

    如果需要,你可以通过 $attrs 这个实例属性来访问组件的所有透传 attribute:

    js

    export default {
      created() {
        console.log(this.$attrs)
      }
    }
    

    相关文章

      网友评论

          本文标题:Vue3 选项式api

          本文链接:https://www.haomeiwen.com/subject/wqrlqdtx.html