Vue3深入组件、TS与组合式API

105 阅读4分钟

哈喽,大家好,这次给大家带来的是属于Vue3的TS版,组件传参、父子传参、子父传参、兄弟之间传参、插槽、内置组件与TS组合式API写法以及实践应用。

计算属性语法

import { reactive, computed } from 'vue'

const author = reactive({
  name: 'John Doe',
  books: [
    'Vue 2 - Advanced Guide',
    'Vue 3 - Basic Guide',
    'Vue 4 - The Mystery'
  ]
})

// 一个计算属性 ref
const publishedBooksMessage = computed(() => {
  return author.books.length > 0 ? 'Yes' : 'No'
})
</script>

<template>
  <p>Has published books:</p>
  <span>{{ publishedBooksMessage }}</span>
</template>

Class 与 Style 绑定

<div :class="{ active: isActive }"></div>
<scrpit>
const isActive = ref(true)
const hasError = ref(false)
</script>

深入组件

生命周期钩子

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

onMounted(() => {
  console.log(`the component is now mounted.`)
})
</script>

侦听器watch

import { ref, watch } from 'vue'

const question = ref(1)
const answer = ref('Questions usually contain a question mark. ;-)')
const loading = ref(false)

// 可以直接侦听一个 ref
watch(question, async (newQuestion, oldQuestion) => {
  if (newQuestion.includes('?')) {
    loading.value = true
    answer.value = 'Thinking...'
    try {
      const res = await fetch('https://yesno.wtf/api')
      answer.value = (await res.json()).answer
    } catch (error) {
      answer.value = 'Error! Could not reach the API. ' + error
    } finally {
      loading.value = false
    }
  }
  console.log(typeof newQuestion,oldQuestion);
  { immediate: true }//第一次执行 
  {deep:true}//深度监听 对性能消耗很大
})
</script>

<template>
  <p>
     question:
    <input v-model="question" :disabled="loading" />
  </p>
  <p>{{ answer }}</p>
</template>

watchEffect()

不需要指定 immediate: true 初始化自动调用 每当值变化 自动更新 深度监听比watch更好 自定义函数 吊销的意思IsRevoked

Props 写法

泛型定义传参的 参数

const pop = defineProps<{
  foo:string,
  bar?:number
}>()

props接口定义 接收父组件参数

// 接口定义
interface pops{
  foo:string,
  bar?:number
}
const pops = defineProps<pops>()

withDefaults设置默认值,避免在使用组件时的繁琐检查,提高组件的可读性和可维护性。下面是WithDefaults的基本用法:

import { withDefaults } from 'vue'
const DEFAULTS = {
  someDefaultValue: 'default',
  otherDefaultValue: 42,
}
export default withDefaults({
  props: {
    someProp: String,
    otherProp: Number,
  },
}, DEFAULTS)

组件事件$defineEmits

子组件 定义defineEmits(【‘事件名’】)

import { defineProps,ref,defineEmits } from 'vue'

const value =  ref<string>("子组件给父组件传参")
const emit = defineEmits(["getvalue"])
const transValue = ()=>{
  emit('getvalue',value.value)
}


// 接口定义
interface pops{
  foo:string,
  bar?:number
}
const pops = defineProps<pops>()
console.log(pops,"当前数据");


</script>

<template>
    <button >
    {{foo}}
  </button>

  <button @click="transValue" style="margin: 5px">传值给父组件</button>
</template>

父组件

import HelloWorld from './components/HelloWorld.vue'
import {ref} from 'vue'
const foo = ref<string>('你好')
const bar = ref<number>(123)

const sonMessage = ref<string>("")
const getValuehello = (vlaue:string)=>{
  sonMessage.value = vlaue
}
</script>

<template>
  <HelloWorld :foo="foo" :bar="bar"  @getvalue="getValuehello"/>
  <div>
    我是父组件: {{sonMessage}}
  </div>
</template>

子组件暴露属性给父组件 defineExpose

子组件可以使用defineExpose暴露自身的属性或者方法,父组件中使用ref调用子组件暴露的属性或方 法。 如下为子组件Son.vue

  <div style="margin: 10px;border: 2px solid red">
    我是子组件

  </div>
</template>

<script setup lang="ts">
import {ref, defineExpose} from "vue";

// 暴露给父组件的值
const toFatherValue = ref<string>("我是要暴露给父组件的值")

// 暴露给父组件的方法
const toFatherMethod = () => {
  console.log("我是要暴露给父组件的方法")
}
// 暴露方法和属性给父组件
defineExpose({toFatherMethod, toFatherValue})

</script>

如下为父组件Father.vue

  <div class="fa">
    <div style="margin: 10px;">我是父组件</div>
    <button @click="getSonMethod">获取子组件的方法</button>
    <Son ref="sonMethodRef"></Son>
  </div>
</template>

<script setup lang="ts">
import Son from './Son.vue'
import {ref} from "vue";

const sonMethodRef = ref()

