Java面试题037:一文深入了解VUE(2)

6、注册

将 Vue 组件定义在一个单独的 .vue 文件中,这被叫做单文件组件(简称 SFC)。

一个 Vue 组件在使用前需要先被"注册",组件注册有两种方式:全局注册和局部注册。

(1)全局注册

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

javascript 复制代码
import { createApp } from 'vue'

const app = createApp({})

app.component(
  // 注册的名字
  'MyComponent',
  // 组件的实现
  {
    /* ... */
  }
)

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

javascript 复制代码
import MyComponent from './App.vue'

app.component('MyComponent', MyComponent)

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

javascript 复制代码
app
  .component('ComponentA', ComponentA)
  .component('ComponentB', ComponentB)
  .component('ComponentC', ComponentC)

全局注册的组件可以在此应用的任意组件的模板中使用:

javascript 复制代码
<!-- 这在当前应用的任意组件中都可用 -->
<ComponentA/>
<ComponentB/>
<ComponentC/>

全局注册的弊端(局部注册的必要性):

  • 全局注册的组件,即使它并没有被实际使用,它仍然会出现在打包后的 JS 文件中。

  • 全局注册在大型项目中使项目的依赖关系变得不那么明确。在父组件中使用子组件时,不太容易定位子组件的实现。

(2)局部注册

局部注册的组件需要在使用它的父组件中显式导入,并且只能在该父组件中使用。它的优点是使组件之间的依赖关系更加明确。

在使用 <script setup> 的单文件组件中,导入的组件可以直接在模板中使用,无需注册:

javascript 复制代码
<script setup>
import ComponentA from './ComponentA.vue'
</script>

<template>
  <ComponentA />
</template>

如果没有使用 <script setup>,则需要使用 components 选项来显式注册:

javascript 复制代码
import ComponentA from './ComponentA.js'

export default {
  components: {
    ComponentA
  },
  setup() {
    // ...
  }
}

局部注册的组件在后代组件中可用。仅在当前组件可用,而在任何的子组件或更深层的子组件中都不可用。

7、组件名格式

使用 PascalCase 作为组件名的注册格式,Vue 支持将模板中使用 kebab-case 的标签解析为使用 PascalCase 注册的组件。这意味着一个以 MyComponent 为名注册的组件,在模板 (或由 Vue 渲染的 HTML 元素) 中可以通过 <MyComponent><my-component> 引用。

8、生命周期钩子

beforeCreate :在实例初始化完成并且 props 被解析后立即调用。props 会被定义成响应式属性,data()computed 等选项也开始进行处理。

created: 当这个钩子被调用时,以下内容已经设置完成:响应式数据、计算属性、方法和侦听器。然而,此时挂载阶段还未开始,因此 $el 属性仍不可用。

**beforeMount:**当这个钩子被调用时,组件已经完成了其响应式状态的设置,但还没有创建 DOM 节点。它即将首次执行 DOM 渲染过程。

**mounted:**在组件被挂载之后调用(所有同步子组件都已经被挂载,自身的 DOM 树已经创建完成并插入了父容器中),这个钩子通常用于执行需要访问组件所渲染的 DOM 树相关的副作用。

**beforeUpdate:**在组件即将因为一个响应式状态变更而更新其 DOM 树之前调用,可以用来在 Vue 更新 DOM 之前访问 DOM 状态。

**updated:**组件因为一个响应式状态变更而更新其 DOM 树之后调用,父组件的更新钩子将在其子组件的更新钩子之后调用,组件的任意 DOM 更新后被调用,不要在 updated 钩子中更改组件的状态,这可能会导致无限的更新循环。

**beforeUnmount:**在一个组件实例被卸载之前调用。

**unmounted:**在一个组件实例被卸载之后调用。在这个钩子中手动清理一些副作用,例如计时器、DOM 事件监听器或者与服务器的连接。

9、组件渲染

(1)条件渲染

v-if、v-else、v-else-if: 条件区块内的事件监听器和子组件都会被销毁与重建。如果在初次渲染时条件值为 false,则不会做任何事。有更高的切换开销,运行时绑定条件很少改变, v-if 会更合适。

javascript 复制代码
<div v-if="type === 'A'">
  A
</div>
<div v-else-if="type === 'B'">
  B
</div>
<div v-else-if="type === 'C'">
  C
</div>
<div v-else>
  Not A/B/C
</div>

v-show 元素无论初始条件如何,始终会被渲染,只有 CSS display 属性会被切换。有更高的初始渲染开销。如果需要频繁切换,则使用 v-show 较好。

(2)列表渲染

v-for: 基于一个数组来渲染一个列表,需要使用 item in items 形式的特殊语法。

javascript 复制代码
<ul>
  <template v-for="item in items">
    <li>{{ item.msg }}</li>
    <li class="divider" role="presentation"></li>
  </template>
</ul>

同时使用 v-ifv-for不推荐的, v-ifv-for 的优先级更高。v-if 的条件将无法访问到 v-for 作用域内定义的变量别名。

javascript 复制代码
<!--
 这会抛出一个错误,因为属性 todo 此时
 没有在该实例上定义
-->
<li v-for="todo in todos" v-if="!todo.isComplete">
  {{ todo.name }}
</li>

在外先包装一层 <template> 再在其上使用 v-for 可以解决这个问题:

javascript 复制代码
<template v-for="todo in todos">
  <li v-if="!todo.isComplete">
    {{ todo.name }}
  </li>
