vue3相关笔记

59 阅读6分钟

组合式API

composition API vs options API

  1. vue2 采用的就是 optionsAPI

    (1) 优点:易于学习和使用, 每个代码有着明确的位置 (例如: 数据放 data 中, 方法放 methods中)

    (2) 缺点: 相似的逻辑, 不容易复用, 在大项目中尤为明显

    (3) 虽然 optionsAPI 可以通过mixins 提取相同的逻辑, 但是也并不是特别好维护

  2. vue3 新增的就是 compositionAPI

    (1) compositionAPI 是基于 逻辑功能 组织代码的, 一个功能 api 相关放到一起

    (2) 即使项目大了, 功能多了, 也能快速定位功能相关的 api

    (3) 大大的提升了 代码可读性可维护性

  3. vue3 推荐使用 composition API, 也保留了options API

    即就算不用composition API, 用 vue2 的写法也完全兼容!!

问题小结:optionsAPI的优缺点是什么? vue3 新增的 compositionAPI 有什么特征? 有什么优势?

体验 composition API

需求: 鼠标移动显示鼠标坐标 x, y

image.png

options API 版本

<template>
    <!-- 可以有多个根元素 -->
    <div>当前鼠标位置</div>
    <div>x: {{ mouse.x }}</div>
    <div>y: {{ mouse.y }}</div>
    <div>当前点击次数:{{ count }}</div>
    <button @click="add">点击</button>
</template><script>
export default {
    // vue2 中采用的是 options API
    // 常见的配置项: data created methods watch computed components
    data() {
        return {
            mouse: {
                x: 100,
                y: 100,
​
            },
            count: 0
        }
    },
    mounted() {
        document.addEventListener("mousemove", this.mouseMove)
    },
    // vue2 叫 destroyed
    destroyed() {
        document.removeEventListener("mousemove", this.mouseMove)
    },
    methods: {
        add() {
            this.count++
        },
        mouseMove(e) {
            this.mouse.x = e.clientX
            this.mouse.y = e.clientY
        }
    }
}
</script>

composition API 版本

<template>
    <!-- 可以有多个根元素 -->
    <div>当前鼠标位置</div>
    <div>x: {{ mouse.x }}</div>
    <div>y: {{ mouse.y }}</div>
    <div>当前点击次数:{{ count }}</div>
    <button @click="add">点击</button>
</template><script>
import { onMounted, onUnmounted, reactive, ref } from 'vue'const useCount = () => {
    // ---------业务A------------
    const count = ref(0)
    const add = () => {
        count.value++
    }
    onMounted(() => {
        // 多个生命周期,不会被合并
        console.log('业务A生命周期');
    })
    // ---------业务A 结束------------
    return { count, add }
}
​
const useMove = () => {
    // -------------业务B-------------
    const mouse = reactive({
        x: 0,
        y: 0,
    })
    const move = (e) => {
        mouse.x = e.pageX
        mouse.y = e.pageY
    }
    onMounted(() => {
        document.addEventListener('mousemove', move)
    })
    onUnmounted(() => {
        document.removeEventListener('mousemove', move)
    })
    // -------------业务B结束-------------
​
    return { mouse }
}
​
/* 
    封装
    1.先把业务代码完成
    2。封装成函数 useMove ,把需要用到的属性/方法等,return { mouse }
    3.页面需要使用,就调用封装好的方法 useMove(),拿到返回的数据
    4. 把返回的数据,return 给 template 使用
*/export default {
    setup() {
        const { count, add } = useCount()
        const { mouse } = useMove()
        // 业务需要的代码,需要 return 出去, template才能使用
        return {
            count,
            add,
            mouse,
        }
    },
}
</script>

抽离逻辑

function useMouse() {
  const mouse = reactive({
    x: 0,
    y: 0,
  })
  const move = (e) => {
    mouse.x = e.pageX
    mouse.y = e.pageY
  }
  onMounted(() => {
    document.addEventListener('mousemove', move)
  })
  onUnmounted(() => {
    document.removeEventListener('mousemove', move)
  })
  return mouse
}
​
function useCount() {
  const count = ref(0)
  const add = () => {
    count.value++
  }
  return {
    count,
    add,
  }
}
​

