Vue3 | Composition API 学习总结

为了可以使用Composition API, 咱们须要有一个能够实际使用它的地方。在vue组件中,咱们将此位置称为setuphtml

setup函数

setup函数是在组件建立以前执行的,setup函数中的props一旦被解析,就将做为Composition API 的入口vue

须要注意的是: 咱们不能在setup函数中使用this, 由于它不会找到组件实例。 缘由是 虽然组件实例是在执行setup以前就会被建立出来了,可是 setup函数被调用以前,data,computed,methods等都没有被解析,因此它们没法在setup中被获取。node

setup接收俩个参数(props,context)

props

做为setup函数中的第一个参数,props是响应式的,当传入新的prop时,它将会被更新react

其实就是父组件传递过来的属性会被放到props对象中web

  • 对于定义props的类型,和Vue2同样,在props选项对象中定义
  • 在template中的写法依然是不变的
  • 由于props有直接做为参数传递到setup函数中,因此咱们能够直接经过参数来使用。
export default {
    props:{
        message:{
            type:String,
            default:"hello"
        }
    },
    setup(props){
        consloe.log(props.message)
    }
}
复制代码

!! 因为 props 是响应式的, 因此咱们不能用 解构,它会消除props的响应性。api

若是须要结构props,那么须要用到 toRefs函数来完成操做。数组

import {toRefs} from 'vue'
setup(props){
    const message = toRefs(props,'message')
    console.log(message.value)
}
复制代码

context

做为setup函数的第二个参数, context是一个普通的JavaScript对象,它暴露组件的三个property安全

  • **attrs ** :全部的非prop的attribute
  • slots :父组件传递过来的插槽
  • emit : 当咱们组件内部须要发出事件时用到emit, 和vue2中 this.$emit用法一致

!!因为context只是一个普通的JavaScript对象,因此它不是响应式的,这就意味着咱们能够安全地对 context 使用 解构markdown

export default{
    setup(props,{attrs,slots,emit}){
        ...
    }
}
复制代码

setup函数的返回值

因为setup是一个函数,那么它就会有返回值网络

若是setup的返回值是一个对象,那么该对象的property就能够在模板中访问到。

注意的是:从setup中返回的 refs在模板中是会自动浅解包的,因此没必要在模板中用 .value

有关响应性的API

reactive

返回对象的响应式副本

若是想在setup中定义的数据是具备响应式的,那么就可使用 reactive

const state = reactive({ name:'wangpf',age:18 })
复制代码

为何会经过 reactive 就能够变成响应式的呢?

  • 由于当咱们使用reactvie函数处理咱们的数据以后,数据再次被使用时会进行以依赖收集
  • 当数据发生变化时,全部收集到的依赖都是进行对象的响应式的操做
  • 事实上,在vue2中,咱们编写的 data选项,也是在内部交给了 reactive函数将其变成了响应式对象的

注意的俩点:

  • reactive 将解包全部深层的 refs,同时维持着 ref 的响应性

    const count = ref(0)
    const obj = reactive({ count })
    
    // ref 会被解包
    console.log(count.value === obj.value) // true
    
    // 会更新到 'obj.count'
    count.value++ 
    console.log(count.value) // 1
    console.log(obj.count) // 1
    
    // 也会更新到 ref 'count'
    obj.count++
    console.log(count.value) // 2
    console.log(obj,value) // 2
    复制代码
  • 当把 ref 分配给 reactive 时,将会自动解包

    const count = ref(1)
    const obj = reactive({ })
    obj.count = count
    console.log(obj.count) // 1
    console.log(obj.count === count.value) // true
    复制代码

可是因为 reactive 对传入的类型是有限制的,它要求咱们必须传入的是一个对象或者数组

若是咱们传入的是一个基本的数据类型(String,Number,Boolean)则会报警告.

reactive的限制.png 这时,咱们可使用另外一个API, ref

ref

接受一个内部值并返回一个响应式且可变的 ref 对象。 ref对象具备指向内部值的单个property (.vlaue)

