拒绝标题党,哈哈哈,看完你就基本可以上手搞开发了,本文适合Vue
初学者,或者Vue2
迁移者,当然还是建议Vue3
官网完全过一遍。不适合精通原理,源码的大佬们。
先推荐两个vscode插件
Volar
首先推荐Volar
,使用vscode
开发Vue
项目的小伙伴肯定都认识Vetur
这个神级插件,有了它可以让我们得开发如鱼得水。 那么Volar
可以理解为Vue3
版本的Vetur
,代码高亮,语法提示,基本上Vetur
有的它都有。
特色功能
当然作为新的插件出山,肯定有它独有的功能。
多个根节点编辑器不会报错
Vue3
是允许我们有多个根节点的,但是我们如果使用Vetur就会报错,不会影响运行,但是看起来就很烦。 所以当我们转向Volar
那么就不会出现这个问题了。
编辑器分隔
即便Vue
的组件化开发,可以将单文件的代码长度大幅缩短,但还是动辄几百行甚是上千行。那么我们切换template
,script
和style
的时候就要频繁上下翻,虽然有的插件可以直接定位到css
,但是你回不去啊!所以这个功能简直是太人性化了。
安装完Volar
以后,打开一个.vue
文件,看vscode
的右上角,有这么一个图标,点一下。
它就会自动给你分隔成三个页面,分别对应template
,script
和style
,这样就太舒服了有没有。
还有很多新功能,可以参考下面这篇文章
Volar - vue终极开发神器!
Vue 3 Snippets
推荐的第二个插件叫做Vue 3 Snippets
,同样的,他也有自己的Vue2
版本。它是干什么的呢,可以看一下下面这张图,我只输入了“v3”
,它有很多提示,我们就先选择v3computed
,选中回车即可。
然后它就给自动给我们写了如下代码
是不是超级省事,摸鱼的时间又增加了!还有更多有趣的使用方式,小伙伴们自行探索吧。
创建Vue3项目
那么正式开始学习我们的Vue3
,先从创建项目开始。
使用 vue-cli 创建
输入下面的命令然后选择配置项进行安装即可,这里注意vue-cli
的版本一定要在4.5.0
以上
// 安装或者升级
npm install -g @vue/cli
//查看版本 保证 vue cli 版本在 4.5.0 以上
vue --version
// 创建项目
vue create my-project
//然后根据提示一步一步傻瓜式操作就行了
...
使用 Vite 创建
都说Vue3.0
和Vite2
更配,各种优化,各种快,但都不属于本文的内容,本文的目的我们只需要知道它特别好用,怎么用就行了。我这里是多选择了TS
,每行都有注释,一目了然。
// 初始化viete项目
npm init vite-app <project-name>
// 进入项目文件夹
cd <project-name>
// 安装依赖
npm install
//启动项目
npm run dev
创建完以后我们先来看看入口文件main.ts
// 引入createApp函数,创建对应的应用,产生应用的实例对象
import { createApp } from 'vue';
// 引入app组件(所有组件的父级组件)
import App from './App.vue';
// 创建app应用返回对应的实例对象,调用mount方法进行挂载 挂载到#app节点上去
createApp(App).mount('#app');
然后看看根组件app.vue
//Vue2组件中的html模板中必须要有一对根标签,Vue3组件的html模板中可以没有根标签
<template>
<img alt="Vue logo" src="./assets/logo.png">
<!-- 使用子级组件 -->
<HelloWorld msg="Welcome to Your Vue.js TypeScript App" />
</template>
<script lang="ts">
// 这里可以书写TS代码
// defineComponent函数,目的是定义一个组件 内部可以传入一个配置对象
import { defineComponent } from 'vue';
//引入子级组件
import HelloWorld from './components/HelloWorld.vue';
// 暴露出去一个定义好的组件
export default defineComponent({
// 当前组件的名字
name: 'App',
// 注册组件
components: {
// 注册一个子级组件
HelloWorld,
},
});
</script>
Composition API
接下来到了重头戏,Vue3
的招牌特性,Composition API
关于Composition API
这里有大佬做的动画演示,极力推荐。
那个忙了一夜的Vue3动画很好,就是太短了
Composition API
可以更方便的抽取共通逻辑,但是不要过于在意逻辑代码复用,以功能提取代码也是一种思路。
顺便提一句,Vue3
兼容大部分Vue2
语法,所以在Vue3
中书写Vue2
语法是没有问题的(废除的除外),但是既然我们已经升级Vue3
了,不建议混合使用,除非一些大型特殊项目需要兼容两个版本。
setup
setup
是组合Composition API
中的入口函数,也是第一个要使用的函数。
setup
只在初始化时执行一次,所有的Composition API
函数都在此使用。
setup() {
console.log('我执行了') //我执行了
},
可以通过console.log
看到setup
是在beforeCreate
生命周期之前执行的(只执行一次)
beforeCreate() {
console.log('beforeCreate执行了');
},
setup() {
console.log('setup执行了');
return {};
},
//setup执行了
//beforeCreate执行了
由此可以推断出setup
执行的时候,组件对象还没有创建,组件实例对象this
还不可用,此时this
是undefined
, 不能通过this
来访问data/computed/methods/props
。
返回对象中的属性会与data
函数返回对象的属性合并成为组件对象的属性,返回对象中的方法会与methods
中的方法合并成功组件对象的方法,如果有重名, setup
优先。因为在setup
中this
不可用,methods
中可以访问setup
提供的属性和方法, 但在setup
方法中不能访问data
和methods
里的内容,所以还是不建议混合使用。
setup
函数如果返回对象, 对象中的 属性 或 方法 , 模板 中可以直接使用
//templete
<div>{{number}}</div>
//JS
setup() {
const number = 18;
return {
number,
};
},
注意:setup
不能是一个async
函数: 因为返回值不再是return
的对象, 而是promise
, 模板中就不可以使用return
中返回对象的数据了。
setup的参数(props,context)`
props: 是一个对象,里面有父级组件向子级组件传递的数据,并且是在子级组件中使用props
接收到的所有的属性
context:上下文对象,可以通过es6
语法解构 setup(props, {attrs, slots, emit})
- attrs: 获取当前组件标签上所有没有通过
props
接收的属性的对象, 相当于this.$attrs
- slots: 包含所有传入的插槽内容的对象, 相当于
this.$slots
- emit: 用来分发自定义事件的函数, 相当于
this.$emit
演示attrs
和props
//父组件
<template>
<child :msg="msg" msg2='哈哈哈' />
</template>
<script lang='ts'>
import { defineComponent, ref } from 'vue';
// 引入子组件
import Child from './components/Child.vue';
export default defineComponent({
name: 'App',
components: {
Child,
},
setup() {
const msg = ref('hello,vue3');
return {
msg,
};
},
});
</script>
//子组件
<template>
<h2>子组件</h2>
<h3>msg:{{ msg }}</h3>
</template>
<script lang='ts'>
import { defineComponent } from 'vue';
export default defineComponent({
name: 'Child',
props: ['msg'],
setup(props, {attrs, slots, emit}) {
console.log('props:', props);//msg: "hello,vue3"
console.log('attrs:', attrs);//msg2: "哈哈哈"
return {};
},
});
</script>
演示emit
//父组件
<template>
<child @show="show" />
</template>
<script lang='ts'>
setup() {
const show = () => {
console.log('name:', 'hzw');
};
return {
show,
};
},
</script>
//子组件
<template>
<button @click='emitFn'>事件分发</button>
</template>
<script lang='ts'>
import { defineComponent } from 'vue';
export default defineComponent({
name: 'Child',
setup(props, { emit }) {
const emitFn = () => {
emit('show');
};
return {
emitFn,
};
},
});
</script>
ref
作用
定义一个响应式的数据(一般用来定义一个基本类型的响应式数据Undefined
、Null
、Boolean
、Number
和String
)
语法
代码语言:javascript复制const xxx = ref(initValue):
注意:script
中操作数据需要使用xxx.value
的形式,而模板中不需要添加.value
用一个例子来演示:实现一个按钮,点击可以增加数字
代码语言:javascript复制<template>
<div>{{count}}</div>
<button @click='updateCount'>增加</button>
</template>
在Vue2中
代码语言:javascript复制data() {
return {
conunt: 0,
};
},
methods: {
updateCount() {
this.conunt ;
},
},
在Vue3中
代码语言:javascript复制 setup() {
// ref用于定义一个响应式的数据,返回的是一个Ref对象,对象中有一个value属性
//如果需要对数据进行操作,需要使用该Ref对象的value属性
const count = ref(0);
function updateCount() {
count.value ;
}
return {
count,
updateCount,
};
},
在Vue2
中我们通过this.$refs
来获取dom
节点,Vue3
中我们通过ref
来获取节点
首先需要在标签上添加ref='xxx'
,然后再setup
中定义一个初始值为null
的ref
类型,名字要和标签的ref
属性一致
const xxx = ref(null)
注意:一定要在setup
的return
中返回,不然会报错。
还是用一个例子来演示:让输入框自动获取焦点
代码语言:javascript复制<template>
<h2>App</h2>
<input type="text">---
<input type="text" ref="inputRef">
</template>
<script lang="ts">
import { onMounted, ref } from 'vue'
/*
ref获取元素: 利用ref函数获取组件中的标签元素
功能需求: 让输入框自动获取焦点
*/
export default {
setup() {
const inputRef = ref<HTMLElement|null>(null)
onMounted(() => {
inputRef.value && inputRef.value.focus()
})
return {
inputRef
}
},
}
</script>
reactive
语法
代码语言:javascript复制const proxy = reactive(obj)
作用
定义多个数据的响应式,接收一个普通对象然后返回该普通对象的响应式代理器对象(Proxy)
,响应式转换是“深层的”:会影响对象内部所有嵌套的属性,所有的数据都是响应式的。
代码演示
代码语言:javascript复制<template>
<h3>姓名:{{user.name}}</h3>
<h3>年龄:{{user.age}}</h3>
<h3>wife:{{user.wife}}</h3>
<button @click="updateUser">更新</button>
</template>
setup() {
const user = reactive({
name: 'hzw',
age: 18,
wife: {
name: 'xioaohong',
age: 18,
books: ['红宝书', '设计模式', '算法与数据结构'],
},
});
const updateUser = () => {
user.name = '小红';
user.age = 2;
user.wife.books[0] = '金瓶梅';
};
return {
user,
updateUser,
};
},
computed函数:
与Vue2
中的computed
配置功能一致,返回的是一个ref
类型的对象
计算属性的函数中如果只传入一个回调函数 表示的是get
操作
import { computed } from 'vue';
const user = reactive({
firstName: '韩',
lastName: '志伟',
});
const fullName1 = computed(() => {
return user.firstName user.lastName;
});
return {
user,
fullName1,
};
计算属性的函数中可以传入一个对象,可以包含set
和get
函数,进行读取和修改的操作
const fullName2 = computed({
get() {
return user.firstName '_' user.lastName;
},
set(val: string) {
const names = val.split('_');
user.firstName = names[0];
user.lastName = names[1];
},
});
return {
user,
fullName2,
};
watch函数:
与Vue2
中的watch
配置功能一致,
- 参数1:要监听的数据
- 参数2:回调函数
- 参数3:配置作用 监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调
默认初始时不执行回调, 但可以通过配置immediate
为true
, 来指定初始时立即执行第一次
通过配置deep
为true
, 来指定深度监视
import { watch, ref } from 'vue';
const user = reactive({
firstName: '韩',
lastName: '志伟',
});
const fullName3 = ref('');
watch(
user,
({ firstName, lastName }) => {
fullName3.value = firstName '_' lastName;
},
{ immediate: true, deep: true }
);
return {
user,
fullName3,
};
watch
监听多个数据,使用数组
watch
监听非响应式数据的时候需要使用回调函数的形式
watch([()=>user.firstName,()=>user.lastName,fullName3],()=>{console.log('我执行了')})
watchEffect函数:
作用
监视数据发生变化时执行回调,不用直接指定要监视的数据, 回调函数中使用的哪些响应式数据就监视哪些响应式数据,默认初始时就会执行第一次, 从而可以收集需要监视的数据。
代码语言:javascript复制import { watchEffect, ref } from 'vue';
const user = reactive({
firstName: '韩',
lastName: '志伟',
});
const fullName4 = ref('');
watchEffect(() => {
fullName4.value = user.firstName '_' user.lastName;
});
return {
user,
fullName4,
};
watchEffect可以实现计算属性set方法
watchEffect(() => {
const names = fullName3.value.split('_');
user.firstName = names[0];
user.lastName = names[1];
});
生命周期对比:
注意:3.0
中的生命周期钩子要比2.X
中相同生命周期的钩子要快
Composition API
还新增了以下调试钩子函数:但是不怎么常用
- onRenderTracked
- onRenderTriggered 代码演示onBeforeMount(() => { console.log('--onBeforeMount') }) onMounted(() => { console.log('--onMounted') }) onBeforeUpdate(() => { console.log('--onBeforeUpdate') }) onUpdated(() => { console.log('--onUpdated') }) onBeforeUnmount(() => { console.log('--onBeforeUnmount') }) onUnmounted(() => { console.log('--onUnmounted') })
}
代码语言:javascript复制## toRefs
### 作用
把一个响应式对象转换成普通对象,该普通对象的每个属性都是一个` ref`
### 应用
我们使用`reactive`创建的对象,如果想在模板中使用,就必须得使用`xxx.xxx`的形式,如果大量用到的话还是很麻烦的,但是使用`es6`解构以后,会失去响应式,那么`toRefs`的作用就体现在这,,利用` toRefs `可以将一个响应式 `reactive `对象的所有原始属性转换为响应式的` ref `属性。当然小伙伴们可以自行开发更多应用场景。
### 代码演示
```js
<template>
<div>
代码语言:txt复制name:{{name}}
</div>
</template>
<script lang='ts'>
import { defineComponent, reactive, toRefs } from 'vue';
export default defineComponent({
name: '',
setup() {
代码语言:txt复制const state = reactive({
代码语言:txt复制 name: 'hzw',
代码语言:txt复制});
代码语言:txt复制const state2 = toRefs(state);
代码语言:txt复制setInterval(() => {
代码语言:txt复制 state.name = '===';
代码语言:txt复制}, 1000);
代码语言:txt复制return {
代码语言:txt复制 //通过toRefs返回的对象,解构出来的属性也是响应式的
代码语言:txt复制 ...state2,
代码语言:txt复制};
},
});
</script>
代码语言:txt复制## provide 与 inject
### 作用
实现跨层级组件(祖孙)间通信
### 代码演示
父组件
```javascript
<template>
<h1>父组件</h1>
<p>当前颜色: {{color}}</p>
<button @click="color='red'">红</button>
<button @click="color='yellow'">黄</button>
<button @click="color='blue'">蓝</button>
<hr>
<Son />
</template>
<script lang="ts">
import { provide, ref } from 'vue'
import Son from './Son.vue'
export default {
name: 'ProvideInject',
components: {
代码语言:txt复制Son
},
setup() {
代码语言:txt复制const color = ref('red')
代码语言:txt复制provide('color', color)
代码语言:txt复制return {
代码语言:txt复制 color
代码语言:txt复制}
}
}
</script>
代码语言:txt复制子组件
```javascript
<template>
<div>
代码语言:txt复制<h2>子组件</h2>
代码语言:txt复制<hr>
代码语言:txt复制<GrandSon />
</div>
</template>
<script lang="ts">
import GrandSon from './GrandSon.vue'
export default {
components: {
代码语言:txt复制GrandSon
},
}
</script>
代码语言:txt复制孙子组件
```javascript
<template>
<h3 :style="{color}">孙子组件: {{color}}</h3>
</template>
<script lang="ts">
import { inject } from 'vue'
export default {
setup() {
代码语言:txt复制const color = inject('color')
代码语言:txt复制return {
代码语言:txt复制 color
代码语言:txt复制}
}
}
</script>
代码语言:txt复制# 其他特性
## Teleport(瞬移)
### 作用
`Teleport` 提供了一种干净的方法, 让组件的`html`在父组件界面外的特定标签(很可能是`body`)下插入显示 换句话说就是可以把 **子组件** 或者 **dom节点** 插入到任何你想插入到的地方去。
### 语法
使用`to`属性 引号内使用选择器
```javascript
<teleport to="body">
</teleport>
代码语言:txt复制### 代码演示
```javascript
//父组件
<template>
<div class="father">
代码语言:txt复制<h2>App</h2>
代码语言:txt复制<modal-button></modal-button>
</div>
</template>
<script lang="ts">
import ModalButton from './components/ModalButton.vue'
export default {
setup() {
代码语言:txt复制return {}
},
components: {
代码语言:txt复制ModalButton,
},
}
</script>
//子组件
<template>
<div class="son">
代码语言:txt复制<button @click="modalOpen = true">
代码语言:txt复制 点我打开对话框
代码语言:txt复制</button>
代码语言:txt复制<teleport to="body">
代码语言:txt复制 <div v-if="modalOpen"
代码语言:txt复制 class="looklook">
代码语言:txt复制 看看我出现在了哪里
代码语言:txt复制 <button @click="modalOpen = false">
代码语言:txt复制 Close
代码语言:txt复制 </button>
代码语言:txt复制 </div>
代码语言:txt复制</teleport>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'modal-button',
setup() {
代码语言:txt复制const modalOpen = ref(false)
代码语言:txt复制return {
代码语言:txt复制 modalOpen,
代码语言:txt复制}
},
}
</script>
代码语言:txt复制可以看到在子组件中的`looklook`元素跑到了`body`下面,而之前的位置默认出现了两行注释
![请在此添加图片描述](https://ask.qcloudimg.com/http-save/yehe-6181779/5e80000793c6a3990f6597ee2fe21dbd.png?qc_blockWidth=596&qc_blockHeight=372)
## Suspense(不确定的)
### 作用
它们允许我们的应用程序在等待异步组件时渲染一些后备内容,可以让我们创建一个平滑的用户体验
### 语法
```javascript
<Suspense>
代码语言:txt复制<template v-slot:default>
代码语言:txt复制 <!-- 异步组件 -->
代码语言:txt复制 <AsyncComp />
代码语言:txt复制</template>
代码语言:txt复制<template v-slot:fallback>
代码语言:txt复制 <!-- 后备内容 -->
代码语言:txt复制 <h1>LOADING...</h1>
代码语言:txt复制</template>
</Suspense>
代码语言:txt复制`vue3`中引入异步组件的方式
```javascript
const AsyncComp = defineAsyncComponent(() => import('./AsyncComp.vue'))
代码语言:txt复制### 代码演示
父组件
```javascript
<template>
<Suspense>
代码语言:txt复制 <!-- v-slot:defaul可以简写成#defaul -->
代码语言:txt复制<template v-slot:default>
代码语言:txt复制 <AsyncComp/>
代码语言:txt复制</template>
代码语言:txt复制<template v-slot:fallback>
代码语言:txt复制 <h1>LOADING...</h1>
代码语言:txt复制</template>
</Suspense>
</template>
<script lang="ts">
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() => import('./AsyncComp.vue'))
export default {
setup() {
代码语言:txt复制return {
代码语言:txt复制}
},
components: {
代码语言:txt复制AsyncComp,
}
}
</script>
代码语言:txt复制子组件
```javascript
<template>
<h2>AsyncComp22</h2>
<p>{{msg}}</p>
</template>
<script lang="ts">
export default {
name: 'AsyncComp',
setup () {
代码语言:txt复制 return new Promise((resolve, reject) => {
代码语言:txt复制 setTimeout(() => {
代码语言:txt复制 resolve({
代码语言:txt复制 msg: 'abc'
代码语言:txt复制 })
代码语言:txt复制 }, 2000)
代码语言:txt复制 })
}
}
</script>
代码语言:txt复制通过下图可以看到在异步组件加载出来之前,显示的是`fallback`中的内容
![请在此添加图片描述](https://ask.qcloudimg.com/http-save/yehe-6181779/44e57dfc490e4722cf9341043b566585.gif?qc_blockWidth=793&qc_blockHeight=302)
## 响应式数据的判断
### 作用
- **isRef**: 检查一个值是否为一个 `ref` 对象
- **isReactive**: 检查一个对象是否是由 `reactive` 创建的响应式代理
- **isReadonly**: 检查一个对象是否是由 `readonly` 创建的只读代理
- **isProxy**: 检查一个对象是否是由 `reactive` 或者 `readonly` 方法创建的代理
### 代码演示
```javascript
setup() {
代码语言:txt复制const state1 = ref(1);
代码语言:txt复制console.log('isref:', isRef(state1));//isref: true
代码语言:txt复制const state2 = reactive({});
代码语言:txt复制console.log('isReactive:', isReactive(state2));//isReactive: true
代码语言:txt复制const state3 = readonly({});
代码语言:txt复制console.log('isReadonly:', isReadonly(state3));//isReadonly: true
代码语言:txt复制const state4 = reactive({});
代码语言:txt复制console.log('isProxy:', isProxy(state2));//isProxy: true
代码语言:txt复制console.log('isProxy:', isProxy(state4));//isProxy: true
代码语言:txt复制return {};
},
代码语言:txt复制## 其他不常用特性
还有很多很多不常用的新特性,我在日常开发中是没有用到的,很多都是用来做优化的,感兴趣的小伙伴们自行去官网查看,或者大佬们可以介绍一下应用场景。
- shallowReactive
- shallowRef
- readonly
- shallowReadonly
- markRaw
- customRef
- ...语法糖
虽然`Composition API`用起来已经非常方便了,但是我们还是有很烦的地方,比如
- 组件引入了还要注册
- 属性和方法都要在`setup`函数中返回,有的时候仅一个`return`就十几行甚至几十行
- ...
- 不想写啊怎么办 好办,`Vue3`官方提供了`script setup`语法糖
只需要在`script`标签中添加`setup`,组件只需引入不用注册,属性和方法也不用返回,`setup`函数也不需要,甚至`export default`都不用写了,不仅是数据,计算属性和方法,甚至是自定义指令也可以在我们的`template`中自动获得。
但是这么过瘾的语法糖,还是稍微添加了一点点心智负担,因为没有了`setup`函数,那么`props`,`emit`,`attrs`怎么获取呢,就要介绍一下新的语法了。
`setup script`语法糖提供了三个新的`API`来供我们使用:`defineProps`、`defineEmit`和`useContext`
- **defineProps** 用来接收父组件传来的值`props`。
- **defineEmit** 用来声明触发的事件表。
- **useContext** 用来获取组件上下文`context`。代码演示
父组件
```javascript
<template>
<div>
代码语言:txt复制<h2>我是父组件!</h2>
代码语言:txt复制<Child msg="hello"
代码语言:txt复制 @child-click="handleClick" />
</div>
</template>
<script setup>
import Child from './components/Child.vue'
const handleClick = (ctx) => {
console.log(ctx)
}
</script>
代码语言:txt复制子组件
```javascript
<template>
<span @click="sonClick">msg: {{ props.msg }}</span>
</template>
<script setup>
import { useContext, defineProps, defineEmit } from 'vue'
const emit = defineEmit('child-click')
const ctx = useContext()
const props = defineProps({
msg: String,
})
const sonClick = () => {
emit('child-click', ctx)
}
</script>
代码语言:txt复制我们点击一下子组件
![请在此添加图片描述](https://ask.qcloudimg.com/http-save/yehe-6181779/1cc6a66d2a460bd1ef15e555fb2c909f.png?qc_blockWidth=793&qc_blockHeight=216)
可以看到`context`被打印了出来,其中的`attrs`、`emit`、`slots`、`expose`属性和方法依然可以使用。 `props`也可以输出在页面上,事件也成功派发。
# 其他知识点
接下来介绍一下我使用`Vue3`过程中遇到的问题或者小技巧,不全面,想起什么就写什么吧
## script setup语法糖请注意
如果在父组件中通过`ref='xxx'`的方法来获取子组件实例,子组件使用了`script setup`语法糖,那么子组件的数据需要用expose的方式导出,否则会因为获取不到数据而报错。
### 代码演示
父组件
```javascript
<template>
<div>
代码语言:txt复制<h2>我是父组件!</h2>
代码语言:txt复制<Child ref='son' />
</div>
</template>
<script setup>
import Child from './components/Child.vue'
import { ref } from 'vue'
const son = ref(null)
console.log('