# Vue3

# 1.Vue3的优势

1.更容易维护 组合式API 更好的typeScript支持
2.更快的速度 重写diff算法 模板编译优化 更高效的组件初始化
3.更小的体积 良好的TreeShaking 按需引入
4.更优的数据响应式 Proxy
1
2
3
4

# 2.create-vue

create-vue 是Vue官方新的脚手架工具 底层切换到了Vite(下一代构建工具) 为开发提供极速响应
// Vue-cli 底层 webpack
// create-vue 底层 vite
1
2
3

# 3.创建项目

1.前提环境条件:
	node版本 大于16.0
	node -v 查看版本
2.创建Vue应用
	npm create vue@latest
	npm init vue@latest
	(安装并执行create-vue)
3.Vue3 插件:
	volar
1
2
3
4
5
6
7
8
9

# 4.组合式API-setup

<!-- 加上setup允许在script中直接编写组合式API -->
<script>
export default {
  // 1.执行时机比beforeCreate早
  // 2.获取不到this  this=undefined
  // 3.数据和函数 需要在setup中return 才能在模板中使用
  setup() {
    console.log('setup函数');
    console.log(this);

    // 函数
    const logMessage = () => {
      console.log('message');
    }
    // 数据
    const msg = 'hello world'

    return {
      msg,
      logMessage
    }
  },
  beforeCreate() {
    console.log('beforeCreate函数');
    console.log(this);
  }
}
</script>

<template>
  <!-- 不再要求唯一根元素 -->
  <div>学习Vue3</div>
  <div>{{ msg }}</div>
  <button @click="logMessage">按钮</button>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# 5.setup语法糖写法

<!-- 语法糖写法 -->
<script setup>
const message = 'hello world'
const logMessage = () => {
  console.log('message: ' + message);
}
</script>
<template>
  <div>
    {{ message }}
    <button @click="logMessage">点击我</button>
  </div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13

# 6.setup总结

1.setup选项的执行时机?
    最早的  比beforeCrete早
2.setup写代码的特点是什么?
   	定义数据 + 函数 然后以对象的方式return
3.<script setup> 解决了什么问题?
    经过语法糖的封装更简单的使用组合式API (不用再以对象的方式return)
4.setup中的this还指向组件实例吗?
	this=undefined
1
2
3
4
5
6
7
8

# 7.reactive()

作用:接受对象类型数据的参数传入并返回一个响应式的对象
<!-- 语法糖写法 -->
<script setup>
// reactive:接收一个对象类型的数据 返回一个响应式的对象
import { reactive } from 'vue';

const state = reactive({
  count: 100,
})
console.log(state.count); // 100
const setCount = () => {
  state.count++
}
</script>
<template>
  <div>
    {{ state.count }}
    <button @click="setCount">+</button>
  </div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 8.ref()

推荐: 以后声明数据 统一用ref
<script setup>
作用:可以接收简单数据类型 和 对象数据类型 传入并返回一个响应式对象
// ref:接收简单数据类型和复杂数据类型 返回一个响应式对象
// 本质:是在原有传入数据的基础上 外层包了一层对象 包成了复杂类型
// 底层:包成复杂类型之后 再借助reactive实现的响应式
// script中访问数据需要通过 .value
// template中访问数据需要通过 不需要加.value
const number = ref(1)

console.log(number.value); // 1

const setNumber = () => {
  number.value++
}
</script>
<template>
  <div>
    <h1>reactive写法</h1>
    {{ state.count }}
    <button @click="setCount">+</button>
    <hr>
    <h1>ref写法</h1>
    {{ number }}
    <button @click="setNumber">+</button>
  </div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 9.reactive() 和 ref() 总结

1.reactive()和ref()的共同作用是什么?
    用函数调用的方式生成响应式数据 将数据变成响应式数据
2.reactive vs ref?
    reactive不能处理简单的数据类型
	ref参数类型支持更好但必须通过.value访问修改
	ref函数的内部实现依赖于reactive函数
3.在实际工作中 推荐使用 ref 统一编码规范
1
2
3
4
5
6
7

# 10.组合式API-computed

计算属性基本思想和Vue2的完全一致 组合式API下的计算属性只是修改了写法
<script setup>
import { computed, ref } from 'vue';

const arr = ref([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])

// 计算属性
const computedArr = computed(() => {
  return arr.value.filter((item) => item > 5)
})
// 修改数组的方法
const updatedArr = () => {
  arr.value.push(11)
}