ref 会返回一个可变的响应式对象,该对象做为一个响应式的引用(reference) 维护着它内部的值。

内部的值是在 ref的 vlaue属性中被维护的

readonly

咱们经过 reactive 或者 ref 能够获取到一个响应式的对象,可是某些状况下, 咱们传入给其余地方(组件) 的这个响应式对象但愿在另一个地方(组件)被使用,可是不能被修改,这个时候就能够用 readonly 了

readnoly 会返回原生对象的只读代理 原理在于: 利用 proxy 中的set,在set方法中将其劫持,而且设置该值不能修改

关于reactive 判断 的API

isProxy

检查对象是否由 reactivereadonly 建立的 proxy

const state = reactive({ count : 0 })
const count = 0
isProxy(state) // true
isProxy(readonly(state)) // true
isProxy(count) // false
复制代码

isReactive

检查对象是否由 reactive 建立处理的相应手机代理

但若是代理是由 readonly建立的,但包裹了由 reactive建立 另外一个代理,也会返回 true

const state = reactive({ count : 0 })
isReactive(state) // true
isReactive(readonly(state)) // true
复制代码

isReadOnly

检查对象是否由 readonly建立的只读代理

toRaw

返回 reactive 或 readonly 代理的原始对象 (不建议保留对原始对象的持久化引用,谨慎使用)

shallowReactive

shallow(浅层)

建立一个响应式代理,他跟踪其自身property的响应性,但不执行嵌套对象的深层响应式转换 (深层仍是原生对象)

相似于浅拷贝,只把第一层转为响应式了,深层仍是原始对象

shallowReadonly

建立一个 proxy , 使其自身的 property 为只读, 但不执行嵌套对象的深度只读转换

就是说第一层是只读的,可是深层仍是可读,可写的

toRefs

因为咱们使用ES6的解构语法对 reactive 返回的对象进行解构赋值,那么解构后的数据是不具备响应式的

而使用 toRefs 能够将 reative 返回的对象中的属性都转成 ref 这样咱们再次解构出来的数据都是 ref的。

const state = reactive({ name:'wangpf' , age:18 });
const { name, age } = state; // 这样解构出来的数据是没有响应式的

const { name, age } = toRefs(state) // 这样的解构出来的数据转换成了ref的,是响应式的
// 上述的作法, 至关于在 state.name 和 name.value 之间创建了链接, 修改任何一个都会引发另一个变化
复制代码

toRef

若是咱们但愿转换一个 reactive 对象中的属性为 ref ,那么可使用 toRef 的方法

const state = reactive({ name:'wangpf' , age:18 });
const name = toRef(state,"name"); // 该 name 是ref的, 
// 一样的, name.value 和 state.name 之间创建了链接, 修改会互相影响
复制代码

ref其余的API

unref

若是想要获取一个ref引用中的value,能够经过 unref 方法:

  • 若是参数是一个 ref, 则返回内部值,不然返回参数本事

  • 实际上是一个语法糖:

    • val = isRef(val) ? val.value : val
      复制代码
function useFoo(x: number | Ref<number>) {
  const unwrapped = unref(x) // unwrapped 如今必定是数字类型
}
复制代码

isRef

判断值是不是一个ref对象

shallowRef

建立一个浅层的ref对象

const info = shallowRef({ name: "wangpf" })

const changeInfo  = () => {
    info.value.name = 'wpf'  // 此时的修改是不可以实现响应式的
}
复制代码

triggerRef

手动触发和 shallowRef 相关联的反作用

const info = shallowRef({ name: "wangpf" })

const changeInfo  = () => {
    info.value.name = 'wpf'  // 此时的修改是不可以实现响应式的
    // 使用 triggerRef 能够触发
    triggerRef(info)
}
复制代码

customRef

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

  • 它须要一个工厂函数,该函数接受 track 和 trigger 函数 做为参数
  • 应该返回一个带有 get 和 set 的对象
