The paper

Recently I learned that the setup syntax for Vue3 has been finalized. It is still in the experimental stage, but it has been confirmed. Although there may be some problems, I believe that it will be fixed in the next version and will be released in the near future. Here are a few examples of the use of setup as described in the official documentation and some minor issues to date.

Eslint-plugin-vue is also a plugin for ESlint-plugin-vue in conjunction with the setup syntax

The sample

The basic case

<script setup> import Foo from './components/Foo.vue'; const msg = 'Hello! ' </script> <template> <div>{{ msg }}</div> </template>Copy the code

Compiled Output:

<script> import Foo from './components/Foo.vue'; export default { compontents: { Foo }, setup() { const msg = 'Hello! ' return function render() { // has access to everything inside setup() scope return h('div', msg) } } } <script>Copy the code

Props and emits

App.vue

<template> <Foo :count="count" @click="inc" /> </template> <script setup lang="ts"> import { ref } from 'vue' import Foo  from './components/Foo.vue'; const count = ref(0); const inc = () => { count.value ++; } </script>Copy the code

Foo.vue – export default

<template> <div class="container"> count: {{count}} < button@click ="inc"> add </button> </div> </template> <script lang="ts"> export default {props: {count: Number }, emits: ['inc'], setup(props, {attrs, slots, emit}) { const inc = () => { emit('inc') } return { inc } } } </script>Copy the code

Foo.vue – setup

<template> <div class="container"> count: {{count}} < button@click ="inc"> add </button> </div> </template> <script setup lang="ts"> import {defineProps, defineEmits } from 'vue'; const props = defineProps({ count: Number }); const emit = defineEmits(['inc']); const inc = () => { emit('inc') } </script>Copy the code

Notice that attr, slots, emit can be obtained from useContext under the previous Setup syntax sugar mode, but now this Api has been deprecated.

setup – defineProps

// const props = defineProps('count'); // const props = defineProps({count: Number}); -ts type Props = {count: number,} const Props = defineProps<Props>();Copy the code

Setup-defineprops How to set the default value

import { defineProps, withDefaults } from 'vue';

type Props = {
  count: number,
}
const props = withDefaults(defineProps<Props>(), {
  count: 1
});
Copy the code

The use of the components

<script setup> import Foo from './Foo.vue' import MyComponent from './MyComponent.vue' </script> <template> <Foo /> <! -- kebab-case also works --> <my-component /> </template>Copy the code

Compiled Output:

import Foo from './Foo.vue'
import MyComponent from './MyComponent.vue'

export default {
  setup() {
    return function render() {
      return [h(Foo), h(MyComponent)]
    }
  }
}
Copy the code

Use slots and attrs

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

  const slots = useSlots();
  const attrs = useAttrs();
</script>
Copy the code

You can also use $slots and $attrs~ directly on the template

Using dynamic Components

<template> <component :is="HelloWorld" msg="Welcome to Your Vue.js + TypeScript App"></component> <component : is = "Math. The random () > 0.5? Foo : Boo" ></component> </template> <script setup lang="ts"> import { ref } from 'vue' import HelloWorld from './components/HelloWorld.vue'; import Foo from './components/Foo.vue'; import Boo from './components/Boo.vue'; </script>Copy the code

Use instruction

vFocus.ts

export default { mounted(el: Window): void { console.log(el); el.focus(); }},Copy the code

App.vue

<template> <div class="container"> <input type="text" v-focus/> </div> </template> <script setup lang="ts"> import vFocus from '.. /vFocus'; </script>Copy the code

Vue3 directives must have a v prefix. The reason for the v prefix is that globally registered directives (such as V-focus) are likely to conflict with local declared variables of the same name. If you do not use the V prefix, you will get an error, such as app.vue

<template>
  <div class="container">
    <input type="text" v-focus/>
  </div>
</template>

<script setup lang="ts">
- import vFocus from '.. /vFocus';
+ import Focus from '.. /vFocus';
</script>
Copy the code

Using await

On the setup syntax sugar, the top layer supports async/await syntax

<script setup lang="ts"> const delay = () => new Promise(reslove => { setTimeout(() => { reslove('ok') }, 1000) }) console.time('Await'); await delay(); console.timeEnd('Await'); </script> // Output: Await: 1004.998779296875msCopy the code

There is a scenario for the use of await here, such as:

This scenario applies only to: export default {async setup() {}}

import { defineComponent, getCurrentInstance } from 'vue'; export default defineComponent({ async setup() { const delay = () => new Promise(reslove => { setTimeout(() => { Reslove ('ok')}, 1000)}) console.log('Vue instance [before await] : ', getCurrentInstance()); const res = await delay(); Console. log('Vue instance [await] : ', getCurrentInstance(), res); }}); </script> // Output: // Vue instance [await] : {uid: 4, vnode: {... }, type: {... }, the parent: {... }, appContext: {... },... } // Vue instance [await] : null OKCopy the code

As to why this is the case, here is a snippet of source code:

/** * File: runtime-core.esm-bundler.js * Func: setupStatefulComponent */ function setupStatefulComponent(instance, isSSR) { ... if (setup) { const setupContext = (instance.setupContext = setup.length > 1 ? createSetupContext(instance) : null); currentInstance = instance; pauseTracking(); const setupResult = callWithErrorHandling(setup, instance, 0 /* SETUP_FUNCTION */, [(process.env.NODE_ENV !== 'production') ? shallowReadonly(instance.props) : instance.props, setupContext]); resetTracking(); currentInstance = null; . }... }Copy the code

You can see that when setup is present, currentInstance = instance; Set the current instance; Then call callWithErrorHandling(setup, instance, 0…) ; Setup is called without waiting. CurrentInstance = null; .

GetCurrentInstance () = null; getCurrentInstance() = null;

/**
 * File: runtime-core.esm-bundler.js
 * Func: getCurrentInstance
 */
 const getCurrentInstance = () => currentInstance || currentRenderingInstance;
Copy the code

I’m getting currentInstance

Solution: Discussion: github.com/vuejs/rfcs/…

import { defineComponent, getCurrentInstance } from 'vue'; export default defineComponent({ async setup() { const delay = () => new Promise(reslove => { setTimeout(() => { Reslove ('ok')}, 1000)}) console.log('Vue instance [before await] : ', getCurrentInstance());- const res = await delay();
+ const res = await withAsyncContext(delay());Console. log('Vue instance [await] : ', getCurrentInstance(), res); }}); </script>Copy the code

Expose the component’s public interface to the parent

By default,

Foo.vue

<template>
  <div class="container"> Foo </div>
</template>

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

  const count = ref(0);
  const inc = () => {
    emit('inc')
  }
</script>
Copy the code

App.vue

<template>
  <Foo ref="foo" />
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import Foo from './components/Foo.vue';

onMounted(() => {
  console.log(foo.value);
});
</script>

// Output:
//  Proxy {__v_skip: true}
//  [[Handler]]: Object
//  [[Target]]: Proxy
//  [[Handler]]: Object
//  [[Target]]: Object
//    __v_skip: true
//    __proto__: Object
//  [[IsRevoked]]: false
//  [[IsRevoked]]: false
Copy the code

Instead of getting the internal variables and methods of the Foo component, use defineExpose:

Foo.vue

<template>
  <div class="container"> Foo </div>
</template>

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

  const count = ref(0);
  const inc = () => {
    emit('inc')
  }
+ defineExpose({
+ count: props.count,
+ inc
+})</script> // Output: // Proxy {__v_skip: true} // [[Handler]]: Object // [[Target]]: Proxy // [[Handler]]: The Object / / [[Target]] : the Object / / count: 0: / / inc ƒ inc () / / __v_skip: true / / __proto__ : Object / / [[IsRevoked]] : false // [[IsRevoked]]: falseCopy the code

One problem here is that the defineExpose operation above will cause problems if await is added: foo.vue

<template>
  <div class="container"> Foo </div>
</template>

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

  const count = ref(0);
  const inc = () => {
    emit('inc')
  }
  
+ const res = await withAsyncContext(delay());defineExpose({ count: props.count, inc }) </script> // Output: // Proxy {__v_skip: true} // [[Handler]]: The Object / / [[Target]] : the Proxy / / [[Handler]] : the Object / / [[Target]] : the Object / / / / __proto__ Vue instance: Object // [[IsRevoked]]: false // [[IsRevoked]]: falseCopy the code

“Foo” is not a Proxy but a Vue instance after adding “await”. This should be a Bug

Martians thought, have you ever considered an operation like this:

import Foo, { getName } from './components/Foo.vue';
Copy the code

Add a property to Foo or get getName:

Foo.vue

<template>
  <div class="container"> Foo </div>
</template>

+ <script lang="ts">
+ export default {
+ name: 'Benson',
+ age: 20
+}
+ export const getName = (): void => console.log('Benson');
+ </script>


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

  const count = ref(0);
  const inc = () => {
    emit('inc')
  }
  
  defineExpose({
    count: props.count,
    inc
  })
</script>
Copy the code

App.vue

import Foo, { getName } from './components/Foo.vue'; <script setup lang="ts"> import { ref, onMounted } from 'vue' import Foo, { getName } from './components/Foo.vue'; const foo = ref(null); onMounted(() => { console.log(foo.value); console.log(Foo); getName(); }); // Foo Proxy contains internal variables and functions // Foo instance: {name: "Benson", age: 20, props: {... }, emits: Array(1), setup: ƒ,... } // Benson </script>Copy the code

Note that

These are some of the related uses and instructions for the Setup syntax sugar.

reference

  • Official document link

A series of

  • Vue 3.0 new features and usage 1
  • Vue 3.0 new features and usage ii
  • Vue 3.0 new features and usage three
  • Vue 3.0 new features and use four