Vue Composition API(更新中)

setup函数简介

函数主要参数:

  • props
  • context

props就是父组件传递过来的属性,当然我们需要事先在组件中配置接收props

    props:{
      name:{
        type:String,
        default:""
      },
      age:{
        type:Number,
        default:10
      }
    },
    setup(props, context) {
        console.log(props);
    }

context是个对象,也可以称之为SetupContext ,包含三个对象:

  • attrs 所有非props中配置的attr,比如再父组件中对组件设置的id class 等属性
  • slots 父组件传递过来的插槽,一般我们使用render函数渲染组件的时候会用到
  • emit 当我们租价内部想向父组件发出事件时会用到

注意:setup函数中不能使用this ,因为 this并没有指向当前组件实例,并且在setup被调用之前,datacomputedmethods等都没有被解析.所以无法在setup中获取this

函数返回值

setup函数返回值首先应该是一个对象(不是的话会报警告)可以用作template 模板中使用,用来代替data选项,我们也可以在返回对象中添加函数,也就代替methods 选项了

    setup(props, context) {
      console.log(props, context);
      const increment = () => {
          //...
      }
      return {
        message:"hello setup",
        title:"zhang",
        increment
      }

注意我们在setup返回的数据是没有响应式特性了,所以要想使用响应式的数据我们可以使用以下API:

Reactive API

使用方式

import { reactive } from "vue"
setup() {
  const counterState = reactive({counter:100});
  const increment = () => {
      counterState.counter++;
  }
  return {
      increment,
      counterState
  }
}
<div>{{counterState.counter}}</div>

这样就可以实现响应式了,但是ractive 这个API只能传入一个对象或者数组类型,传入基本类型会报警告,而且使用的时候还需要以对象取属性方式手动解包,所以我们还可以使用另一个API ref

Ref API

ref 会返回一个可变的响应式对象。该对象作为一个响应式的引用维护着它内部的值,这就是ref名称的来源

import { ref } from "vue"
setup() {
  const counter = ref(100)
  const increment = () => {
      //这里使用是不会为我们自动解包
      counter.value++
  }
  return {
      increment,
      counter
  }
}
<!--模板中vue为了让我们书写更加简便,给我们自动做了解包处理(实际写法为 counter.value)-->
<div>{{counter}}</div>

另外将ref处理后的返回值放到ractive的对象中也是会为我们解包的

const name = ref("zhangsan");
const infoState = reactive({
    age:18,
    name//实际为name:name.value
});

readonly

readonly 返回一个原生对象的代理,这个代理是只读的,有时候我们传递给其他组件数据,但是只想让其展示,不做修改就可以这样使用

//一般readonly方法会传入三个类型的参数
/*
  1. 普通对象
  2. reactive返回的对象
  3. ref返回的对象
*/
setup(props, context) {      //普通对象
  const info = {name:"lilisi"};
  const readOnlyInfo = readonly(info);
//reactive
  const info2 = reactive({name:"wang"});
  const readOnlyInfo2 = readonly(info2);
 //ref
  const info3 = ref("saber");
  const readOnlyInfo3 = readonly(info3);
  const increment = () => {
          readOnlyInfo.name = "saber";
          readOnlyInfo2.name = "haha";
          readOnlyInfo3.value = "hello";
        }
      return {
        readOnlyInfo,
        readOnlyInfo2,
        readOnlyInfo3
      }
  }

模板中展示:

<div>{{readOnlyInfo.name}}---{{readOnlyInfo2.name}}---{{readOnlyInfo3}}</div>

readonly返回的对象都是不允许修改的, 是经过readonly处理的原来的对象是允许被修改的,所以我们要修改的话就要修改原来的对象,当然数据都是响应式的,毕竟本质上就是readonly返回的对象的setter方法被劫持了而已

Reactive 判断的API

isProxy

判断对象是否是reactive或者readonly创建的proxy

    const state = reactive({name:'zhangsan'});
    const read = readonly({age:18});
    const counter = ref(100);
    console.log(isProxy(state));//true
    console.log(isProxy(read));//true
    console.log(isProxy(counter));//false

isReactive

检查对象是否由reactive 创建的响应式代理,如果该代理是readonly创建的但包裹了由reactive 创建的另一个代理,也会返回true

    const state = reactive({name:'zhangsan'});
    const read = readonly(state);
    const counter = ref(100);
    console.log(isReactive(state));//true
    console.log(isReactive(read));//true
    console.log(isReactive(counter));//false

isReadonly

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

    const state = reactive({name:'zhangsan'});  
    const read = readonly(state);  
    const counter = ref(100);   
    console.log(isReadonly(state));//false  
    console.log(isReadonly(read));//true  
    console.log(isReadonly(counter));//false

toRaw

返回reactive或者readonly代理的原始对象(但是不建议过久的保留对原始对象的引用),该API需要谨慎使用

    const state = reactive({name:'zhangsan'});  
    const read = readonly(state);  
    console.log(toRaw(state));  
    console.log(state);  
    console.log(toRaw(read));  
    console.log(read);

返回结果:
image-20210721212802939

shallowReactive

创建一个响应式代理,它跟踪对象自身的property的响应式,但是不执行嵌套对象的深层次响应式转换(浅响应式转换)

shallowReadonly

创建一个只读代理但是只是浅层次的property为只读,不执行嵌套对象的深层次只读只读转换

Ref相关API

toRefs

有时候我们想使用解构语法来获取值,对于reactive 返回的代理对象我们使用解构后其数据就不再是响应式的

const state = reactive({name:"zhangsan", age:18});
const {name, age} = state;//此时name和age不再具有响应式特性

通过toRefs这个函数可以将reactive返回的对象中的属性转成ref,所以我们解构出来的nameage 都是等同于通过ref创建的对象了,就具有响应式特性

const state = reactive({name:"zhangsan", age:18})
;const {name, age} = toRefs(state);//此时name和age具有响应式特性

toRef

我们有时候不希望将reactive 返回的对象中的所有属性都转换成ref,就只需要转换其中的一个属性为ref,我们就可以使用这个API

const state = reactive({name:"zhangsan", age:18});
const {name} = toRef(state, "name");

unref

有时候我们需要获取一个ref中的value,可以通过该方法
该方法其实就是一个语法糖,实际的代码如下:

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

isRef

判断值是否为ref对象

    const state = reactive({name:'zhangsan'});
    const read = ref(100);
    console.log(isRef(state));//false
    console.log(isRef(read));//false
    console.log(isRef(read.value));//false

shallowRef

创建一个浅层的ref对象

const info = shallowRef({name: "zhangsan"});//修改name值
info.value.name = "lls";//该操作不是响应式的

triggerRef

可以手动触发和shallowRef相关联的副作用,比如shallowRef的副作用就是进行视图的更新,但是深层次属性修改并不会触发该副作用,所以我们可以手动触发该副作用来更新视图

    const info = shallowRef({name:"zhangsan"});  
    setTimeout(() => {  
      info.value.name = "lls";//修改是不会更新视图的
      triggerRef(info);//手动来触发更新视图   
     }, 5000)

customRef

创建一个自定义的ref ,并对其依赖项跟踪和更新触发进行显示控制,该API需要一个工厂函数,函数需要接收tracktrigger函数作为参数,同时该工厂函数需要返回一个对象,该对象带有getset方法
这个其实还比较难理解,这里展示一个例子来简单解释下如何使用:对双向绑定的属性进行节流操作

import { createApp, customRef } from 'vue';
// 自定义ref
export default function (value, delay = 300) {
  let timer = null;
  return customRef((track, trigger) => {
    return {
      get() {
        track(); // 收集依赖
        return value;
      },
      set(newValue) {
        if (timer) {
          clearTimeout(timer);
        }
        timer = setTimeout(() => {
          value = newValue;
          trigger();
        }, delay);
      }
    };
  });
}
 <input type="text" v-model="message"> {{message}}
import debounceRef from './hook/useDebounceRef';   
 setup() {   
   const message = debounceRef("Hello World");   
   return {  
     message  
   }  
 }

computed函数

Composition API中,我们可以在setup函数中使用computed方法来编写一个计算属性

写法一:
接收一个getter函数,并以getter函数返回的值返回一个不变的ref对象(只读)

const firstName = ref("Kobe");
const lastName = ref("Bryant");
const fullName = computed(() => firstName.value + " " + lastName.value);

写法二:

接收一个具有getset的对象,返回一个可变的(可读写)ref对象

const firstName = ref("Kobe");
const lastName = ref("Bryant");   
const fullName = computed({
        get: () => firstName.value + " " + lastName.value,
        set(newValue) {
          const names = newValue.split(" ");
          firstName.value = names[0];
          lastName.value = names[1];
        }
    }); 

setup中使用ref

这里的ref指的是我们用来获取组件或者元素的ref属性
我们需要定义一个ref对象,然后绑定到元素和组件的ref属性上即可(注意这里不能动态绑定该属性)

const title = ref(null);
 <h2 ref="title">哈哈哈</h2>

我们使用title.value就可以获取该元素dom对象了

数据的侦听

Composition API中,我们可以使用watchEffectwatch来完成响应式数据的侦听

watchEffect基本使用

watchEffect传入的函数会被立即执行一次,并且在执行的过程中会收集依赖,只有收集的依赖发生变化时,watchEffect传入的函数才会再次执行

setup() {  
  const name = ref("why");  
  const age = ref(18);  
  const changeName = () => name.value = "kobe"  
  const changeAge = () => age.value++  
  watchEffect(() => {  
    console.log("name:", name.value, "age:", age.value);   
 });

结果是控制台先打印一次,然后修改操作执行时再执行

watchEffect是自动收集响应式数据的依赖,函数内部会自动检测收集响应式的对象,不用我们自己去指定

watchEffect的停止监听
watchEffect函数会返回一个函数,我们执行该函数就可以停止监听

      const name = ref("why");
      const age = ref(18);
      const stop = watchEffect(() => {
        console.log("name:", name.value, "age:", age.value);
      });
     //执行该函数就会停止监听
     stop()

watchEffect清除副作用
有时候我们监听某个数据发生变化再发送网络请求或者其他耗时的操作,但是我们在请求结果还未达到等情况时又触发了侦听,或者停止了侦听器,我们应该把上次的网络请求任务取消掉,这就是副作用
我们可以在给传入watchEffect的回调函数中添加一个参数,我们一般命名为onInvalidate 该参数是一个函数,接收一个回调函数,当副作用即将重新执行或者侦听器被停止时会执行该函数传入的回调函数,我们就可以在该回调函数中做一些清除工作,如取消监听,清除定时器等

      // watchEffect: 自动收集响应式的依赖
      const name = ref("zhangsan");
      const age = ref(18);
      const stop = watchEffect((onInvalidate) => {
        const timer = setTimeout(() => {
          console.log("网络请求成功~");
        }, 2000)
        // 根据name和age两个变量发送网络请求
        onInvalidate(() => {
          // 在这个函数中清除额外的副作用
          clearTimeout(timer);
          console.log("onInvalidate");
        })
        console.log("name:", name.value, "age:", age.value);
      });

watchEffect的执行机制
实例代码如下:

 <h2 ref="title">哈哈哈</h2>
    setup() {
      const title = ref(null);
      watchEffect(() => {
        console.log(title.value);
      })
      return {
        title
      }
    }
  }

默认情况下组件的更新会在副作用函数执行之前,比如我们在watchEffect中通过ref属性获取元素dom,因为watchEffect传入的回调函数会默认立即执行一次,但这时候dom并没有挂载,所以打印对象为null ,当组件挂载完毕后为给我们title.value 赋值,就会触发侦听更新然后再次打印, 结果如下

image-20210721231107055

我们希望第一次打印的时候就获取到dom元素,所以我们就需要修改副作用函数的执行时机
watchEffect函数的第二个参数就是一个配置对象,里面有个属性flush有三个值可设置

  • pre 默认值,会在元素挂载或者更新前就会执行
  • post 会在组件的首次渲染完成后执行
  • sync 强制效果始终同步触发。然而,这是低效的,应该很少使用
    setup() {
      const title = ref(null);
      watchEffect(() => {
        console.log(title.value);
      }, {
        flush: "post"
      })
      return {
        title
      }
    }
  }

