按照官方文档的教程步骤来,大部分的代码Demo,都是通过脚手架创建的项目的基础上创建的,用的版本的Vue3,Typescript,组合式的写法

基础

创建一个应用

多个应用实例

没什么用,现在项目都是从头开始都是vue项目,不会用来一步一步替换,没有想到应用场景

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
43
44
<!DOCTYPE html>
<html>
<head>
<title>Vue Multiple Instances Example</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
</head>
<body>
<div id="app1">
<p>{{ message }}</p>
<button @click="updateMessage">更新消息</button>
</div>

<div id="app2">
<p>{{ message }}</p>
<button @click="updateMessage">更新消息</button>
</div>

<script>
var app1 = new Vue({
el: '#app1',
data: {
message: '这是第一个 Vue 实例的消息'
},
methods: {
updateMessage() {
this.message = '第一个实例的消息已更新';
}
}
});

var app2 = new Vue({
el: '#app2',
data: {
message: '这是第二个 Vue 实例的消息'
},
methods: {
updateMessage() {
this.message = '第二个实例的消息已更新';
}
}
});
</script>
</body>
</html>

模板语法

文本插值

双大括号标签会被替换为msg 属性的值。同时每次 msg 属性更改时它也会同步更新。

1
2
3
4
5
6
7
8
<script setup lang="ts">
import {ref} from "vue";
const text = ref<string>("文本插值语法");
</script>

<template>
{{text}}
</template>

原始 HTML

  1. 有的时候想想渲染源码,有的时候不想渲染源码

  2. v-html就是指令语法

1
2
3
4
5
6
7
8
9
<script setup lang="ts">
import {ref} from "vue";
const rawHtml = ref<string>("<span style='color: red'>This should be red.</span>");
</script>

<template>
<p>Using text interpolation: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>
</template>

Attribute 绑定

如果想给html的属性绑定数据要怎么操作呢,下面以给div的id和class属性绑定做演示

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
<script setup lang="ts">
import {ref} from "vue";
const myId = ref<string>("myId");
const showButton = ref<boolean>(false);

type objectOfAttrs={
id: string,
class:string
}

const objectOfAttrsObj=ref<objectOfAttrs>({
id: "myId4",
class:"demo"
});


</script>

<template>
<!-- ❎错误做法 -->
<div id={{myId}}></div>

<!-- ✅正确做法,如果绑定的值是 null 或者 undefined,那么该 attribute 将会从渲染的元素上移除。 -->
<div v-bind:id="myId"></div>

<!-- ✅正确做法,简写写法 -->
<div :id="myId"></div>

<!-- ✅正确做法,同名简写写法,就是属性(Id),和等号对应的值是一样的话 -->
<div :id></div>

<!-- ✅正确做法,同名简写写法,第二种写法 -->
<div v-bind:id></div>

<!-- -------------------------------------------------------- -->

<!-- 绑定布尔类型 -->
<button :disabled="showButton">按钮</button>

<!-- 动态绑定多个值 -->
<div v-bind="objectOfAttrsObj">动态绑定多个值,只是需要写v-bind</div>
</template>

响应式基础

这个有点底层,不去了解了,大概意思是简单的数据类型就用ref,对象或者复杂嵌套的结构就用reactive

计算属性

基础示例

Vue 的计算属性会自动追踪响应式依赖。它会检测到 authorComputed 依赖于 author.books,所以当 author.books 改变时,任何依赖于 authorComputed 的绑定都会同时更新。

计算属性会被缓存,并且只调用一次,方法是调用一次执行一次,在例子中,触发了一次页面渲染(页面的数据有变动),方法就会被执行一次

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<script setup lang="ts">

import {computed, reactive, ref} from "vue";

type authorType={
name:string,
books:string[]
}

const author = reactive<authorType>({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})

// 计算属性,使用泛型,返回值必须是字符串
const authorComputed = computed<string>(()=>{
return author.books.length >0?author.books.length.toString():"没有书"
})

function add(){
author.books.push("Vue 5 - Advanced Guide")
}

// 计算属性,使用泛型,返回值必须是字符串
const myDateTimeComputed = computed<number>(() => {
return getDateTime();
});

function getDateTime(){
console.log("方法被调用")
return Date.now();
}

function myDateTimeMethod(){
return getDateTime();
}

// 定义一个用于触发重新渲染的状态
const renderCounter = ref(0);
function forceRerender() {
console.log("12321")
renderCounter.value++;
}

</script>

