vue3笔记

响应式基础

在组合式 API 中,推荐使用  ref()函数来声明响应式状态

ref() 接收参数,并将其包裹在一个带有 .value 属性的 ref 对象中返回

该 .value 属性给予了 Vue 一个机会来检测 ref 何时被访问或修改(使用.value解包

Vue 会在“next tick”更新周期中缓冲所有状态的修改,要等待 DOM 更新完成后再执行额外的代码,可以使用 nextTick() 全局 API

reactive() 的局限性:有限的值类型(只有对象类型 )  不能替换整个对象 (不能“替换”响应式对象)    对解构操作不友好(解构为本地变量时,将丢失响应性连接

额外的 ref 解包细节(作为 reactive 对象的属性):

一个 ref 会在作为响应式对象的属性被访问或修改时自动解包

与 reactive 对象不同的是,当 ref 作为响应式数组或原生集合类型(如 Map) 中的元素被访问时,它不会被解包

模板渲染上下文中,只有顶级的 ref 属性才会被解包

计算属性

推荐使用计算属性来描述依赖响应式状态的复杂逻辑

计算属性值会基于其响应式依赖被缓存。一个计算属性仅会在其响应式依赖更新时重新计算。相比之下,方法调用总是会在重渲染发生时次执行函数。

生命周期钩子

最常用的是 onMountedonUpdated 和 onUnmounted

侦听器

 watch 函数在每次响应式状态发生变化时触发回调函数

watch vs. watchEffect

watch 和 watchEffect 都能响应式地执行有副作用的回调。它们之间的主要区别是追踪响应式依赖的方式:

  • watch 追踪明确侦听的数据源。它不会追踪任何在回调中访问到的东西。另外,仅在数据源确实改变时才会触发回调。watch 会避免在发生副作用时追踪依赖,因此,我们能更加精确地控制回调函数的触发时机。

  • watchEffect,则会在副作用发生期间追踪依赖。它会在同步执行过程中,自动追踪所有能访问到的响应式属性。这更方便,而且代码往往更简洁,但有时其响应性依赖关系会不那么明确

    watchEffect(async () => {
      const response = await fetch(
        `https://jsonplaceholder.typicode.com/todos/${todoId.value}`
      )
      data.value = await response.json()
    })

    这个例子中,回调会立即执行,不需要指定 immediate: true。在执行期间,它会自动追踪 todoId.value 作为依赖(和计算属性类似)。每当 todoId.value 变化时,回调会再次执行。有了 watchEffect(),我们不再需要明确传递 todoId 作为源值。

默认情况下,用户创建的侦听器回调,都会在 Vue 组件更新之前被调用。这意味着你在侦听器回调中访问的 DOM 将是被 Vue 更新之前的状态。

如果想在侦听器回调中能访问被 Vue 更新之后的 DOM,你需要指明 flush: 'post' 选项:

watch(source, callback, {
  flush: 'post'
})

watchEffect(callback, {
  flush: 'post'
})

后置刷新的 watchEffect() 有个更方便的别名 watchPostEffect()

注意,需要异步创建侦听器的情况很少,请尽可能选择同步创建。如果需要等待一些异步数据,你可以使用条件式的侦听逻辑:

// 需要异步请求得到的数据
const data = ref(null)

watchEffect(() => {
  if (data.value) {
    // 数据加载后执行某些操作...
  }
})

模板引用

直接访问底层 DOM 元素,使用 ref attribute,在组件挂载后才能访问模板引用

<input ref="input">

模板引用也可以被用在一个子组件上。这种情况下引用中获得的值是组件实例

<script setup>
import { ref, onMounted } from 'vue'
import Child from './Child.vue'

const child = ref(null)

onMounted(() => {
  // child.value 是 <Child /> 组件的实例
})
</script>

<template>
  <Child ref="child" />
</template>

如果一个子组件使用的是选项式 API 或没有使用 <script setup>被引用的组件实例和该子组件的 this 完全一致,这意味着父组件对子组件的每一个属性和方法都有完全的访问权。这使得在父组件和子组件之间创建紧密耦合的实现细节变得很容易,当然也因此,应该只在绝对需要时才使用组件引用。大多数情况下,你应该首先使用标准的 props 和 emit 接口来实现父子组件交互。

有一个例外的情况,使用了 <script setup> 的组件是默认私有的:一个父组件无法访问到一个使用了 <script setup> 的子组件中的任何东西,除非子组件在其中通过 defineExpose 宏显式暴露

组件基础

传递 props 与 监听事件 

 App.vue

<script setup>
import { ref } from 'vue'
import BlogPost from './BlogPost.vue'
  
const posts = ref([
  { id: 1, title: 'My journey with Vue' },
  { id: 2, title: 'Blogging with Vue' },
  { id: 3, title: 'Why Vue is so fun' }
])

const postFontSize = ref(1)
</script>

<template>
	<div :style="{ fontSize: postFontSize + 'em' }">
    <BlogPost
      v-for="post in posts"
      :key="post.id"
      :title="post.title"
      @enlarge-text="postFontSize += 0.1"
    ></BlogPost>
  </div>
</template>

 BlogPost.vue

<script setup>
defineProps(['title'])
defineEmits(['enlarge-text'])
</script>

<template>
  <div class="blog-post">
    <h4>{{ title }}</h4>
    <button @click="$emit('enlarge-text')">Enlarge text</button>
  </div>
</template>

 defineProps 和defineEmits 仅可用于 <script setup> 之中,并且不需要导入, 

 defineProps 会返回一个对象(props.title),其中包含了可以传递给组件的所有 props:

const props = defineProps(['title'])
console.log(props.title)

通过插槽来分配内容

一些情况下我们会希望能和 HTML 元素一样向组件中传递内容:

App.vue

<script setup>
import AlertBox from './AlertBox.vue'
</script>

<template>
	<AlertBox>
  	Something bad happened.
	</AlertBox>
</template>

这可以通过 Vue 的自定义 <slot> 元素来实现: 

 AlertBox.vue

<template>
  <div class="alert-box">
    <strong>Error!</strong>
    <br/>
    <slot />
  </div>
</template>

<style scoped>
.alert-box {
  color: #666;
  border: 1px solid red;
  border-radius: 4px;
  padding: 20px;
  background-color: #f8f8f8;
}
  
strong {
	color: red;    
}
</style>

动态组件

有些场景会需要在两个组件间来回切换,比如 Tab 界面:

在演练场中查看示例

上面的例子是通过 Vue 的 <component> 元素和特殊的 is attribute 实现的:

<!-- currentTab 改变时组件也改变 -->
<component :is="tabs[currentTab]"></component>

在上面的例子中,被传给 :is 的值可以是以下几种:

  • 被注册的组件名
  • 导入的组件对象

你也可以使用 is attribute 来创建一般的 HTML 元素。

当使用 <component :is="..."> 来在多个组件间作切换时,被切换掉的组件会被卸载。我们可以通过 <KeepAlive> 组件强制被切换掉的组件仍然保持“存活”的状态。

组件注册 

全局注册

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

import { createApp } from 'vue'
import MyComponent from './App.vue'

const app = createApp({})

app.component('MyComponent', MyComponent)

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

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

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

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

所有的子组件也可以使用全局注册的组件,这意味着这三个组件也都可以在彼此内部使用。

局部注册

全局注册虽然很方便,但有以下几个问题:

  1. 全局注册,但并没有被使用的组件无法在生产打包时被自动移除 (也叫“tree-shaking”)。如果你全局注册了一个组件,即使它并没有被实际使用,它仍然会出现在打包后的 JS 文件中。

  2. 全局注册在大型项目中使项目的依赖关系变得不那么明确。在父组件中使用子组件时,不太容易定位子组件的实现。和使用过多的全局变量一样,这可能会影响应用长期的可维护性。

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

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

<script setup>
import ComponentA from './ComponentA.vue'
</script>

<template>
  <ComponentA />
</template>

请注意:局部注册的组件在后代组件中并可用。在这个例子中,ComponentA 注册后仅在当前组件可用,而在任何的子组件或更深层的子组件中都不可用。

组件名格式

使用 PascalCase 作为组件名的注册格式

为了方便,Vue 支持将模板中使用 kebab-case 的标签解析为使用 PascalCase 注册的组件。这意味着一个以 MyComponent 为名注册的组件,在模板中可以通过 <MyComponent> 或 <my-component> 引用。这让我们能够使用同样的 JavaScript 组件注册代码来配合不同来源的模板。

组件 v-model

基本用法

v-model 可以在组件上使用以实现双向绑定,使用 defineModel() 宏。

多个 v-model 绑定

//父组件
<UserName
  v-model:first-name="first"
  v-model:last-name="last"
/>
//子组件
<script setup>
const firstName = defineModel('firstName')
const lastName = defineModel('lastName')
</script>

<template>
  <input type="text" v-model="firstName" />
  <input type="text" v-model="lastName" />
</template>

透传 Attributes

“透传 attribute”指的是传递给一个组件,却没有被该组件声明为 props 或 emits 的 attribute 或者 v-on 事件监听器。最常见的例子就是 classstyle 和 id

插槽 Slots

为子组件传递一些模板片段

依赖注入

父组件向其所有的后代组件,传递数据

异步组件

从服务器加载相关组件 ,emits 方法来实现此功能

 

状态管理

<script setup>
import { ref } from 'vue'

// 状态
const count = ref(0)

// 动作
function increment() {
  count.value++
}
</script>

<!-- 视图 -->
<template>{{ count }}</template>

它是一个独立的单元,由以下几个部分组成:

  • 状态:驱动整个应用的数据源;
  • 视图:对状态的一种声明式映射;
  • 交互:状态根据用户在视图中的输入而作出相应变更的可能方式。

下面是“单向数据流”这一概念的简单图示:

state flow diagram

用响应式 API 做简单状态管理

状态在多个组件实例间共享,你可以使用 reactive() (或者ref(), computed()组合式函数来返回一个全局状态)来创建一个响应式对象,并将它导入到多个组件中

在演练场中尝试一下

 

 

  • 10
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值