这样第一次就可以获取dom元素了

watch

watch的API完全等同于组件watch选项的Property
watch 需要侦听特定的数据源,并在回调函数中执行副作用

watchEffect相比,watch是惰性的,默认只在侦听源发生改变才会执行(第一次不会执行),而且能够更详细的说明当那些状态发生改变时,触发侦侦听器执行,当然可以访问侦听源的变化前后的值

侦听单个数据源

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

这里以一个按钮修改name来演示侦听结果

      const changeData = () => {
        info.name = "kobe";
       //name.value = "kobe";
      }

一个是getter函数,该函数必须返回可响应式的对象(reactive/ref

const info = reactive({name: "why", age: 18});
watch(() => info.name, (newValue, oldValue) => {
   console.log("newValue:", newValue, "oldValue:", oldValue);
})

输出结果:
image-20210721233514766

另一个是直接传入一个可响应式的对象(reactive/ref)一般为ref

      watch(info, (newValue, oldValue) => {
        console.log("newValue:", newValue, "oldValue:", oldValue);
      },)

image-20210721233600463

我们可以发现上面的输出结果还是代理对象,如果我们希望newValueoldValue是一个普通的对象,可以使用getter函数返回响应式对象的副本

      watch(() => {
        return {...info}
        }, (newValue, oldValue) => {
        console.log("newValue:", newValue, "oldValue:", oldValue);
      }) 

image-20210721233847907

监听一个ref对象 ,ref对象获取newValueoldValuevalue值的本身

      const name = ref("why");
      watch(name, (newValue, oldValue) => {
        console.log("newValue:", newValue, "oldValue:", oldValue);
      })

image-20210721234113599

侦听多个数据源

使用数组对多个数据源进行侦听

      const info = reactive({name: "why", age: 18});
      const name = ref("why");
      watch([() => ({...info}), name], ([newInfo, newName], [oldInfo, oldName]) => {
        console.log(newInfo, newName, oldInfo, oldName);
      })

watch的选项

我们可以设置深层次的监听和立即执行监听

watch(name, (newValue, oldValue) => {
        console.log("newValue:", newValue, "oldValue:", oldValue);
 }, {deep:true, immediate:true});

生命周期函数

setup函数中也有特定的函数来代替以往的options API 函数,这些函数都是以onX命名

image-20210722143946802

我们用的时候需要自行导入

  import { onMounted, onUpdated, onUnmounted, ref } from 'vue';
      onMounted(() => {
        console.log("App Mounted1");
      })
      onMounted(() => {
        console.log("App Mounted2");
      })
      onUpdated(() => {
        console.log("App onUpdated");
      })
      onUnmounted(() => {
        console.log("App onUnmounted");
      })

注意setup函数 是围绕 beforeCreatecreated 生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在 setup 函数中编写。

Provide和inject

setup中也可以编写provideinject

使用provide我们需要显式的导入该函数

provide 函数允许你通过两个参数定义 property

  • name(String类型)
  • value
import { provide } from 'vue'
setup() {
    provide('location', 'North Pole')
    provide('geolocation', {
      longitude: 90,
      latitude: 135
    })
 }

使用inject也需要显式的导入函数

inject函数也有两个参数:

  • injectpropertyname
  • 默认值 (可选)
import { inject } from 'vue'  
const userLocation = inject('location', 'The Universe')
const userGeolocation = inject('geolocation')

为了增加 provide 值和 inject 值之间的响应性,我们可以在 provide 值时使用refreactive

    const location = ref('North Pole')
    const geolocation = reactive({
      longitude: 90,
      latitude: 135
    })
    provide('location', location)
    provide('geolocation', geolocation)

另外我们修改响应式的数据最好在数据的提供位置来修改,比如将修改的方法共享,在后代组件中调用

Last modification:November 15, 2021
如果觉得我的文章对你有用,请随意赞赏