一、新特性
Composition API
- ref和reactive
- computed和watch
- 新的生命周期函数
- 自定义函数 - Hooks函数
其它新增特性
- Teleport - 瞬移组件的位置
- Suspense - 异步加载组件的新福音
- 全局API的修改和优化
更好的 Typescript支持
二、使用vue-cli设置vue3开发环境
2.1、环境配置
安装node:https://nodejs.org/zh-cn/
配置npm在安装全局模块时的路径和缓存cache的路径,在node.js安装目录下新建两个文件夹 node_global和node_cache,然后在cmd命令下执行如下两个命令:
npm config set prefix "D:\Program Files\nodejs\node_global"
npm config set cache "D:\Program Files\nodejs\node_cache"
然后将路径:D:\Program Files\nodejs\node_global
加入到Path系统环境变量中
最后在环境变量 -> 系统变量中新建一个变量名为 “NODE_PATH”, 值为:“D:\Program Files\nodejs\node_modules”
:
设置淘宝镜像源:npm config set registry https://registry.npm.taobao.org/
安装更新命令:npm install -g @vue/cli
查看版本:vue --version
创建项目:vue create xxx
2.2、vue3基本项目创建
2.2.1、命令行模式创建项目
进入命令行模式,输入:vue create vue3-basic
,回车执行命令后,使用方向键,选择 Manually select features
使用方向键进行切换,空格键进行选择,选择好项目需要的特性后,回车进入下一步
选择vue版本,选择 3.x
Use class-style component syntax? (y/N):是否需要使用类组件,选择N
Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? (Y/n):结合babel使用typescript,选择n
保存时进行Lint
把配置文件放到一个单独的文件里
Save this as a preset for future projects? (y/N):将此另存为预设值以供将来的项目使用,选择N
回车进行项目的创建。
创建完成后,进行项目根目录:cd vue3-basic
,可以通过命令:npm run serve
,启动项目。
2.2.2、ui界面创建项目
命令行,输入命令:vue ui
,会自动启动服务,通过默认的浏览器打开创建项目的页面。
点击“创建”,再点击“在此创建新项目”按钮,进入项目创建页面。
后续的创建流程和命令行模式创建是类似,根据提示进行创建。
2.3、项目文件结构分析
上个步骤,通过vue-cli创建了项目的结构
各个文件和目录的功能和作用
public:存放公共文件和项目入口的html文件
src:原始文件
- asserts:存放静态文件
- components:存放子组件
- App.vue:根组件
- main.ts:入口文件
- shims-vue.d.ts:为vue创建的定义文件
package.json:项目包管理文件
2.4、插件安装
推荐安装的插件
- Chinese:简体中文安装包
- ESLint:代码检测
解决eslint不生效的的方法,通过在项目中新建目录.vscode,新建文件settings.json{ "eslint.validate": ["typescript"] }
- Vetur:给vue文件的开发提供支持
插件安装
三、新特性的使用
3.1、ref的妙用
定义ref类型的数据,传入的数据都是【原始类型】,通过ref创建的变量类型数据,是响应式的。
import { ref } from 'vue'
const count = ref(0)
赋值需要通过value属性:count.value++
模板中可以直接使用:<h1>{{count}}</h1>
示例
<template>
<div id="app">
<img src="./assets/logo.png" alt="Vue logo">
<!-- Ref类型的数据,展示的时候,不需要通过count.value显示数据 -->
<h1>{{count}}</h1>
<h1>{{double}}</h1>
<button @click="increase">👍+1</button>
</div>
</template>
<script lang="ts">
import { ref, computed } from 'vue'
export default {
name: 'App',
setup() {
const count = ref(0)
// 计算类型函数,computed接收一个函数,进行回调
const double = computed(() => {
return count.value * 2
})
const increase = () => {
// count是Ref类型的数据,值存在value属性中
count.value++
}
return {
// 键和变量名称一致,可以省略key
count,
increase,
double
}
}
};
</script>
3.2、reactive
reactive和ref非常相似,都是一个函数类型。不同的是,reactive定义数据,传入的数据类型是【对象】。
通过reactive创建的对象,对象中的每一个变量都是响应式的。但是如果需要把对象的变量数据展开,然后返回模板调用时,此时对象的变量是非响应式的,需要通过toRefs函数对对象中的每个属性进行处理,再次展开返回的单独对象属性,也是响应式的。
vue3中对象和数组中值的变更之后,变更的值依然是响应式的。
示例
<template>
<div id="app">
<img src="./assets/logo.png" alt="Vue logo">
<!-- Ref类型的数据,展示的时候,不需要通过count.value显示数据 -->
<h1>{{count}}</h1>
<h1>{{double}}</h1>
<button @click="increase">👍+1</button>
</div>
</template>
<script lang="ts">
import { ref, computed, reactive, toRefs } from 'vue'
interface DataProps {
count: number;
double: number;
increase: () => void;
}
export default {
name: 'App',
setup() {
const data: DataProps = reactive({
count: 0,
increase: () => {data.count++},
// 函数表达式只有一行且return,可以简写
// computed会造成类型推论的循环,定义通过定义类型来解决
double: computed(() => data.count * 2)
})
// 通过refData函数,把data对象中的每一个数据,都变成ref类型,可以响应式
const refData = toRefs(data)
return {
// 键和变量名称一致,可以省略key
// 通过..., 可以把对象里的变量数据展开,模板中就可以直接通过变量名称来使用
...refData
}
}
};
</script>
3.3、生命周期
- setup() : 开始创建组件之前,在 beforeCreate 和 created 之前执行,创建的是 data 和 method
- onBeforeMount() : 组件挂载到节点上之前执行的函数;
- onMounted() : 组件挂载完成后执行的函数;
- onBeforeUpdate(): 组件更新之前执行的函数;
- onUpdated(): 组件更新完成之后执行的函数;
- onBeforeUnmount(): 组件卸载之前执行的函数;
- onUnmounted(): 组件卸载完成后执行的函数;
- onActivated(): 被包含在 中的组件,会多出两个生命周期钩子函数,被激活时执行;
- onDeactivated(): 比如从 A 组件,切换到 B 组件,A 组件消失时执行;
- onErrorCaptured(): 当捕获一个来自子孙组件的异常时激活钩子函数。
调试使用
- onRenderTracked
- onRenderTriggered
示例
import { onMounted, onUpdated, onRenderTriggered } from 'vue'
setup() {
onMounted(() => {
console.log('mounted')
})
onUpdated(() => {
console.log('updated')
})
// 调试
onRenderTriggered((event) => {
console.log(event)
})
}
3.4、watch:监听器
import { watch } from 'vue'
// watch 简单应用
watch(data, () => {
document.title = 'updated ' + data.count
})
// watch 的两个参数,代表新的值和旧的值
watch(refData.count, (newValue, oldValue) => {
console.log('old', oldValue)
console.log('new', newValue)
document.title = 'updated ' + data.count
})
// watch 多个值,返回的也是多个值的数组
watch([greetings, data], (newValue, oldValue) => {
console.log('old', oldValue)
console.log('new', newValue)
document.title = 'updated' + greetings.value + data.count
})
// 使用 getter 的写法 watch reactive 对象中的一项
// 不能直接监听data.count,因为是非响应式的
watch([greetings, () => data.count], (newValue, oldValue) => {
console.log('old', oldValue)
console.log('new', newValue)
document.title = 'updated' + greetings.value + data.count
})
3.5、鼠标追踪器
鼠标追踪器:页面点击鼠标左键,记录点击的坐标
在src目录中创建hooks目录,在hooks目录中创建useMousePosition.ts文件。
import { reactive,toRefs, onMounted, onUnmounted } from 'vue'
// 将组件内逻辑抽象成可复用的函数
function useMousePosition() {
const position = reactive({
x: 0,
y: 0
})
// 鼠标事件的回调函数
const updateMouse = (e: MouseEvent) => {
position.x = e.pageX
position.y = e.pageY
}
// 组件挂载时,添加事件到dom中
onMounted(() => {
console.log('mounted')
document.addEventListener('click', updateMouse)
})
// 组件卸载时,移除dom的监听事件
onUnmounted(() => {
document.removeEventListener('click', updateMouse)
})
return toRefs(position)
}
export default useMousePosition
3.6、useURLLoader:异步获取请求数据
axios安装:npm install axios --save
import { reactive, toRefs } from 'vue'
import axios from 'axios'
function useURLLoader(url: string) {
// 请求状态和结果数据
const loaderData = reactive({
result: null,
loading: true,
loaded: false,
error: null
})
// 发送异步请求,获得data
// 由于 axios 都有定义,所以rawData 可以轻松知道其类型
axios.get(url).then((rawData) => {
loaderData.loading = false
loaderData.loaded = true
loaderData.result = rawData.data
}).catch(e => {
loaderData.error = e
})
return toRefs(loaderData)
}
export default useURLLoader
3.7、模块化结合typescript-泛型改造
import { ref } from 'vue'
import axios from 'axios'
function useURLLoader<T>(url: string) {
// 请求状态和结果数据
// 声明几个ref,代表不同的状态和结果
const result = ref<T | null>(null)
const loading = ref(true)
const loaded = ref(false)
const error = ref(null)
// 发送异步请求,获得data
// 由于 axios 都有定义,所以rawData 可以轻松知道其类型
axios.get(url).then((rawData) => {
loading.value = false
loaded.value = true
result.value = rawData.data
}).catch(e => {
error.value = e
})
return {result, loading, loaded, error}
}
export default useURLLoader
3.8、Teleport - 瞬间移动
index.html中添加挂载点:<div id="modal"></div>
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- Teleport 挂载点 -->
<div id="modal"></div>
</body>
</html>
Modal.vue
<template>
<teleport to="#modal">
<div id="center" v-if="isOpen">
<h2><slot>this is a modal</slot></h2>
<button @click="buttonClick">Close</button>
</div>
</teleport>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
props: {
isOpen: Boolean
},
// 定义向外发送的事件的名称
emits: {
'close-modal': null
},
setup(props, context) {
const buttonClick = () => {
// 向外发送事件
context.emit('close-modal')
}
return {
buttonClick
}
}
})
</script>
<style>
#center {
width: 200px;
height: 200px;
border: 2px solid black;
background: white;
position: fixed;
left: 50%;
top: 50%;
margin-left: -100px;
margin-top: -100px;
}
</style>
3.9、Suspense异步请求组件
- Suspense是vue3推出的一个内置的特殊组件
- 如果使用Suspense,要返回一个promise,通过async和await实现
<template>
<img :src="result && result.message">
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import axios from 'axios'
export default defineComponent({
async setup() {
const rawData = await axios.get('https://dog.ceo/api/breeds/image/random')
return {
result: rawData.data
}
}
})
</script>
使用
<Suspense>
<template #default>
<!-- 多个异步组件,使用div包裹 -->
<div>
<async-show />
<dog-show />
</div>
</template>
<!-- 异步组件未加载成功显示的内容 -->
<template #fallback>
<h1>Suspense Loading...</h1>
</template>
</Suspense>
3.10、全局API修改
- 全局配置:
app.config
- 全局注册类API:
app.component:组件注册、app.directive:全局指令
- 行为扩展类API:
app.mixin:全局混入、app.use:安装全局插件
main.ts 入口主文件中编写
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.config.isCustomElement = tag =>
tag.startswith('app-')
app.use(/* ... */)
app.mixin(/* ... */)
app.component(/* ... */)
app.directive(/* ... */)
app.config.globalProperties.customProperty = () => {}
app.mount('#app')