Why write this

The reason: Vue + TSX has been used for a large scale migration in the project. Recently, I looked at some relevant use summary to help you understand what Vue 3 has changed, so that you can use Vue 3 more smooth. Here is a comparison of how Vue 3 + Setup Script is written versus Vue 3 + TSX.

This paper mainly refers to:

Vue official document

Vue JSX plugin plugin

1. View layer writing

There are three recommended ways to write Vue 3:

  1. It still exports a Vue object, with a reactive variable returned by setup
  2. The setup tag does not need to be returned and can be used directly in the template (recommended).
  3. Using JSX notation, you need to go through the defineComponent package layer (vant 3.x has been fully embraced)
  4. The Class component approach (seems to be used sparingly and has not been investigated yet)
<template>
  <div class="home">
    <! -- Vue automatically deconstructs the ref.value when compiling a template
    <div>{{ count }}</div>
    <button @click="addCount">+ 1</button>
  </div>
</template>

<script lang="ts" setup>
// Use the setup script
// Export default is an instance of vue
// The variables created here can be used directly in the vue source code
import { ref } from "vue";

// Methods are defined directly in setup
// Variables are defined directly in setup
const count = ref(0);
const addCount = () = > (count.value = count.value + 1);
</script>
Copy the code
import { ref, defineComponent } from "vue";

