关于vue.js:Vue3-setup语法糖Composition-API全方位解读

  1. 起初 Vue3.0 裸露变量必须 return 进去,template 中能力应用;
  2. Vue3.2 中 只须要在 script 标签上加上 setup 属性,组件在编译的过程中代码运行的上下文是在 setup() 函数中,无需 returntemplate 可间接应用。
  3. 本文章以Vue2的角度学习Vue3的语法,让你疾速了解Vue3的Composition Api
  4. 本文章第十四节为状态库 Pinia 的装置、应用解说

一、文件构造

Vue2中,<template> 标签中只能有一个根元素,在Vue3中没有此限度

<template>
  // ...
</template>

<script setup>
  // ...
</script>

<style lang="scss" scoped>
  // 反对CSS变量注入v-bind(color)
</style>

二、data

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

  // ref申明响应式数据,用于申明根本数据类型
  const name = ref('Jerry')
  // 批改
  name.value = 'Tom'

  // reactive申明响应式数据,用于申明援用数据类型
  const state = reactive({
    name: 'Jerry',
    sex: '男'
  })
  // 批改
  state.name = 'Tom'

  // 应用toRefs解构
  const {name, sex} = toRefs(state)
  // template可间接应用{{name}}、{{sex}}
</script>

vue实战视频解说:进入学习

三、method

<template>
  // 调用办法
  <button @click='changeName'>按钮</button>  
</template>

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

  const state = reactive({    name: 'Jery'
  })  // 申明method办法
  const changeName = () => {    state.name = 'Tom'
  }  
</script>

四、computed

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

  const count = ref(1)

  // 通过computed取得doubleCount
  const doubleCount = computed(() => {
    return count.value * 2
  })
  // 获取
  console.log(doubleCount.value)
</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父传子

子组件

<template>
  <span>{{props.name}}</span>
  // 可省略【props.】
  <span>{{name}}</span>
</template>

<script setup>
  // import { defineProps } from 'vue'
  // defineProps在<script setup>中主动可用,无需导入
  // 需在.eslintrc.js文件中【globals】下配置【defineProps: true】

  // 申明props
  const props = defineProps({    name: {      type: String,      default: ''
    }  })  
</script>

父组件

引入子组件,组件会主动注册

<template>
  <child name='Jerry'/>  
</template>

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

七、emit子传父

子组件

<template>
  <span>{{props.name}}</span>
  // 可省略【props.】
  <span>{{name}}</span>
  <button @click='changeName'>更名</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: ''
    }  })   // 申明事件
  const emit = defineEmits(['updateName'])    const changeName = () => {    // 执行
    emit('updateName', 'Tom')  }
</script>

父组件

<template>
  <child :name='state.name' @updateName='updateName'/>  
</template>

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

  const state = reactive({    name: 'Jerry'
  })    // 接管子组件触发的办法
  const updateName = (name) => {    state.name = name  }
</script>

八、v-model

反对绑定多个v-modelv-modelv-model:modelValue 的简写
绑定其余字段,如:v-model:name

子组件

<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"
  />
</template>

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

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

九、nextTick

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

  nextTick(() => {
    // ...
  })
</script>

十、ref子组件实例和defineExpose

  • 在规范组件写法里,子组件的数据都是默认隐式裸露给父组件的,但在 script-setup 模式下,所有数据只是默认 return 给 template 应用,不会裸露到组件外,所以父组件是无奈间接通过挂载 ref 变量获取子组件的数据。
  • 如果要调用子组件的数据,须要先在子组件显示的裸露进去,才可能正确的拿到,这个操作,就是由 defineExpose 来实现。

子组件

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

<script setup>
  import { reactive, toRefs } from 'vue'
  // defineExpose无需引入
  // import { defineExpose, reactive, toRefs } from 'vue'

  // 申明state
  const state = reactive({    name: 'Jerry'
  })       // 将办法、变量裸露给父组件应用,父组件才可通过ref API拿到子组件裸露的数据
  defineExpose({    // 解构state
    ...toRefs(state),    // 申明办法
    changeName () {      state.name = 'Tom'
    }  })
</script>

父组件

1. 获取一个子组件实例

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

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

  // 子组件ref(TypeScript语法)
  const childRef = ref<InstanceType<typeof child>>()

  // nextTick
  nextTick(() => {
    // 获取子组件name
    console.log(childRef.value.name)
    // 执行子组件办法
    childRef.value.changeName()
  })
</script>