问题小结:optionsAPI的优缺点是什么? vue3 新增的 compositionAPI 有什么特征? 有什么优势?

optionsAPI:

  • 优点:易于学习和使用, 每个代码有着明确的位置
  • 缺点: 相似的逻辑, 不容易复用

compositionAPI:

  • 基于 逻辑功能 组织代码
  • 可维护性好!

setup 函数

setup - 生命周期图示

composition api的使用, 需要配置一个setup 函数

  1. setup 函数是一个新的组件选项, 作为组件中 compositionAPI 的起点
  2. 从生命周期角度来看, setup 会在 beforeCreate 钩子函数之前执行
  3. setup 中不能使用 this, this 指向 undefined
  4. 在模版中需要使用的数据和函数,需要在 setup 返回。
<template>
  <div class="container">
    <h1 @click="say()">{{msg}}</h1>
  </div>
</template><script>
export default {
  setup () {
    console.log('setup执行了')
    console.log(this)
    // 定义数据和函数
    const msg = 'hi vue3'
    const say = () => {
      console.log(msg)
    }
​
    return { msg , say}
  },
  beforeCreate() {
    console.log('beforeCreate执行了')
    console.log(this)
  }
}
</script>

reactive 函数

前置说明:

  1. setup 需要有返回值, 只有返回的值才能在模板中使用
  2. 默认普通的数据, 不是响应式的

作用: 传入一个复杂数据类型,转换成响应式数据 (返回该对象的响应式代理)

<template>
    <h1>hello vue3</h1>
    <div>{{ obj }}</div>
    <button @click="add">点我+1</button>
</template><script>
// 1. 必须要导入,才能使用
import { reactive } from 'vue';
​
export default {
    setup() {
        // 2. reactive 包裹起来才是响应式对象
        const obj = reactive({
            name: 'zs',
            age: 20
        })
​
        const add = () => {
            obj.age++
            console.log('obj', obj);
        }
        return { obj, add }
    },
}
</script>

总结: 通常是用来定义响应式 对象数据

问题小结:

  1. 默认 setup 函数中返回的 普通对象 是响应式的么 ?
  2. reactive 函数的作用是什么 ?

ref 函数

reactive 处理的数据, 必须是复杂类型, 如果是简单类型无法处理成响应式,此时需要使用 ref 函数!

image.png

作用: 对传入的数据(一般简单数据类型),包裹一层对象, 转换成响应式。

  1. ref 函数接收一个的值, 返回一个ref 响应式对象, 有唯一的属性 value
  2. 在 setup 函数中, 通过 ref 对象的 value 属性, 可以访问到值
  3. 在模板中, ref 属性会自动解套, 不需要额外的 .value
  4. ref函数也支持传入复杂类型,传入复杂类型,也会做响应式处理
<template>
    <h1>hello vue3</h1>
    <!-- 4. template自动解套,不需要.value -->
    <div>{{ money }}</div>
    <button @click="add">点我+1000</button>
    <button @click="money += 1000">点我+1000</button>
</template><script>
/* 
1. 先引进 ref
2. 调用 ref ,变成响应式数据
3. ref调用的话,在 script 里面需要 .value 
4. template自动解套,不需要.value 
*/// 1. 先引进 ref
import { ref } from 'vue'
export default {
    setup() {
        // 2. 调用 ref ,变成响应式数据
        let money = ref(10000)
        console.log('money', money);
​
        const add = () => {
            // 3. ref调用的话,在 script 里面需要 .value 
            money.value += 1000
        }
​
        return { money, add }
    },
}
</script>

ref 和 reactive 的最佳使用方式:

  • 明确的对象,明确的属性,用reactive,其他用 ref
  • 建议尽量使用 ref

问题小结:

  1. 默认 setup 函数中返回的 普通数据 是响应式的吗 ?
  2. 如果不是,那可以用什么函数包裹起来 ?
  3. ref 函数会返回 ref对象,在script中使用需要添加 .value吗? 在模板中需要 .value 吗?

script setup语法(★)

script setup是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。相比于普通的 script 语法更加简洁

要使用这个语法,需要将 setup attribute 添加到 <script> 代码块上:

