Basic use of Vue3 setup and setup syntax sugar

When does setup execute

setup is used to write combined api, from the perspective of life cycle hook function, it is equivalent to replacing beforeCreate and will be executed before creted.

setup(props) {
  console.log("setup", props);
},
beforeCreate() {
  console.log("beforeCreate");
},
created() {
  console.log("created");
},

After execution, the setup print result is always at the front.

How setup data and methods are used

The properties and methods inside the setup must be exposed by return to mount the properties on the instance, otherwise there is no way to use them:

<template>
  <div class="hello">
    <h1>{
   
   { msg }}</h1>
  </div>
</template>
setup(props) {
  let msg = "hello";
  return {
    msg,
  };
},

Is there this inside the setup?

Print this in the setup by yourself, and the returned result is undefined. Because the setup will be executed once before beforeCreate, so this is undefined, this does not exist inside the setup, and things related to this cannot be mounted.

setup(props) {
  console.log("setup", props);
  console.log("setup this", this);
},
beforeCreate() {
  console.log("beforeCreate");
},
created() {
  console.log("created");
},

How to use the hook function in setup

Vue3 is an option-style writing method that is compatible with vue2, so the hook function can exist side by side with the setup, which is equivalent to the Options API; but if there is a conflict between the configurations of vue2 and vue3, the setup of vue3 takes precedence.

export default{ 
    setup(){  
        console.log('setup'); 
    }, 
    mounted(){  
        console.log('mounted'); 
    }
}

The new setup() function of vue3 is used to write combined API, so it is not recommended to write code like this. Therefore, you need to use the onXXX family of functions to register the hook function. After the registration is successful, a callback function is passed when calling.  

import { onMounted } from "vue";
export default{ 
    setup(){  
        const a = 0  
        return{   a  }  
        onMounted(()=>{   
            console.log("执行"); 
        }) 
    }
}

These registered lifecycle hook functions can only be used synchronously during setup, because they rely on the global internal state to locate the current component instance, and an error will be thrown when the function is not called under the current component.

The other hook functions are the same, just import them as needed.

The relationship between setup and hook function

When setup is paralleled with hook functions, setup cannot call lifecycle-related functions, but lifecycle can call setup-related properties and methods.

<template> 
    <button @click="log">点我</button>
</template><script>

export default{ 
    setup(){  
        const a = 0  return{   a  } 
    }, 
    methods:{  
        log(){   
            console.log( this.$options.setup() );//返回一个对象  
        } 
    }
}
</script>

this.$options.setup() returns a large object, which contains all the properties and methods in the setup.

setup parameters

When using setup, it will receive two parameters: **props and context. **

props

The first parameter is props, which means that the parent component passes the value to the child component, and the received property is declared inside the component. When received in the child component, the received data is packaged into a proxy object, which can realize responsiveness. When passing Automatically update when new props are imported.

export default{ 
    props: {  
        msg: String,  
        ans: String, 
    }, 
    setup(props,context){  
        console.log(props);//Proxy {msg: "着急找对象", ans: "你有对象吗?"} 
    },
}

Because props is responsive, ES6 deconstruction cannot be used, which will eliminate the responsiveness of props. In this case, toRefs deconstruction needs to be borrowed.

import { toRefs } from "vue"; 
export default{  
    props: {   
        msg: String,   
        ans: String,  
    },  
    setup(props,context){   
        console.log(props);   
        const { msg,ans } = toRefs(props)   
        console.log(msg.value); //着急找对象   console.log(ans.value); //你有对象吗?  
    }, 
}

When using components, we often encounter optional parameters. Some places need to pass a certain value, and some times do not. How to deal with it?

If ans is an optional parameter, ans may not be passed in props. In this case toRefs will not create a ref for ans and toRef needs to be used instead.

import { toRef } from "vue";
setup(props,context){ 
    let ans  = toRef(props ,'ans')// 不存在时,创建一个ans 
    console.log(ans.value);
}

context

context The context environment, which includes three parts: attributes, slots, and custom events.