2. 获取多个子组件实例:在 v-for 中获取子组件实例

这种状况仅实用于 v-for 循环数是固定的状况 ,因为如果 v-for 循环数 在初始化之后产生扭转,那么就会导致 childRefs 再一次反复增加,childRefs 中会呈现反复的子组件实例

<template>
  <div v-for="item in 3" :key="item">
    <child :ref='addChildRef'/>
  </div>
</template>

<script setup>
  // 省略...
    // 子组件实例数组
  const childRefs = ref([])  // 通过 addChildRef 办法向 childRefs 增加子组件实例
  const addChildRef = (el) => {    childRefs.value.push(el)  }
</script>

3. 获取多个子组件实例:动静 v-for 获取子组件实例

通过下标来向 childRefs 增加/批改,初始化之后,动静批改 v-for 循环数,会主动依据下标从新批改该下标对应的数据

<template>
  <button @click='childNums++'></button>
  <div v-for="(item, i) in childNums" :key="item">
    // 通过下标向 childRefs 动静增加子组件实例    <child :ref='(el) => childRefs[i] = el'/>
  </div>
  <button @click='childNums--'></button>
</template>

<script setup>
  // 省略...
    // 子组件数量
  const childNums = ref(1)  // 子组件实例数组
  const childRefs = ref([])
</script>

十、插槽slot

子组件

<template>
  // 匿名插槽
  <slot/>
  // 具名插槽
  <slot name='title'/>
  // 作用域插槽
  <slot name="footer" :scope="state" />
</template>

<script setup>
  import { useSlots, reactive } from 'vue'
  const state = reactive({    name: '张三',    age: '25岁'
  })    const slots = useSlots()  // 匿名插槽应用状况
  const defaultSlot = reactive(slots.default && slots.default().length)  console.log(defaultSlot) // 1
  // 具名插槽应用状况
  const titleSlot = reactive(slots.title && slots.title().length)  console.log(titleSlot) // 3
</script>

父组件

<template>
  <child>
    // 匿名插槽    <span>我是默认插槽</span>
    // 具名插槽    <template #title>
      <h1>我是具名插槽</h1>
      <h1>我是具名插槽</h1>
      <h1>我是具名插槽</h1>
    </template>
    // 作用域插槽    <template #footer="{ scope }">
      <footer>作用域插槽——姓名:{{ scope.name }},年龄{{ scope.age }}</footer>
    </template>
  </child> 
</template>

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

十二、路由useRoute和useRouter

<script setup>
  import { useRoute, useRouter } from 'vue-router'

  // 必须先申明调用
  const route = useRoute()
  const router = useRouter()

  // 路由信息
  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>

十四、store

Vuex

*Vue3 中的Vuex不再提供辅助函数写法

<script setup>
  import { useStore } from 'vuex'
  import { key } from '../store/index'

  // 必须先申明调用
  const store = useStore(key)

  // 获取Vuex的state
  store.state.xxx

  // 触发actions的办法
  store.commit('fnName')

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

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

Pinia

*全面拥抱 Pinia 吧!
2021年11月24日,尤大在 Twitter 上发表:Pinia 正式成为 Vue 官网的状态库,意味着 Pinia 就是 Vuex 5Pinia 的长处:

  • 同时反对 Composition Api 和 Options api 的语法;
  • 去掉 mutations ,只有 state 、getters 和 actions ;
  • 不反对嵌套的模块,通过组合 store 来代替;
  • 更欠缺的 Typescript 反对;
  • 清晰、显式的代码拆分;

装置

# 应用 npm
npm install pinia

# 应用 yarn
yarn add pinia

main.js 引入

import App from './App.vue'
import { createApp } from 'vue'
import { createPinia } from 'pinia'

const app = createApp(App)
app.use(createPinia())
app.mount('#app')

配置 store.js

import { defineStore } from 'pinia'

// defineStore 调用后返回一个函数,调用该函数取得 Store 实体
export const useStore = defineStore({
  // id: 必须,在所有 Store 中惟一
  id: 'globalState',
  // state: 返回对象的函数
  state: () => ({
    count: 1,
    data: {
      name: 'Jerry',
      sex: '男'
    }
  }),
  // getter 第一个参数是 state,是以后的状态,也能够应用 this 获取状态
  // getter 中也能够拜访其余的 getter,或者是其余的 Store
  getters: {
    // 通过 state 获取状态
    doubleCount: (state) => state.count * 2,
    // 通过 this 获取状态(留神this指向)
    tripleCount() {
      return this.count * 3
    }
  },
  actions: {
    updateData (newData, count) {
      // 应用 this 间接批改
      this.data = { ...newData }
      this.count = count

      // 应用 $patch 批改多个值
      this.$patch({
        data: { ...newData },
        count
      })
    }
  }
})