<script setup>
console.log('hello script setup')
</script>

顶层的绑定会自动暴露给模板,所以定义的变量,函数和import导入的内容都可以直接在模板中直接使用

<template>
    <h1>{{ money }}</h1>
    <button @click="add">点我+1000</button>
</template><!-- <script>
import { ref } from 'vue';
​
export default {
    setup() {
        let money = ref(10000)
        console.log('money', money);
        const add = () => {
            money.value += 1000
        }
        return { money, add }
    },
}
</script> --><!-- 
    需要在 script 加上  setup 的属性,开启语法糖
    1. export default 不用自己写
    2. setup() 不需要自己写
    3. return 的数据,不需要自己写
 --><script setup>
import { ref } from 'vue';
​
let money = ref(10000)
console.log('money', money);
const add = () => {
    money.value += 1000
}
</script>

案例:显示鼠标案例

使用setup语法完成鼠标案例

<template>
    <div>当前鼠标位置</div>
    <div>x: {{ mouse.x }}</div>
    <div>y: {{ mouse.y }}</div>
    <div>当前点击次数:{{ count }}</div>
    <button @click="add">点击</button>
</template><script setup>
import { onMounted, onUnmounted, reactive, ref } from 'vue';
// -------------业务A------------
const mouse = reactive({
    x: 100,
    y: 100
})
const mouseMove = (e) => {
    mouse.x = e.clientX
    mouse.y = e.clientY
}
​
onMounted(() => {
    document.addEventListener('mousemove', mouseMove)
})
onUnmounted(() => {
    document.removeEventListener('mousemove', mouseMove)
})
​
​
// -------------业务B------------
const count = ref(0)
const add = () => {
    count.value++
}
​
</script>

计算属性computed函数

computed函数调用时, 要接收一个处理函数, 处理函数中, 需要返回计算属性的值

<template>
    <div>我今年的年纪 <input type="text" v-model.number="age" /></div>
    <div>我明年的年龄 {{ nextAge }}</div>
    <div>我后年的年龄 <input type="text" v-model="nextAge2" /></div>
</template><script setup>
import { computed, ref } from 'vue';
​
const age = ref(20)
​
// 写法1,直接返回数据
const nextAge = computed(() => {
    return age.value + 1
})
​
// 写法2,使用set/get钩子函数,一般应用在 既可以输入,也可以依赖其他数据
const nextAge2 = computed({
    get() { return age.value + 2 },
    set(val) { age.value = val - 2 },
})
</script>

侦听器watch函数

watch监视, 接收三个参数
1. 参数1: 监视的数据源
2. 参数2: 回调函数
3. 参数3: 额外的配置
<script setup>
import { ref, watch } from 'vue';
​
const money = ref(10000)
const count = ref(0)
const obj = ref({
    name: 'zs',
    age: 20
})
// watch监视, 接收三个参数
// 1. 参数1: 监视的数据源
// 2. 参数2: 回调函数
// 3. 参数3: 额外的配置// 1. 监听单个属性(常见)
// watch(money, (val, oldVal) => {
//     console.log('现在的工资', val);
//     console.log('原来的工资', oldVal);
// })// 2. 监听多个数据,使用不多(了解即可)
// watch([money, count], (val, oldVal) => {
//     console.log('现在的数据', val);
//     console.log('原来的数据', oldVal);
// })// 3. 监听整个对象的变化(常见)
// watch(obj, (val, oldVal) => {
//     console.log('现在的数据', val);
//     console.log('原来的数据', oldVal);
// }, { deep: true, immediate: true })// 4.监听对象的某个属性(了解即可)
// 写法注意: 直接写 obj.value.age,等于只是取值,watch不能建立依赖
// 我们需要的是告诉watch,帮我监听 obj.value.age 的属性 ---->  () => obj.value.age
watch(
    () => obj.value.age,
    (val, oldVal) => {
        console.log('现在的数据', val);
        console.log('原来的数据', oldVal);
    }
)
</script><template>
    <h1>我的工资 {{ money }}</h1>
    <button @click="money += 1000">点我加工资 1000</button>
    <h1>点击次数 {{ count }}</h1>
    <button @click="count++">点我次数 + 1</button>
    <h1>{{ obj.age }}</h1>
    <button @click="obj.age++">点我年龄 + 1</button>