// 定义可读可写 computed
const computedTest = computed({
  get: () => {
    return arr.value
  },
  set: (value) => {
    return arr.value = value
  }
})
console.log(computedTest.value);
const setArr = () => {
  computedTest.value = [1]
}
</script>
<template>
  <div>{{ arr }}</div>
  <div>{{ computedArr }}</div>
  <button @click="updatedArr">+</button>
  <div>{{ computedTest }}</div>
  <button @click="setArr">修改</button>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# 11.computed总结

1.计算属性中不应该有副作用
	不应该有 比如异步请求 操作dom
2.避免直接修改计算属性值
	计算属性应该是只读的 特殊情况可以配置get set
1
2
3
4

# 12.组合式API-watch函数

作用:侦听一个或多个数据的变化 数据变化时立即执行回调函数
1.immediate(立即执行) 2.deep(深度监听)
1
2

# 1.监听单个数据

<script setup>
import { ref, watch } from 'vue';

const count = ref(0)
watch(count, (newValue, oldValue) => {
  console.log(newValue, oldValue);
})

setInterval(() => {
  count.value++
}, 1000)
</script>

<template>
  <div>{{ count }}</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 2.监听多个数据

<script setup>
import { ref, watch } from 'vue';

const count = ref(0)
const number = ref(1)

// 监听单个数据
// watch(count, (newValue, oldValue) => {
//   console.log(newValue, oldValue);
// })

// 监听多个数据
watch([count, number], ([newCount, newNumber], [oldCount, oldNumber]) => {
  console.log(newCount, newNumber);
  console.log(oldCount, oldNumber);
})

setTimeout(() => {
  count.value++
  number.value++
}, 1000)
</script>

<template>
  <div>{{ count }}</div>
  <div>{{ number }}</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 3.immediate 立即执行

<script setup>
import { ref, watch } from 'vue';

const count = ref(0)

// 监听单个数据
watch(count, (newValue, oldValue) => {
  console.log(newValue, oldValue);
}, {
  immediate: true // 立即执行
})
</script>
<template>
  <div>{{ count }}</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 4.deep 深度监视

默认watch 进行的是浅层的监视(修改了对象的地址才能监视到)
const ref1 = ref(简单数据类型) 可以直接监视
const ref2 = ref(复杂类型) 默认watch监视不到复杂类型内部数据的变化
<script setup>
import { ref, watch } from 'vue';

const info = ref({
  name: '小明',
  age: 12,
  sex: '男'
})

const setAge = () => {
  info.value.age++
}

// 深度监听 对象数据
watch(info, (newValue) => {
  console.log(newValue)
}, {
  deep: true
})
</script>

<template>
  <div>
    {{ info.name }}
    {{ info.age }}
    {{ info.sex }}
    <button @click="setAge">age++</button>
  </div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 4.精确监听对象的某个属性

在不开启deep的前提下 侦听age的变化 只有age变化时才执行回调
<script setup>
import { ref, watch } from 'vue';

const info = ref({
  name: '小明',
  age: 12,
  sex: '男'
})

const setAge = () => {
  info.value.age++
}

// 精确侦听某个属性
watch(
  () => info.value.age,
  (newValue) => {
    console.log(newValue);
  }
)
</script>

<template>
  <div>
    {{ info.name }}
    {{ info.age }}
    {{ info.sex }}
    <button @click="setAge">age++</button>
  </div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 13.watch 总结

1.作为watch函数的第一个参数 ref对象需要添加.value吗?
    不需要 第一个参数就是传入ref对象 
2.watch只能侦听单个数据吗?
    单个或多个
3.不开启deep 直接监视复杂类型  修改属性能触发回调吗?
	不能 默认是浅层监听
4.不开启deep 如何精确侦听对象的某个属性?
    // 精确侦听某个属性
    watch(
      () => info.value.age,
      (newValue) => {
        console.log(newValue);
      }
    )
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 14.生命周期

<script setup>
import { onMounted } from 'vue';

// beforeCrate 和 created 的相关代码 
// 一律放在 setup 中执行 

const getList = () => {
  setTimeout(() => {
    console.log('发送请求 获取数据');
  }, 2000);
}
// 一进入页面的请求
getList()

// 如果有些代码 需要在mounted生命周期中执行
onMounted(() => {
  console.log('mounted生命周期函数1');
})

onMounted(() => {
  console.log('mounted生命周期函数2');
})
</script>

