# vue3 核心技术

# 1. 介绍

vue 开发者工具:

# 2. vite 创建 vue3 工程

Vite 需要 Node.js 版本 18+ 或 20+。

示例:

npm create vite@latest

总结:

  • Vite 项目中,index.html 是项目的入口文件
  • Vite 解析 <script type="module" src="src/main.ts">

参考:

# 3. 核心语法

# 3.1. OptionsAPI 与 CompositionAPI

Vue2 的 API 设计是 Options(配置)风格的

  • 按配置项来拆分(组织)代码

Vue3 的 API 设计是 Composition(组合)风格的

  • 按业务功能来拆分(组织)代码

# 3.2. setup

# 3.2.1. 概述

说明:

  • setup 是 Vue3 中一个新的配置项,组合式 API 在 setup 中才会生效

特点:

  • setup 函数返回的对象中的内容,可直接在模板中使用。
  • setup 中的 this 指向 undefined
  • setup 函数会在 beforeCreate 之前调用,它是“领先”所有钩子执行的。

示例:

<template>
  <div>{{ name }}</div>
  <button @click="showName">showName</button>
</template>
<script>
export default {
  setup() {
    const name = '张三';

    const showName = () => {
      alert(name);
    };

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

# 3.2.2. setup() 的返回值

setup() 的返回值:

  • 对象: 对象中属性、方法可以直接在模板中使用
  • 函数: 可以自定义渲染内容

示例:

export default {
  setup() {
    let name = '张三';
    
    return { name };
  }
}

export default {
  setup() {
    return () => '张三';
  }
}

# 3.2.3. setup 与 Options API 的关系

说明:

  • 在 Options API 中可以通过 this 读取到 setup() 返回的内容
  • 因为 setup() 先执行, 如果与 Options API 中暴露的属性名称冲突,则以 Options API 为准

示例:

<template>
  <div>{{ message }}</div> <!-- hello -->
  <div>{{ msg }}</div>     <!-- hello -->
  <div>{{ name }}</div>    <!-- 李四 -->
</template>
<script lang="ts">
export default {
  setup() {
    return {
      name: '张三',
      message: 'hello',
    }
  },

  data() {
    return {
      name: '李四',
      msg: this.message,
    }
  }
}
</script>

# 3.2.4. setup 语法糖

说明:

  • 彻底废弃选项式的写法

示例:

<template>

  <div>{{ name }}</div>
  <button @click="handleClick">button</button>
  
</template>

<script lang="ts" setup>

  let name = '张三';

  function handleClick() {
    console.log(`hello, ${name}`);
  }

</script>

# 3.2.5. vite-plugin-vue-setup-extend

说明:

  • 引入此插件后,可以通过 <script name="组件的名称"> 的方式设置组件的名称

安装:

npm i -D vite-plugin-vue-setup-extend

## "vite-plugin-vue-setup-extend": "^0.4.0",

配置: (vite.config.ts)

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import VitePluginVueSetupExtend from 'vite-plugin-vue-setup-extend'

export default defineConfig({
  plugins: [
    vue(),
    VitePluginVueSetupExtend(),
  ],
})

使用:

<template>
  <div>{{name}}</div>
</template>

<script lang="ts" setup name="MyComponent">
let name = 'zhangsan'
</script>

# 3.3. ref 创建:基本类型的响应式数据

作用:

  • 定义响应式变量

语法:

  • let xxx = ref(初始值)

返回值:

  • RefImpl 的实例对象,简称 ref对象ref
  • ref对象value 属性是响应式的

注意点:

  • 在 JS 中读写 ref对象 需要带 value 属性
  • 在 模板 中使用 ref对象 不能带 value 属性
  • 对于let name = ref('张三')来说,name不是响应式的,name.value是响应式的

示例:

<template>
  <div>{{age}}</div>
  <button @click="handleClick">button</button>
</template>
<script lang="ts" setup>
import { ref } from 'vue';

let age = ref(18);

function handleClick() {
  age.value += 1;
}
</script>

# 3.4. reactive 创建:对象类型的响应式数据

作用:

  • 定义一个 响应式对象 (基本类型不要用它,要用ref,否则报错)

语法:

  • let 响应式对象= reactive(源对象)

返回值:

  • 一个 Proxy 的实例对象,简称:响应式对象

注意点:

  • reactive 定义的响应式数据是“深层次”的,即任意层级的属性都是响应式的

示例:

<template>
  <div>{{person.age}}</div>
  <button @click="handleClick">button</button>
</template>
<script lang="ts" setup>
import { reactive } from 'vue';

let person = reactive({
  name: '张三',
  age: 18
});

function handleClick() {
  person.age += 1;
}
</script>

# 3.5. ref 创建:对象类型的响应式数据

说明:

  • ref() 接收的是数据可以是 基本类型、对象类型
  • ref() 接收对象类型数据时,内部实际调用了 reactive()
  • ref() 接收对象类型数据时,也需要使用 .value 来获取代理对象

示例:

<template>
  <div>{{person.age}}</div>
  <button @click="handleClick">button</button>
</template>
<script lang="ts" setup>
import { ref } from 'vue';

let person = ref({
  name: '张三',
  age: 18
});

function handleClick() {
  person.value.age += 1;
}
</script>

# 3.6. ref 对比 reactive

用法:

  • ref用来定义:基本类型数据、对象类型数据
  • reactive用来定义:对象类型数据

区别:

  • ref创建的变量必须使用.value
  • reactive创建的变量,重新赋值(整个对象)会失去响应式

原则:

  1. 若需要一个基本类型的响应式数据,必须使用ref
  2. 若需要一个响应式对象,层级不深,refreactive都可以。
  3. 若需要一个响应式对象,且层级较深,推荐使用reactive

示例:

<template>
  <div>{{person}}</div>
  <button @click="handleClick">button</button>
</template>
<script lang="ts" setup>
import { reactive } from 'vue';

let person = reactive({
  name: '张三',
  age: 18
});

function handleClick() {
  // 如果变量的值 是 reactive 定义的响应式数据,则只能修改其属性,不能重新赋值
  // ref 则没有这个问题
  Object.assign(person, { name: '李四', age: 20 })
}
</script>

# 3.7. toRefs 与 toRef

说明:

  • 将响应式对象中的属性定义为 ref 对象,ref 对象的值变化 时 响应式属性的值也跟着变化
  • toRef 针对单个属性
  • toRefs 针对所有属性

示例:

<template>
  <div>{{person}}</div>
  <div>name: {{ name }}</div>
  <div>age: {{ age }}</div>
  <div>gender: {{ gender }}</div>
  <button @click="changeName">changeName</button>
  <button @click="changeAge">changeAge</button>
  <button @click="changeGender">changeGender</button>
</template>
<script lang="ts" setup>
import { reactive, toRef, toRefs } from 'vue';

let person = reactive({
  name: '张三',
  age: 18,
  gender: '男',
});

// 直接解构响应式对象的属性,name 不是响应式的
let { name } = person;

// 将 响应式对象 中的 某个 属性定义为 ref 对象
let age = toRef(person, 'age');

// 将 响应式对象 中的 所有 属性定义为 ref 对象
let { gender } = toRefs(person);

// 执行后,不会触发模板的重新渲染
function changeName() {
  name += '~';
}

function changeAge() {
  age.value += 1;
}

function changeGender() {
  gender.value += '!';
}
</script>

# 3.8. computed

说明:

  • Vue2 中的 computed 作用一致

示例:

<template>
<div>姓: <input type="text" v-model="firstName"></div>
<div>名: <input type="text" v-model="lastName"></div>
<div>姓名1: {{ fullName1 }}</div>
<div>姓名2: {{ fullName2 }}</div>
<div><button @click="changeFullName2">changeFullName2</button></div>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue';

const firstName = ref('张');
const lastName = ref('三');

// 只读
const fullName1 = computed(() => {
  return firstName.value + lastName.value;
});

// 可读可写
const fullName2 = computed({
  get() {
    return firstName.value + lastName.value;
  },
  set(val) {
    firstName.value = val.slice(0, 1);
    lastName.value = val.slice(1);
  }
}); 

function changeFullName2() {
  fullName2.value = '娃哈哈';
}
</script>

# 3.9. watch

作用:

  • 监视数据的变化
  • Vue2 中的 watch 作用一致

Vue3 中的 watch 只能监视以下 四种数据:

  1. ref定义的数据。
  2. reactive定义的数据。
  3. 函数返回一个值(getter函数)。
  4. 一个包含上述内容的数组。

# 3.9.1. 情况一

说明:

  • 监视ref定义的【基本类型】数据
  • 直接写数据名即可,监视的是其value值的改变。

示例:

<template>
<div>sum: {{ sum }}</div>
<div><button @click="changeNum">changeNum</button></div>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';

const sum = ref(0);

const stopWatchSum = watch(sum, (newVal, oldVal) => {
  console.log(`newVal=${newVal}, oldVal=${oldVal}`);

  if (newVal > 3) {
    stopWatchSum();
    console.log('取消监视');
  }
});

function changeNum() {
  sum.value += 1;
}
</script>

# 3.9.2. 情况二

说明:

  • 监视ref定义的【对象类型】数据:直接写数据名,监视的是对象的【地址值】,若想监视对象内部的数据,要手动开启深度监视。

注意:

  • 若修改的是ref定义的对象中的属性,newValueoldValue 都是新值,因为它们是同一个对象。
  • 若修改整个ref定义的对象,newValue 是新值, oldValue 是旧值,因为不是同一个对象了。

示例:

<template>
<div>{{ person }}</div>
<button @click="changeName">changeName</button>
<button @click="changePerson">changePerson</button>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';

const person = ref({ name: '张三', age: 18 });

function changeName() {
  person.value.name += '~';
}

function changePerson() {
  person.value = { name: '李四', age: 20 };
}

watch(
  person,
  // 属性改变后,新旧值是同一个值,即 person 的对象
  // person 引用改变后,新旧值不一样
  (newVal, oldVal) => {
    console.log('watch', newVal, oldVal );
  }, 
  { 
    // 如果为 false,则对象的数据变化不会触发 watch
    deep: true, 
    // 初始化时,数据没变化也会执行一次
    immediate: true,
  }
);

</script>

# 3.9.3. 情况三

说明:

  • 监视reactive定义的【对象类型】数据,且默认开启了深度监视(且无法关闭)

示例:

<template>
<div>{{ person }}</div>
<button @click="changeName">changeName</button>
<button @click="changePerson">changePerson</button>
</template>
<script lang="ts" setup>
import { reactive, watch } from 'vue';

const person = reactive({ name: '张三', age: 18 });

function changeName() {
  person.name += '~';
}

function changePerson() {
  Object.assign(person, { name: '李四', age: 20 });
}

watch(person, (newVal, oldVal) => {
  console.log('watch', newVal, oldVal );
});
</script>

# 3.9.4. 情况四

说明:

  • 监视refreactive定义的【对象类型】数据中的某个属性

注意:

  • 监视的属性是基本类型,只能写函数式
  • 监视的属性是对象类型,建议写函数式
    • 对象的引用变化时,新旧值不一样
    • 对象的属性变化时,新旧值为同一个

示例:

<template>
<div>{{ person }}</div>
<button @click="changeName">changeName</button>
<button @click="changeCarBrand">changeCarBrand</button>
<button @click="changeCar">changeCar</button>
</template>
<script lang="ts" setup>
import { reactive, watch } from 'vue';

const person = reactive({ 
  name: '张三', 
  age: 18, 
  car: { brand: '比亚迪', color: '白色' } 
});

function changeName() {
  person.name += '~';
}

function changeCarBrand() {
  person.car.brand = '奔驰'
}

function changeCar() {
  person.car = { brand: '宝马', color: '红色' }
}

// 监视基本类型的属性
watch(() => person.name, (newVal, oldVal) => {
  console.log('person.name', newVal, oldVal );
});

// 监视对象的类型的属性: 默认监视 整个对象(对象的引用),可以设置深度监视
watch(() => person.car, (newVal, oldVal) => {
  console.log('person.name 函数式', newVal, oldVal );
}, { deep: true })

// 监视对象的类型的属性: 只能监视到 对象的属性变化
watch(person.car, (newVal, oldVal) => {
  console.log('person.name 直接写', newVal, oldVal );
})
</script>

# 3.9.5. 情况五

说明:

  • 监视上述的多个数据

示例:

watch(
  [
    () => person.name, // 基本类型
    () => person.car // 对象类型,深度监视需要开启 deep
  ], 
  (newVal, oldVal) => {
    // newVal: [ name的新值,car的新值 ]
    // oldVal: [ name的旧值,car的旧值 ]
    console.log(newVal, oldVal);
  },
  { deep: true }
)

# 3.10. watchEffect

说明:

  • 官网:立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行该函数。
  • 类比 Vue2 中的计算属性

watch对比watchEffect

  • 都能监听响应式数据的变化,不同的是监听数据变化的方式不同
  • watch:要明确指出监视的数据
  • watchEffect:不用明确指出监视的数据(函数中用到哪些属性,那就监视哪些属性)

示例:

<template>
<div>({{ x }}, {{ y }})</div>
<button @click="changeX">changeX</button>
<button @click="changeY">changeY</button>
</template>
<script lang="ts" setup>
import { ref, watchEffect } from 'vue';

const x = ref(0);
const y = ref(0);

function changeX() {
  x.value += 1;
}
function changeY() {
  y.value += 1;
}

const stopWatch = watchEffect(() => {
  if (x.value >= 3 || y.value >= 4) {
    console.log('send request');
    stopWatch();
  } 
})
</script>
本章目录