vue3 常用

367 阅读10分钟

一、 setup(语法糖)

1、基本使用

在vue3.2中不再需要进行return,当使用 <script setup> 的时候,任何在 <script setup> 声明的顶层的绑定 (包括声明的变量,函数声明,以及 import 引入的内容) 都可以在模板中直接使用,这是因为在setup函数中,所有的ES模板都被认为是暴露給上下文的值,并包含在setup()返回对象中。

要使用这个语法,需要将 setup 属性添加到 <script> 代码块上,示列:

<script setup>
import {ref} from 'vue'
let property = ref('alex');
</script>

<script setup> 中的代码会在每次组件实例被创建的时候执行

2、自动注册

无需再通过components进行注册,可以直接引入使用。示列:

<template>
  <child @getChild="getChild" :title="msg" />
</template>

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

//这里我们引入了子组件 child
import child from './child.vue'
</script>

3、组件通信

1)defineProps ----> [用来接收父组件传来的 props] 代码示列

父组件代码:

<template>
  <div>我是父组件----1</div>
  <subassembly @getChili="getChili" :title="msg" />
</template>

<script setup>
import {ref} from 'vue'
import subassembly from './subassembly.vue'

//把值传递给子组件 ---> :title="msg"  <Home @getChili="getChili" :title="msg" />
const msg = ref('父的值')

</script>

子组件代码:

<template>
  <div>我是子组件----1</div>
  <div style="color: red">{{props.title}}</div>
</template>

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

//接收父组件 传过来的值!
const  props = defineProps({
  title: {
    type: String
  }
});

//打印一下 接收父组件的值
console.log(props.title) //父的值
</script>

2)defineEmit ----> [子组件向父组件事件传递] 代码示列

子组件代码:

<template>
  <hr>
  <div>我是子组件----2</div>
  <button @click="toEmits">点击传到父组件</button>
</template>

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


//先定义一下子2 在发送值
const  emits = defineEmits(['getChili']);

const  toEmits = () => {
  emits('getChili','子2的值')
}

</script>

父组件代码:

<template>
  <div>我是父组件----2</div>
  <div>{{data}}</div>
  <subassembly @getChili="getChili" :title="msg" />
</template>

<script setup>
import {ref} from 'vue'
import subassembly from './subassembly.vue'

//空值接收 子组件2的传值
let data = ref(null)
const getChili = (e) => {
  data.value = e
  console.log(e)  //子组件2的值
}

</script>

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

defineExpose ----> [组件暴露出自己的属性] 代码示列

子组件代码:

<template>
  <div>我是子组件----3> {{ alex.stator }}</div>
</template>

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

let alex = reactive({
  stator: 'alex',
  age: 27
})

let alexNew = ref('alexNew');
console.log(alexNew)

defineExpose({
  alex,
  alexNew
})
</script>

父组件代码:

<template>
  <button @click="shiEmots">获取暴露</button>
  <subassembly ref="shield"/>
</template>
<script setup>
import subassembly from './subassembly.vue'
import {defineEmits,defineProps,ref} from 'vue'

const shield = ref()

const  shiEmots = () =>{
  //子组件接收暴露出来得值
  console.log('接收reactive暴漏出来的值',shield.value.alex)
  console.log('接收ref暴漏出来的值',shield.value.alexNew)
}
</script>

二、 hook函数

介绍

  • Vue3 的 hook函数 相当于 vue2 的 mixin, 不同在于 hooks 是函数
  • Vue3 的 hook函数 可以帮助我们提高代码的复用性, 让我们能在不同的组件中都利用 hooks 函数 示列 :

2.1、首先我们需要创建一个hooks的文件 文件示列

微信图片_20211209225929.png

2.2、在hookds文件下,我们创建一个我们需要使用的.js文件 这里我们比如时usePoint.js

这里我们在usePoint里面写了一个获取鼠标点击位置的方法 代码示列

import {reactive, onMounted, onBeforeUnmount} from 'vue'
export  default function () {
    //展示的数据  可以通过App.vue 界面去隐藏
    let point = reactive({
        x: 0,
        y: 0
    })

    //获取鼠标点击事件
    function savePonint(event) {
        point.x = event.pageX
        point.y = event.pageY
        console.log(event.pageX, event.pageY)
    }

    //现实之后调用 挂载完毕
    onMounted(() => {
        window.addEventListener('click', savePonint)
    })

    //在隐藏之前调用 卸载之前
    onBeforeUnmount(() => {
        window.removeEventListener('click', savePonint)
    })

    return point
}

