乐趣区

关于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>
退出移动版