<template>
<!-- 计算属性 -->
<span>通过计算属性计算出来的结果:{{ authorComputed }}</span>
<br>
<button @click="add()">添加数据</button>
<br>
<!-- 计算属性和方法的区别 -->
<button @click="forceRerender">触发重新渲染</button>
计算属性和方法的区别-计算属性:{{ myDateTimeComputed }}
计算属性和方法的区别-方法:{{renderCounter}}-{{ myDateTimeMethod() }}
</template>

可写计算属性

计算属性默认是只读的。当你尝试修改一个计算属性时,你会收到一个运行时警告。只在某些特殊场景中你可能才需要用到“可写”的属性,你可以通过同时提供 getter 和 setter 来创建

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
<script setup lang="ts">

import {computed, reactive, ref} from "vue";

const firstName = ref('Anthony')

/**
* 错误的写法
*/
// const fullName = computed(()=>{
// return firstName.value + ' ' + lastName.value
// })

/**
* 正确的写法
*/
const fullName = computed({
// getter
get() {
return firstName.value
},
// setter
set(newValue) {
// 注意:我们这里使用的是解构赋值语法
firstName.value = newValue
}
})

/**
* 这样是不对的
*/
function showWarn(){
console.log("修改计算属性")
fullName.value="DiDiDi"
}
</script>

<template>
演示修改计算属性,控制台报错:{{fullName}}
<button @click="showWarn">尝试修改计算属性</button>
</template>

Class 与 Style 绑定

太麻烦了,不看了

条件渲染

v-if 可以单独使用

v-else-if 要跟v-if 一起使用

v-else 要跟v-if 或者 v-else-if 一起使用

1
2
3
4
5
6
7
8
9
10
11
12
13
<script setup lang="ts">

import {computed, reactive, ref} from "vue";

const season = ref<number>(6);

</script>

<template>
<h1 v-if="season >=1 && season <=3">春季</h1>
<h1 v-else-if="season >=4 && season <=6">不是春季</h1>
<h1 v-else>数据错误</h1>
</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
<script setup lang="ts">

import {computed, reactive, ref} from "vue";

const items = ref([{ message: 'Foo' }, { message: 'Bar' }])

</script>

<template>
for循环
<li v-for="(item,index) in items">
{{index}}-{{ item.message }}
</li>

解构函数
<li v-for="{ message } in items">
{{ message }}
</li>

解构函数,有索引的时候
<li v-for="({message},index) in items">
{{index}}-{{ message }}
</li>
</template>

事件处理

我们可以使用 v-on 指令 (简写为 @) 来监听 DOM 事件,并在事件触发时执行对应的 JavaScript。用法:v-on:click="handler"@click="handler"

内联事件处理器

1
2
3
4
5
6
7
8
9
10
<script setup lang="ts">

import {computed, reactive, ref} from "vue";
const count = ref(0)
</script>

<template>
<button @click="count++">Add 1</button>
<p>Count is: {{ count }}</p>
</template>

方法事件处理器

1
2
3
4
5
6
7
8
9
10
11
<script setup lang="ts">

function greet() {
console.log("方法事件处理器")
}
</script>

<template>
<!-- `greet` 是上面定义过的方法名 -->
<button @click="greet">Greet</button>
</template>

表单输入绑定

主要是看框架了,先不学了,太多细节了

生命周期钩子

这个API好多个,以后慢慢看

侦听器

每次修改被监听的对象,就会打印旧值和新值

基本示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script setup lang="ts">
import {ref, watch} from 'vue'

/*被监听的对象*/
const question = ref<number>(1);

// 可以直接侦听一个 ref
watch(question, async (newQuestion, oldQuestion) => {
console.log("老值:", oldQuestion)
console.log("新值:", newQuestion)
})
</script>

<template>
<input v-model="question"/>
</template>

侦听不同的数据源类型

watch 的第一个参数可以是不同形式的“数据源”:它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组:

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
43
44
45
46
47
48
49
50
51
52
53
54
55
<script setup lang="ts">
import {reactive, ref, watch} from 'vue'

const x = ref<number>(0)
const y = ref<number>(0)

type authorType={
name: string,
}

const z = reactive<authorType>({
name:"anthony"
})

// ref
watch(x, (newX,oldValue) => {
console.log(`监听单个ref,x的旧值是:${oldValue},新值是:${newX}`)
})

// getter 函数
watch(
() => x.value + y.value,
(newSum, oldSum) => {
console.log(`监听getter函数, 原来的和值是:${oldSum},新值是: ${newSum}`)
}
)

// 多个来源组成的数组
watch([x, () => y.value], ([newX, newY],[oldX, oldY]) => {
console.log(`监听多个数据源:X的旧值是:${oldX},X的新值是:${newX},Y的旧值是:${oldY},Y的新值是:${newY}`)
})

