Vue3 grammatical sugar detailed explanation (setup(), <script setup>)

Vue3 grammatical sugar detailed explanation of three writing methods

The first way of writing is the same as the way of writing Vue2 in the past, so we won't introduce it too much.
The second way of writing, all objects and methods need return to be used, which is too verbose. In addition to old projects, you can experience the new features of Vue3 in this way.
The third way of writing <script setup> is essentially the grammatical sugar of the second way of writing. After mastering this way of writing, in fact, the second way of writing can basically be mastered. (Another reason not to learn the second way of writing).

<script setup>First compare the writing of setup() and

insert image description here

insert image description here


最基本的 Vue2 写法

<template>
   <div>{
    
    {
    
     count }}</div>
   <button @click="onClick">
     增加 1
   </button>
 </template>
 <script>
 export default {
    
    
   data() {
    
    
     return {
    
    
       count: 1,
     };
   },
   methods: {
    
    
     onClick() {
    
    
       this.count += 1;
     },
   },
 }
 </script>

组合式 API:setup()

This is a common way of writing for many beginners. For a more concise code, it is recommended to learn the third method below<script setup>

basic use

The setup() hook is the entry point for using the combined API in the component, and it is usually only used in the following cases:

  • When you need to use a composition API in a non-single-file component.
  • When you need to integrate Composition API-based code in an Option-API-based component.

We can use the reactive API to declare reactive state, and the object returned in the setup() function will be exposed to templates and component instances. Other options are also available through the component instance to get the properties exposed by setup():

<script>
import {
    
     ref } from 'vue'

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

    // 返回值会暴露给模板和其他的选项式 API 钩子
    return {
    
    
      count
    }
  },

  mounted() {
    
    
    console.log(this.count) // 0
  }
}
</script>

<template>
  <button @click="count++">{
    
    {
    
     count }}</button>
</template>
  • When the ref returned from setup is accessed in the template, it will be shallow-unwrapped automatically, so you don't need to write .value for it in the template anymore. The same is true for unwrapping when accessed via this.

  • setup() itself does not have access to the component instance, that is, accessing this in setup() will be undefined. You can access values ​​exposed by the composition API in the options API, but not the other way around.

  • setup() should return an object synchronously. The only time you can use async setup() is if the component is a descendant of a Suspense component.

Access Props

The first parameter of the setup function is the props of the component. Consistent with standard components, a setup function's props are reactive and will be updated synchronously when new props are passed in.

export default {
    
    
  props: {
    
    
    title: String
  },
  setup(props) {
    
    
    console.log(props.title)
  }
}

Note that if you destructure the props object, the destructured variable will lose reactivity. Therefore, we recommend using props in the form of props.xxx.

If you really need to destructure the props object, or need to pass a prop to an external function and maintain responsiveness, then you can use the two utility functions toRefs() and toRef():

import {
    
     toRefs, toRef } from 'vue'

export default {
    
    
  setup(props) {
    
    
    // 将 `props` 转为一个其中全是 ref 的对象,然后解构
    const {
    
     title } = toRefs(props)
    // `title` 是一个追踪着 `props.title` 的 ref
    console.log(title.value)

    // 或者,将 `props` 的单个属性转为一个 ref
    const title = toRef(props, 'title')
  }
}

Setup context

The second parameter passed to the setup function is a Setup context object. The context object exposes some other values ​​that may be used in setup:

export default {
    
    
  setup(props, context) {
    
    
    // 透传 Attributes(非响应式的对象,等价于 $attrs)
    console.log(context.attrs)

    // 插槽(非响应式的对象,等价于 $slots)
    console.log(context.slots)

    // 触发事件(函数,等价于 $emit)
    console.log(context.emit)

    // 暴露公共属性(函数)
    console.log(context.expose)
  }
}

The context object is non-reactive and can be safely destructured:

export default {
    
    
  setup(props, {
    
     attrs, slots, emit, expose }) {
    
    
    ...
  }
}

Both attrs and slots are stateful objects that are always updated when the component itself is updated. This means that you should avoid deconstructing them and always use attributes from them as attrs.x or slots.x. Also note that unlike props, attrs and slots properties are not reactive. If you want to perform side effects based on attrs or slots changes, you should write the relevant logic in the onBeforeUpdate lifecycle hook.