</template>

(3)事件

v-on: 监听 DOM 事件,并在事件触发时执行对应的 JavaScript。用法:v-on:click="handler"@click="handler"

事件修饰符:为了方便访问原生 DOM 事件,更专注于数据逻辑不用去处理 DOM 事件的细节

javascript 复制代码
<!-- 单击事件将停止传递 -->
<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>

**按键修饰符:**监听键盘事件,检查特定的按键。

  • .enter
  • .tab
  • .delete (捕获"Delete"和"Backspace"两个按键)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right
javascript 复制代码
<!-- 仅在 `key` 为 `Enter` 时调用 `submit` -->
<input @keyup.enter="submit" />

可以直接使用 KeyboardEvent.key 暴露的按键名称作为修饰符,但需要转为 kebab-case 形式。

javascript 复制代码
<input @keyup.page-down="onPageDown" />

10、组件传值

(1)父组件向子组件传值

父组件通过 v-bind 绑定需要传递的数据,子组件上使用 props接收数据。

在 Vue 3 中,props 的使用与 Vue 2 类似,但有一些重要变化。首先,props 的声明方式使用 ES6 的解构语法来声明。其次,Vue 3 强制 props 的类型检查,并提供了更多的选项来控制 props 的行为,例如默认值和必需性等。

在使用 <script setup> 的单文件组件中,props 可以使用 defineProps() 宏来声明,在没有使用 <script setup> 的组件中,props 可以使用 props 选项来声明。

javascript 复制代码
<script setup>
const props = defineProps(['foo'])

console.log(props.foo)
</script>


//没有script setup 

export default {
  props: ['foo'],
  setup(props) {
    // setup() 接收 props 作为第一个参数
    console.log(props.foo)
  }
}

任何类型的值都可以作为 props 的值被传递。

javascript 复制代码
<!-- 虽然这个对象字面量是个常量,我们还是需要使用 v-bind -->
<!-- 因为这是一个 JavaScript 表达式而不是一个字符串 -->
<BlogPost
  :author="{
    name: 'Veronica',
    company: 'Veridian Dynamics'
  }"
 />

<!-- 根据一个变量的值动态传入 -->
<BlogPost :author="post.author" />

所有的 props 都遵循着单向绑定 原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。不应该在子组件中去更改一个 prop。

javascript 复制代码
const props = defineProps(['foo'])

// ❌ 警告!prop 是只读的!
props.foo = 'bar'

Vue3 组件可以更细致地声明对传入的 props 的校验要求。

javascript 复制代码
defineProps({
  // 基础类型检查
  // (给出 `null` 和 `undefined` 值则会跳过任何类型检查)
  propA: Number,
  // 多种可能的类型
  propB: [String, Number],
  // 必传,且为 String 类型
  propC: {
    type: String,
    required: true
  },
  // 必传但可为 null 的字符串
  propD: {
    type: [String, null],
    required: true
  },
  // Number 类型的默认值
  propE: {
    type: Number,
    default: 100
  },
  // 对象类型的默认值
  propF: {
    type: Object,
    // 对象或数组的默认值
    // 必须从一个工厂函数返回。
    // 该函数接收组件所接收到的原始 prop 作为参数。
    default(rawProps) {
      return { message: 'hello' }
    }
  },
  // 自定义类型校验函数
  // 在 3.4+ 中完整的 props 作为第二个参数传入
  propG: {
    validator(value, props) {
      // The value must match one of these strings
      return ['success', 'warning', 'danger'].includes(value)
    }
  },
  // 函数类型的默认值
  propH: {
    type: Function,
    // 不像对象或数组的默认,这不是一个
    // 工厂函数。这会是一个用来作为默认值的函数
    default() {
      return 'Default function'
    }
  }
})

(2)子组件向父组件传值

Vue2 中在父组件自定义事件,并使用 v-on 进行监听,在子组件中使用 $emit 触发自定义事件。

javascript 复制代码
<template>
  <button @click="sendMessage">Send Message</button>
</template>
 
<script>
export default {
  methods: {
    sendMessage() {
      this.$emit('message-sent', 'Hello from child!');
    }
  }
};
</script>

Vue 3 引入了新的 setup() API,setup语法糖的是defineEmits。

javascript 复制代码
<template>
  <button @click="clickChild">点击子组件</button>
</template>
 
<script setup>
import { defineEmits } from 'vue'
// 使用defineEmits创建名称,接受一个数组
const emit = defineEmits(['clickChild'])
const clickChild=()=>{
  let param={
    content:'b'
  }
  //传递给父组件
  emit('clickChild',param)
}
</script>
 
<style>
 
</style>

(3)兄弟组件之间传值

可以通过使用一个共同的父组件,然后将需要共享的数据放在父组件的 data 中,再通过 props 将数据传递给各自的子组件。

(4)跨级组件之间传值

Provide/Inject 是一种在祖先组件和后代组件之间共享数据的方式。通过在祖先组件中使用 provide 方法提供数据,在后代组件中使用 inject 方法获取数据。

javascript 复制代码
<template>
  <child-component></child-component>
</template>
 
<script>
import ChildComponent from './ChildComponent.vue';
 
export default {
  components: {
    ChildComponent
  },
  provide() {
    return {
      message: 'Hello from ancestor!'
    };
  }
};
</script>
javascript 复制代码
<template>
  <div>{{ message }}</div>
</template>
 
<script>
export default {
  inject: ['message']
};
</script>