setup(props,context){ 
    const { attrs,slots,emit } = context // attrs 获取组件传递过来的属性值, // slots 组件内的插槽 // emit 自定义事件 子组件
}
  • attrs is a non-responsive object, which mainly receives the no-props attribute passed from outside the component. It is often used to pass some style attributes, but the attributes that are not declared in the props configuration are equivalent to; during the communication process between parent and child components, the parent component sends this.$attrsdata Passed over, if the subcomponent is not received with props, it will appear in attrs, but not in vm. If it is received with props, it will appear on vm but not in attrs.

  • slots is a proxy object, where slots.default() gets an array, the length of the array is determined by the slot of the component, and the slot content is inside the array.

  • emit: A function that distributes custom events, equivalent to this.$emit.

This does not exist in the setup, so emit is used to replace the previous this.$emit, and is used when the child is passed to the parent, and the custom event is triggered.

<template> 
    <div :style="attrs.style">  
        <slot></slot>    
        <slot name="hh"></slot>  
		<button @click="emit('getVal','传递值')">子向父传值</button>  
	</div> 
</template>

<script>
import { toRefs,toRef } from "vue";
export default{ 
    setup(props,context){  
        const { attrs,slots,emit } = context  // attrs 获取组件传递过来 style 属性     
        console.log('slots',slots.default());//插槽数组 
        console.log('插槽属性',slots.default()[1].props); //获取插槽的属性    
        return{  attrs,  emit   }  
    }, 
}
</script>

Summary of setup features

  • This function will be executed before created, as explained above.

  • There is no this inside the setup, and things related to this cannot be mounted.

  • The properties and methods inside the setup must be exposed through return, otherwise there is no way to use them.

  • Setup internal data is not reactive.

  • Setup cannot call life cycle related functions, but life cycle functions can call functions in setup.

Points to note: (1) Try not to mix the configurations of vue2 and vue3 (2) setup cannot be an async function, because the return value is no longer the return object, but a promise, and the template cannot see the attributes in the return object. (You can also return a Promise instance later, but it requires the cooperation of Suspense and asynchronous components)

// 引入的不再是Vue构造函数了,引入的是一个名为createApp的工厂函数
import { createApp } from 'vue'
import App from './App.vue'

// 创建应用实例对象——app(类似于之前Vue2中的vm,但app比vm更“轻”)
const app = createApp(App)

// 挂载
app.mount('#app')

<script setup> syntactic sugar

<script setup>Variables, functions, and import-introduced content that do not need to be declared in return can be <template/>used in

<script setup>
import { getToday } from './utils'
// 变量
const msg = 'hello'
// 函数
function log() {
    console.log(msg)
}
</script>

// 在 templete 中直接使用声明的变量、函数以及 import 引入的内容
<template>
    <div @click="log">{
   
   { msg }}</div>
	<p>{
   
   { getToday() }}</p>
</template>

Standard component <script> needs to write setup function and return return

<script>
//import引入的内容
import { getToday } from './utils'  
export default{
 setup(){
    // 变量
    const msg = 'Hello!'
    // 函数
    function log() {
      console.log(msg)
    }
    //想在tempate里面使用需要在setup内return暴露出来
    return{
       msg,
       log,
       getToday 
    }
 }
}
</script>

<template>
  <div @click="log">{
   
   { msg }}</div>
   <p>{
   
   {getToday()}}</p>
</template>

The code in <script setup> syntactic sugar will be compiled into the content of the component setup() function, and any bindings at the top level of the `<script setup>` statement (including variables, functions, and the content introduced by `import`) will be It can be used directly in the template, without exposing the declared variables, functions and import content through return, it can be used in <templet>, and there is no need to write export default{}, when using `<script setup>` .

The code in <script setup> syntactic sugar will be compiled into the content of the component setup() function. This means that unlike normal <script> which is only executed once when the component is first introduced, the code inside <script setup> will be executed every time the component instance is created

<script>
  console.log('script');//多次实例组件,只触发一次
  export default {
      setup() {
          console.log('setupFn');//每次实例化组件都触发和script-setup标签一样
      }
  }
  </script>

The <script setup> tag will eventually be compiled into setup()the content of the function, and every time the component is instantiated, the setup function is instantiated once. The setup function in the script tag is also the same. Every time you instantiate a component, you instantiate the setup function once, but the script tag setup needs to be written in the export default{}, and the outside is only executed once when it is first introduced.

<script setup>Imported components will be automatically registered

