Vue基础二

模板引用

  1. ref 是一个特殊的 attribute,用来绑定指定的DOM元素
  2. ref 在DOM的值,和js中接收组件的变量名需要一致
  3. ref 需要在组件挂载之后,才可以访问
<input ref="input">
<script setup>
import { ref, onMounted } from 'vue'

// 声明一个 ref 来存放该元素的引用
// 必须和模板里的 ref 同名
const input = ref(null)

onMounted(() => {
 input.value.focus()
})
</script>

<template>
 <input ref="input" />
</template>
处理未挂载之前,对应的元素是null
watchEffect(() => {
 if (input.value) {
   input.value.focus()
 } else {
   // 此时还未挂载,或此元素已经被卸载(例如通过 v-if 控制)
 }
})

  1. v-for 配合 ref使用,对应ref接收的DOM,是v-for循环渲染的DOM元素
<script setup>
import { ref, onMounted } from 'vue'

const list = ref([
  /* ... */
])

const itemRefs = ref([])
// itemRefs是渲染后的li DOM元素是数组对象
onMounted(() => console.log(itemRefs.value))
</script>

<template>
  <ul>
    <li v-for="item in list" ref="itemRefs">
      {{ item }}
    </li>
  </ul>
</template>
  1. ref 中除了可以放置一个js的响应式变量名之外,对应的值还可以是函数,这里的函数可以是内联,或者组件方法
<input :ref="(el) => { /* 将 el 赋值给一个数据属性或 ref 变量 */ }">

  1. 组件上的ref,子组件上也可以使用,ref中获取的是对应组件的实例
  • 如果一个子组件使用的是选项式 API 或没有使用 <script setup>,被引用的组件实例和该子组件的 this 完全一致,这意味着父组件对子组件的每一个属性和方法都有完全的访问权
  • 这种方式的子组件<script setup>,中的数据默认是私有的,必须通过defineExpose函数来暴露,此时父组件中得到的是子组件在defineExpose中暴露的对象,如下面中是{ a: number, b: number } ,而不是子组件实例
<script setup>
import { ref } from 'vue'

const a = 1
const b = ref(2)

// 像 defineExpose 这样的编译器宏不需要导入
defineExpose({
  a,
  b
})
</script>

组件基础-SFC组件模式

  1. 在父组件导入子组件后可以直接使用子组件,每次使用子组件时,都会新建一个子组件实例,每个子组件的数据是隔离的
  2. defineProps,用来父组件向子组件传值,对应的方法是在子组件内部定义,父组件仅仅需要传值,defineExpose函数是子组件暴露值给父组件
<!-- BlogPost.vue -->
<script setup>
defineProps(['title'])
</script>

<template>
  <h4>{{ title }}</h4>
</template>

// 父组件
<BlogPost title="My journey with Vue" />
<BlogPost title="Blogging with Vue" />
<BlogPost title="Why Vue is so fun" />
<BlogPost
  v-for="post in posts"
  :key="post.id"
  :title="post.title"
 />


  1. defineEmits()函数用来,子组件向父组件暴露方法,让父组件在引用子组件的时候可以感知到子组件的响应事件,从而触发父组件的事件调用
 子组件
<!-- 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>


父组件
const posts = ref([
  /* ... */
])

const postFontSize = ref(1)
<div >
  <BlogPost
    v-for="post in posts"
    :key="post.id"
    :title="post.title"
     @enlarge-text="postFontSize += 0.1"
   />
</div>



  1. 插槽<slot />实现父组件向子组件的指定位置传递HTML
// 父组件
<AlertBox>
  Something bad happened.
</AlertBox>

// 子组件
<template>
  <div class="alert-box">
    <strong>This is an Error for Demo Purposes</strong>
    <slot />
  </div>
</template>

<style scoped>
.alert-box {
  /* ... */
}
</style>

效果:
在这里插入图片描述
6. 动态组件<component> 和 :is结合实现

<script setup>
import Home from './Home.vue'
import Posts from './Posts.vue'
import Archive from './Archive.vue'
import { ref } from 'vue'
 
const currentTab = ref('Home')

const tabs = {
  Home,
  Posts,
  Archive
}
</script>

<template>
  <div class="demo">
    <button
       v-for="(_, tab) in tabs"
       :key="tab"
       :class="['tab-button', { active: currentTab === tab }]"
       @click="currentTab = tab"
     >
      {{ tab }}
    </button>
	  <component :is="tabs[currentTab]" class="tab"></component>
  </div>
</template>

组件注册的方式

  1. 全局注册,可以在任何地方使用,缺点:未使用上的组件依然会被打包
import { createApp } from 'vue'