export default defineComponent({
  setup() {
    const count = ref(0);
    // JSX is written the same as non-setup tags because it is defineComponent
    const addCount = () = > {
      count.value = count.value + 1;
    };

    return () = > (
      <div>{/* react needs to be destructed by count.value */<span>{count.value}</span>
        <button onClick={addCount}>+ 1</button>
      </div>); }});Copy the code

2. A tip to use the Setup tag notation

Effect: You can dynamically bind CSS variables using V-bind

Implementation: using style and CSS var implementation

<template>
  <div class="home">
    <div class="color">{{ count }}</div>
    <button @click="changeColor">Change the color</button>
  </div>
</template>

<script lang="ts" setup>
import { ref } from "vue";
const color = ref("red");
const changeColor = () = >
  (color.value = color.value === "red" ? "green" : "red");
</script>

<style>
.color {
  color: v-bind(color);
}
</style>
Copy the code

In JSX, we can only change the style dynamically

import { ref, defineComponent, computed } from "vue";

export default defineComponent({
  setup() {
    const count = ref(0);

    const color = ref("red");
    const changeColor = () = >
      (color.value = color.value === "red" ? "green" : "red");

    return () = > (
      <div>
        <div style={{ color: color.value}} >{count.value}</div>
        <button onClick={changeColor}>Change the color</button>
      </div>); }});Copy the code

3. The computed and watch

3.1 the computed

import { ref, defineComponent, computed } from "vue";

export default defineComponent({
  setup() {
    const count = ref(0);
    const addCount = () = > {
      count.value = count.value + 1;
    };

    // Just use computed as the hook function
    const doubleCount = computed(() = > {
      return count.value * 2;
    });

    return () = > (
      <div>
        <div>{count.value}</div>
        <div>{doublecount. value}</div>
        <button onClick={addCount}>+ 1</button>
      </div>); }});Copy the code
<script lang="ts" setup>
import { ref, computed } from "vue";

const count = ref(0);
const color = ref("red");
const addCount = () = > (count.value = count.value + 1);
const changeColor = () = >
  (color.value = color.value === "red" ? "green" : "red");

// Register using computed this function
const doubleCount = computed(() = > {
  return count.value * 2;
});
</script>
Copy the code

3.2 watch and watchEffect

As with computed, you can call the watch and watchEffect methods directly

Q1: What is the difference between watch and watchEffect?

A1: Watch can know the difference before and after the change, but watchEffect is a callback after the change as a side effect

Q2: What are the differences between watch and watchEffect implementations?

In this case, the new and old values will be assigned to the callback of watch. In this case, it mainly determines whether the second parameter is a callback. If watchEffect is called, null will be directly passed in the source code

Since there is no difference between JSX and Setup usage, there is only one code

Usage:

import { ref, defineComponent, computed, watch, watchEffect } from "vue";

export default defineComponent({
  setup() {
    const count = ref(0);

    const color = ref("red");
    const changeColor = () = >
      (color.value = color.value === "red" ? "green" : "red");

    // The proxy object to listen on and the callback to invoke
    // We can only specify color because we want to operate in the corresponding proxy
    // Cannot be bound directly by the execution procedure
    watch(color, (newVal, oldVal) = > {
      console.log(`color is Change -> ${oldVal} -> ${newVal}`);
    });

    // The proxy get method can be used to bind
    // Because there is no parameter (QAQ, my understanding is HHHH)
    watchEffect(() = > {
      console.log("current color ->", color.value);
    });

    return () = > (
      <div>
        <div style={{ color: color.value}} >{count.value}</div>
        <button onClick={changeColor}>Change the color</button>
      </div>); }});Copy the code

4. Component values and component references

4.1 Registering components

It can be used in setup and JSX writing directly as an import, without the need to call components separately to register components

Note: If it is a. Vue file, the component must be lowercase, no hump, and then use – to add lowercase letters instead of uppercase

The JSX notation can be introduced as a hump component

4.2 Parent-child Data interaction

The specific operations are the same as those in VUE 2

Parent -> Child: Currently props

Child -> Parent: currently emit in emit mode

4.2.1 JSX

Note:

  1. The parent component listens in a way fromv-on– > the JSXonXXX
  2. < div class ==props: onXXX== ts < div class ==props: onXXX==
import { defineComponent, toRefs } from "vue";

/ / child component
export default defineComponent({
  // Define props as in Vue 2
  props: {
    parentName: {
      type: String,},parentAge: {
      type: Number,}},setup(props) {
    // If the object is of type proxy, each variable must be changed to ref by toRefs
    // Officials say that there may be a response loss due to deconstruction
    const { parentAge, parentName } = toRefs(props);

    return () = > (
      <div>
        <div>Name: value} {parentName.</div>
        <div>Age: value} {parentAge.</div>
      </div>); }});Copy the code
/ / the parent component
import { defineComponent } from "vue";
import TestSon from "./test-son";

export default defineComponent({
  setup() {
    const title = "Parent Node";

    return () = > (
      <>
        <div>{title}</div>
        <TestSon parentAge={60} parentName="Taro watermelon" />
      </>); }});Copy the code

4.2.2 Vue Setup writing

Note:

  • Props needs to be defined through defineProps in Setup
  • Emit can be defined at defineEmits
  • The parent component listens for events that are the same still throughv-on

You may encounter parameter deconstruction pit!

At present, vUE 3 uses Proxy objects to do in-depth packaging, so the directly deconstructed Proxy may have strange problems (loss of responsiveness). It is suggested to convert Reactive to multiple refs through toRefs, so that the responsiveness can be retained

<! -- Subcomponent -->
<template>
  <div>
    <div>Name: {{parentName}}</div>
    <div>Age: {{parentAge}}</div>
    <button @click="sendMsg">Send a message</button>
  </div>
</template>

<script lang="ts" setup>
import { defineProps, toRefs, defineEmits } from "vue";

// Accept parameters via defineProps
const props = defineProps({
  parentName: {
    type: String,},parentAge: {
    type: Number,}});// Use defineEmits to introduce emit
const emit = defineEmits();

// Use toRefs to deconstruct the Proxy
const { parentAge, parentName } = toRefs(props);

const sendMsg = () = > {
  emit("scream", parentName? .value); };</script>

Copy the code
<! -- Parent component -->
<template>
  <div>
    <div>{{ title }}</div>
    <test-son parentAge="60" parentName="Taro watermelon" @scream="handleScream" />
  </div>
</template>

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

const title = ref("Parent Node");

const handleScream = (e) = > {
  console.log(e);
};
</script>
Copy the code

4.3 Bidirectional Binding Operations

4.3.1 V-Model binding of form components

Both JSX and Setup are written to use v-Models for bidirectional binding of form elements, and this is no different

4.3.2 Customizing V-Model Components

The V-model in VUE 2 and VUE 3 is a break changing

The V-model in VUE 2 is the syntactic sugar for value and update.

In VUE 3, V-model is the syntactic sugar of modelValue and Update :modelValue

4.3.3 JSX writing

Methods used:

  1. == Parent components directly bind corresponding reactive variables via V-model ==
  2. Child component directly throughattrsTo get the corresponding boundmodelValue
  3. == when a child component changes == dataemitThe triggerupdate:modelValueEvents can be
/ / the parent component
import { defineComponent, ref } from "vue";
import TestSon from "./test-son";

export default defineComponent({
  setup() {
    const fatherVal = ref("father value");
    const onParentChange = () = > {
      fatherVal.value = `parent -> The ${Math.random().toString()}`;
    };

    return () = > (
      <>
        <TestSon v-model={fatherVal.value}/>
        <button onClick={onParentChange}>The parent component changes the value</button>
      </>); }});Copy the code
import { defineComponent, toRefs } from "vue";

export default defineComponent({
  props: {},
  setup(props, { emit, attrs }) {
    const onSonChange = () = > {
      emit("update:modelValue".`son -> The ${Math.random().toString()}`);
    };

    return () = > (
      <div style={{ border: "1px solid #f44}} ">
        <div>The value of bidirectional binding: {attrs.modelValue}</div>
        <button onClick={onSonChange}>Child component changes</button>
      </div>); }});Copy the code

4.3.4 Setup

Note:

Value and update in Vue 2 are replaced by modelValue and uPOdate: modelValue. The overall writing method is basically consistent with JSX

<template>
  <div class="father">
    <test-son v-model="fatherVal"/>
    <button @click="changeFathVal">Change the value</button>
  </div>
</template>

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

const fatherVal = ref("father Value");

const changeFathVal = () = > {
  fatherVal.value = `father => The ${Math.random().toString(16)}`;
};
</script>

<style>
.father {
  padding: 20px;
  border: 1px solid #f44;
}
</style>

Copy the code
<template>
  <div class="son">
    <div>Parameters for bidirectional binding: {{modelValue}}</div>
    <button @click="changeSonVal">Change the child component value</button>
  </div>
</template>

<script lang="ts" setup>
import { defineProps, toRefs, defineEmits } from "vue";

const props = defineProps({
  modelValue: {
    type: String,}});const emit = defineEmits();
const changeSonVal = () = > {
  emit("update:modelValue".`son -> The ${Math.random().toString(16)}`);
};
</script>

<style>
.son {
  border: 1px solid # 323233;
}
</style>

Copy the code

4.4 v – usage models

4.4.1 JSX writing

What it does: A component binds multiple bidirectionally bound elements at the same time

Note:

  1. JSX is usedv-modelsThe labels and2 d arrayI think it’s a little anti-human
  2. It is recommended to use attr to avoid undefined parameters in props
// Parent component import {defineComponent, ref} from "vue"; import TestSon from "./test-son"; export default defineComponent({ setup() { const fatherVal = ref("father value"); const model2 = ref("model 2"); const model3 = ref("model3"); const onParentChange = () => { fatherVal.value = `parent -> ${Math.random().toString()}`; model2.value = `parent -> ${Math.random().toString()}`; model3.value = `parent -> ${Math.random().toString()}`; }; return () => ( <> <TestSon v-models={[ [fatherVal.value, "modelValue"], [model2.value, "model2"], [model3.value, <button onClick={onParentChange}> </button> </>); }});Copy the code
/ / child component
import { defineComponent, toRefs, nextTick } from "vue";

export default defineComponent({
  props: {},
  setup(props, { emit, attrs }) {
    const onSonChange = (event: string) = > () = > {
      emit(`update:${event}`.`son -> The ${Math.random().toString()}`);
    };

    return () = > (
      <div style={{ border: "1px solid #f44}} ">
        <div>ModelValue: {attrs.modelValue}</div>
        <div>The value of bidirectional binding model2: {attrs.model2}</div>
        <div>The value of bidirectional binding model3: {attrs.model3}</div>
        <button onClick={onSonChange("modelValue")} >The child component modelValue changes</button>
        <button onClick={onSonChange("model2")} >The child component Model2 changes</button>
        <button onClick={onSonChange("model3")} >The child component Model3 changes</button>
      </div>); }});Copy the code

4.4.2 setup writing

Pay attention to the point

  1. usev-model:xxxTo pass in multiple bidirectional bound variables
  2. Note that the callback function may need to be executed immediately if it is a decorator function

writing

<template>
  <div class="father">
    <test-son
      v-model:modelValue="fatherVal"
      v-model:model2="model2"
      v-model:model3="model3"
    />
    <button @click="changeFathVal">Change the value</button>
  </div>
</template>

<script setup lang="ts">
/ / the parent component
import { ref } from "vue";
import TestSon from "./TestSon.vue";

const fatherVal = ref("father Value");
const model2 = ref("model2");
const model3 = ref("model3");
    
const changeFathVal = () = > {
  fatherVal.value = `father => The ${Math.random().toString(16)}`;
  model2.value = `father => The ${Math.random().toString(16)}`;
  model3.value = `father => The ${Math.random().toString(16)}`;
};
</script>

<style>
.father {
  padding: 20px;
  border: 1px solid #f44;
}
</style>

Copy the code
<template>
  <div class="son">
    <div>Bidirectional binding modelValue: {{modelValue}}</div>
    <div>Bidirectional binding model2: {{model2}}</div>
    <div>Bidirectional binding model3: {{model3}}</div>
    <button @click="onSonChange('modelValue')()">Change modelValue value</button>
    <button @click="onSonChange('model2')()">Change the model2 value</button>
    <button @click="onSonChange('model3')()">Change the model3 value</button>
  </div>
</template>

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

const emit = defineEmits();
const attrs = useAttrs();
const { modelValue, model2, model3 } = toRefs(attrs);

const onSonChange = (event: string) = > () = > {
  emit(`update:${event}`.`son -> The ${Math.random().toString()}`);
};

const onModelValueChange = onSonChange("modelValue");
</script>

<style>
.son {
  border: 1px solid # 323233;
}
</style>

Copy the code

5 Subcomponents REF and defineExpose

role

  1. Methods and data used to expose child components

Different points

  1. Vue 2: ref exposes the entire component’s data and methods to the parent component

  2. Vue 3 script-setup: You need to expose by defineExpose or you’ll get an empty Proxy

  3. JSX: the corresponding vNode of the virtual DOM will be obtained, and the methods exposed in methods will be directly attached to the top layer of the exposed ref

5.1 JSX writing method

/ / the parent component
import { defineComponent, ref } from "vue";
import TestSon from "./test-son";

export default defineComponent({
  setup() {
    const sonComponent = ref<any>(null);

    const onParentCall = () = > {
      sonComponent.value.plzParentCall();
    };

    return () = > (
      <>
        <TestSon ref={sonComponent}/>
        <button onClick={onParentCall}>Parent component call</button>
      </>); }});Copy the code
/ / child component
import { defineComponent, toRefs, defineExpose } from "vue";

export default defineComponent({
  methods: {
    plzParentCall() {
      console.log("This is the parent component calling me."); }},setup(props) {

    return () = > (
      <div style={{ border: "1px solid #f44}} ">Hello World</div>); }});Copy the code

5.2 VUE Setup Label

<! -- Parent component -->
<template>
  <div class="father">
    <test-son ref="sonRef" />
    <button @click="onParentCall">Parent component call</button>
  </div>
</template>

<script setup lang="ts">
import { ref, watchEffect } from "vue";
import TestSon from "./TestSon.vue";
    
const sonRef = ref(null);
const onParentCall = () = > {
  sonRef.value.plzParentCall();
};
</script>

<style>
.father {
  padding: 20px;
  border: 1px solid #f44;
}
</style>

Copy the code
<! -- Subcomponent -->
<template>
  <div class="son">
      Hello World
  </div>
</template>

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

const plzParentCall = () = > {
  console.log("This is the parent component calling me.");
};
    
defineExpose({ plzParentCall });
    
</script>

<style>
.son {
  border: 1px solid # 323233;
}
</style>
Copy the code

6

Pay attention to the point

  1. The difference between slot and children is that the concept of slot is more flexible than the concept of children when multiple slots need to be inserted
  2. In JSX, we pass slots as an object and return a render function with a slot context
  3. The setup script is written in the same way as vue 2

6.1 JSX writing

Pay attention to the point

  1. Context is passed directly as the == argument to the ==render function
  2. Slot throughsetupIn thecontextIn theslotsWhere == corresponds to the slot name ==
/ / the parent component
import { defineComponent, ref, onMounted } from "vue";
import TestSon from "./test-son";

export default defineComponent({
  setup() {
    const slots = {
      title: (title: string) = > <h1>This is the slot of the title,{title}</h1>,
      subTitle: (subTitle: string) = > <h2>This is the slot for the subTitle, {subTitle}</h2>};return () = > (
      <>
        <TestSon v-slots={slots}/>
      </>); }});Copy the code
/ / child component
import { defineComponent, toRefs, defineExpose } from "vue";

export default defineComponent({
  setup(props, { slots }) {

    return () = > (
      <div style={{ border: "1px solid #f44}} ">
        <div>Header slots: {slots? .title? .(" Subcomponent parameter 1")}</div>
        <div>Subtitle slots: {slots? .subTitle? .(" subcomponent parameter 2")}</div>
      </div>); }});Copy the code

6.2 Setup Script

Note:

  1. The context throughv-slot:xxxTo retrieve the corresponding data passed from the child component
  2. The child component inserts the element of the slot with the name of the slot tag
<! -- Parent component -->
<template>
  <div class="father">
    <test-son>
      <template v-slot:title="params">
        <h1>This is the slot for the title, {{params.text}}</h1>
      </template>

      <template v-slot:subTitle="params">
        <h2>This is the slot of the subtitle, {{params.text}}</h2>
      </template>
    </test-son> 
  </div>
</template>

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

<style>
.father {
  padding: 20px;
  border: 1px solid #f44;
}
</style>

Copy the code