expose public properties

The expose function is used to explicitly limit the properties exposed by the component. When the parent component accesses the instance of the component through the template reference, it will only be able to access the content exposed by the expose function:

export default {
    
    
  setup(props, {
    
     expose }) {
    
    
    // 让组件实例处于 “关闭状态”
    // 即不向父组件暴露任何东西
    expose()

    const publicCount = ref(0)
    const privateCount = ref(0)
    // 有选择地暴露局部状态
    expose({
    
     count: publicCount })
  }
}

<script setup>

<script setup>Is a compile-time syntactic sugar for using the composition API in single-file components (SFCs). This syntax is the default recommendation when using SFC with the Composition API. <script> It has many advantages over normal syntax:

  • Less boilerplate content, more concise code.
  • Ability to declare props and custom events using pure TypeScript.
  • Better runtime performance (its template will be compiled into a rendering function in the same scope, avoiding the rendering context proxy object).
  • Better IDE type inference performance (reduced work for the language server to extract types from code).

basic grammar

To enable this syntax, you need to <script>add the setup attribute to the code block:
top-level bindings will be exposed to templates
​When using <script setup>, any <script setup>top-level bindings declared in a (including variables, function declarations, and import imports) can be used directly in the template, and import imports will be exposed in the same way.

<script setup>
import {
    
     capitalize } from './helpers'
// 变量
const msg = 'Hello!'

// 函数
function log() {
    
    
  console.log(msg)
}
</script>

<template>
  <button @click="log">{
    
    {
    
     msg }}</button>
  <div>{
    
    {
    
     capitalize('hello') }}</div>
</template>

Responsive ref, reactive

Reactive state needs to be created explicitly using the reactive API. Like the return value of the setup() function, ref will be automatically unwrapped when used in the template:

<script setup>
import {
    
     ref , reactive } from 'vue'

const count = ref(0)
const data = reactive({
    
    
	a:1,
	b:2,
})
</script>

<template>
  <button @click="count++">{
    
    {
    
     count }}</button>
    <button @click="count++">{
    
    {
    
     data.a}}</button>
</template>

use components

Here MyComponent should be understood as referring to a variable. If you've worked with JSX, the mental model here is similar. Its kebab-case format <my-component>can also be used in templates - however, we strongly recommend using PascalCase format for consistency. It also helps distinguish native custom elements.

<script setup>
import MyComponent from './MyComponent.vue'
</script>

<template>
  <MyComponent />
</template>

dynamic component

Since components are registered via variable references rather than based on string component names, <script setup>dynamic :is bindings should be used when using dynamic components in : Note how components are used as variables in ternary expressions.

<script setup>
import Foo from './Foo.vue'
import Bar from './Bar.vue'
</script>

<template>
  <component :is="Foo" />
  <component :is="someCondition ? Foo : Bar" />
</template>

recursive component

A single-file component can be referenced by itself by its filename. For example: a component named FooBar.vue can <FooBar/>reference itself with in its template. Please note that this method has lower priority than imported components. If a named import conflicts with a component's own deduced name, you can add an alias to the imported component:

import {
    
     FooBar as FooBarChild } from './components'

namespace components

Components nested in object properties can be referenced using component tags with a dot, such as <Foo.Bar>. This is useful when you need to import multiple components from a single file:

<script setup>
import * as Form from './form-components'
</script>

<template>
  <Form.Input>
    <Form.Label>label</Form.Label>
  </Form.Input>
</template>

props and emits use defineProps() and defineEmits()

defineEmits => The custom method exposed by the component
defineProps => The value that the component can pass in

In order to get full type deduction support when declaring props and emits options, we can use the defineProps and defineEmits APIs, which will be automatically available in : When <script setup>using
props, also be careful not to use destructuring, which will lose responsiveness

<script setup>
const props = defineProps({
    
    
  foo: String
})

const emit = defineEmits(['change', 'delete'])

</script>
  • defineProps and defineEmits are <script setup>compiler macros that can only be used in . They don't need to be imported, and will <script setup>be compiled along with the processing of .

  • defineProps receives the same values ​​as the props option, and defineEmits receives the same values ​​as the emits option.

  • defineProps and defineEmits will provide proper type deduction after options are passed in.

  • Options passed to defineProps and defineEmits are promoted from setup to the module's scope. Therefore, the options passed in cannot refer to local variables declared in the setup scope. Doing so will cause compilation errors. However, it can refer to imported bindings because they are also in module scope.

