Summary of Vue3 + setup + ts usage skills

c4f1d94a3da8801f422d615815df61af.jpeg

1. Component introduction

When used  setup , the component can be imported directly, no need to manually register

<template>
  <Child />
</template>

<script setup lang="ts">
import Child from "./Child.vue";
</script>

2. ref and reactive

ref It is generally used for basic data types, such as  string, boolean , and reactive the place where it is generally used for object ref is actually the  reactive implementation of the call.

<template>
  <h1>{
    
    { title }}</h1>
  <div>
    {
    
    { data }}
  </div>
</template>

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

const title = ref("title");

const data = reactive({
  userName: "xiaoming",
  age: 18,
});
</script>

3. defineEmits and defineProps get the values ​​and events passed by the parent component

// 第一种不带默认值props
const props = defineProps<{
  foo: string
  bar?: number
}>()
// 第二种带默认值props

export interface ChildProps {
  foo: string
  bar?: number
}

const props = withDefaults(defineProps<ChildProps>(), {
   foo: "1qsd"
  bar?: 3
})

// 第一种获取事件
const emit = defineEmits<{
  (e: 'change', id: number): void
  (e: 'update', value: string): void
}>()

// 第二种获取事件

const emit = defineEmits(["dosth"])

4. Use useAttrs and useSlots

useAttrsid You can get the value  passed from the parent component  class . useSlots The content of the slot can be obtained. In the example, we use to  useAttrs get the content passed by the parent component  id , classand useSlots get the content of the slot.

parent component:

<template>
  <div class="father">{
    
    { fatherRef }}</div>
  <Child :fatherRef="fatherRef" @changeVal="changeVal" class="btn" id="111">
    <template #test1>
      <div>1223</div>
    </template>
  </Child>
</template>

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

import Child from "./Child.vue";

const fatherRef = ref("1");

function changeVal(val: string) {
  fatherRef.value = val;
}
</script>

<style lang="scss" scoped>
.father {
  margin-top: 40px;
  margin-bottom: 40px;
}
.btn {
  font-size: 20px;
  color: red;
}
</style>

Subassembly:

<template>
  <!-- <div class="child">{
    
    { props.fatherRef }}</div> -->
  <div v-bind="attrs">
    <slot name="test1">11</slot>
    <input type="text" v-model="inputVal" />
  </div>
</template>

<script setup lang="ts">
import { computed, useAttrs, useSlots } from "vue";

const props = defineProps<{
  fatherRef: string;
}>();

const emits = defineEmits(["changeVal"]);

const slots = useSlots();

const attrs = useAttrs();

console.log(122, attrs, slots);

const inputVal = computed({
  get() {
    return props.fatherRef;
  },

  set(val: string) {
    emits("changeVal", val);
  },
});
</script>

Using custom directives

setup When customizing instructions inside, you only need to follow this  vNameOfDirective  naming convention

For example, the following custom  focus instructions are named  vMyFocusand used v-my-focus

custom directive

<script setup lang="ts">
const vMyFocus = {
  onMounted: (el: HTMLInputElement) => {
    el.focus();
    // 在元素上做些操作
  },
};
</script>
<template>
  <input v-my-focus value="111" />
</template>

5. Use the defineExpose child component to pass the parent component

Subassembly

<template>
  <div class="child"></div>
</template>

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

function doSth() {
  console.log(333);
}

defineExpose({ doSth });
</script>

parent component

<template>
  <div class="father" @click="doSth1">222</div>
  <Child ref="childRef"></Child>
</template>

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

import Child from "./Child.vue";

const childRef = ref();

function doSth1() {
  childRef.value.doSth();
}
</script>

6. Parent component to child component

parent component

<template>
  <div class="father"></div>
  <Child @doSth="doSth"></Child>
</template>

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

import Child from "./Child.vue";

function doSth() {
  console.log(112);
}
</script>

Subassembly

<template>
  <div class="child">2222</div>
</template>

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

const emits = defineEmits(["doSth"]);

onMounted(() => {
  emits("doSth");
});
</script>

7. toRefs

When passing from a parent component to a child component  props , it must be used  toRefs or  toRef transferred. Why is this?

This is because if you don't use  toRefs one turn, when the parent component  props changes, if the child component uses Es6 parsing, it will lose responsiveness.

You can see the following example

parent component