// 监听reactive
// 错误,因为 watch() 得到的参数是一个 number
// 错误的写法,会提示报错,
// MyClick.vue:35 [Vue warn]: Invalid watch source: anthony A watch source can only be a getter/effect function, a ref, a reactive object, or an array of these types.
// at <MyClick>
// at <App>
// watch(z.name, (count) => {
// console.log(`name is: ${count}`)
// })
// 正确的写法
watch(
() => z.name,
(nameNew,oldName) => {
console.log(`name is: ${nameNew},${oldName}`)
}
)
</script>

<template>
<input v-model="x" type="number"/>
<input v-model="y" type="number"/>
<input v-model="z.name" />
</template>

模板引用

进入到页面会自动聚焦到输入框中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script setup>
import { ref, onMounted } from 'vue'

// 声明一个 ref 来存放该元素的引用
// 必须和模板里的 ref 同名
const input = ref(null)

onMounted(() => {
input.value.focus()
})
</script>

<template>
<input ref="input" />
</template>

组件基础

定义组件

1
2
3
4
5
6
7
8
9
<script setup>
import { ref } from 'vue'

const count = ref(0)
</script>

<template>
<button @click="count++">You clicked me {{ count }} times.</button>
</template>

使用组件

在父组件中引入

1
2
3
4
5
6
7
<script setup lang="ts">
import MyClick from "@/components/MyClick.vue";
</script>

<template>
<MyClick/>
</template>

传递 props(父传子)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 子组件
<script setup lang="ts">
import {ref} from 'vue'

const count = ref<number>(0)
defineProps(['title'])
</script>

<template>
<button @click="count++">{{title}},You clicked me {{ count }} times.</button>
</template>

// 父组件
<script setup lang="ts">
import MyClick from "@/components/MyClick.vue";
</script>

<template>
<MyClick title="计算器1"/>
<MyClick title="计算器2"/>
<MyClick title="计算器3"/>
<MyClick title="计算器4"/>
</template>

emit(子传父)

父组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<script setup lang="ts">

import { ref } from 'vue'
import MyEmit from "@/components/MyEmit.vue";

type Person = {
name: string,
age: number
}

const myName = ref<string>('')
const myAge = ref<number>('')

const getSubmit = (data: Person) => {
myName.value = data.name
myAge.value = data.age
}

</script>

<template>
<MyEmit @myEmit="getSubmit" />
从子组件接收的数据:{{ myName }} {{ myAge }}
</template>

子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script setup lang="ts">

type Person = {
name: string,
age: number
}

const me = {
name: 'anthony',
age: 23
}

const emit = defineEmits<{ (e: 'myEmit', payload: Person): void }>();
emit('myEmit', me)
</script>

$bus 消息总线

main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//关闭Vue的生产提示
Vue.config.productionTip = false

//创建vm
new Vue({
el:'#app',
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this //安装全局事件总线
},
})

App.vue

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
<template>
<div class="app">
<h1>{{msg}}</h1>
<School/>
<Student/>
</div>
</template>

<script>
import Student from './components/Student'
import School from './components/School'

export default {
name:'App',
components:{School,Student},
data() {
return {
msg:'你好啊!',
}
}
}
</script>

<style scoped>
.app{
background-color: gray;
padding: 5px;
}
</style>

Student.vue

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
<template>
<div class="school">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
</template>

<script>
export default {
name:'School',
data() {
return {
name:'尚硅谷',
address:'北京',
}
},
mounted() {
// console.log('School',this)
this.$bus.$on('hello',(data)=>{
console.log('我是School组件,收到了数据',data)
})
},
beforeDestroy() {
this.$bus.$off('hello')
},
}
</script>

<style scoped>
.school{
background-color: skyblue;
padding: 5px;
}
</style>

School.vue

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
<template>
<div class="student">
<h2>学生姓名:{{name}}</h2>
<h2>学生性别:{{sex}}</h2>
<button @click="sendStudentName">把学生名给School组件</button>
</div>
</template>

<script>
export default {
name:'Student',
data() {
return {
name:'张三',
sex:'男',
}
},
methods: {
sendStudentName(){
this.$bus.$emit('hello',this.name)
}
},
}
</script>

<style lang="less" scoped>
.student{
background-color: pink;
padding: 5px;
margin-top: 30px;
}
</style>

$set

$nextTick

深入组件

插槽

插槽内容与出口

主文件

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
<script setup lang="ts">

import { ref, provide, reactive } from 'vue'
import First from "@/components/First.vue";
import MySlotWithName from "@/components/MySlotWithName.vue";
import MySlotWithOutName from "@/components/MySlotWithOutName.vue";

</script>

<template>
<h1>插槽演示</h1>