<template>
  <div></div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 15.组合式API-父传子

1.基本思想
	父组件中给子组件绑定属性
	子组件内部通过props选项接收
1
2
3

子组件

<script setup>
import { defineProps, defineOptions } from 'vue'
defineOptions({
  name: "SonPage"
})
// 接收父组件的传来的数据 编译宏
const props = defineProps({
  text: {
    type: String,
    default: ''
  },
  money: {
    type: Number,
    default: 0
  }
})
console.log(props.text);
// 对于props 传递过来的数据 模板中可以直接使用 script中必须props.属性
</script>
<template>
  <div>子组件接收到的父组件的数据:{{ text }}--{{ money }}</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

父组件

<script setup>
import { ref } from 'vue'
// 局部组件 导入进直接使用
import sonVue from './components/son.vue';
const money = ref(100)
const getMoney = () => {
  money.value++
}
</script>
<template>
  <div>
    <button @click="getMoney">挣钱</button>
    <!-- 给子组件传值 -->
    <sonVue text="大家好" :money="money"></sonVue>
  </div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 16.组合式API-子传父

1.基本思想
	父组件中给子组件标签通过@绑定事件
	子组件内部通过emit方法触发事件
1
2
3

子组件

<script setup>
import { defineProps, defineOptions } from 'vue'
defineOptions({
  name: "SonPage"
})
// 向父组件传递值
const emit = defineEmits(['sub-money'])
const spend = () => {
  emit('sub-money', 10) // 子组件触发 父组件监听的事件 
}
</script>

<template>
  <button @click="spend">花钱</button>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

父组件

<script setup>
import { ref } from 'vue'
// 局部组件 导入进直接使用
import sonVue from './components/son.vue';
const money = ref(100)
const subMoeny = (value) => {
  money.value -= value
}

</script>
<template>
  <div>
    <!-- 给子组件传值 -->
    <sonVue text="大家好" :money="money" @sub-money="subMoeny"></sonVue>
  </div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 17.组件通信 总结

父传子
	1.父传子的过程中通过什么方式接收props?
        defineProps({属性名:类型})
    2.setup语法糖中如何使用父组件传来的数据?
        const props = defineProps({属性名:类型})
        props.xxx
子组件
    1.子传父的过程中通过什么方式得到emit方法?
        defineEmits(['事件名称'])
    2.怎么触发事件
    	emit('事件名称','传递的参数值')
1
2
3
4
5
6
7
8
9
10
11

# 18.组合式API-模板引用

<script setup>
import { ref } from 'vue';
import InputPageVue from './components/InputPage.vue';
// 1.创建 一个ref对象 通过inputRef.value 可以访问到绑定的元素
const inputRef = ref(null)
const inputVue = ref(null)
const focus = () => {
  inputRef.value.focus()
}
const ClickFn = () => {
  // 因为子组件使用defineExpose暴露出来了 属性和 方法 所以可以直接访问
  inputVue.value.sayHi()
}
</script>
<template>
  <div>
    <div>
      <!-- 绑定ref -->
      <input ref="inputRef" type="text">
      <button @click="focus">点击让输入框聚焦</button>
    </div>
    <InputPageVue ref="inputVue"></InputPageVue>
    <button @click="ClickFn">获取组件</button>
  </div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 19.defineExpose()

在默认情况下 script setup语法糖下组件内部的属性和方法是不开放给父组件访问的
可以通过defineExpose()编译宏指定哪些属性和方法允许被访问

<script setup>
defineOptions({
  name: 'InputPage'
})
const count = 999
const sayHi = () => {
  console.log('sayHi');
}
// 暴露出去数据
defineExpose({
  count,
  sayHi
})
</script>

<template>
  <div>
    我是用于测试的组件 - {{ count }}
  </div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 20.模板引用总结

1.获取模板引用的时机是什么时候?
    组件挂载完毕 OnMonted
2.defineExpose编译宏的作用是什么?
    指定哪些属性和方法可以被父组件访问调用
1
2
3
4

# 21.组合式API-provide和inject

作用和场景
	·顶层组件向任意的底层组件传递数据和方法 实现跨层组件通信

1.顶层组件通过provide函数提供数据
2.底层组件通过inject函数获取数据
provide('key',顶层组件中的数据)
const message = inject('key')
1
2
3
4
5
6
7

# 22.Vue3.3新特性-defineOptions

