The article directories

  • The change of the template
  • New grammar
    • setup
    • ref
    • reactive
    • toRefs
  • Life cycle changes
  • The change of the watch
  • The change of the computed
  • The new labels
    • Teleport
    • Suspense
  • Custom HOOKS
  • Develop components with TypeScript

The change of the template

In Vue2, each template node can have only one root node:

<template>
  <div>123</div>
</template>
Copy the code

In Vue3, there can be multiple root nodes:

<template>
  <div>123</div>
  <div>456</div>
</template>
Copy the code

This change allows us to reduce unnecessary HTML tag writing during development. Take this example:

<template>
  <table>
    <tr>
      <columns />
    </tr>
  </table>
</template>
Copy the code

Here, the
component needs to return multiple < TD > elements. If a parent div is used in the
component:

<template>
  <div>
    <td>Hello</td>
    <td>World</td>
  </div>
</template>
Copy the code

Get a

output:

<template>
  <table>
    <tr>
      <div>
        <td>Hello</td>
        <td>World</td>
      </div>
    </tr>
  </table>
</template>
Copy the code

The generated HTML is invalid. The Template change in Vue3 solves this problem.

New grammar

setup

Setup () replaces data() and is executed only once.

Setup () can accept two arguments, props and context(it can be omitted if it is not used).

<template> <div> <h1>num:{{num}}</h1> </div> < button@click ="add"> add 1</button> </template> <script lang="js"> export default { name: 'App', setup() { let num = 0; const add = () => { num++; }; console.log(num); return { add, num, }; }}; </script>Copy the code

The above example is a basic setup procedure. But if we try to click the + 1 button, num doesn’t change. If we look at the console, we see that the output is a value of number, but our click doesn’t change anything. To solve this problem, we will use a new API: Ref.

ref

Let’s modify the above code:

< the template > < div > < h1 > MSG: {{MSG}} < / h1 > < h1 > num: {{num}} < / h1 > < / div > < button @ click = "add" > 1 < / button > < / template > <script> import { ref } from 'vue'; export default { name: 'App', setup() { const msg = ref(0); let num = 0; const add = () => { msg.value++; num++; }; console.log(msg); console.log(num); return { msg, add, num, }; }}; </script>Copy the code

If you look at the import method of ref, you can see that the import is on demand. If we look at the console output here, we can see that MSG prints RefImpl and num prints 0. MSG can be changed, but num cannot.

What is a RefImpl? I understand it as a proxy object. For example, we know that data in Vue2 is intercepted via Object.defineProperty(). So as to achieve the purpose of data response type. Vue3 uses proxy in ES6 to achieve the purpose of data responsiveness.

As for the use of proxy, take a simple example:

const target = {
  message1: "hello".message2: "everyone"
};
const handler = {
  get: function(target, prop, receiver) {
    return "world"; }};const proxy = new Proxy(target, handler);

console.log(proxy.message1); // world
console.log(proxy.message2); // world
Copy the code

Ref is a function that takes an argument and returns a responsive object. In this example, the 0 we initialized is wrapped around the object as a parameter, and changes can be detected and made when the value is manipulated in the future.

reactive

To create a reactive state for an object, use the reactive method. Example code is as follows:

<template> <div> <h1>count:{{ obj.count }}</h1> <h1>double:{{ obj.double }}</h1> </div> <button @click="obj.increase"> add 1</button> </template> <script> import {reactive, computed} from 'vue'; export default { name: 'App', setup() { const obj = reactive({ count: 0, increase: () => { obj.count++; }, double: computed(() => obj.count * 2), // here is computed in vue3}); return { obj, }; }}; </script>Copy the code

At this point we can click the button to change the state. {{obj. Count}} {obj. Count}}

< the template > < div > < h1 > count: {{count}} < / h1 > < h1 > double: {{double}} < / h1 > < / div > < button @ click = "happens" > 1 < / button > </template> <script lang="js"> import { reactive, computed } from 'vue'; export default { name: 'App', setup() { const obj = reactive({ count: 0, increase: () => { obj.count++; }, double: computed(() => obj.count * 2), }); return { ... obj, }; }}; </script>Copy the code

But when we click the button, it doesn’t change the state. Why? This is because deconstruction breaks the agent, turning it into a generic value. As in the ref example above, clicking the button does not change. At this point, it’s time to call out another new API, toRefs:

toRefs

It is easy to use, so you can add it when you return:

<! HTML is omitted as above. --> <script lang="js"> import { reactive, computed, toRefs } from 'vue'; export default { name: 'App', setup() { const obj = reactive({ count: 0, increase: () => { obj.count++; }, double: computed(() => obj.count * 2), }); return { ... toRefs(obj), }; }}; </script>Copy the code

This function is useful when toRefs returns a reactive object from a composite function so that the returned object can be deconstructed/extended using components without losing reactivity.

Life cycle changes

Vue2 Vue3
beforeCreate beforeCreate,use setup()
created created,use setup()
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmount
unmounted onUnmounted
errorCaptured onErrorCaptured
renderTracked onRenderTracked
renderTriggered onRenderTriggered

Example:

<script lang="js"> import { onMounted } from 'vue'; export default { setup() { // mounted onMounted(() => { console.log('Componentis mounted! '); }); }}; </script>Copy the code

The change of the watch

The use of Watch in VUe3 is similar to that in VUe2.

The change of the computed

The use of computed in VUe3 is similar to that in VUe2. In VUE3, however, it can be written inside or outside reactive.

The uses of Watch and computed are as follows:

<script lang="js"> import { reactive, computed, watch, toRefs } from 'vue'; export default { setup() { const data = reactive({ count: 0, increase: () => { data.count++; }, double: computed(() => data.count * 2), }); const conComputed = computed(() => data.count * 2); const number = ref(0); watch(data, () => { console.log(data); document.title = 'updated ' + data.count; }); watch(number, () => { console.log(number); }); return { number, conComputed, ... toRefs(data), }; }}; </script>Copy the code

In VUE3,watch and computed need to be introduced before they can be used.

The new labels

Teleport

Usually our mask layer exists under some multilevel tag, which is actually not reasonable. Teleport makes it possible to move the component we wrote below the specified tag. To is which TAB to move to, and it supports selectors.

Example code is as follows:

<template>
  <teleport to="#modal">
    <div id="center">
      <h1>this is a modal</h1>
    </div>
  </teleport>
</template>

<script lang="js">
export default {
  name: 'modal',
};
</script>

<style scoped>
#center {
  width: 200px;
  height: 200px;
  background: red;
  position: fixed;
  left: 50%;
  top: 50%;
  margin-left: -100px;
  margin-top: -100px;
}
</style>
Copy the code
<template> <div id="modal"> <div> <div> <modal v-if="show"></modal> </div> </div> </div> <button @click="show = ! show">show</button> </template> <script lang="js"> import { ref } from 'vue'; import Modal from './components/modal.vue'; export default { name: 'App', components: { Modal, }, setup() { const show = ref(false); return { show, }; }}; </script>Copy the code

Before clicking the button:

After clicking the button:

Looking at the DOM structure before and after the button is clicked, we see that even though the Modal component is used after multiple divs are nested under Div # Modal, it still moves under Div # Modal when displayed.

Suspense

Suspense is useful in asynchronous request scenarios. The following is an example:

// MyAsyncComponent.vue <template> <h1>I have some async work to do before I can render</h1> </template> <script> export  default { name: 'MyAsyncComponent', async setup() { await someAsyncWork(); } } </script>Copy the code
// SuspenseWithError.vue
<template>
  <slot v-if="error" name="error"></slot>
  <Suspense v-else>
    <template #default>
      <slot name="default"></slot>
    </template>
    <template #fallback>
      <slot name="fallback"></slot>
    </template>
  </Suspense>
</template>

<script>
import { ref, onErrorCaptured } from 'vue'

export default {
  name: 'SuspenseWithError',
  setup() {
    const error = ref(null);
    onErrorCaptured((e) => {
      error.value = e;
      return true;
    });
    return { error };
  }
}
</script>
Copy the code
<template> <SuspenseWithError> <template #default> <MyAsyncComponent /> </template> <template #fallback> <span>Loading... Please wait.</span> </template> <template #error> <h1>I failed to load</h1> </template> </SuspenseWithError> </template>  <script> import MyAsyncComponent from '@/components/MyAsyncComponent.vue'; import SuspenseWithError from '@/components/SuspenseWithError.vue'; export default { name: 'App', components: { MyAsyncComponent, SuspenseWithError }, } </script>Copy the code

Custom HOOKS

The following is an example:

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

const useMousePosition = () = > {
  const x = ref(0);
  const y = ref(0);
  const updateMouse = (e: MouseEvent) = > {
    x.value = e.pageX;
    y.value = e.pageY;
  };

  onMounted(() = > {
    document.addEventListener('mousemove', updateMouse);
  });

  onUnmounted(() = > {
    document.removeEventListener('mousemove', updateMouse);
  });
  return { x, y };
};

export default useMousePosition;
</script>
Copy the code
<script lang="js"> import useMousePosition from '@/hooks/useMousePosition'; export default { setup() { const { x, y } = useMousePosition(); return { x, y, }; }}; </script>Copy the code

Develop components with TypeScript

In VUE3, creating a component requires a defineComponent wrapper.

The following is an example:

<script lang="ts">
import { defineComponent, PropType, computed } from 'vue';

// Export interface types. Import the interface and define the interface during use
export interface ColumnProps {
  id: number;
  title: string; avatar? :string;
  des: string;
}

export default defineComponent({
  name: 'ColumnList'.props: {
    list: {
      type: Array as PropType<ColumnProps[]>,
      required: true,}},setup(props) {
    // props is used here
    const ColumnList = computed(() = > {
      return props.list.map((item) = > {
        if(! item.avatar) { item.avatar =require('@/assets/logo.png'); // Default image
        }
        return item;
      });
    });
    return{ ColumnList, }; }}); </script>Copy the code
<template> <div id="container"> <column-list :list="list"></column-list> </div> </template> <script lang="ts"> import { defineComponent } from 'vue'; import ColumnList, { ColumnProps } from '@/components/column-list.vue'; const testDate: ColumnProps[] = [ { id: 1, title: 'test1', des: 'test1', avatar: 'https://picsum.photos/id/239/200/200', }, { id: 2, title: 'test1', des: 'test1', avatar: 'https://picsum.photos/id/239/200/200', }, { id: 3, title: 'test1', des: 'test1', avatar: 'https://picsum.photos/id/239/200/200', }, { id: 4, title: 'test1', des: 'test1', avatar: 'https://picsum.photos/id/239/200/200', }, ]; export default defineComponent({ name: 'App', components: { ColumnList, }, setup() { return { list: testDate, }; }}); </script>Copy the code

The resources

  • Vue3 official document
  • Vue3+TS experience and development +Vite analysis
  • Go async in Vue 3 with Suspense
  • Taste the new front end toy Vite