</template><style scoped>
</style>

生命周期钩子的使用

生命周期函数 vue3 中的生命周期函数, 需要在 setup 中调用

<script setup>
import { onMounted, onUnmounted, onUpdated, ref } from 'vue';
const count = ref(0)
​
console.log('setup生命周期', document.querySelector('button'))
​
onMounted(() => {
    console.log('onMounted生命周期', document.querySelector('button'))
})
onMounted(() => {
    console.log('多个相同生命周期,会依次执行')
})
onUpdated(() => {
    console.log('onUpdated生命周期')
})
onUnmounted(() => {
    console.log('onUnmounted生命周期')
})
</script><template>
    <h1>{{ count }}</h1>
    <button @click="count++">点我+1</button>
</template><style scoped>
</style>

image.png

组件通讯-父传子

目标:能够实现组件通讯中的父传子组件通讯

步骤:

  1. 父组件提供数据
  2. 父组件将数据传递给子组件
  3. 子组件通过defineProps进行接收
  4. 子组件渲染父组件传递的数据

核心代码:

父组件

<script setup>
import { ref } from "vue";
import Son from "./components/Son.vue";
const money = ref(100000)
const car = ref('宝马')
</script><template>
    <h1>父组件</h1>
    <Son :money="money" :car="car"></Son>
    <Son :money="money"></Son>
</template><style scoped>
</style>

子组件

<script setup>
// vue2的写法
// props:['money','car']import { computed } from 'vue';
​
// vue3 父子组件,传递方式是一样的,接受方式需要使用 defineProps
​
​
// 1.普通写法
// defineProps(['money', 'car'])
// 2.带上类型和默认值
const props = defineProps({
    money: Number,
    car: {
        type: String,
        default: '五菱宏光'
    }
})
​
// 要在 script 使用 props ,需要储存在变量中
console.log('props', props);
const doubleMoney = computed(() => {
    return props.money * 2
})
</script><template>
    <h1>子组件</h1>
    <div>{{ money }}</div>
    <div>双倍的金钱{{ doubleMoney }}</div>
    <div>{{ car }}</div>
</template><style scoped>
</style>

注意:如果使用defineProps接收数据,这个数据只能在模板中渲染,如果想要在script中也操作props属性,应该接收返回值。

组件通讯-子传父

目标:能够实现组件通讯中的子传父

步骤:

  1. 子组件通过defineEmit获取emit对象(因为没有this)
  2. 子组件通过emit触发事件,并且传递数据
  3. 父组件提供方法
  4. 父组件通过自定义事件的方式给子组件注册事件

核心代码

子组件

<script setup>
+   // 子组件接受父组件方法 defineEmits
+   // vue2的方法 this.$emits('changeMoney')
+   const emits = defineEmits(['changeMoney'])
</script><template>
+    <button @click="emits('changeMoney', 1000)">点我改变父组件的money</button>
</template>

父组件

<script setup>
import { ref } from "vue";
import Son from "./components/Son.vue";
const money = ref(100000)
const car = ref('宝马')
​
+   const changeMoney = (val) => {
+       money.value += val
+   }
</script><template>
    <h1>父组件</h1>
+    <Son :money="money" :car="car" @changeMoney="changeMoney"></Son>
</template><style scoped>
</style>

依赖注入 - provide 和 inject

依赖注入, 可以非常方便的实现 跨层级的 组件通信

image.png

父组件利用 provide 提供数据

<script setup>
import { provide, ref } from 'vue';
import Son from './components/Son.vue';
​
const money = ref(10000)
const add = (value) => {
  money.value += value
}
// 语法几乎和 localStorage 一样
// 数据 和 方法 传输方式一样
provide('money', money)
provide('add', add)
</script><template>
  <h1>父组件</h1>
  <Son></Son>
</template><style scoped>
</style>

子组件可以使用

<script setup>
import { inject } from 'vue';
import GrandSon from './GrandSon.vue';
​
// 使用 inject 接收数据
const money = inject('money')
console.log('money', money);
​
// 方法也可以传递数据
const add = inject('add')
</script><template>
    <h1>我是子组件</h1>
    <div>子组件 - {{ money }}</div>
    <button @click="add(1000)">点我加你工资1000</button>