<!-- 匿名插槽 -->
<MySlotWithOutName>
<a href="https://baidu.com">跳转到baidu</a>
</MySlotWithOutName>

<br>

<MySlotWithName>
<!-- 具名插槽 -->
<!-- <template v-slot:url> -->

<!-- 简写的方式 -->
<template #url>
<a href="https://google.com">跳转到谷歌</a>
</template>
</MySlotWithName>
</template>

MySlotWithOutName.vue

1
2
3
4
<template>
匿名插槽
<slot />
</template>
1
2
3
4
<template>
具名插槽
<slot name="url" />
</template>

作用域插槽

1
2
3
4
5
6
7
8
9
10
11
12
13
<script setup lang="ts">
import MySlotWithName from "@/components/MySlotWithName.vue";

</script>

<template>
<MySlotWithName>
<template #url="data">
{{ data.title }}- {{ data.age }}
<a href="https://google.com">跳转到谷歌</a>
</template>
</MySlotWithName>
</template>
1
2
3
4
<template>
具名插槽
<slot name="url" title="anthony" age="12" />
</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
37
<script setup lang="ts">

import { ref, provide, reactive } from 'vue'
import First from "@/components/First.vue";

type Person = {
name: string,
age: number
}

const me: Person = reactive({
name: 'anthony',
age: 23
})

provide("provideDemo", me)

const update = () => {
me.age = me.age + 1
console.log(me);
}

const updateTwo = () => {
me.age = me.age + 2
console.log(me);
}

provide("provideUpdateTwo", updateTwo)

</script>

<template>
最顶层组件,接收到的数据:{{ me.name }},{{ me.age }}<button @click="update">修改所有层数据+1</button>
<br>
<First />
</template>

第一层组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script setup lang="ts">

import { inject } from 'vue'
import Second from "./Second.vue";

type Person = {
name: string,
age: number
}

const me = inject<Person>("provideDemo")
</script>
<template>
这是第一层,接收到的数据:{{ me.name }},{{ me.age }}
<br>
<Second />
</template>

第二层组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script setup lang="ts">
import { inject } from 'vue'
import Third from "./Third.vue";

type Person = {
name: string,
age: number
}

const me = inject<Person>("provideDemo")
</script>
<template>
这是第二层,接收到的数据:{{ me.name }},{{ me.age }}
<br>
<Third />
</template>

第三层组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script setup lang="ts">
import { inject } from 'vue'

type Person = {
name: string,
age: number
}

const me = inject<Person>("provideDemo")
const provideUpdateTwo = inject("provideUpdateTwo")
</script>

<template>
这是第三层,接收到的数据:{{ me.name }},{{ me.age }}<button @click="provideUpdateTwo">修改最顶层数据+2</button>
</template>

yarn

yarn的安装

1
npm install -g yarn

yarn常用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 初始化
yarn init
// 添加包
yarn add [package]
yarn add [package]@[version]
yarn add [package]@[tag]
// 添加到不同依赖项
yarn add [package] --dev
yarn add [package] --peer
yarn add [package] --optional
// 升级包
yarn upgrade [package]
// 移除依赖包
yarn remove [package]
// 安装所有依赖
yarn 或 yarn install

报错

1.node新版本引起的报错

1
2
3
4
this[kHandle] = new _Hash(algorithm, xofLen);
^

Error: error:0308010C:digital envelope routines::unsupported

解决方法1:

1
2
3
4
5
6
推荐:修改package.json,在相关构建命令之前加入SET NODE_OPTIONS=--openssl-legacy-provider

"scripts": {
"serve": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve",
"build": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service build"
},

解决方法2:

降低node版本

前端Vue父组件点击按钮打开子组件弹窗案例

父组件

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
<template>
<div>
# 需要定义ref=child
<indexChild ref="child"></indexChild>
<el-button @click="open">打开弹窗</el-button>
</div>
</template>
<script>
import indexChild from "../../components/indexChild.vue";
export default {
components: {
indexChild
},
data () {
return {
};
},
methods: {
open () {
this.$refs.child.open(); // 这样可以直接访问子组件方法,用ref拿子组件方法
}
}
}
</script>

子组件

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
<template>
<div>
<el-dialog title="收货地址" :visible.sync="dialogFormVisible">
<span>这是一段信息</span>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取 消</el-button>
<el-button type="primary" @click="dialogFormVisible = false">确 定</el-button>
</div>
</el-dialog>
</div>
</template>

<script>
export default {
data () {
return {
dialogFormVisible: false,
};
},
methods: {
open () { // 在父组件调用打开
this.dialogFormVisible = true
}
}
};
</script>

参考:前端Vue父组件点击按钮打开子组件弹窗案例

Pinia