const getSonMethod = () => {
  sonMethodRef.value.toFatherMethod()
  console.log(sonMethodRef.value.toFatherValue)
}

</script>

<style scoped>
.fa{
  border: 3px solid cornflowerblue;
  width: 400px;
  text-align: center;
}
</style>


为 provide / inject 标注类型 (组件与组件之间的值)

根组件

  <div>
    我是root组件
    <Footer></Footer>
  </div>
</template>

<script setup lang="ts">
import { provide, ref } from 'vue'
import Footer from './components/Footer.vue'

const toChildValue= ref<string>("我是给所有子组件的值")

// 将toChildValue注入到所有子组件中
provide(/* 注入名 */ 'toChildValue', /* 值 */ toChildValue)

</script>

第二组件

  <div>
    我是footer组件
    <div>
      接收父组件的值:{{getFatherValue}}
    </div>
    <DeepChild></DeepChild>
  </div>
</template>

<script setup lang="ts">
import DeepChild from "./DeepChild.vue"
import {ref,inject,Ref} from "vue";

// 获取父组件提供的值
// 如果没有祖先组件提供 "toChildValue"
// ref("") 会是 "这是默认值"
const getFatherValue = inject<Ref<string>>(/* 注入名 */"toChildValue",/* 默认值 */ ref(""))

</script>

第三组件

  <div>
    我是deepChild组件
    <div>
      接收爷爷组件的值:{{getGrandFatherValue}}
    </div>
  </div>
</template>

<script setup lang="ts">
import {inject, ref, Ref} from "vue";

// 获取爷爷组件提供的值
// 如果没有爷爷组件提供 "toChildValue"
// value 会是 ""
const getGrandFatherValue = inject<Ref<string>>(/* 注入名 */"toChildValue",/* 默认值 */ ref(""))
</script>

兄弟组件之间 动态赋值 根组件 provide

  <div>
    我是root组件
    <Footer></Footer>
  </div>
</template>

<script setup lang="ts">
import {InjectionKey, provide, Ref, ref} from 'vue'
import Footer from './components/Footer.vue'

const toChildValue= ref<string>("我是给所有子组件的值")
/**
 * 修改父组件值的方法
 */
const changeValue = () => {
  toChildValue.value = "我是父组件修改的值"
}
// 定义一个注入key的类型(建议将注入 key 的类型放在一个单独的文件中,这样它就可以被多个组件导入)
interface ProvideType {
  toChildValue: Ref<string>;
  changeValue: () => void;
}
// 为注入值标记类型
const toValue = Symbol() as InjectionKey<ProvideType>
// 将toChildValue和changeValue注入到所有子组件中
provide(/* 注入名 */ 'toValue', /* 值 */{
  toChildValue,
  changeValue
})
</script>

无数根组件 inject

  <div>
    我是deepChild组件
    <div>
      <button @click="changeValue">改变祖先组件的值</button>
      {{toChildValue}}
    </div>
  </div>
</template>

<script setup lang="ts">
import {inject, Ref} from "vue";

// 定义注入值的类型
interface ProvideType {
  toChildValue: Ref<string>;
  changeValue: () => void;
}
// 解构获取父组件传的值,需要进行强制类型转换
const {toChildValue, changeValue} = inject(/* 注入名 */"toValue") as ProvideType
// 不解构时,只需指定类型即可
// const value = inject<ProvideType>(/* 注入名 */"toValue")
</script>

插槽 Slots

子组件:SlotsComponent.vue

  <div class="red-text">
    <slot>
      子组件插槽
    </slot>
  </div>
</template>
<style scoped>
.red-text {
  color: red;
}
</style>

父组件:index.vue

  <div>
    <label>父组件</label>
    <SlotComponent></SlotComponent>
  </div>
</template>
<script>
import SlotComponent from "./component/SlotsComponent";
export default {
  components: {
    SlotComponent
  }
};
</script>

##具名插槽

就是具有名称的插槽,公司项目中会用到

子组件

设置插槽名字 <slot name="名字">

      <header >
        <slot name="header">
          你好
        </slot>
      </header>
      <main>
        <slot name="main">
          中部
        </slot>
      </main>
      <footer>
        <slot name="footer">
          尾巴
        </slot>
      </footer>
    </div>

父组件 使用 <template v-slot:插槽名字>

      <template v-slot:header>
        尾巴
      </template>
      <template v-slot:footer>尾巴1</template>
    </Admin>

 动态插槽名

我们可以将父组件的内容,动态渲染到到子组件插槽接口中。

父组件 <template v-slot:[dynamicSlotName]>

JS声明:let dynamicSlotName = ref("header")

针对reactive的响应式引用类型 同类型。

  • 作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性。
  • 语法:const name = toRef(person,'name')
  • 应用: 要将响应式对象中的某个属性单独提供给外部使用时。
  • 扩展:toRefstoRef功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)
import { toRef,reactive } from "vue";
const person = reactive({
  name:"你好"
})
const name = toRef(person,'name')
console.log(name.value);
</script>