我们在组件中引入此文件 代码示列

<template>

  <h2>当前求和:{{ sum }}</h2>
  <button @click="sum++">点我加一</button>
  <hr>
  <h2>当前鼠标点击坐标为:x:{{ point.x }},y:{{ point.y }}</h2>
</template>

<script>
import {ref} from 'vue'
//复用的usePoint
import usePoint from "../hooks/usePoint";

export default {
  name: 'App',
  setup() {
    //数据
    let sum = ref(0)
    let point = usePoint()
    return {sum,point}
  },
}
</script>

结果展示:

微信图片_20211209230630.png

总结:

新引入的setup语法糖的 目的是简化使用Composition API时冗长的模板代码。

而在组件中引入并使用自定义hook 自定义Hook的优势: 很清楚复用功能代码的来源, 更清楚易懂!

三、 Reactivity APIs

3.1 reactive

接收一个对象返回原对象的响应式proxy对象,是深度的代理

其实vue2.x也有一个Vue.observable()和其功能相同

isReactive

检测一个对象是否由reactive创建出来的对象

注意以下情况

const state = reactive({
    name:'zs'
})
const test = readonly({
    name:'zs'
})
const copyState = readonly(state)
​
isReactive(test) === false
isReactive(copyState) === true

shallowReactive

不会对嵌套的对象执行深度响应

3.2 ref

1.接收一个inner value返回一个reactive并且可变的ref对象

这个对象有唯一的一个.value属性指向inner value

2.如果接收一个对象,会默认调用reactive进行处理

3.如果reactive某个属性值是ref:

const state = reactive({
    count:ref(1)
})
//访问和修改
state.count

4.如果传入的是个Array或者Map对象或者其他原生集合类型包含的ref,默认不会对ref自动展开

const arr = reactive([ref(0)])
const map = reactive(new Map([['foo',ref(0)]]))
//访问和修改
arr[0].value
map.get('foo').value

3.3 unref/isRef

如果参数是一个 ref,则返回内部值,否则返回参数本身。

IsRef检测值是否是一个 ref对象

即 unref === isRef(val) ? val.value : val

3.4 toRef

为一个reactive对象的某个属性新创建一个ref

这个新创建的ref对象会和源property进行响应式连接

经常用于只需要使用响应式对象的某个property的情况

const state = reactive({
  foo: 1,
  bar: 2
})
const fooRef = toRef(state, 'foo')
​
fooRef.value++
console.log(state.foo) // 2

3.5 toRefs

将响应式对象转为普通对象, 结果对象的每个property都指向原始对象相应property的ref

通常用于在不丢失响应式的前提下对返回的对象进行解构/展开

const state = reactive({
  foo: 1,
  bar: 2
})
​
const stateAsRefs = toRefs(state)
//stateAsRefs 的类型:
{
  foo: Ref<number>,
  bar: Ref<number>
}
​
//在template中可以直接使用foo / bar
return{
    ...toRefs(stateAsRefs)
}

3.6 customRef

本质是一个自定义的composition API,接收一个工厂函数

该函数接收tracktrigger两个函数,返回一个带getset的对象

track即追踪依赖,trigger即触发更新

通常用于图片的懒加载和防抖或节流

//使用customRef的实现debounce
function useDebounce(value,delay=200){
    let t=null;
    return customRef((track,trigger)=>{
        return {
            get(){
                track();
                return value
            },
            set(newVal){
                clearTimeout(t)
                t = setTimeout(()=>{
                    value = newVal
                    trigger()
                },delay)
            }
        }
    })
}
const text = useDebounce('',500)
return {
    text
}

四、 computed

1.可以接收一个getter函数并返回一个不可更改的响应式对象

2.可以接收一个setget函数的对象,创建一个可写的ref对象

const count = ref(1)
const plusOne = computed({
  get: () => count.value + 1,
  set: val => {
    count.value = val - 1
  }
})
​
plusOne.value = 1
console.log(count.value) // 0

五、 readonly

接收一个对象(reactiveplainref)