const app = createApp({})

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

  1. 局部注册,只能在当前引入的组件中使用
<script setup>
import ComponentA from './ComponentA.vue'
</script>

<template>
  <ComponentA />
</template>

props

  1. 在子组件中定义需要从父组件中接收的变量
  2. 可以采用数组方式或者对象方式
<script setup>
// 数组方式  数组的元素是待接收的变量名
const props = defineProps(['foo'])

console.log(props.foo)
</script>
// 对象方式
<script setup lang="ts">
defineProps<{
  title?: string
  likes?: number
}>()
</script>

  1. 父组件传递静态数据和动态数据,动态数据需要使用v-bind
<BlogPost title="My journey with Vue" />
<!-- 根据一个变量的值动态传入 -->
<BlogPost :title="post.title" />

<!-- 根据一个更复杂表达式的值动态传入 -->
<BlogPost :title="post.title + ' by ' + post.author.name" />

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

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


  1. 将一个对象的属性直接解构赋值到对应的props上
<BlogPost v-bind="post" />
// 等价于
<BlogPost :id="post.id" :title="post.title" />

  1. props是一个单向数据流,对应的子组件不能修改传递进来的变量,如果需要修改,可以根据传递进来的变量,进行备份
  2. 单向传递中,由于是引用赋值,可能会导致修改传递进来的对象的值,此时应该子组件应该抛出一个事件,让父组件修改
const props = defineProps(['foo'])

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

const props = defineProps(['initialCounter'])

// 计数器只是将 props.initialCounter 作为初始值
// 像下面这样做就使 prop 和后续更新无关了
const counter = ref(props.initialCounter)


const props = defineProps(['size'])

// 该 prop 变更时计算属性也会自动更新
const normalizedSize = computed(() => props.size.trim().toLowerCase())


  1. prop 校验
defineProps({
  // 基础类型检查
  // (给出 `null` 和 `undefined` 值则会跳过任何类型检查)
  propA: Number,
  // 多种可能的类型
  propB: [String, Number],
  // 必传,且为 String 类型
  propC: {
    type: String,
    required: true
  },
  // Number 类型的默认值
  propD: {
    type: Number,
    default: 100
  },
  // 对象类型的默认值
  propE: {
    type: Object,
    // 对象或数组的默认值
    // 必须从一个工厂函数返回。
    // 该函数接收组件所接收到的原始 prop 作为参数。
    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'
    }
  }
})

  1. boolean的特殊处理
<!-- 等同于传入 :disabled="true" -->
<MyComponent disabled />

<!-- 等同于传入 :disabled="false" -->
<MyComponent />

事件

  1. defineEmit() 函数用来自定义一些事件名称,在template中定义触发事件的时机
  2. 在子组件中定义对应的事件名称,在父组件监听对应的事件,父组件中推荐使用 kebab-case
  3. 自定义的DOM事件不支持冒泡事件
<!-- MyComponent -->
<button @click="$emit('someEvent')">click me</button>
// 父组件
<MyComponent @some-event.once="callback" />

  1. 自定义的事件,可以添加特定的参数值
<button @click="$emit('increaseBy', 1)">
  Increase by 1
</button>

<MyButton @increase-by="(n) => count += n" />

<MyButton @increase-by="increaseCount" />
function increaseCount(n) {
  count.value += n
}

  1. e m i t 方法只能在 t e m p l a t e 中调用,如果需要在 j s 中调用, d e f i n e E m i t 方法返回的就是 emit方法只能在template中调用,如果需要在js中调用,defineEmit方法返回的就是 emit方法只能在template中调用,如果需要在js中调用,defineEmit方法返回的就是emit对象
<script setup>
defineEmits(['inFocus', 'submit'])
</script>

<script setup>
const emit = defineEmits(['inFocus', 'submit'])

function buttonClick() {
  emit('submit')
}
</script>

  1. 对触发事件的参数进行校验
<script setup lang="ts">
const emit = defineEmits<{
  (e: 'change', id: number): void
  (e: 'update', value: string): void
}>()
</script>

  1. 原生的dom事件,自定义的dom事件同名之后,会当做自定义的dom来处理
  2. defineEimt函数也可以采用对象的方式,可以对每个方法添加对应的校验
<script setup>
const emit = defineEmits({
  // 没有校验
  click: null,

  // 校验 submit 事件
  submit: ({ email, password }) => {
    if (email && password) {
      return true
    } else {
      console.warn('Invalid submit event payload!')
      return false
    }
  }
})

function submitForm(email, password) {
  emit('submit', { email, password })
}
</script>