​
    <GrandSon></GrandSon>
</template><style scoped>
</style>

孙子组件也可以直接使用

<script setup>
import { inject } from 'vue';
// 不用中间商,孙组件也可以直接使用
const money = inject('money')
​
const add = inject('add')
</script><template>
    <h1>我是孙组件</h1>
    <div>我也想要钱 {{ money }}</div>
    <button @click="add(2000)">点我加你工资2000</button>
</template><style scoped>
</style>

模板中 ref 的使用

联想之前的 ref 和 $refs, 获取模板的元素(dom元素,组件)

1 创建 ref => const hRef = ref(null)

2 模板中建立关联 => <h1 ref="hRef">钩子函数-----123</h1>

3 使用 => hRef.value

<script setup>
/* 
  获取模板元素
  1. 创建 空 h1Ref
  2. h1Ref 和 dom 元素绑定,等 dom 渲染完成(onMounted),就会存放到h1Ref
  3. 可以通过 h1Ref 获取到 dom 并修改内容
*/import { onMounted, ref } from 'vue';
​
const h1Ref = ref(null)
// 渲染完成后才可以读取到
console.log('setup', h1Ref.value);
onMounted(() => {
  console.log('onMounted', h1Ref.value);
})
​
const change = () => {
  h1Ref.value.innerHTML = '我变成了胡哥'
}
</script><template>
  <h1 ref="h1Ref">Hello vue3</h1>
  <button @click="change">点我改变 h1 的 innerHTML</button>
</template><style scoped>
</style>

ref操作组件

<script setup>
import { onMounted, ref } from "vue";
import Son from "./components/Son.vue";
// vue2 获取子组件的方法 this.$refs.form.validate()const son = ref(null)
onMounted(() => {
  son.value.sayHi()
})
const sayHi = () => {
  son.value.sayHi()
}
</script><template>
  <h1>Hello vue3</h1>
  <Son ref="son"></Son>
  <button @click="sayHi">sayHi</button>
</template><style scoped>
</style>

需要配合defineExpose

<script setup>
const sayHi = () => {
    alert('大家跟我喊,传智播客 yyds')
}
​
// 父组件想使用子组件的方法,需要子组件主动暴露 defineExpose
// 要先定义完,再暴露,不能写在 sayHi 之前
defineExpose({
    sayHi
})
​
</script><template>
    <h1>我是子组件</h1>
</template><style scoped>
</style>

vue3中废弃了过滤器

vue3.0中不能使用过滤器,直接使用函数进行替代

<script setup>
import dayjs from 'dayjs'
import moment from 'moment'
const now = new Date()
const other = new Date('2020-11-12 12:00:00')
const formatTime = (value) => {
  // dayjs 和 moment 语法基本一样
  // return dayjs(value).format('YYYY-MM-DD hh:mm:ss')
  return moment(value).format('YYYY-MM-DD hh:mm:ss')
}
​
</script><template>
  <h3>{{ formatTime(now) }}</h3>
  <h3>{{ formatTime(other) }}</h3>
  <hr />
</template><style scoped>
</style>

补充-toRefs 函数

使用场景: 如果对一个响应数据, 进行解构 或者 展开, 会丢失他的响应式特性!

原因: vue3 底层是对 对象 进行监听劫持

作用: 对一个响应式对象的所有内部属性, 都做响应式处理

  1. reactive/ref的响应式功能是赋值给对象的, 如果给对象解构或者展开, 会让数据丢失响应式的能力
  2. 使用 toRefs 可以保证该对象展开的每个属性都是响应式的
<template>
  <div>{{ money }}</div>
  <div>{{ car }}</div>
  <div>{{ name }}</div>
  <button @click="money++">改值</button>
</template><script setup>
import { reactive, ref, toRefs } from 'vue'
const user = ref({
  name: 'zs',
  age: 18,
})
const { name, age } = toRefs(user.value)
</script>

问题小结: toRefs 函数的作用是什么 ?

作用: 对一个 响应式对象 的所有内部属性, 都做响应式处理, 保证展开或者解构出的数据也是响应式的

\