vue3+typescript【7】

setup函数

举个栗子:(进行了context的解构)

setup(props,{attrs,slots,emit}){
    console.log(props.message);
    console.log(attrs.id, attrs.class);
    console.log(slots);
    console.log(emit);
    const increment=()=>{
        console.log("increment");
    }
    return{
        increment;
    }
}

setup特性

它主要有两个参数:

  • 第一个参数:props

    • 不可以使用this
      • this并没有指向当前组件实例
      • 在setup被调用之前,data、computed、methods等都没有被解析
        • 调用 createComponentInstance 创建组件实例;
        • 调用 setupComponent 初始化component内部的操作;
        • 调用 setupStatefulComponent 初始化有状态的组件;
        • 在 setupStatefulComponent 取出了 setup 函数;
        • 通过callWithErrorHandling 的函数执行 setup;
        • 从上面的代码我们可以看出, 组件的instance肯定是在执行 setup函数之前就创建出来的
  • 第二个参数:context

    • attrs:所有的非prop的attribute;
    • slots:父组件传递过来的插槽
    • emit:当我们组件内部需要发出事件时会用到emit
  • setup的返回值可以在模板template中被使用;

  • 也就是说我们可以通过setup的返回值来替代data选项;

reactive API

reactive

import {reactive} from 'vue'
如果想为在setup中定义的数据提供响应式的特性,那么我们可以使用reactive的函数

const state = reactive({
    name:"hahhaha",
    counter:100
})

变成响应式的原因:

  • 当我们使用reactive函数处理我们的数据之后,数据再次被使用时就会进行依赖收集
  • 当数据发生改变时,所有收集到的依赖都是进行对应的响应式操作(比如更新界面)
  • 事实上,我们编写的data选项,也是在内部交给了reactive函数将其编程响应式对象的

reactive判断的API

import {} from 'vue'

isProxy

  • 检查对象是否是由 reactive 或 readonly创建的 proxy。
    isReactive
  • 检查对象是否是由 reactive创建的响应式代理:
  • 如果该代理是 readonly 建的,但包裹了由 reactive 创建的另一个代理,它也会返回 true;
    isReadonly
  • 检查对象是否是由 readonly 创建的只读代理。
    toRaw
  • 返回 reactive 或 readonly 代理的原始对象(不建议保留对原始对象的持久引用。请谨慎使用)。
    shallowReactive
  • 创建一个响应式代理,它跟踪其自身 property 的响应性,但不执行嵌套对象的深层响应式转换 (深层还是原生对象)。
    shallowReadonly
  • 创建一个 proxy,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换(深层还是可读、可写的)

ref API

import {ref} from 'vue'
reactive API对传入的类型是有限制的,它要求我们必须传入的是一个对象或者数组类型

  • ref 会返回一个可变的响应式对象,该对象作为一个 响应式的引用 维护着它内部的值,这就是ref名称的来源;
  • 它内部的值是在ref的 value 属性中被维护的
const message = ref("Hello World");