<template>
  <div class="father" @click="changeVal">{
    
    { fatherRef }}</div>
  <Child :fatherRef="fatherRef"></Child>
</template>

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

import Child from "./Child.vue";

const fatherRef = ref(1);

function changeVal() {
  fatherRef.value = 2;
}
</script>

<style lang="scss" scoped>
.father {
  margin-bottom: 40px;
}
</style>

Subassembly

<template>
  <div class="child" @click="changeVal">{
    
    { fatherRef }}</div>
</template>

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

const props = defineProps<{
  fatherRef: any;
}>();

const { fatherRef } = props;

function changeVal() {
  fatherRef.value = 34;
}
</script>

It can be seen that when the parent component is clicked,  const { fatherRef } = props;it loses responsiveness because it is parsed using

So when the parent component becomes 2, the child component is still 1.

There are two solutions here

  1. use  const { fatherRef } = toRefs(props);

  2. use in the template props.fatherRef

8. Subcomponents use v-model

8.1 You can use computed in subcomponents to achieve two-way binding

parent component

<template>
  <div class="father">{
    
    { fatherRef }}</div>
  <Child :fatherRef="fatherRef" @changeVal="changeVal"></Child>
</template>

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

import Child from "./Child.vue";

const fatherRef = ref("1");

function changeVal(val: string) {
  fatherRef.value = val;
}
</script>

<style lang="scss" scoped>
.father {
  margin-top: 40px;

  margin-bottom: 40px;
}
</style>

Subassembly

<template>
  <!-- <div class="child">{
    
    { props.fatherRef }}</div> -->
  <input type="text" v-model="inputVal" />
</template>

<script setup lang="ts">
import { computed } from "vue";

const props = defineProps<{
  fatherRef: string;
}>();

const emits = defineEmits(["changeVal"]);

const inputVal = computed({
  get() {
    return props.fatherRef;
  },

  set(val: string) {
    emits("changeVal", val);
  },
});
</script>

8.2 You can pass values ​​and change values ​​from parent components, and then child components can also use v-model

In the example, the parent component passes  modelValue and  update:modelValue methods the parent component:

<template>
  <Child :modelValue="searchText" @update:modelValue="changeVal"> </Child>
</template>

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

import Child from "./Child.vue";

const searchText = ref(1);

function changeVal(val: number) {
  searchText.value = val;
}
</script>

<style lang="scss" scoped>
.father {
  margin-top: 40px;

  margin-bottom: 40px;
}

.btn {
  font-size: 20px;

  color: red;
}
</style>

Subassembly:

<template>
  <!-- <div class="child">{
    
    { props.fatherRef }}</div> -->
  <!-- <div v-bind="attrs">
        <slot name="test1">11</slot>
        <input type="text" v-model="inputVal" />
    </div> -->
  <input v-model="modelValue" />
</template>

<script setup lang="ts">
import { computed, useAttrs, useSlots } from "vue";

const props = defineProps<{
  modelValue: number;
}>();

// const emits = defineEmits(["changeVal"]);
</script>

9. Recursive components

The component itself can call the component itself, that is, recursion. In vue3, the file name is automatically registered as the name of the component. For example, a component named   can   refer to itself Child.vue  in its template   . <Child/>What needs to be noted here is that a conditional statement needs to be set to interrupt the recursion, otherwise the recursion will recurse infinitely.

parent component

<template>
  <Child :modelValue="searchText" @update:modelValue="changeVal"> </Child>
</template>

<script setup lang="ts">
import { ref } from "vue";
import Child from "./Child.vue";
const searchText = ref(1);
function changeVal(val: number) {
  searchText.value = val;
}
</script>

<style lang="scss" scoped>
.father {
  margin-top: 40px;
  margin-bottom: 40px;
}
.btn {
  font-size: 20px;
  color: red;
}
</style>

Subassembly

<template>
  <input v-model="modelValue" />
  <Child
    :modelValue="test"
    @update:modelValue="changeTest"
    v-if="modelValue > 2"
  ></Child>
</template>

<script setup lang="ts">
import { computed, useAttrs, useSlots, ref } from "vue";
const props = defineProps<{
  modelValue: number;
}>();
const test = ref(0);
function changeTest(val: number) {
  test.value = val;
}

// const emits = defineEmits(["changeVal"]);
</script>

<style lang="scss" scoped>
.child {
  position: relative;
}
</style>