用 customRef 作的一个防抖案例
import { customRef } from "vue";

export default function (value, delay = 300) {
  let timer = null;
  return customRef((track, trigger) => {
    return {
      get() {
        track();
        return value;
      },
      set(newValue) {
        clearTimeout(timer);
        timer = setTimeout(() => {
          value = newValue;
          trigger();
        }, delay);
      },
    };
  });
}

复制代码
<template>
  <input type="text" v-model="message" />
  <p>{{ message }}</p>
</template>

<script>
import useDebounceRef from "../hooks/useDebounceRef";

export default {
  name: "Demo2",
  setup() {
    const message = useDebounceRef("hello", 300);
    return {
      message,
    };
  },
};
</script>
复制代码

computed

该API 的方法 和 vue2 的同样, 只不过写的地方是在setup函数中了, 但还需注意的点是 computed 返回的值是一个 ref

  • 方式一: 接收一个 getter 函数,并为 getter 函数返回的值,返回一个不变的 ref 对象
const firstName = ref("wangpf");
    const lastName = ref("ok");
	const fullName = computed(()=> `${firstName.value} ${lastName.value}`)
复制代码
  • 方式二 : 接收一个具备 get 和 set 的对象,返回一个可变的(可读写的)ref对象
const firstName = ref("wangpf");
    const lastName = ref("ok");
    const fullName = computed({
      get() {
        return `${firstName.value} ${lastName.value}`;
      },
      set(newValue) {
        const names = newValue.split(" ");
        firstName.value = names[0];
        lastName.value = names[1];
      },
    });
    const changeName = () => {
      fullName.value = "wpf err";
      console.log(fullName.value);
    };
复制代码

侦听数据的变化 (watch watchEffect)

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

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

watchEffect

响应式计算和侦听 | Vue.js (vuejs.org)

当侦听到某些响应式数据变化时,咱们但愿执行某些操做,这个时候就能够用 watchEffect

来看一个案例:

const name = ref('wangpf')
const age = ref(18)

watchEffect(() => {
    console.log('watchEffect执行了',name.value,age.value)
})
复制代码
  • 经过以上代码,首先 watchEffect 传入的函数会当即被执行一次,而且在执行的过程当中会收集依赖
    • (为何须要当即执行一次的缘由就是须要去收集依赖)
  • 其次,只有收集的依赖发生变化时,watchEffect 传入的函数才会被执行
watchEffect 的中止侦听
  • 若是在发生某些状况下,咱们但愿中止侦听,这个时候咱们能够获取 watchEffect 的返回值函数, 调用它便可

看一个案例:

const stopWatch = watchEffect(() => {
    console.log('watchEffect执行了',name.value,age.value)
})
const changeAge = () => {
    age.value++;
    if(age.value > 20){
        stopWatch() // 掉用 watchEffect 的返回值就能够中止侦听了
    }
}
复制代码
watchEffect 清除反作用

用途: 好比在开发中咱们须要在侦听函数中执行网络请求,可是在网络请求尚未达到的时候,咱们中止了侦听器,或者侦听器侦听函数被再次执行了,这时咱们须要把上一次的网络请求 取消掉, 就能够用到该方法了

看了一个案例:

watchEffect((onInvalidate) => {
    console.log('watchEffect执行了',name.value,age.value)
    const timer = setTimeout(() => {
        console.log('1s后执行的操做')
    },1000)
    onInvalidate(() => {
        // 在这里操做一些清除工做
        clearTimeour(timer)
    })
})
复制代码

在上述代码中,咱们给 watchEffect 传入的函数被回调时,能够获取到一个参数:onInvalidate (该参数是一个函数),能够在这个参数中,执行一些清除工做。

watchEffect 的执行时间(刷新时机)

在默认状况下 , 组件的更新会在 watchEffect (反作用函数) 以前执行

const title = ref(null);  // 该title 已和 div 标签绑定了
    watchEffect(() => {
      console.log(title.value);
    });
    return { title };
复制代码

