返回

Vue3 组合式 API

Table of contents

Open Table of contents

setup

setup 函数实际上就是 选项式 API 的 data 函数 和 methods 函数 的合集

setup 参数

setup 是一个函数

setup 返回值

所有的内容都写在 setup 函数中,若需要 template 模板访问 setup 中定义的变量和方法,则必须让 setup 函数返回这些变量和方法

<template>
  <div id="app">
    <h2>当前count:{{ count }}</h2>
    <button @click="add">+1</button>
    <button @click="reduce">-1</button>
  </div>
</template>

<script>
  export default {
    setup() {
      let count = 0;
      const add = () => {};
      const reduce = () => {};

      // 所有需要template模板访问的变量和方法,都必须写在 setup 函数的返回值中
      return {
        count,
        add,
        reduce,
      };
    },
  };
</script>

响应式数据

ref

参照 setup 返回值 渲染的数据,是非响应式的,虽然数据本身修改,但视图无法监听到其改变。需要使用ref 函数将数据变成响应式

<script>
  import { ref } from "vue";

  export default {
    name: "App",

    setup() {
      // 默认定义的数据都不是响应式,需要使用 ref 修饰
      const count = ref(0);
      const add = () => {
        // 这里的 count 是一个被修饰过对象,而非 数字 0
        console.log(count);
        // 若需要操作变量,需要 .value 才能访问
        count.value++;
      };
      const reduce = () => {
        count.value--;
      };

      return {
        count,
        add,
        reduce,
      };
    },
  };
</script>

count打印

template 模板中引入 ref 的值时,Vue 会自动进行解包操作,所以并不需要在模板中通过 ref.value 的方式来使用。

setup 函数内部,它依然是一个 ref 引用, 所以对其进行操作时,依然需要使用 ref.value 的方式

ref 函数适用于基本类型

封装 hook

若当 setup 内部的逻辑被复用时,可以封装为一个 hooks函数 原版代码:

<template>
  <div id="app">
    <h2>当前count:{{ count }}</h2>
    <button @click="add">+1</button>
    <button @click="reduce">-1</button>
  </div>
</template>

<script>
  import { ref } from "vue";
  export default {
    setup() {
      const count = ref(0);
      const add = () => count.value++;
      const reduce = () => count.value--;

      return {
        count,
        add,
        reduce,
      };
    },
  };
</script>

封装 hooks:

// useCount.js
import { ref } from "vue";

export default function useCount() {
  const count = ref(0);
  const add = () => count.value++;
  const reduce = () => count.value--;

  return {
    count,
    add,
    reduce,
  };
}

使用 hook:

<!-- App.vue -->
<!-- template 模板内容不变 -->
<script>
  // 引入 hooks
  import useCount from "@/hooks/useCount";
  export default {
    name: "App",
    setup() {
      return { ...useCount() };
    },
  };
</script>

reactive

reactive 要求必须传入的是一个对象或者数组类型,如果传入基本类型Number、String、Boolean等,控制台会报警告

reactive警告

<template>
  <div id="app">
    <h2>{{ userInfo.name }}</h2>
    <ul>
      <li v-for="item in userList" :key="item">{{ item }}</li>
    </ul>
    <button @click="userList.splice(0, 1)">删除userList第0项</button>
  </div>
</template>

<script>
  import { reactive } from "vue";

  export default {
    setup() {
      const userInfo = reactive({
        name: "张三",
        age: 20,
      });
      const userList = reactive(["张三", "李四", "王五"]);

      return { userInfo, userList };
    },
  };
</script>

reactive 必须传入对象或者数组类型ref 传入的是基本类型

readonly

传递给其他组件数据时,往往希望其他组件使用我们传递的内容但是不允许它们修改时,可以使用 readonly

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

const reactiveInfo = reactive({ name: "张三", age: 20 });
// readonly 修饰的数据,虽然是响应式,但是只读,无法通过 readonly 修饰的变量去更改数据
const readonlyInfo = readonly(reactiveInfo);

// ref也是一样的
const refName = ref("666");
const readonlyName = readonly(refName);

readonly示例

toRefs

reactive 返回的对象进行解构得到的数据,都不再是响应式的:

const info = reactive({
  name: "张三",
  age: 20,
});
// 直接解构的数据,再去更改 info.name info.age,视图是不会变化的,响应式丢失
// const { name, age } = info

// 使用 toRefs 修饰,可以使数据保持响应式
const { name, age } = toRefs(info);
toRef

当只转换一个 reactive 对象中的属性为 ref, 那么可以使用 toRef 的方法

// toRef 第一个参数:需要解构的对象,第二个参数:需要解构的属性名
const nameRef = toRef(info, "name");

changeName = () => (info.name = "李四");