10. vue3 ts get component ref instance

  • Get the dom reference directly through ref

<template>
    <div class="demo1-container">
        <div ref="sectionRef" class="ref-section"></div>
    </div>
</template>

<script setup lang="ts">
import {ref} from 'vue'
const sectionRef = ref()
</script>

By adding the ref attribute to the div element, in order to obtain this element, we declare a variable sectionRef with the same name as the ref attribute, and then we can obtain the div element in the form of sectionRef.value

  • Get the dom reference through the ref traversal of the parent container

<template>
    <div class="demo2-container">
        <div ref="listRef" class="list-section">
            <div @click="higherAction(index)" class="list-item" v-for="(item, index) in state.list" :key="index">
                <span>{
    
    {item}}</span>
            </div>
        </div>
    </div>
</template>

<script setup lang="ts">
import { ref, reactive } from 'vue'
const listRef = ref()
</script>

By adding the ref attribute to the parent element and declaring a variable listRef with the same name as the ref attribute, the dom object containing the child element will be obtained through listRef.value. At this time, the child element dom can be obtained in the form listRef.value.children[index]of

  • Put the dom reference into the array by: ref

    <template>
      <div class="demo2-container">
          <div class="list-section">
              <div :ref="setRefAction" @click="higherAction(index)" class="list-item" v-for="(item, index) in state.list" :key="index">
                  <span>{
          
          {item}}</span>
              </div>
          </div>
      </div>
    
      </template>
    
      <script setup lang="ts">
      import { reactive } from 'vue'
    
      const state = reactive({
          list: [1, 2, 3, 4, 5, 6, 7],
          refList: [] as Array<any>
      })
    
      const setRefAction = (el: any) => {
          state.refList.push(el);
      }
      </script>

    Call the method through the :ref loop , which will receive an el parameter by default. This parameter is the form of setRefActionthe div element we need to get at this time to get the child element domstate.refList[index]

  • Pass ref through child component emit

<template>
    <div ref="cellRef" @click="cellAction" class="cell-item">
        <span>{
    
    {item}}</span>
    </div>
</template>

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

const props = defineProps({
    item: Number
})
const emit = defineEmits(['cellTap']);
const cellRef = ref();
const cellAction = () => {
    emit('cellTap', cellRef.value);
}
</script>

By adding the ref attribute to the subcomponent and declaring a variable cellRef with the same name as the ref attribute, you can pass cellRef.value as a dom reference through emit

  • The way to obtain it in render components such as tsx is simpler

import { defineComponent, ref, onMounted } from "@vue/runtime-core";
import { ElForm } from "element-plus";

export default defineComponent({
  setup() {
    const $form = ref<InstanceType<typeof ElForm>>(null);

    onMounted(() => {
      $form.value?.validate; // 类型正确
    });

    return () => <ElForm ref={$form}></ElForm>;
  },
});

It should be noted that if the exposed method is used, the corresponding type cannot be obtained, and you need to customize the type github.com/vuejs/rfcs/… [1]

// 组件 MyForm
import { defineComponent, ref, onMounted } from "@vue/runtime-core";
import { ElForm } from "element-plus";

type ELEForm = InstanceType<typeof ElForm>;

// 在外界通过 ref 获取组件实例 请使用这个类型
export interface MyFormExpose {
  validate: ELEForm["validate"];
}

export default defineComponent({
  name: "MyForm",

  setup(props, { expose }) {
    const $form = ref<InstanceType<typeof ElForm>>(null);

    expose({
      validate: (callback) => $form.value?.validate(callback),
    } as MyFormExpose);

    return () => <ElForm ref={$form}></ElForm>;
  },
});
<!-- Home.vue -->
<template>
  <MyForm :ref="$form" />
</template>

<script>
import { defineComponent, ref, onMounted } from '@vue/runtime-core'
import MyForm, { MyFormExpose } from '@/components/MyForm'
export default defineComponent({
  components: { MyForm }

  setup(){
    const $form = ref<InstanceType<typeof MyForm> & MyFormExpose>(null)

    onMounted(() => {
       $form.value?.validate // 类型正确
    })
  }
})
</script>

References

[1]

https://github.com/vuejs/rfcs/pull/210#issuecomment-727067392: https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fvuejs%2Frfcs%2Fpull%2F210%23issuecomment-727067392

Guess you like

Origin blog.csdn.net/Ed7zgeE9X/article/details/131255259