应用 store

<template>
  // 获取 store 的 state
  <p>姓名:{{store.data.name}}</p>
  <p>性别:{{store.data.sex}}</p>

  // 调用 actions 办法 / 批改 store
  <button @click='update'>批改用户信息</button>

  // 获取 getter
  <p>获取getter:{{store.doubleCount}}</p>
</template>

<script setup>
  import { useStore } from '@store/store.js'
  const store = useStore()    function update () {    // 通过 actions 定义的办法批改 state
    store.updateData({ name: 'Tom', sex: '女' })        // 通过 store 间接批改
    store.data = { name: 'Tom', sex: '女' }        // 同时扭转多个状态
    store.$patch((state) => {      state.data = { name: 'Tom', sex: '女' }      state.count = 2
    })  }
</script>

<style lang="scss" scoped>
</style>

其余办法

替换整个 state
$state 能够让你通过将 store 的属性设置为新对象来替换 store 的整个 state

const store = useStore()
store.$state = {
  name: 'Bob',
  sex: '男'
}

重置状态
调用 store 上的 $reset() 办法将状态重置为初始值

const store = useStore()
store.$reset()

十五、生命周期

通过在生命周期钩子后面加上 “on” 来拜访组件的生命周期钩子。

下表蕴含如何在 Option API 和 setup() 外部调用生命周期钩子

Option API setup中
beforeCreate 不须要
created 不须要
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmount
unmounted onUnmounted
errorCaptured onErrorCaptured
renderTracked onRenderTracked
renderTriggered onRenderTriggered
activated onActivated
deactivated onDeactivated

十六、原型绑定与组件内应用

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>

十七、v-bind() CSS变量注入

<template>
  <span>Jerry</span>
</template>

<script setup>
  import { ref, reactive } from 'vue'
  // prop接管款式
  const props = defineProps({    border: {      type: String,      default: '1px solid yellow'
    }  })    // 常量申明款式
  const background = 'red'
    // 响应式数据申明款式
  const color = ref('blue')  const style = reactive({    opacity: '0.8'
  })
</script>

<style lang="scss" scoped>
  span {    // 应用常量申明的款式    background: v-bind(background);        // 应用响应式数据申明的款式    color: v-bind(color);    opacity: v-bind('style.opacity');        // 应用prop接管的款式    border: v-bind('props.border');  }
</style>

十八、provide和inject

父组件

<template>
  <child/>
</template>

<script setup>
  import { ref, watch, provide } 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>

十九、自定义指令

Vue3相较于Vue2的自定义申明办法有些不同

const app = createApp({})

// 使 v-demo 在所有组件中都可用
app.directive('demo', {
  // 在绑定元素的 attribute 前或事件监听器利用前调用
  created(el, binding, vnode, prevVnode) {},
  // 在元素被插入到 DOM 前调用
  beforeMount(el, binding, vnode, prevVnode) {},
  // 在绑定元素的父组件
  // 及他本人的所有子节点都挂载实现后调用
  mounted(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件更新前调用
  beforeUpdate(el, binding, vnode, prevVnode) {},
  // 在绑定元素的父组件
  // 及他本人的所有子节点都更新后调用
  updated(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件卸载前调用
  beforeUnmount(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件卸载后调用
  unmounted(el, binding, vnode, prevVnode) {}
})

比方实现一个默认密文身份证号,点击才展现的指令

app.directive('ciphertext', {
  created: (el: any) => {
    console.log(el, 1111)
    el.style.cursor = 'pointer'
    const value = el.innerText
    if (!value || value === 'null' || value === '--') {
      el.innerText = '--'
    } else {
      el.setAttribute('title', '点击查看')
      el.innerText = hideText(value)
      el.addEventListener('click', () => {
        if (el.innerText.indexOf('*') > -1) {
          el.innerText = value
        } else {
          el.innerText = hideText(value)
        }
      })
    }
  }
})

<span v-ciphertext>{{idNumber}}</span>

二十、对 await 的反对

不用再配合 async 就能够间接应用 await 了,这种状况下,组件的 setup 会主动变成 async setup 。

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

二十一、定义组件的name

用独自的<script>块来定义

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

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理