watchEffect 的执行时间.png 那么,当咱们在 watchEffect (反作用函数) 获取元素时, 第一次执行是确定是个null, 是不能够的。只有当DOM挂载完毕后,才会给 title 赋新的值,watchEffect (反作用函数)才会再次执行, 打印出对应的元素

咱们但愿第一次就打印出该元素的话,这时候须要给 watchEffect 传入第二个参数, 该参数是一个 对象,对象中的 flush 可取三个值 : 'pre' (默认的) , 'post' , 'async' (不建议使用)

// 在组件更新后触发,这样你就能够访问更新的 DOM。
// 注意:这也将推迟反作用的初始运行,直到组件的首次渲染完成。
watchEffect(
  () => {
    /* ... */
  },
  {
    flush: 'post' // 在 DOM元素挂载或更新以后执行, "pre" 当即执行 (默认的)
  }
)
复制代码

watch

watch API 的功能和 vue 2 的 option API 中的 watch 功能同样, 默认状况下, watch 只有当被侦听的源发送改变时才会去回调

与 watchEffect 比较,不一样的是:

  • watch 是 懒执行反作用
  • 更具体地说明了什么状态下应该触发侦听器从新运行
  • 能够访问到侦听状态变化先后的值
侦听单个数据源

想要侦听单个数据源的话, 有俩种方法: 传入 getter 函数 或 ref 对象

// 侦听 getter 
const state = reactive({ name : 'wangpf' })
watch(() => state.name , (newVal,oldVal) => { 
	/* ... */
})

// 侦听 ref
const name = ref('wamgpf')
watch(name,(newVal,oldVal) => { 
  /* ... */
  // 这里的 newVal,oldVal 的值是 是返回的 ref.value 的值
})
复制代码
侦听多个数据源

方法: 传入数组

const firstName = ref("AAA");
    const lastName = ref("bbb");
    const changeName = () => {
      firstName.value = "A";
      lastName.value = "b";
    };
    watch([firstName, lastName], (newVal, oldVal) => {
      console.log("newVal:", newVal, "oldVal:", oldVal);
    });
// newVal:  ["A", "b"] oldVal:  ["AAA", "bbb"]
复制代码
侦听响应式对象

就是侦听 reactive 对象

const numbers = reactive([1, 2, 3, 4])

watch(
  () => [...numbers],
  (numbers, prevNumbers) => {
    console.log(numbers, prevNumbers)
  }
)

numbers.push(5) // logs: [1,2,3,4,5] [1,2,3,4]
复制代码

想要深度侦听嵌套对象或数组时,须要 deep 设为 true,

const state = reactive({ 
  id: 1,
  attributes: { 
    name: '',
  }
})

watch(
  () => state,
  (state, prevState) => {
    console.log(
      'not deep',
      state.attributes.name,
      prevState.attributes.name
    )
  }
)

watch(
  () => state,
  (state, prevState) => {
    console.log(
      'deep',
      state.attributes.name,
      prevState.attributes.name
    )
  },
  { deep: true }
)

state.attributes.name = 'Alex' // 日志: "deep" "Alex" "Alex"
复制代码

可是会发现 新的值和旧的值是同样的。这时候为了彻底侦听,须要使用深拷贝了

其余API

生命周期函数

em.... 去看文档吧,

生命周期钩子 | Vue.js (vuejs.org)

Provide / Inject

功能和以前同样,

咱们能够经过 provide 来提供数据

  • 经过 provide 方法来定义每一个 property
  • 传入俩个参数: name(提供的属性名称), value(提供的属性值)

咱们能够经过 jnject 来注入须要的内容

  • 要 inject 的 property 的 name
  • 默认值 (可选)
let count = ref(100)
let info = { name : "wangpf" , age : 18 }
provide("count",readonly(count))
provide("info",readonly(info))   // 这里建议使用 readonly 对值进行包裹,防止传递的数据不会被 inject 的组件更改

// 在后代组件中 经过 inject 来获取
const count = inject("count")
const info = inject("info")
复制代码