setup 中的 emit(子传父)

setup 中没有 this,所以子传父的this.$emits("changeEmit",xxx)在 setup 的写法中是不适用的,需要使用如下写法:

export default {
  emits: ["changeEmit"],
  setup(props, context) {
    function changeParentValue() {
      context.emit("changeEmit", xxx);
    }

    return { changeParentValue };
  },
};

setup 接收的第二个参数 context,可以使用 context.emit 进行子传父操作

computed

选项式 computed 写法:

详情可查看 初识计算属性 computed

<script>
  export default {
    data() {
      return {
        firstName: "",
        lastName: "狗蛋",
      };
    },
    computed: {
      // 简化写法
      fullName() {
        return this.firstName + " " + this.lastName;
      },
      // 完整写法
      fullName: {
        get() {
          return this.firstName + " " + this.lastName;
        },
        set(value) {
          const handleName = value.split(" ");
          this.firstName = handleName[0];
          this.lastName = handleName[1];
        },
      },
    },
  };
</script>

组合式 computed 写法:

<script>
  import { reactive computed } from "vue";
  export default {
    setup() {
      const name = {
        firstName:"",
        lastName:"狗蛋"
      }
      // setup 简化写法
      const fullName = computed(() => {
        return name.firstName + " " + name.lastName;
      });
      // setup 完整写法
      const fullName = computed({
        get() {
          return name.firstName + " " + name.lastName;
        },
        set(value) {
          const handleName = value.split(" ");
          name.firstName = handleName[0];
          name.lastName = handleName[1];
        },
      });

      // fullName 经过 computed 修饰,其本身就是一个 Ref,设置值需要通过 .value 访问
      // 若要设置 computed 监听的值,需要书写完整的 get set,并在 set 中书写需要操作的逻辑
      function setFullName(){
        fullName.value = "张 全蛋"
      }

      return {
        fullName,
        setFullName
      };
    },
  };
</script>

computed修饰的返回结果

computed 修饰后,返回的结果就是一个 Ref 修饰的响应式值

ref 获取 Ele 节点

和选项式中的 $ref 的目的是相同的。在 Vue2 中,获取元素 DOM 节点需要 给元素绑定 ref=“xxx” 和 通过 this.$refs.xxx进行访问

详情可查看 refs Vue3 的 setup 中,使用如下方式

<template>
  <div id="app">
    <h2 ref="titleRef">h2标题</h2>
    <button @click="getElements">获取ref</button>
    <MyList ref="cmpRef" />
  </div>
</template>

<script>
  import { ref } from "vue";
  import MyList from "@/components/MyList.vue";

  export default {
    name: "App",
    components: { MyList },

    setup() {
      // 创建 ref 相当于 React 的 const titleRef = useRef()
      const titleRef = ref();
      // 获取 ref 相当于 React 的 titleRef.current
      function getElements() {
        console.log("h2的ref", titleRef.value);
        // 可以通过 cmpRef.value.xxx 访问 组件内部的数据
        console.log("组件的ref", cmpRef.value);
      }
      return {
        titleRef,
        getElements,
        cmpRef,
      };
    },
  };
</script>

ref绑定节点

生命周期

setup 可以用来替代 data 、 methods 、 computed 等等这些选项,也可以替代 生命周期钩子

选项式生命周期详情可查看 Vue2 生命周期

选项式 API组合式 API
beforeCreate-
created-
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeUnmountonBeforeUnmount
unmountedonUnmounted
activatedonActivated
deactivatedonDeactivated

组合式 API 写法:

export default {
  beforeMount() {
    console.log("beforeMount");
  },
};

选项式 API 写法:

import { onMounted } from "vue";

export default {
  setup() {
    onMounted(() => {
      console.log("onMounted");
    });
  },
};

provide/inject

关于 Vue2 的 Provide 和 Inject 点击查看详情

provide 提供数据(响应式和非响应式):

// App.vue
import { ref, provide } from "vue";
import MyList from "./components/MyList.vue";

export default {
  components: { MyList },
  setup() {
    const name = ref("张三");
    // provide 提供的是响应式的,inject注入时得到的也是响应式的数据
    provide("name", name);
    provide("age", 18);
    return { name };
  },
};

inject 注入:

// MyList.vue
import { inject } from "vue";
export default {
  setup() {
    const name = inject("name");
    const age = inject("age");
    // const age = inject("age",100)   // 设置默认值
    return { name, age };
  },
};

侦听数据的变化

watch

function watch(source: string, callback: function, options?: object)

关于 Vue2 的 watch 点击查看详情

Vue2 选项式 API watch

data() {
    return {
      question: null,
    }
  }
  watch: {
    // question 就是需要侦听的变量
    question(newV, oldV) {
      console.log("newV:" + newV, "oldV:" + oldV);
    },
  },

