Vue3 单文件组件<script setup> 中的 组合式API 使用总结

<script setup> 是在单文件组件 (SFC) 中使用 组合式 API 的编译时语法糖。相比于普通的 <script> 语法,它具有更多优势:

  • 更少的样板内容,更简洁的代码。
  • 能够使用纯 Typescript 声明 props 和抛出事件。
  • 更好的运行时性能 (其模板会被编译成与其同一作用域的渲染函数,没有任何的中间代理)。
  • 更好的 IDE 类型推断性能 (减少语言服务器从代码中抽离类型的工作)。 

一、CSS变量注入

<template>
  <!-- Vue2中,template标签中只能有一个根元素,在Vue3中没有此限制 -->
  <div class="mt-60px"></div>
  <div class="text-custom">我是自定义的文字内容</div>
</template>

<script setup>
  import { reactive } from 'vue';
  const text = reactive({
    color: 'red',
    size: '24px',
  });
</script>

<style lang="scss" scoped>
  .text-custom {
    // 支持CSS变量注入v-bind(color)
    color: v-bind('text.color');
    font-size: v-bind('text.size');
  }
</style>

二、响应式变量

<script setup>
  // 个人认为响应式变量和普通变量的最大区别是:响应式变量会同步更新视图模板中的显示,但是普通变量更改后不会触发视图更新
  import { ref, toRef, toRefs, reactive, onMounted } from 'vue';

  const num1 = ref(0); // 任意数据类型的响应式变量
  const num2 = reactive({ num: 1 }); // reactive 多用于响应式对象定义
  const { num } = toRefs(num2); // 将响应式对象转换为普通对象
  const numCopy = toRef(num2, 'num'); // 将响应式对象num2中num字段创建为一个新的响应式变量

  onMounted(() => {
    console.log(num.value); // 1
    console.log(num1.value); // 0
    console.log(num2.num); // 1
    console.log(numCopy.value); // 1
    setTimeout(() => {
      num.value = 2;
      // 虽然看起来是新的变量,但是最终还是指向原始对象的响应式变量,
      console.log(numCopy); // 2
      console.log(num2.num); // 2
    }, 1000);
  });
</script>

三、函数/方法(methods)

<template>
  <el-button size="small" @click="updateNum(1)">num变成1</el-button>
</template>

<script setup>
  import { ref } from 'vue';
  const num = ref(0);
  const updateNum = (val) => {
    num.value = val;
    console.log(num.value); // 1
  };
</script>

四、计算属性(computed)

<script setup>
  import { computed, ref } from 'vue'
  const count = ref(1)

  // 通过computed获得doubleCount
  const doubleCount = computed(() => {
    return count.value * 2
  })
</script>

五、观察属性(watch) 

<script setup>
  import { watch, reactive } from 'vue'
  const state = reactive({
    count: 1
  })

  // 声明方法
  const changeCount = () => {
    state.count = state.count * 2
  }

  // 监听count
  watch(
    () => state.count,
    (newVal, oldVal) => {
      console.log(state.count)
      console.log(`watch监听变化前的数据:${oldVal}`)
      console.log(`watch监听变化后的数据:${newVal}`)
    },
    {
      immediate: true, // 立即执行
      deep: true // 深度监听
    }
  )
</script>

六、父子组件传值 props 和 emit

  • 子组件

<template>
  <span>{{ props.name }}</span>
  // 可省略【props.】
  <span>{{ name }}</span>
  <span>{{ friends }}</span>
  <el-button size="small" @click="changeName">我想改名</el-button>
</template>
<script setup>
  // import { defineEmits, defineProps } from 'vue'
  // defineEmits和defineProps在<script setup>中自动可用,无需导入
  // 需在.eslintrc.js文件中【globals】下配置【defineEmits: true】、【defineProps: true】
    
  // 声明 props
  const props = defineProps({
    name: {
      type: String,
      default: ''
    },
    friends: {
      type: Object,
      default: () => {
        return {};
      }
    }
  }) 
  // 声明 emit 事件,没有定义的事件名不会执行
  const emit = defineEmits(['updateName'])
  
  const changeName = () => {
    // 执行 updateName 事件
    emit('updateName', 'Tom')
  }
</script>
  • 父组件

<template>
  <child :name="state.name" :friends="state.friends" @updateName="handleUpdateName"></child> 
</template>
<script setup>
  // 引入子组件, setup 中会自动注册引入的组件
  import child from './child.vue';
  import { reactive } from 'vue';
  const state = reactive({
    name: 'Jerry',
    friends: {
      tom: true,
      jerry: true,
    }
  })
  
  // 接收子组件触发的方法
  const handleUpdateName = (name) => {
    state.name = name
  }
</script>

七、双向绑定 v-model 

  • 子组件

<template>
  <span @click="changeInfo">我叫{{ modelValue }},今年{{ age }}岁</span>
</template>
<script setup>
  // import { defineEmits, defineProps } from 'vue'
  // defineEmits和defineProps在<script setup>中自动可用,无需导入
  // 需在.eslintrc.js文件中【globals】下配置【defineEmits: true】、【defineProps: true】

  defineProps({
    modelValue: String,
    age: Number
  })

  const emit = defineEmits(['update:modelValue', 'update:age'])
  const changeInfo = () => {
    // 触发父组件值更新
    emit('update:modelValue', 'Tom')
    emit('update:age', 30)
  }
