本Vue3笔记是根据jspang.com视频教程完成,全程由ts编写
前置知识
- Vue2.x
- TypeScript
- npm
Vue3项目的搭建
这里使用 Vue-cli
搭建项目
首先全局安装 vue-cli
,版本需在 4.5
以上
npm install -g @vue/cli
找到一个空目录下,打开终端执行 vue create vue3-1
命令
出现如下对话,这里选择手动自定义模板,因为我们要使用 TypeScript
选择完 手动自定义模板
之后,出现如下对话,这里我们选择 TypeScript
,按 空格
进行选择,按 回车
执行下一步
出现如下对话,我们选择 3.x
的版本,然后 回车
执行下一步
出现如下对话,是否选择编译为 JSX
语法,这里我们选择 No
出现如下选择,直接选择 No
或者 第一项
即可
这里出现选择 单独写入文件
还是写入 package.json
,这里我们选择第一项单独写入文件
出现如下对话,询问是否选择将设置保存起来,这里我们选择 No
出现如下即表示项目项目构建完成
执行提示命令 启动项目
,至此 vue/cli
搭建 vue3
项目就完成了
cd vue3-1
npm run serve
使用vue3自动模板详情请见【前端靓仔】
初始目录结构
node_modules
所有的项目依赖包
public
公共文件夹
- favicon.ico
- index.html 入口
html
文件
src
所有写的代码
- assets 静态文件
- components 自定义组件
- App.vue 跟组件
- main.ts 入口文件
- shims-vue.d.ts 类文件/定义文件 定义一个文件让 ts 认识 vue
.browserslistrc
兼容作用,不同前端工具之间功能浏览器对node版本进行配置
.eslintrc.js
eslint的配置文件
.gitignore
配置不上传到git的文件
package.json
{
"name": "vue3-1",//项目名称
"version": "0.1.0",//项目版本
"private": true,
"scripts": {
"serve": "vue-cli-service serve",//本地启动
"build": "vue-cli-service build",//打包
"lint": "vue-cli-service lint",//代码检测
},
"dependencies": {//生产环境
"vue": "^3.0.0-0"
},
"devDependencies": {//开发环境
"@typescript-eslint/eslint-plugin": "^2.33.0",
"@typescript-eslint/parser": "^2.33.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-typescript": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/compiler-sfc": "^3.0.0-0",
"@vue/eslint-config-typescript": "^5.0.2",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^7.0.0-0",
"typescript": "~3.9.3"
}
}
tsconfig.json
ts 的配置文件
src/main.ts 详解
import { createApp } from 'vue' //引入createApp挂载用来App
import App from './App.vue'//根组件
createApp(App).mount('#app') //创建App组件挂载到根节点
setup函数和ref函数
首先打开App.vue
,将多余的代码都删除掉
<template></template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'App'
});
</script>
修改 template
为如下
<template>
<div>
<h2>欢迎来到XX课堂</h2>
<p>请选择学习的语言</p>
<div><button></button></div> <!--这里是需要循环生成的-->
</div>
</template>
修改 script
为如下
<script lang="ts">
import { defineComponent,ref } from 'vue';//引入ref函数
export default defineComponent({
name: 'App',
//在vue2中使用的是 data(){return{}} methods:{} ...
//在vue3中直接使用setup函数 将template中需要的事件、数据return出去
setup(){
const languages = ref(['html','css','javascript']);//定义所有的语言
return {
languages //并将数据return出去
}
}
});
</script>
然后修改 template
来循环出来 script
中 return
的数据
<button v-for="(item,index) in languages" :key="index">{{index}}:{{item}}</button>
这时候就可以先预览一下页面内容
然后我们需要先在 template
中添加一行用来显示选择的内容
<div>你选择了【{{selectLan}}】语言</div> <!--注意:这里有一个selectLan请接着往下看-->
然后在 script
的 setup
函数中添加方法和选择之后的值
setup(){
const languages = ref(['html','css','javascript']);
const selectLan = ref("");//选择的语言,起初让他为空
//定义方法,接受一个参数为选择的下标,因为用的是ts,所以参数可以加一个类型注解
const selectLanFun = (index: number) => { //注意:注解“冒号”后面需要添加空格
//重新赋值选择的数据 设置和获取ref值都需要加value
selectLan.value = languages.value[index]
}
return {
languages,//暴露语言数组,用于在template中循环渲染
selectLan,//暴露选择之后的数据
selectLanFun,//因为template中需要使用该方法,所以需要return出去
}
}
然后给 template
中的 button
添加点击事件
<button
v-for="(item,index) in languages"
:key="index"
@click="selectLanFun(index)"
>{{index}}:{{item}}</button>
reactive函数
该函数主要是优化上一节的代码
- 主要优化
setup
函数中的方法
和变量
都进行了混淆 - 优化了
设置
和获取``ref
值都需要用value
的形式
首先我们先进行一个小小的改动
去掉引入的 defineComponent
,并将暴露改成如下写法
import { ref } from 'vue'
export default {
...
}
检测之后没有报错程序可以正常运行
接下来我们优化 setup
内部
setup(){ //使用一个data来包装,最后只需要返回data即可
const data = reactive({
languages:['html','css','javascript'],//定义数组,template模板中循环需要
selectLan:'',//被选中的语言
selectLanFun:(index: number)=>{//选择语言的方法
data.selectLan = data.languages[index]//这里就不需要使用value,但是赋值是给data下的数据赋值,需要加上data
}
})
// const languages = ref(['html','css','javascript']);
// const selectLan = ref("");//选择的语言
// const selectLanFun = (index: number) => {
// selectLan.value = languages.value[index]
// }
return {
data
// languages,
// selectLan,
// selectLanFun
}
}
修改 template
中的数据,在前面加上 data.
<div>
<button
v-for="(item,index) in data.languages"
:key="index"
@click="data.selectLanFun(index)">{{index}}:{{item}}</button>
</div>
<div>你选择了【{{data.selectLan}}】语言</div>
因为我们这里使用了 TypeScript
,为了使代码更加严谨,加上 ts
专属的的类型注解
什么是类型注解,指的是对一个变量或者参数进行数据类型的确定,不使用类型推断的形式(javascript自己判断数据类型)
我们在 export default
上面写一个接口的形式定义类型注解
interface DataProps{
languages:string[];
selectLan:string;
selectLanFun:(index:number)=> void;
}
这样的话,我们在 setup
函数内部给 data
添加这个类型注解,则 data
下面的属性都具有确定的数据类型
const data: DataProps = reactive({
languages:['html','css','javascript'],
selectLan:'',
selectLanFun:(index: number)=>{
data.selectLan = data.languages[index]
}
})
现在我们不想要在 template
中使用 data
来取值怎么操作呢,请接着看下篇
toRefs函数
首先,我们引入 reactive
的时候引入 toRefs
import { reactive,toRefs } from 'vue';
修改 setup 函数里面的写法,其实就是给 data 使用 toRefs 进行包装
const data: DataProps = reactive({
languages:['html','css','javascript'],
selectLan:'',
selectLanFun:(index: number)=>{
data.selectLan = data.languages[index]
}
})
const refData = toRefs(data);
这个时候 return
的时候就需要返回 包装之后的 data
为 refData
,并且需要使用扩展运算符
来展开
return{
...refData
}
最后去掉 template
中的 data
,经检查,发现并无报错,程序可以正常使用即可
vue3的生命周期
-
setup(): 开始创建组件之前,在beforeCreate和created之前执行。创建的是data和method
-
onBeforeMount(): 组件挂载到节点上之前执行的函数。
-
onMounted(): 组件挂载完成后执行的函数。
-
onBeforeUpdate(): 组件更新之前执行的函数。
-
onUpdated(): 组件更新完成之后执行的函数。
-
onBeforeUnmount(): 组件卸载之前执行的函数。
-
onUnmounted(): 组件卸载完成后执行的函数
-
onActivated(): 被包含在中的组件,会多出两个生命周期钩子函数。被激活时执行。
-
onDeactivated(): 比如从 A 组件,切换到 B 组件,A 组件消失时执行。
-
onErrorCaptured(): 当捕获一个来自子孙组件的异常时激活钩子函数(以后用到再讲,不好展现)。
使用生命周期函数之前,须先引入( setup
除外 )
import {
onMounted,
onBeforeMount,
onBeforeUpdate,
onUpdated,
} from "vue";
export default {
name: "App",
setup() {
console.log("1-开始创建组件-----setup()");
const data: DataProps = reactive({
language: ["html", "css", "javascript"],
selectLan: "",
selectLanFun: (index: number) => {
data.selectLan = data.languages[index]
},
});
onBeforeMount(() => {
console.log("2-组件挂载到页面之前执行-----onBeforeMount()");
});
onMounted(() => {
console.log("3-组件挂载到页面之后执行-----onMounted()");
});
onBeforeUpdate(() => {
console.log("4-组件更新之前-----onBeforeUpdate()");
});
onUpdated(() => {
console.log("5-组件更新之后-----onUpdated()");
});
const refData = toRefs(data);
return {
...refData,
};
}
}
}
还可以在setup()
函数之后编写Vue2
的生命周期函数,代码如下
beforeCreate() {
console.log("1-组件创建之前-----beforeCreate()");
},
beforeMount() {
console.log("2-组件挂载到页面之前执行-----BeforeMount()");
},
mounted() {
console.log("3-组件挂载到页面之后执行-----Mounted()");
},
beforeUpdate() {
console.log("4-组件更新之前-----BeforeUpdate()");
},
updated() {
console.log("5-组件更新之后-----Updated()");
},
Vue2.x 和 Vue3.x 生命周期对比
Vue2--------------vue3
beforeCreate -> setup()
created -> setup()
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted
activated -> onActivated
deactivated -> onDeactivated
errorCaptured -> onErrorCaptured
onRenderTracked()和 onRenderTriggered()钩子函数的使用
这两个钩子函数是
Vue3.x
版本新加的两个钩子函数,官方说是用来调试使用的,但是目前还没有给出具体的调试案例
对新旧钩子函数的使用原则
Vue 官方的文档里,明确指出了。如果使用 Vue3,请尽量使用新的生命周期钩子函数,也就是上篇写在
setup()
函数中带on
的这些钩子函数
onRenderTracked 状态跟踪
onRenderTracked
直译过来就是状态跟踪
,它会跟踪页面上所有响应式变量和方法的状态,也就是我们用return
返回去的值,他都会跟踪。只要页面有update
的情况,他就会跟踪,然后生成一个event
对象,我们通过event
对象来查找程序的问题所在
使用onRenderTracked
同样要使用import
进行引入
引用之后就可以在 setup()
函数中使用了
import { .... ,onRenderTracked,} from "vue";
onRenderTracked((event) => {
console.log("状态跟踪组件----------->");
console.log(event);
});
写完后可以到终端中启动测试服务yarn serve
,然后看一下效果,在组件没有更新的时候onRenderTracked
是不会执行的,组件更新时,他会跟组里边每个值和方法的变化
onRenderTriggered 状态触发
onRenderTriggered
直译过来是状态触发
,它不会跟踪每一个值,而是给你变化值的信息,并且新值和旧值都会给你明确的展示出来
使用它同样要先用import
进行引入
import { .... ,onRenderTriggered,} from "vue";
在使用onRenderTriggered
前,记得注释相应的onRenderTracked
代码,这样看起来会直观很多。 然后把onRenderTriggered()
函数,写在setup()
函数里边
onRenderTriggered((event) => {
console.log("状态触发组件--------------->");
console.log(event);
});
对 event 对象属性的详细介绍:
- key 哪边变量发生了变化
- newValue 更新后变量的值
- oldValue 更新前变量的值
- target 目前页面中的响应变量和函数
Vue3中Watch的使用和注意事项
Vue2中也有
watch
-监听器(侦听器),作用是用来侦测响应式数据的变化,并且可以得到newValue
新值和oldValue
老值,但在Vue3中watch
有了一些细微的变化
这篇来实现一个功能,用来修改页面title
首先在 template
部分添加如下代码
<div><button>【选择完成】</button></div>
<div></div>
使得页面变成如下图所示
然后在 ts
部分引入 ref
和 watch
import { ref,reactive,toRefs,watch } from 'vue';
在 setup
函数中 return
前面添加如下代码
const overText = ref("xx课堂") //定义一个属性用来在template中显示
const overAction = () => {
//定义一个方法用来点击
overText.value = "点餐完成|" + overText.value;
}
watch([overText,()=>data.selectLan],(newValue,oldValue) => {
//watch事件监听器,稍后解释
console.log(newValue,oldValue)
document.title = newValue[0]; //修改页面的title}
)
在 setup
函数中 return
出去定义的 overText
和 overAction
return {
...refData,
overText,
overAction
}
然后在 template
中按钮上绑定点击事件,渲染出内容
<div><button @click="overAction">【选择完成】</button></div>
<div>{{overText}}</div>
这里来说一下 watch
事件监听器
watch是个函数,使用时直接调用,但需要传两个参数
- 第一个参数是个 Array 或者 String
- 第二个参数是个回调函数,该回调函数也有两个参数,第一个参数为newValue,第二个参数是oldValue
当第一个参数是 Array
的时候,newValue
和 oldValue
分别是数组,对应参数
【注意】:如果监听的参数为
toRefs
包装出来的对象下面的参数时,必须使用getter
,所以这里使用了 箭头函数来return
出来,造一个假的getter
Vue3中模块化介绍
这次我们要做一个功能,需要在页面上显示一个时间,效果如下图
首先打开 App.vue
,先把功能实现,把无用代码全部删除掉,删除完的代码见下方
<template>
<div></div>
</template>
<script lang="ts">
import { ref } from 'vue'
export default {
name: 'App',
setup(){
}
}
</script>
接下来我们开始实现功能
在 template
中添加如下
<div>
<p>{{nowTime}}</p>
<button @click="getNowTime">显示时间</button>
</div>
在 setup 函数中添加如下
setup(){
const nowTime = ref('00:00:00'); //定义当前显示的时间
const getNowTime = () => {
const now = new Date();
const hour = now.getHours() < 10 ? '0'+now.getHours() : now.getHours();//获取时
const minu = now.getMinutes() < 10 ? '0'+now.getMinutes() : now.getMinutes();//获取分
const sec = now.getSeconds() < 10 ? '0'+now.getSeconds() : now.getSeconds();//获取秒
nowTime.value = hour+":"+minu+":"+sec;//字符串拼接
setTimeout(getNowTime,1000)//递归调用
}
return{
nowTime,
getNowTime
}
}
代码基本上就实现了,可以点击 显示时间
按钮,显示时间了
但是,很显然,都是在我们
App.vue
中实现的,接下来我们要对时间显示功能以模块化
的方式来修改
首先在 src
目录下新建一个 hooks 目录,在 hooks
目录下新建一个 useNowTime.ts
文件
将 App.vue
中 setup
函数下的内容除 return
外全部拷贝到 useNowTime.ts
下
此时会报错,因为我们没有 ref
, 所以需要将 ref
引入
因为我们要在外面使用该模块,所以需要 export
出去,useNowTime.ts
具体代码如下
import { ref } from 'vue';
const nowTime = ref('00:00:00');
const getNowTime = () => {
const now = new Date();
const hour = now.getHours() < 10 ? '0'+now.getHours() : now.getHours();
const minu = now.getMinutes() < 10 ? '0'+now.getMinutes() : now.getMinutes();
const sec = now.getSeconds() < 10 ? '0'+now.getSeconds() : now.getSeconds();
nowTime.value = hour+":"+minu+":"+sec;
setTimeout(getNowTime,1000)
}
export {nowTime,getNowTime}
接下来我们回到 App.ts
文件中,需要引入 useNowTime.ts
模块暴露出来的 属性
和 方法
并 return
出去,才可以在 template
中使用
修改之后的 App.vue
的 script
如下
<script lang="ts">
import {nowTime,getNowTime} from './hooks/useNowTime'; //引入模块里面的暴露出来的属性和方法
export default {
name: 'App',
setup(){
return{ //将模块中的属性和方法暴露到当前文件,才可以在template中使用
nowTime,
getNowTime
}
}
}
</script>
Vue3中模块化的练习
这次实现一个功能,随机选择一个小姐姐
首先我们在 App.vue
中将多余的代码清空掉,只留下如下代码
<div>
<h2>随机选择小姐姐</h2>
</div>
<script lang="ts">
export default {
name: 'App',
setup(){
}
}
</script>
接下来我们先使用 npm install axios -s
先进行安装 axios
安装完成之后我们在 hooks
目录下新建一个 useUrlAxios.ts
文件,用来请求数据,代码如下
import {ref} from 'vue' //因为使用到了ref 所以先引入
import axios from 'axios' //引入axios//定义一个方法传入参数url
function useUrlAxios(url: string){
const result = ref(null); //定义结果,默认为null
const loading = ref(true); //定义加载中状态,默认true
const loaded = ref(false); //定义是否加载结束,默认false
const error = ref(null); //定义出错反馈
axios.get(url).then((res) => { //使用axios来进行请求接口api,修改定义的四个变量
loading.value = false;
loaded.value = true;
result.value = res.data;
}).catch(err => {
error.value = err;
loading.value = false
})
return { //将四个变量return出去
result,
loading,
loaded,
error
}
}
export default useUrlAxios; //因为该模块中只有一个方法,这里就使用export default来导出
然后我们回到 App.vue
首先引入模块
import useUrlAxios from './hooks/useUrlAxios'
然后在 setup
函数中将模块返回值 return
出去
setup(){
const url = 'https://apiblog.jspang.com/default/getGirl';
const { result,loading,loaded } = useUrlAxios(url);
return{
result,
loading,
loaded
}
}
最后我们修改 template
<div>
<h2>随机选择小姐姐</h2>
<div v-if="loading">loading...</div>
<img v-if="loaded" :src="result.imgUrl">
</div>
然后就可以实现刷新页面随机出现小姐姐照片
这里的api使用的是jspang教程上的:【apiblog.jspang.com/default/get…
Teleport独立组件的使用
Teleport组件的作用是将一个组件挂载到任意dom节点上,这在以前的vue2.x是做不到的,2.x中所有的组件都是直接挂载在#app下的
首先我们先写一个弹窗框组件,具体点击按钮显示隐藏暂时不做
在 components
目录下新建 model.vue
,编写组件
<template>
<div id="center">Hello xueshuai.top !</div>
</template>
<script lang="ts"></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>
然后进入 App.vue
中,先将以前的代码删除掉,template
中只留下一个 div
,script
中只留下 export default
里面只有一个 name
然后引入 modal
组件并在 components
中注册
import modal from './components/modal.vue';
export default {
name: 'App',
components:{
modal
}
}
接下来就可以直接在 template
中使用 <modal />
来用该组件了
但是我们会发现,此时该组件是挂载在 #app
下的,要想让他可以挂载在任意 dom
节点下,我们需要先打开 model.vue
,加入 Teleport
<template>
<!-- 用teleport来包裹自定义编写的组件,to指向需要挂载的dom节点id上 -->
<teleport to="#modal">
<div id="center">Hello xueshuai.top !</div>
</teleport>
</template>
然后需要在 public
目录下的 index.html
文件中添加一个 id 为 modal
的 div
节点,这样才可以把弹出框组件挂载到其他 dom
节点下
<div id="app"></div>
<div id="modal"></div>
可以打开浏览器的控制台看效果
这在以前的vue2.x是不能实现的,但是在vue3中就很容易实现了
Suspense-初识异步请求组件
首先我们先把以前写的代码清空,然后重新编写一个 AsyncShow.vue
自定义组件
注意:如果你要使用
Suspense
的话,要返回一个promise对象,而不是原来的那种JSON
对象。
在 components
目录下新建 AsyncShow.vue
组件,编写如下代码
<template>
<h1>{{ result }}</h1>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
setup() {
return new Promise((resolve, reject) => {
setTimeout(() => {
return resolve({ result: "XueShuai" });
}, 2000);
});
}
});
</script>
defineComponent
是用来解决TypeScript
情况下,传统的Vue.extends
无法对组件给出正确的参数类型推断的。也就是说在TypeScript
环境中如果参数类型推断不正常时,用defineComponent()
组件来进行包装函数
然后我们new
了一个Promise
对象,然后用setTimeout
等待两秒后返回XueShuai
文字就可以了
接下来需要把这个组件放置到App.vue
中
首先在 App.vue
中 引入
并 注册
组件
import AsyncShow from './components/AsyncShow.vue'
export default {
name: 'App',
components:{
AsyncShow
}
}
然后在 template
中使用组件
这里使用
suspense
组件包裹,该组件内有两个template
插槽
- 插槽
default
内包裹的内容是异步请求成功时显示的内容- 插槽
fallback
内包裹的内容是异步请求失败时显示的内容
<div>
<suspense>
<template #default>
<AsyncShow />
</template>
<template #fallback>
<h1>loading...</h1>
</template>
</suspense>
</div>
Suspense-深入学习 真实请求
上一篇我们其实是使用
setTimoout
来模拟请求,这篇我们来写一个真实的请求
先给出jspang提供的随机小姐姐API:【apiblog.jspang.com/default/get…】
首先在 components
目录下新建一个 GirlShow.vue
组件,并写下如下代码
上一篇返回的是
promise
对象,这篇来使用async
和awit
的写法,后者是前者的语法糖
<template>
<img :src="result && result.imgUrl" />
</template>
<script lang="ts">
import axios from 'axios'
import { defineComponent } from 'vue'
export default defineComponent({
async setup() { //promise 语法糖 返回之后也是promise对象
const rawData = await axios.get('https://apiblog.jspang.com/default/getGirl')
return {result:rawData.data}
}
})
</script>
接下来在 App.vue
引入该组件并使用 suspense
import GirlShow from './components/GirlShow.vue'
export default {
name: 'App',
components:{
GirlShow
}
}
在 template
中使用 suspense
<div>
<suspense>
<template #default>
<girl-show />
</template>
<template #fallback>
<h1>loading...</h1>
</template>
</suspense>
</div>
至此,suspense
的真实请求已经实现了,接下来有一个 异常的捕获
在
vue3.x
的版本中,可以使用onErrorCaptured
这个钩子函数来捕获异常。在使用这个钩子函数前,需要先进行引入
import {ref,onErrorCaptured} from 'vue'
钩子引用完毕之后我们就可以在 setup
函数中使用它
钩子函数要求我们返回一个布尔值,代表错误是否向上传递
setup() {
onErrorCaptured((error) => {
console.log(`error====>`,error)
return true
})
return {};
}
个人博客:点击进入