Vue3 组合式 API watch

import { reactive, watch } from "vue";

export default {
  setup() {
    // 1、定义数据
    const info = reactive({ name: "张三", age: 20 });

    // 侦听数据的变量
    watch(
      info,
      (newV, oldV) => {
        // Proxy{...}, Proxy{...}
        console.log(newV, oldV);
      },
      { immediate: true }
    );

    // 侦听 reactive 的变化,需要返回普通对象,而不是 Proxy,需要侦听的参数为回调,并解构
    // 且侦听的参数解构后,默认的 deep:true 会失效,需手动设置为 true
    watch(
      () => ({ ...info }),
      (newV, oldV) => {
        // Proxy{...}, {...}
        console.log(newV, oldV);
      },
      { immediate: true, deep: true }
    );

    return { msg };
  },
};

默认会开启深度侦听

侦听 reactive 的变化,需要返回普通对象,而不是 Proxy,需要侦听的参数为回调,并解构。且侦听的参数解构后,默认的 deep:true 会失效,需手动设置为 true

watchEffect

watchEffect 的原理和 React 的 useEffect 比较类似,不同的是:watchEffect 会自动收集函数内部的依赖,而 useEffect 需要手动传入依赖项

<template>
  <div id="app">
    <h2>当前counter:---{{ counter }}</h2>
    <h2>当前name:---{{ name }}</h2>

    <button @click="counter++">修改counter</button>
    <button @click="name = '李四'">修改name</button>
  </div>
</template>

<script>
  import { ref, watchEffect } from "vue";

  export default {
    setup() {
      // 1、定义数据
      const counter = ref(0);
      const name = ref("张三");
      // 1、watchEffect 传入的函数默认会直接执行
      // 2、在执行过程中,会自动收集依赖,依赖改变,函数重新执行
      watchEffect(() => {
        // 当更改 name 的时候,因为函数内部没有 name 的依赖,所以该函数不会重新执行
        console.log("---", counter.value);
      });

      return { counter, name };
    },
  };
</script>

watchEffect基本使用

停止侦听

当需要手动停止 watchEffect 的侦听时候,可以采用如下写法:

// 1、拿到 watchEffect 的返回值
const stopWatch = watchEffect(() => {
  console.log("---", counter.value);
  if (counter.value > 5) {
    // 重新调用 返回值即可停止侦听
    stopWatch();
  }
});

watchEffect 停止侦听

script setup 语法糖

setup 语法糖,是在 Vue3.2 版本才成为正式版。

定义响应式数据/事件

<template>
  <h2>{{ message }}</h2>
  <button @click="changeMsg">改变msg</button>
  <MyList />
</template>

<script setup>
  import { ref } from "vue";
  import MyList from "@/components/MyList.vue";

  const message = ref("Hello World");

  function changeMsg() {
    message.value = "李四";
  }
</script>

setup 语法糖基本使用

defineProps()和 defineEmits()

defineProps 使用:

<!-- MyList.vue -->
<template>
  <h4>{{ props.name }}---{{ props.age }}</h4>
</template>

<script setup>
  import { defineProps } from "vue";
  const props = defineProps({
    name: {
      type: String,
      default: "默认值",
    },
    age: {
      type: Number,
      default: 0,
    },
  });
</script>

defineProps() 实际上是可以不用导入的,也不用定义 props,访问时直接通过 {{ name }} 就可以进行访问。但是 ESLint 严格模式下,还是需要导入 defineProps 的

defineEmits 使用:

<template>
  <h4>MyList组件</h4>
  <button @click="sendListClick">传给父组件</button>
</template>

<script setup>
  import { defineEmits } from "vue";
  // defineEmits 接收数组,数组内部就是需要发出的 所有事件名
  const emits = defineEmits(["showList"]);
  function sendListClick() {
    // emits 的第一个参数:选择需要发出的事件名,第二个参数:需要发出的数据
    emits("showList", [{ name: "张三", age: 18 }]);
  }
</script>

defineEmits使用

defineExpose()

正常我们可以通过ref="xxx"xxx.value来访问组件内部的数据,但在 setup 语法糖中,使用 <script setup> 的组件是默认关闭的,不会暴露任何在 <script setup> 中声明的绑定,是无法访问其数据实例的。

通过 defineExpose 编译器宏来显式指定在 <script setup> 组件中要暴露出去的 property。这样绑定的 ref 才能访问这个 property。

<script setup>
  import { defineExpose } from "vue";

  function foo() {
    console.log("Children foo Function");
  }
  // 选择性暴露出可以访问的实例
  defineExpose({
    foo,
  });
</script>


上一篇
Vue 路由 状态管理
下一篇
Vue 组件