</script>
  • 父组件

<template>
  // v-model:modelValue简写为v-model
  // 可绑定多个v-model
  <child v-model="state.name" v-model:age="state.age"></child>
</template>
<script setup>
  import child from './child.vue'

  import { reactive } from 'vue'
  const state = reactive({
    name: 'Jerry',
    age: 20
  })
</script>

八、组件ref (vue2  $refs)和defineExpose


在标准组件写法里,子组件的数据都是默认隐式暴露给父组件的,但在 script-setup 模式下,所有数据只是默认 return 给template 使用,不会暴露到组件外,所以父组件是无法直接通过挂载 ref 变量获取子组件的数据。
如果要调用子组件的数据,需要先在子组件显示的暴露出来,才能够正确的拿到,这个操作,就是由 defineExpose 来完成。

  • 子组件

<template>
  <span>{{state.name}}</span>
</template>

<script setup>
  import { defineExpose, reactive, toRefs } from 'vue'
    
  // 声明state
  const state = reactive({
    name: 'Jerry'
  }) 
    
  // 声明方法
  const changeName = () => {
      // 执行
      state.name = 'Tom'
  }
  
  // 将方法、变量暴露给父组件使用,父组见才可通过ref API拿到子组件暴露的变量或方法
  defineExpose({
    // 解构state
    ...toRefs(state),
    changeName
  })
</script>
  • 父组件

<template>
  <child ref="childRef"></child>  
</template>

<script setup>
  import { ref, onMounted } from 'vue'
  // 引入子组件
  import child from './child.vue'

  // 子组件ref
  const childRef = ref('childRef')
  
  // onMounted 生命周期
  onMounted(() => {
    // 获取子组件name
    console.log(childRef.value.name)
    // 父组件中触发子组件暴露出来的方法
    childRef.value.changeName()
  })
</script>

九、路由useRoute和useRouter

<script setup>
  import { useRoute, useRouter } from 'vue-router'
    
  // 必须先声明调用
  const route = useRoute(); // route 对应了 vue2 中的 $route 对象
  const router = useRouter(); // router 对应了 vue2 中的 $router 对象
    
  // 路由信息
  console.log(route.query)
  // 路由跳转
  router.push('/newPage')
</script>

十、路由守卫

<script setup>
  import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
    
  // 添加一个导航守卫,在当前组件将要离开时触发。
  onBeforeRouteLeave((to, from, next) => {
      next()
  })
  // 添加一个导航守卫,在当前组件更新时触发。
  // 在当前路由改变,但是该组件被复用时调用。
  onBeforeRouteUpdate((to, from, next) => {
      next()
  })
</script>

十一、全局状态管理器

*Vue3 中的Vuex不再提供辅助函数写法
<script setup>
  import { useStore } from 'vuex'

  // 必须先声明调用
  const store = useStore()
    
  // 获取Vuex的state
  store.state.xxx

  // 触发mutations的方法
  store.commit('fnName')

  // 触发actions的方法
  store.dispatch('fnName')

  // 获取Getters
  store.getters.xxx
</script>

十二、生命周期 

通过在生命周期钩子前面加上 “on” 来访问组件的生命周期钩子。
下表包含如何在 Option API 和 setup() 内部调用生命周期钩子

选项式 APIHook inside setup
beforeCreateNot needed*
createdNot needed*
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeUnmountonBeforeUnmount
unmountedonUnmounted
errorCapturedonErrorCaptured
renderTrackedonRenderTracked
renderTriggeredonRenderTriggered
activatedonActivated
deactivatedonDeactivated

TIP

因为 setup 是围绕 beforeCreate 和 created 生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在 setup 函数中编写。

 十三、nextTick

<script setup>
  import { nextTick } from 'vue'
    
  nextTick(() => {
      // ...
  })
</script>

十四、原型绑定与组件内使用

// main.js
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
// 获取原型
const prototype = app.config.globalProperties

// 绑定参数
prototype.name = 'Jerry'
// 单文件组件内使用
<script setup>
  import { getCurrentInstance } from 'vue'
  
  // 获取原型
  const { proxy } = getCurrentInstance()
  
  // 输出
  console.log(proxy.name)
</script>

十五、对 await 的支持 

不必再配合 async 就可以直接使用 await 了,这种情况下,组件的 setup 会自动变成 async setup 。

<script setup>
  const post = await fetch('/api').then(() => {})
</script>

十六、定义组件的name 

用单独的 <script></script> 块来定义

<script>
  export default {
    name: 'ComponentName',
  }
</script>

十七、provide和inject

  • 父组件

<template>
  <child></child>
</template>
<script setup>
  import { provide } from 'vue'
  import { ref, watch } from 'vue'
  // 引入子组件
  import child from './child.vue'

  let name = ref('Jerry')
  // 声明provide
  provide('provideState', {
    name,
    changeName: () => {
      name.value = 'Tom'
    }
  })

  // 监听name改变
  watch(name, () => {
    console.log(`name变成了${name}`)
    setTimeout(() => {
      console.log(name.value) // Tom
    }, 1000)
  })
</script>
  • 子组件

<script setup>
  import { inject } from 'vue'
  // 注入
  const provideState = inject('provideState')
  
  // 子组件触发name改变
  provideState.changeName()
</script>
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值