返回一个只读的readonlyoriginal源对象的proxy代理

六、 isReadonly

检测是否是由readonly创建的proxy代理的对象

七、 isProxy

检测是否是一个由reactivereadonly创建的proxy代理的对象

注意以下情况则为false

const state = new Proxy({
    a:'属性a'
},{})
//原生Proxy代理的这种特殊情况
isProxy(state)===false

八、 watchEffect

  • 默认在onBeforeUpdate前被调用,若观测DOMref注册节点

需要在onMountedhook里面执行watchEffect

  • 接收一个回调函数并响应式追踪其依赖(数据)
  • 当依赖改变会re-run
  • 返回一个stop handle,调用可以明确停止其监听

在组件卸载时会自动停止

const count = ref(0)
setTimeout(() => {
  count.value = 1
}, 1000)
​
const stop = watchEffect(async (onInvalidate) => {
  onInvalidate(() => {
    //在此处进行拦截来消除副作用 需要在Promise被resolve前被注册
    //此外,Vue依赖这个返回的Promise来自动处理Promise链的潜在错误
    console.log('onInvalidate is triggered')
  })
  console.log(count.value) 
  //在此处有可能是异步操作 为了取消异步操作的副作用(失败的回调)
  //类似data.value = await fetchData(props.id)
},
{ //接收第二个参数 是个对象 对象内两个函数只有在开发模式下可以使用
    onTrack(e){ 
      console.log('trick') //追踪 一开始就会执行
    },
    onTrigger(e){
      //通常在这里放置debugger 交互式的调试检查
      console.log('trigger') //依赖 以来改变就会执行
    },
    flush:'post' //默认是`pre`在beforeUpdate执行,
    //可以通过设置为`post`使其在beforeUpdate之后执行,但是肯定是在updated之前执行
})
​
setTimeout(() => {
  stop()
  console.log('watchEffect is closed')
}, 2000)
/**先后打印为:
 * 0
 * onInvalidate is triggered
 * 1
 * onInvalidate is triggered
 * watchEffect is closed
**/

九、 watch

传入一个返回需要观测的明确的数据的回调(getter的方式)

传入一个执行副作用的回调

对比watchEffect:

  • 可以懒执行副作用
  • 更加明确了什么时候执行副作用函数
  • 可以访问newValoldVal
  • 同样是在onBeforeUpdate前执行
  • 同样返回stop具柄函数,可以调用手动关闭watch监听
  • 同样接收第三个回调,里面可以调用onTrackonTrigger回调
  • watch可接受的回调和回调中常用的方法和属性如下
<template>
  <h2>count: {{ count }}</h2>
  <button @click="changeBtn" type="button">更新count</button>
</template><script lang="ts" setup>
const count = ref(1)
const changeBtn = () => {
  count.value++
}
onBeforeUpdate(() => {
  console.log('onBeforeUpdate') //点击后4
})
​
const stop = watch(
  () => count.value,
  (newVal, oldVal, onInvalidate) => {
    console.log('newVal', newVal) //初始化 2 点击后 3
    onInvalidate(() => {
      console.log('onInvalidate', onInvalidate) //点击后 2
    })
  },
  {
    onTrack() {
      console.log('onTrack') // 初始化 1
    },
    onTrigger() {
      console.log('onTrigger') //点击后 1
    },
    immediate: true,
    deep: true,
  }
)
</script>

十、 toRaw

raw可以理解为不是reactivereadonly创建的proxy代理的原始对象

是一个逃生舱,用于临时读取数据不承担跟踪的开销

视图不进行更新的话,比如列表数据仅展示,可以使用此属性

十一、 markRaw

标记一个对象,此对象不会被转化成proxy代理对象,而会返回其本身

会给标记的对象添加一个_v_skip:true的属性,在响应式代理时会被跳过

在另外的响应式对象中的某个属性使用markRaw仍然是有效的

注意以下情况

//情况1
const foo = markRaw({
  nested: {},
})
const bar = reactive({
  nested: foo.nested,
})
console.log(foo.nested === bar.nested) //false 因为前者是原始对象,后者是Proxy代理对象//情况2
const foo = markRaw({
  nested: markRaw({
    a: 1,
  }),
})
const bar = reactive({
  nested: foo.nested,
})
bar.nested.a++ //a:2 表明指向的仍是同一个内存对象
console.log(foo.nested === bar.nested) //true 表明markRaw也是表层标注