<script setup>
import { defineOptions } from 'vue';
defineOptions({
  name: 'App'
})
</script>
<template>
  <div></div>
</template>
1
2
3
4
5
6
7
8
9

# 23.Vue3.3新特性-defineModel

// Vue2中 v-model原理是 value 和 input
// Vue3中 v-model原理是 modelValue 和 update:modelValue
1
2

# 1.不使用defineModel

父组件

<script setup>
import MyInputVue from '@/components/MyInput.vue'
import { ref } from 'vue';
const text = ref('123')
</script>
<template>
  <div>
    <MyInputVue v-model="text"></MyInputVue>
    {{ text }}
  </div>
</template>
1
2
3
4
5
6
7
8
9
10
11

子组件

<script setup>
defineProps({
  modelValue: {
    type: String,
    default: ''
  }
})
const emit = defineEmits(['update:modelValue'])
const onInput = (e) => {
  console.log(e.target.value); // 获取到输入框输入的值
  emit('update:modelValue', e.target.value) // 触发事件 更改父组件的值
}
</script>
<template>
  <div>
    <h1>Hello World!</h1>
    <input type="text" :value="modelValue" @input="onInput">
  </div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 2.使用defineModel

让这个生效 先在vite.config.js中开启
import { fileURLToPath, URL } from "node:url";

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue({
      script: {
        defineModel: true, // 开启
      },
    }),
  ],
  resolve: {
    alias: {
      "@": fileURLToPath(new URL("./src", import.meta.url)),
    },
  },
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

父组件

<script setup>
import MyInputVue from '@/components/MyInput.vue'
import { ref } from 'vue';
const text = ref('123')
</script>
<template>
  <div>
    <MyInputVue v-model="text"></MyInputVue>
    {{ text }}
  </div>
</template>
1
2
3
4
5
6
7
8
9
10
11

子组件

<script setup>
import { defineModel } from 'vue';
const modelValue = defineModel()
</script>
<template>
  <div>
    <input type="text" :value="modelValue" @input="e => modelValue = e.target.value">
  </div>
</template>
1
2
3
4
5
6
7
8
9

# 24.Vue3最新状态管理工具-pinna

1.提供了更加简单的API(去掉了mutations)
2.提供符合 组合式风格的API (和Vue3新语法统一)
3.去掉了modules的概念 每个store都是一个独立的模块
4.配色ts 更加友好 提供可靠的类型推断
1
2
3
4

# 1.手动添加pinia到Vue项目

1.使用vite此次创建一个空的Vue3项目
	npm create vue@latest
2.按照官方文档安装pinia到项目中
	import { createApp } from 'vue'
    import { createPinia } from 'pinia'
    import App from './App.vue'

    const pinia = createPinia()
    const app = createApp(App)

    app.use(pinia)
    app.mount('#app')
1
2
3
4
5
6
7
8
9
10
11
12

# 2.基本使用

Setup Store 中:

  • ref() 就是 state 属性
  • computed() 就是 getters
  • function() 就是 actions

创建仓库 ./store/counter.js