注意:

  • 在模板中引入ref的值时,Vue会自动帮助我们进行解包操作,所以我们并不需要在``模板中`通过 ref.value 的方式来使用;
  • 但是在 setup 函数内部,它依然是一个 ref引用, 所以对其进行操作时,我们依然需要使用 ref.value的方式

toRefs

如果我们使用ES6的解构语法,对reactive返回的对象进行解构获取值,那么之后无论是修改结构后的变量,还是修改reactive返回的state对象,数据都不再是响应式的:
Vue为我们提供了一个toRefs的函数,可以将reactive返回的对象中的属性都转成ref;
举个栗子:

const info = reactive({
    name:"hahaha",
    age:19
})
//const {name, age} = info; //此时解构出来的属性不是响应式,只相当于一次赋值
const { name, age } = toRefs(info);
  • 那么我们再次进行结构出来的 name 和 age 本身都是 ref的;
  • 这种做法相当于已经在state.name和ref.value之间建立了链接,任何一个修改都会引起另外一个变化

toRef

如果我们只希望转换一个reactive对象中的属性为ref, 那么可以使用toRef的方法

const age = toRef(info,"age");
//toRef(reactive对象,key值)

ref其它API

unref

  • 如果我们想要获取一个ref引用中的value,那么也可以通过unref方法:
    • 如果参数是一个 ref,则返回内部值,否则返回参数本身;
    • 这是 val = isRef(val) ? val.value : val 的语法糖函数;
      isRef
      判断值是否是一个ref对象。
      shallowRef
  • 创建一个浅层的ref对象;
    triggerRef
  • 手动触发和 shallowRef 相关联的副作用:

customRef

创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显示控制:

  • 它需要一个工厂函数,该函数接受 track 和 trigger 函数作为参数;
  • 并且应该返回一个带有 getset 的对象
    举个栗子:双向绑定的属性进行debounce(节流)的操作
//debounceRef.js
import {customRef} from 'vue'
export function DebounceRef(value,delay=200){
    let timeout = null;
    return customRef((track,trigger)=>{
        return{
            get(){
                track();
                return value;
            },
            set(newValue){
                clearTimeout(timeout);
                timeout = setTimeout(()=>{
                    value = newValue;
                    trigger();
                },delay);
            }
        }
    })
}
//App.vue
<template>
    <div>
        <input v-model="message">
        <h2>{{message}}</h2>
    </div>
</template>

<script>
    import {DebounceRef} from '../hook/DebounceRef.js'
    export default{
        setup(){
            const message = DebounceRef("Hello World");
            return {
                message
            }
        }
    }
</script>

readonly

reactive或者ref可以获取到一个响应式的对象,但是某些情况下,我们传入给其他地方(组件)的这个响应式对象希望在另外一个地方(组件)被使用,但是不能被修改

readonly会返回原生对象的只读代理(也就是它依然是一个Proxy,这是一个proxy的set方法被劫持,并且不能对其进行修改)

//常用方法
const info1 = {name:"hahha"};
const readonlyInfo = readonly(info1);

computed

composition API中的computed计算属性使用:

  • 方式一:接收一个getter函数,并为 getter 函数返回的值,返回一个不变的 ref 对象;
const fullName = computed(()=>{
    return firstName.value + " " + laseName.value;
})
  • 方式二:接收一个具有 get 和 set 的对象,返回一个可变的(可读写)ref 对象;
const fullName = computed({
    get:()=>{
        return firstName.value + " " + laseName.value;
    },
    set:()=>{
        const names = newValue.split(" ");
        firstName.value = names[0];
        laseName.value = names[1];
    }
})

侦听数据变化

在前面的Options API中,我们可以通过watch选项来侦听data或者props的数据变化,当数据变化时执行某一些操作

在Composition API中,我们可以使用watchEffect和watch来完成响应式数据的侦听;

  • watchEffect用于自动收集响应式数据的依赖;
  • watch需要手动指定侦听的数据源;

watchEffect

  • 首先,watchEffect传入的函数会被立即执行一次,并且在执行的过程中会收集依赖;
  • 其次,只有收集的依赖发生变化时,watchEffect传入的函数才会再次执行
const name = ref("hahha");
const age = ref(18);
const changeName = ()=> name.value="lalala"
const changeAge = ()=> age.value++;
watchEffect(()=>{
    console.log("watchEffect-----执行:", name.value,age.value);
    //自动收集依赖
})

如果在发生某些情况下,我们希望停止侦听,这个时候我们可以获取watchEffect的返回值函数,调用该函数即可 stopWatch();

const changeAge = ()=> {
    age.value++;
    if(age.value>30){
        stopWatch();
    }
}

watchEffect清除副作用
在开发中我们需要在侦听函数中执行网络请求,但是在网络请求还没有达到的时候,我们停止了侦听器,或者侦听器侦听函数被再次执行了。那么上一次的网络请求应该被取消掉,这个时候我们就可以清除上一次的副作用。
在我们给watchEffect传入的函数被回调时,其实可以获取到一个参数:onInvalidate

  • 副作用即将重新执行 或者 侦听器被停止 时会执行该函数传入的回调函数;
  • 我们可以在传入的回调函数中,执行一些清除工作
const stopWatch = watchEffect((onInvalidate)=>{
    console.log("watchEffect-----执行:", name.value,age.value);
    const timer = setTimeout(()=>{
        console.log("2s后......");
    },2000);
    onInvalidate(()=>{
        //onInvalidate回调函数执行清除工作
        clearTimeout(timer);
    });
});

setup中使用ref

<h2 ref="title">哈哈哈</h2>
...
this.$refs.title

setup中使用refimport {ref} from 'vue

setup(){
    const titleRef = ref(null);
    //先默认赋值null,等挂载后就会赋值
    console.log(titleRef.value)//此时还是null
    watchEffect(()=>{
        console.log(titleRef.value)
        //不适用flush时会输出两个值,第一次默认输出null,然后输出真实值
    },{
        flush:"post"
        //调整watchEffect的执行时机
        //等挂载完再执行
    })
    return{
        titleRef
    }
}

watch

  • watch的API完全等同于组件watch选项的Property:
    • watch需要侦听特定的数据源,并在回调函数中执行副作用;
    • 默认情况下它是惰性的,只有当被侦听的源发生变化时才会执行回调;
  • 与watchEffect的比较,watch允许我们:
    • 懒执行副作用(第一次不会直接执行);
    • 更具体的说明当哪些状态发生变化时,触发侦听器的执行;
    • 访问侦听状态变化前后的值
侦听单个数据源

watch侦听函数的数据源有两种类型:

  • 一个getter函数:但是该getter函数必须引用可响应式的对象(比如reactive或者ref);
    reactive对象
const state = reactive({
    name: "hahha",
    age:18
})
watch(()=>state.name, (newValue,oldValue)=>{
    console.log(newValue,oldValue);
})
const changeName = ()=>{
    state.name = "lalalala"
}
  • 直接写入一个可响应式的对象,reactive或者ref(比较常用的是ref)
    ref对象
const name = ref("kobe")
watch(name, (newValue, oldValue)=>{
    console.log(newValue,oldValue);
})
const changeName = ()=>{
    name.value = "wuyuzi"
}

能用ref就尽量少用reactive,不方便抽离

侦听多个数据源
const name = ref("ahhaha")
const age = (18)
const changeName = ()=>{
    name.value="lalala"
}
watch([name,age],(newValues, oldValues)=>{
    //还可以对newValues做解构--> newName,newAge,oldName,oldAge
    //如果数组是reactive对象,也可以做解构-->  [()=>({...info}),name]
    console.log(newValues,oldValues);
})
watch的选项
  • 如果是reactive对象是会默认进行深度监听的,
  • 如果解构成了普通对象,则不会深度监听,
  • 如果要深度侦听可以传入第三个参数(对象)
    {
        deep:true,
        immediate:true
    }
    

生命周期钩子

options API | Hook inside setup
beforeCreate | Not needed*
created | Not needed*
beforeMount | onBeforeMount
mounted | onMounted
beforeUpdate | onBeforeUpdate
updated | onUpdated
beforeUnmount | onBeforeUnmount
unmounted | onUnmounted
activated | onActivated
deactivated | onDeactivated

简单的hook

hook的引入
引入js
import useCounter from './hook/useCounter.js'
setup内部:
const {counted, doubleCounter} = useCounter()

useTitle
import { ref, watch } from 'vue'
export function useTitle( title = '默认值' ){
    const titleRef = ref(title);
    watch(titleRef,(newValue)=>{
        document.title = newValue;
    },{
        immediate:true
    })
    return titleRef;
}
useScrollPosition
import { ref } from 'vue'
export function useScrollPosition(){
    const scrollX = ref(0);
    const scrollY = ref(0);
    document.addEventListener('scroll',()=>{
        scrollX.value = window.scrollX;
        scrollY.value = window.scrollY;
    })
    return{
        scrollX,
        scrollY
    }
}
useMousePosition
import { ref } from 'vue'
export function useMousePosition(){
    const mouseX = ref(0);
    const mouseY = ref(0);
    window.addEventListener('mousemove',(event)=>{
        mouseX.value = event.pageX;
        mouseY.value = event.pageY;
    })
    return{
        mouseX,
        mouseY
    }
}
useLocalStorage
import { ref } from 'vue'
export function useLocalStorage(key,defaultValue){
    const data = ref(defaultValue);
    if(defaultValue){
        window.localStorage.setItem(key,JSON.stringify(defaultValue))
    }else{
        data.value = JSON.parse(window.localStorage).getItem(key)
    }
    watch(data,()=>{
        window.localStorage.setItem(key,JSON.stringify(data.value))
    })
    return data;
}

渲染函数(h()函数)

Vue推荐在绝大数情况下使用模板来创建HTML
一些特殊的场景,需要JavaScript的完全编程的能力,这个时候可以使用 渲染函数 ,它比模板更接近编译器---->h()函数

h()函数

h()函数是一个用于创造vnode的函数,更准确的命名时createvNode()函数
它有三个参数:(tag,props,children)

  • 如果没有props,那么通常可以将children作为第二个参数传入;
  • 如果会产生歧义,可以将null作为第二个参数传入,将children作为第三个参数传入

h函数可以在两个地方使用:

  • render函数选项中;
import {h} from 'vue'
export default{
    render(){
        return h('div', {class:"app"}, "Hello APP")
    }
}
  • setup函数选项中(setup本身需要是一个函数类型,函数再返回h函数创建的VNode);
import {h} from 'vue'
export default{
    setup(){
        return () => h('div', {class:"app"}, "Hello App")
    }
}

案例

计算器案例

data(){
    return{
        counter:0
    }
},
render(){
    return h(
        'div',
        {class:"app"},
        [
            h("h2", null, `当前计数:${this.counter}`),
            h("button",{
                onClick:() => this.counter++
            }, "+1"),
            h("button",{
                onClick:() => this.counter--
            }, "-1")
        ]
    )
}
//setup计算器案例
setup(){
    const counter = ref(0);
    return ()=>{
        return h(
        'div',
        {class:"app"},
        [
            h("h2", null, `当前计数:${this.counter}`),
            h("button",{
                onClick:() => this.counter++
            }, "+1"),
            h("button",{
                onClick:() => this.counter--
            }, "-1")
        ])
    }
}

自定义指令

自定义指令分为两种:

  1. 自定义局部指令:组件中通过 directives 选项,只能在当前组件中使用;
//挂载自动获取焦点
<input type="text" v-focus>

export default{
    directives:{
        focus:{
            mounted(el){
                el.focus();
            }
        }
    }
}
  1. 自定义全局指令:app的 directive 方法,可以在任意组件中被使用;
app.directive("focus",{
        mounted(el){
            el.focus();
        }
    })

指令的生命周期

  • created:在绑定元素的 attribute 或事件监听器被应用之前调用;
  • beforeMount:当指令第一次绑定到元素并且在挂载父组件之前调用;
  • mounted:在绑定元素的父组件被挂载后调用;
  • beforeUpdate:在更新包含组件的 VNode 之前调用;
  • updated:在包含组件的 VNode 及其子组件的 VNode 更新后调用;
  • beforeUnmount:在卸载绑定元素的父组件之前调用;
  • unmounted:当指令与元素解除绑定且父组件已卸载时,只调用一次;

Teleport

  • 在组件化开发中,封装一个组件A,在另外一个组件B中使用:
    • 那么组件A中template的元素,会被挂载到组件B中template的某个位置;
    • 最终的应用程序会形成一颗DOM树结构;
  • 但是某些情况下,希望组件不是挂载在这个组件树上的,可能是移动到Vue app之外的其他位置:
    • 比如移动到body元素上,或者有其他的div#app之外的元素上;
    • 这个时候就可以通过teleport来完成;
  • Teleport是什么呢?
    • 它是一个Vue提供的内置组件,类似于react的Portals;
    • teleport翻译过来是心灵传输、远距离运输的意思;
    • 它有两个属性:
      • to:指定将其中的内容移动到的目标元素,可以使用选择器;
      • disabled:是否禁用 teleport 的功能;
<teleport to="#body">
    <h2>lalalala</h2>
</teleport>

Vue插件

  • 向Vue全局添加一些功能时,会采用插件的模式,它有两种编写方式:
    • 对象类型:一个对象,但是必须包含一个 install 的函数,该函数会在安装插件时执行;
    export default {
        install(app, options){
            console.log(app);
            app.config.globalProperties.$name = "hahaha"
        }
    }
    
    • 函数类型:一个function,这个函数会在安装插件时自动执行;
    export default function(app, options){
        console.log(app);
    }
    
  • 插件可以完成的功能没有限制,比如下面的几种都是可以的:
    • 添加全局方法或者 property,通过把它们添加到 config.globalProperties 上实现;
    • 添加全局资源:指令/过滤器/过渡等;
    • 通过全局 mixin 来添加一些组件选项;
    • 一个库,提供自己的 API,同时提供上面提到的一个或多个功能;
      vue2中使用全局属性
    mounted(){
        console.log(this.$name);
    }

composition API使用全局属性

import { getCurrentInstance } from 'vue'
setup(){
    const instance = getCurrentInstance();
    console.log(instance.$name)
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值