Type-specific props/emit declarations

props and emit can also be declared by passing pure type parameters to defineProps and defineEmits:

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

const emit = defineEmits<{
      
      
  (e: 'change', id: number): void
  (e: 'update', value: string): void
}>()

// 3.3+:另一种更简洁的语法
const emit = defineEmits<{
      
      
  change: [id: number] // 具名元组语法
  update: [value: string]
}>()

defineExpose() Component exposes its own properties and methods

Components using <script setup> are turned off by default - that is, public instances of components obtained through template references or the $parent chain do not expose any <script setup> bindings declared in .

<script setup> You can explicitly specify the properties to be exposed in the component through the defineExpose compiler macro :

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

const a = 1
const b = ref(2)

defineExpose({
    
    
  a,
  b
})
</script>

When the parent component obtains the instance of the current component through template reference, the obtained instance will be like this { a: number, b: number } (ref will be automatically unwrapped as in a normal instance)

The following is an example of defineExpose()
Note that calling subcomponents is the use of ref



<!-- 子组件 -->
<template>
  <h1>子组件</h1>
  <div class="input-group">
    <input type="text" v-model="val">
    <button @click="handle">添加</button>
  </div>
</template>
 
<script setup>
import {
    
     ref } from 'vue';
const val = ref('')
const demo = ref('zkj')
const list = ref(['默认测试数据!'])
const handle = () => {
    
    
  list.value.push(val.value);
}
defineExpose({
    
    msg:list, demo:demo,handle:handle})
</script>



<!-- 父组件 -->
<template>
  <Child ref="childRef"/>
  <h1>父组件</h1>
  <h1>{
    
    {
    
    childRef.demo}}</h1>
  <ul class="list">
    <li class="list-item"
        v-for="(item, index) in childRef?.msg" :key="index">
      {
    
    {
    
     item }}
    </li>
  </ul>
  <button @click="childRef.handle()">banniu </button>
</template>
 
<script setup>
import {
    
     ref } from 'vue';
import Child from './components/Child.vue';
const childRef = ref("");
</script>

defineOptions()

This macro can be used to declare component options directly in <script setup> a component without having to use a separate <script>block:

<script setup>
defineOptions({
    
    
  inheritAttrs: false,
  customOptions: {
    
    
    /* ... */
  }
})
</script>
  • Only Vue 3.3+ is supported.
  • This is a macro definition, options will be hoisted into the module scope, and <script setup> local variables that are not literal constants cannot be accessed.

useSlots() and useAttrs()

The use <script setup> of slots and attrs should be relatively rare, since they can be accessed directly in templates via $slots and $attrs. In the rare cases where you do need to use them, there are two helper functions useSlots and useAttrs respectively:

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

const slots = useSlots()
const attrs = useAttrs()
</script>

useSlots and useAttrs are actual runtime functions whose return is equivalent to setupContext.slots and setupContext.attrs. They can also be used in the normal composition API.

top-level await

<script setup> You can use top-level await in . The resulting code will be compiled into async setup():
In addition, await expressions will be automatically compiled into a format that preserves the context of the current component instance after the await.

<script setup>
const post = await fetch(`/api/post/1`).then((r) => r.json())
</script>

Generic​

can use

<script setup lang="ts" generic="T">
defineProps<{
      
      
  items: T[]
  selected: T
}>()
</script>

The value of generic is exactly the same as the parameter list between <…> in TypeScript. For example, you can use multiple parameters, extends constraints, default types and referencing imported types:

<script
  setup
  lang="ts"
  generic="T extends string | number, U extends Item"
>
import type {
    
     Item } from './types'
defineProps<{
      
      
  id: T
  list: U[]
}>()
</script>

limit​

Due to differences in module execution semantics, <script setup> code in . When moving this to an external .js or .ts file, it can be confusing for both developers and tooling. Therefore, <script setup> it cannot be used with the src attribute.

Guess you like

Origin blog.csdn.net/qq_43940789/article/details/131773300