11.1 markRaw小结

markRawshallowXXX都是允许不参与深度reactive/readonly转换

通常见于Vue component object或第三方的class instance

当渲染庞大且不可变的数据图表跳过响应可以提高性能

即手动对嵌套层进行markRaw或者直接在最外层使用shallowXXX

十二、 vue3.x

生命周期钩子都是以onX为名称,同步使用在setup配置项中

下表包含如何在 setup () 内部调用生命周期钩子:

选项式 APIHook inside setup
beforeCreateNot needed*
createdNot needed*
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeUnmountonBeforeUnmount
unmountedonUnmounted
errorCapturedonErrorCaptured
renderTrackedonRenderTracked(dev环境使用)
renderTriggeredonRenderTriggered(dev环境使用)
activatedonActivated
deactivatedonDeactivated
  1. onErrorCaptured表示在父组件里面可以捕获子组件的错误信息
  2. onRenderTracked追踪render的行为,通常在此debugger调试
  3. onRenderTriggeredre-render时触发
  4. onActivated被 keep-alive 缓存的组件激活时调用
  5. onDeactivated被 keep-alive 缓存的组件失活时调用

十三、 依赖注入

Provide/Inject

当你仅需要祖级组件的某些数据

祖级组件不需知道哪些子组件使用了它provideproperty

子组件不需要知道injectproperty来自哪里

可以使用这一对属性

处理响应式:

provide时使用computed进行处理数据;

或者在定义数据的时候使用refreactive

修改响应式数据:

祖级组件直接provide一个方法给子组件,仍然在祖级组件进行操作

如果不想让子组件修改的话,用readonly进行数据修饰

十五、 节点引用

vue2.x中为了直接访问到子组件,可以通过ref属性给子组件或者DOM节点注册一个reference ID,可以在refs访问到

vue3.xtemplate中使用ref注册组件或者DOM节点时,在setup可以定义同名ref代理的变量,会指向同一个引用reference

如果一个VNode ref的在渲染函数中和setup定义的ref的变量的key是对应一致的,虚拟节点对应的元素或组件实例会被指定为这个变量的值,并且在VDOM的挂载和更新进程中起作用,所以在初始化render之后才会设置这个值

v-for的情况下使用refs

<template>
 <ul>
    <li
      v-for="(item, index) in students"
      :key="index"
      :ref="
        (el) => {
          if (el) lists[index] = el
        }
      "
    >
      {{ item.name }}
    </li>
 </ul>
</template>
​
const students = reactive([{ name: 'zs' }, { name: 'ls' }])
const lists = reactive([])
onMounted(() => {
  console.log('lists', lists)
})

十六、 globalProperties

添加一个可以再应用的任何组件实例中访问的全局property

此属性命名优先级高于某个组件setup声明的变量

const app = createApp({})
app.config.globalProperties.$util = ()=>{}
//等同于vue2.x的
Vue.prototype.$util = () => {}

十七、 内置组件slot

使用非常频繁的内置组件,简单来说

父组件使用子组件的结构,但是子组件的内容是由父组件控制并传入

<slot>即为我们想要插入内容的占位符

十八、应用API:

18.1 directive

对视图上一些逻辑的抽离封装通常利用directive来操作

// 注册 (函数指令)
app.directive('my-directive', () => {
  // 这将被作为 `mounted` 和 `updated` 调用
})
// 也可以把第二个参数写成对象写法
app.directive('my-directive', {
  created(el, binding, vnode) {},
  beforeMount(el, binding, vnode) {
    el.style.background = binding.value
  },
  mounted() {},
  beforeUpdate(a,b,c,prevNode) { 
    //第四个参数 prevNode 只在beforeUpdate和updated才有!
    a.style.background = b.value
  },
  updated() {},
  beforeUnmount() {
    // 当指令绑定的元素 的父组件销毁前调用。  
  },
  unmounted() {},// 当指令与元素解除绑定且父组件已销毁时调用。
​
})
​
// getter, 如果已注册,则返回指令定义
const myDirective = app.directive('my-directive')

18.2 nextTick

将回调推迟到下一个DOM更新周期之后执行

在更改了一些数据之后等待DOM更新的时机立即使用它