import { defineStore } from "pinia";
import { ref } from "vue";
// 定义store
// 参数一:仓库的唯一标识
export const useCounterStore = defineStore("counter", () => {
  // 声明数据 state
  const count = ref(10);
  // 声明操作数据的方法 actions function
  const double = function(){
    count.value = count.value * 2;
  }
  // 声明基于数据派生的方法 getters computed
  const message = ref("counter");
  // 返回数据
  return {
    count,
    message,
    double
  };
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

使用数据

<script setup>
// 引入仓库
import { useCounterStore } from './store/counter';
import Son1ComVue from './components/Son1Com.vue';
import Son2ComVue from './components/Son2Com.vue';
const counterStore = useCounterStore()
console.log(counterStore.count); // 获取数据
</script>

<template>
  <div>App.vue根组件 - {{ counterStore.count }}</div>
  <Son1ComVue />
  <Son2ComVue />
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 3.发起异步请求

仓库代码

import axios from "axios";
import { defineStore } from "pinia";
import { ref } from "vue";
// 定义一个仓库
export const useChannelStore = defineStore("channel", () => {
  // 声明数据
  const channelList = ref([]);
    
  const getList = async () => {
    const {
      data: { data },
    } = await axios.get("http://geek.itheima.net/v1_0/channels");
    channelList.value = data.channels;
    console.log(data.channels);
  };
    
  // 必须返回
  return {
    channelList,
    getList,
  };
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

使用仓库

<script setup>
import { useCounterStore } from './store/counter';

import Son1ComVue from './components/Son1Com.vue';
import Son2ComVue from './components/Son2Com.vue';

const counterStore = useCounterStore()
console.log(counterStore.count); // 获取数据


// 使用仓库
import { useChannelStore } from './store/channel'
const channelStore = useChannelStore()
channelStore.getList()
</script>
<template>
  <div>App.vue根组件 - {{ counterStore.count }} - {{ counterStore.message }} - {{ counterStore.double }}</div>
  <Son1ComVue />
  <Son2ComVue />
  <ul>
    <li v-for="item in channelStore.channelList" :key="item.id">{{ item.name }}</li>
  </ul>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 4.storeToRefs 恢复数据响应式

state 不能直接解构
actions 可以直接解构
getters 可以直接解构
<script setup>
import { useCounterStore } from './store/counter';
import { storeToRefs } from 'pinia'

import Son1ComVue from './components/Son1Com.vue';
import Son2ComVue from './components/Son2Com.vue';

const counterStore = useCounterStore()
console.log(counterStore.count); // 获取数据

// 直接解构会导致数据不是响应式的 使用storeToRefs() 恢复数据的响应式
const { count, message } = storeToRefs(counterStore)

import { useChannelStore } from './store/channel'
const channelStore = useChannelStore()
channelStore.getList()

</script>

<template>
  <div>App.vue根组件 - {{ count }} - {{ message }} - {{ counterStore.double }}</div>
  <Son1ComVue />
  <Son2ComVue />
  <ul>
    <li v-for="item in channelStore.channelList" :key="item.id">{{ item.name }}</li>
  </ul>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# 5.pinia 持久化

https://prazdevs.github.io/pinia-plugin-persistedstate/zh/guide/ 官网

安装插件

npm i pinia-plugin-persistedstate
1

main.js

import { createApp } from "vue";
import App from "./App.vue";
import { createPinia } from "pinia";

// 引入pinia持久化插件
import { piniaPluginPersistedstate } from "pinia-plugin-persistedstate";

const pinia = createPinia(); // 创建pinia 实例

pinia.use(piniaPluginPersistedstate); // pinia 使用插件

const app = createApp(App); // 创建根实例

app.use(pinia); // pinia插件的安装

app.mount("#app"); // app实例的挂载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

用法

创建 Store 时,将 persist 选项设置为 trueimport { defineStore } from 'pinia'

export const useStore = defineStore(
  'main',
  () => {
    const someState = ref('你好 pinia')
    return { someState }
  },
  {
    persist: true, // 开启持久化存储
  },
)
1
2
3
4
5
6
7
8
9
10
11
12
13

自定义key

自定义配置 https://prazdevs.github.io/pinia-plugin-persistedstate/zh/guide/config.html

import { defineStore } from "pinia";
import { computed, ref } from "vue";
// 定义store
// 参数一:仓库的唯一标识
export const useCounterStore = defineStore(
  "counter",
  () => {
    // 声明数据 state
    const count = ref(10);
    // 声明操作数据的方法 actions function

    const sub = function () {
      count.value--;
    };
    const add = function () {
      count.value++;
    };

    // 声明基于数据派生的方法 getters computed

    const double = computed(() => {
      return count.value * 2;
    });

    const message = ref("counter");

    return {
      count,
      message,
      add,
      sub,
      double,
    };
  },
  {
    persist: {
      key: "counterStore", // 指定key
      paths:['count'] // 指定对count进行持久化
    }, // 开启当前模块持久化
  }
);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

# 6.pinia总结

1.pinia是用来做什么的?
	新一代的状态管理工具 替代vuex
2.pinia还需要mutations?
	不需要 actions 支持同步和异步
3.pinia如何实现getter?
	通过computed实现
4.pinia产生的Store如何解构赋值数据保持响应式?
	storeToRefs
5.Pinia如何快速实现持久化?
	pinia-plugin-persistedstate
1
2
3
4
5
6
7
8
9
10

# 2.Vue3大事件项目管理系统

# 1.pnpm包 管理器

优势:比同类工具快两倍左右 节省磁盘空间
// 安装pnpm
npm install -g pnpm
// 创建项目
pnpm create vue

pnpm install
pnpm add axios
pnpm add axios -D
pnpm remove axios
pnpm dev
1
2
3
4
5
6
7
8
9
10
11