h函数

vue在生成真实DOM以前,会将咱们的节点转换成VNode,而VNode组合起来会造成一颗树结构,即 虚拟DOM

在 template 中的 html 是使用渲染函数生成的对应的VNode

若是咱们想要利用 JavaScript 来编写 createVNode 函数,生成对应的VNode 那么就可使用 h() 函数

h() 函数是一个用于常见 VNode 的一个函数, 其实更准确的命名是 createVNode() 函数,但为了简便 vue 将其简化为 h() 函数

h() 函数接收三个参数: (标签,属性,后代)

h(
  // {String | Object | Function} tag
  // 一个 HTML 标签名、一个组件、一个异步组件、或
  // 一个函数式组件。
  //
  // 必需的。
  'div',

  // {Object} props
  // 与 attribute、prop 和事件相对应的对象。
  // 咱们会在模板中使用。
  //
  // 可选的。
  {},

  // {String | Array | Object} children
  // 子 VNodes, 使用 `h()` 构建,
  // 或使用字符串获取 "文本 Vnode" 或者
  // 有插槽的对象。
  //
  // 可选的。
  [
    'Some text comes first.',
    h('h1', 'A headline'),
    h(MyComponent, {
      someProp: 'foobar'
    })
  ]
)
复制代码

注意:若是没有props,能够将 children 做为第二个参数传入, 可是会产生歧义,因此通常会将 null 做为第二个参数传入,将 children 做为第三个参数传入

h函数的基本使用

  • 能够在 render 函数选项中使用
  • 能够在 setup 函数选项中使用
export default {
    render(){
        return h('div', { class:'app' }, 'hello app')
    }
}

export default {
    setup(){
        return () => h('div', { class:'app' }, 'hello app')
    }
}
复制代码

这样写代码,不只慢并且阅读性通常, 因此 推荐使用 jsx, 语法和 react同样, 这里不细说了。可看文档

jsx | Vue.js (vuejs.org)

自定义指令

自定义指令 | Vue.js (vuejs.org)

在 Vue 中,代码复用和抽象的主要形式是组件。然而,有的状况下,你仍然须要对普通 DOM 元素进行底层操做,这时候就会用到自定义指令。

自定义指令分为俩种:

  • 自定义局部指令: 组件中经过 directives 选项,只能在当前组件中使用。
  • 自定义全局指令: app的 directive 方法, 能够在任意组件中被使用。

指令的生命周期

一个指令定义的对象,Vue提供了以下的几个钩子函数:

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

这几个钩子函数中可传入四个参数:elbindingvnodeprevNnode

案例:时间格式化的指令

时间格式化的指令.png

Teleport

Teleport | Vue.js (vuejs.org)

瞬移组件, 能够将该组件转移到其余dom元素上。

一般用于 封装模态框、土司之类的,将它放在Body元素上和 div#app 元素平级

俩个属性

  1. to: 指定将其中的内容移动到的目标元素,可使用选择器
  2. disabled: 是否禁用 teleport 的功能
<teleport :to='#demo'>
    <h2>hello</h2>
</teleport>

// 该元素就会被转移到 id 为 demo 元素上
复制代码

Vue插件

插件 | Vue.js (vuejs.org)

一般状况下,咱们向Vue全局去添加一些功能时,会采用插件的模式

俩种编写方式

  1. 对象类型
    • 一个 对象,可是必须包含一个 install 的函数,该函数会在安装插件时执行
  2. 函数类型
    • 一个 function,这个函数会在 安装插件时自动执行
// plugin_obect.js
export default {
  install(app) {
    app.config.globalProperties.$name = "wangpf";
  },
};

// main.js
import plugin_object from "./plugins/plugin_object";
app.use(plugin_object);

// app.vue 
import { getCurrentInstance } from "vue";
 setup() {
    const Instance = getCurrentInstance();
    console.log("Instance", Instance.appContext.config.globalProperties.$name);
 }
复制代码
相关文章
相关标签/搜索