组件上使用 v-model

  1. 用来进行数据的双向绑定,子组件上的输入数据改变,会触发对应的响应事件,执行对应的响应事件
  2. 子组件中需要将绑定的数据和事件在define中定义一下
<!-- CustomInput.vue -->
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>

<template>
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>

父组件
<CustomInput
  :modelValue="searchText"
  @update:modelValue="newValue => searchText = newValue"
/>
父组件的代码等级于使用 v-model
<CustomInput v-model="searchText" />
  1. 可以使用computed属性来,直接监听对应的数据改变,调用对应的方法
  • 注意:之前是在父组件上使用的v-model , 现在是在子组件上结合computed和v-model实现自动调用函数
<!-- CustomInput.vue -->
<script setup>
import { computed } from 'vue'

const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])

const value = computed({
  get() {
    return props.modelValue
  },
  set(value) {
    emit('update:modelValue', value)
  }
})
</script>

<template>
  <input v-model="value" />
</template>

  1. v-model 在组件上使用的时候,默认使用的是modelValue作为prop,可以指定对应双向绑定的prop
<MyComponent v-model:title="bookTitle" />

<!-- MyComponent.vue -->
<script setup>
defineProps(['title'])
defineEmits(['update:title'])
</script>

<template>
  <input
    type="text"
    :value="title"
    @input="$emit('update:title', $event.target.value)"
  />
</template>

  1. v-model 上使用自定义的事件修饰符,原理modelModifiers对象中放置了修饰符属性,添加了修饰符为true,没有修饰符为false,从而执行对应的方法
<MyComponent v-model.capitalize="myText" />

<script setup>
const props = defineProps({
  modelValue: String,
  modelModifiers: { default: () => ({}) } // 修饰符对象, 内部结构是 修饰符:boolean
})

const emit = defineEmits(['update:modelValue'])

// 事件处理函数
function emitValue(e) {
  let value = e.target.value
  // 修饰符处理
  if (props.modelModifiers.capitalize) {
    value = value.charAt(0).toUpperCase() + value.slice(1)
  }
  emit('update:modelValue', value)
}
</script>

<template>
  <input type="text" :value="modelValue" @input="emitValue" />
</template>

  • 注意:v-model:带参的情况
<MyComponent v-model:title.capitalize="myText">

const props = defineProps(['title', 'titleModifiers'])
defineEmits(['update:title'])

console.log(props.titleModifiers) // { capitalize: true }

透传 Attributes

  1. 父组件在引用子组件的时候,传递了一些属性,没有在子组件中声明接收的prop或者emit,直接传递到子组件上
  2. 常见的如 class style id v-on绑定的事件等
<!-- <MyButton> 的模板 -->
<button class="btn">click me</button>
父组件
<MyButton class="large" />

<button class="btn large">click me</button>

<MyButton @click="onClick" />
当子组件的被点击的时候,父组件会检测到,触发对应的响应函数
  1. 透传的属性 支持 深层组件继承,
<!-- <MyButton/> 的模板,只是渲染另一个组件 -->
<BaseButton />
此时 <MyButton> 接收的透传 attribute 会直接继续传给 <BaseButton>。
注意:
透传的 attribute 不会包含 <MyButton> 上声明过的 props 或是针对 emits 声明事件的 v-on 侦听函数,换句话说,声明过的 props 和侦听函数被 <MyButton>“消费”了。

透传的 attribute 若符合声明,也可以作为 props 传入 <BaseButton>
  1. 父组件传递的数据,都用对象数组的形式封装到了$attrs上,数组包含了,除了声明过的prop和emit之外的传递进来的数据
<span>Fallthrough attribute: {{ $attrs }}</span>


  • 和 props 有所不同,透传 attributes 在 JavaScript 中保留了它们原始的大小写,所以像 foo-bar 这样的一个 attribute 需要通过 $attrs[‘foo-bar’] 来访问。

  • 像 @click 这样的一个 v-on 事件监听器将在此对象下被暴露为一个函数 $attrs.onClick。

  1. 禁用 Attributes 继承,可以直接使用$attrs对象,将对应的属性作用到指定的对象上
<script>
// 使用普通的 <script> 来声明选项
export default {
  inheritAttrs: false
}
</script>

<script setup>
// ...setup 部分逻辑
</script>
<div class="btn-wrapper">
  <button class="btn" v-bind="$attrs">click me</button>
</div>


  1. 多根节点的模板,透传的属性不会自动绑定到某个元素上,必须要通过手动使用v-bind和$attrs进行手动绑定,否则会有警示
<CustomLayout id="custom-layout" @click="changeValue" />
<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>

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

const attrs = useAttrs()
</script>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值