There is no need to register the component through `components:{}` after importing the component, it can be used directly

<script setup>
import MyComponent from './MyComponent.vue'
//components:{MyComponent}  不需要注册直接使用
</script>

<template>
  <MyComponent />
</template>

Since components are referenced as variables rather than registered as string keys, <script setup>when using dynamic components in , you should use dynamic :isbinding

component communication

<script setup>must use definePropsand defineEmitsAPI instead of props and emits in

`defineProps` and `defineEmits` have complete type inference and are directly available in `<script setup>` (after browsing the Nuggets, I found that most of the article demos still introduce these two APIs through import, which is the official document clearly written)

defineProps instead of props, receives the data passed by the parent component (the parent component passes parameters to the child component)

parent component:

<template>
  <div>父组件</div>
  <Child :title="msg" />
</template>

<script setup>
import {ref} from 'vue'
import Child from './child.vue'
const msg = ref('父的值')  //自动返回,在template直接解套使用
</script>

Subassembly:

  • <template/>You can directly use the props passed by the parent component (props can be omitted.)

  • <script-setup>Need to get the props passed by the parent component through props.xx

<template>
  <div>子组件</div>
  <div>父组件传递的值:{
   
   {title}}</div>
</template>

<script setup>
//import {defineProps} from 'vue'   不需要引入

//语法糖必须使用defineProps替代props
const  props = defineProps({
  title: {
    type: String
  }
});
//script-setup 需要通过props.xx获取父组件传递过来的props
console.log(props.title) //父的值
</script>

defineEmit replaces emit, the child component passes data to the parent component (the child component exposes data to the outside)

Subcomponent code:

<template>
  <div>子组件</div>
  <button @click="toEmits">子组件向外暴露数据</button>
</template>

<script setup>
import {ref} from 'vue'
const name = ref('我是子组件')
//1、暴露内部数据
const  emits = defineEmits(['childFn']);

const  toEmits = () => {
  //2、触发父组件中暴露的childFn方法并携带数据
  emits('childFn',name)
}
</script>

Parent component code:

<template>
  <div>父组件</div>
  <Child  @childFn='childFn' />
  <p>接收子组件传递的数据{
   
   {childData}} </p>
</template>

<script setup>
import {ref} from 'vue'
import Child from './child.vue'
    
const childData = ref(null)    
const childFn=(e)=>{
    consloe.log('子组件触发了父组件childFn,并传递了参数e')
    childData=e.value
}    
</script>

<script setup>It is necessary to actively expose the child component properties to the parent component: defineExpose

<script setup>For components using , the parent component cannot $parentobtain response data such as the ref of the child component through ref or , and needs to actively expose it through defineExpose

Subcomponent code:

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

const a = 1
const b = ref(2)
//主动暴露组件属性
defineExpose({
  a,
  b
})
</script>

Parent component code:

<template>
  <div>父组件</div>
  <Child  ref='childRef' />
  <button @click='getChildData'>通过ref获取子组件的属性 </button>
</template>

<script setup>
import {ref} from 'vue'
import Child from './child.vue'
const childRef= ref()  //注册响应数据  
const getChildData =()=>{
  //子组件接收暴露出来得值
  console.log(childRef.value.a) //1
  console.log(childRef.value.b) //2  响应式数据
}    
</script>

Syntactic sugar for other features

`useSlots` and `useAttrs` (** rarely used**, since most people are developing in SFC mode, slots can be rendered through the `<slot/>` tag in `<template/>`)

If you need to use `slots` and `attrs` in `script-setup`, you need to use `useSlots` and `useAttrs` instead

Need to introduce: `import { useSlots ,useAttrs } form 'vue'`

It is more convenient to access through `$slots` and `$attrs` in `<template/>` (attrs is used to obtain the non-props parameters/methods passed to the child component in the parent component, and attrs is used to obtain non-props in the parent component Parameters/methods passed from props to subcomponents, attrs are used to obtain non-props parameters/methods passed to subcomponents in the parent component, slots can obtain virtual dom objects passed by slots in the parent component, and should not be useful in SFC mode Large, used more in JSX/TSX)

parent component:

<template>
  <Child msg="非porps传值子组件用attrs接收" >
    <!-- 匿名插槽 -->
    <span >默认插槽</span>
    <!-- 具名插槽 -->
    <template #title>
      <h1>具名插槽</h1>
    </template>
    <!-- 作用域插槽 -->
    <template #footer="{ scope }">
      <footer>作用域插槽——姓名:{
   
   { scope.name }},年龄{
   
   { scope.age }}</footer>
    </template>
  </Child>
</template>

<script setup>
// 引入子组件
import Child from './child.vue'
</script>

Subassembly:

<template>
  <!-- 匿名插槽 -->
  <slot />
  <!-- 具名插槽 -->
  <slot name="title" />
  <!-- 作用域插槽 -->
  <slot name="footer" :scope="state" />
  <!-- $attrs 用来获取父组件中非props的传递到子组件的参数 -->
  <p>{
   
   { attrs.msg == $attrs.msg }}</p>
  <!--true  没想到有啥作用... -->
  <p>{
   
   { slots == $slots }}</p>
</template>

  
<script setup>
import { useSlots, useAttrs, reactive, toRef } from 'vue'
const state = reactive({
  name: '张三',
  age: '18'
})

const slots = useSlots()
console.log(slots.default()); //获取到默认插槽的虚拟dom对象
console.log(slots.title());   //获取到具名title插槽的虚拟dom对象
// console.log(slots.footer()); //报错  不知道为啥有插槽作用域的无法获取
//useAttrs() 用来获取父组件传递的过来的属性数据的(也就是非 props 的属性值)。
const attrs = useAttrs()
</script>

Access routing in setup

Access routing instance component information: route and router

`this` cannot be accessed in `setup`, and `this.$router` or `this.$route` can no longer be accessed directly. (getCurrentInstance can replace this but not recommended)

Recommendation: use `useRoute` function and `useRouter` function instead of `this.$route` and `this.$router`

<script setup>
import { useRouter, useRoute } from 'vue-router'
    const route = useRoute()
    const router = useRouter()
    
    function pushWithQuery(query) {
      router.push({
        name: 'search',
        query: {
          ...route.query,
        },
      })
    }
<script/>

navigation guard

Navigation guards for route instance components can still be used

import router from './router'
router.beforeEach((to,from,next)=>{

})

You can also use the navigation guard of the combined apionBeforeRouteLeave, onBeforeRouteUpdate

<script setup>
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'

    // 与 beforeRouteLeave 相同,无法访问 `this`
    onBeforeRouteLeave((to, from) => {
      const answer = window.confirm(
        'Do you really want to leave? you have unsaved changes!'
      )
      // 取消导航并停留在同一页面上
      if (!answer) return false
    })

    const userData = ref()

    // 与 beforeRouteUpdate 相同,无法访问 `this`
    onBeforeRouteUpdate(async (to, from) => {
      //仅当 id 更改时才获取用户,例如仅 query 或 hash 值已更改
      if (to.params.id !== from.params.id) {
        userData.value = await fetchUser(to.params.id)
      }
    })
<script/>

Composite API guards can also be used in any component <router-view>rendered , they don't have to be used directly on routing components like in-component guards do.

Use with normal script

<script setup> can be used with normal <script>. Ordinary <script> may be used in these cases.

  • Cannot be used in <script setup>declared options, such as inheritAttrscustom options enabled by plugins

  • Declare named exports

  • Run side effects or create objects that only need to be executed once

<script>
    // 普通 <script>, 在模块范围下执行(只执行一次)
    runSideEffectOnce()
    
    // 声明额外的选项
    export default {
      inheritAttrs: false,
      customOptions: {}
    }
</script>

<script setup>
    // 在 setup() 作用域中执行 (对每个实例皆如此)
</script>

Summary: The syntactic sugar of setup is a supplement to Vue3, making Vue3 fuller.

<script setup> is compile-time syntactic sugar for using the Composition API in single-file components. It has many advantages over normal <script> syntax

  • Less boilerplate content, more concise code
  • Ability to declare props and throw events using pure Typescript
  • Better runtime performance (its template will be compiled into a render function in the same scope as it, without any intermediate proxy)
  • Better IDE type inference performance (less work for language server to extract types from code)

Guess you like

Origin blog.csdn.net/qq_43641110/article/details/129981821