听到ref、reactive,脑子就6个字ref一把梭,哈哈哈
听到watch结束,现在脑子里就只有五个字函数一把梭,哈哈哈,vue3官网也是函数一把梭!
还是规范用比较好。
npm create vue@latest
官方用来引入声明文件的配置文件。
vue
/// <reference types="vite/client" />
比如在 App.vue 中引入 componentsxxx.vue 模块能让 vue识别。
入口文件
vue
安装插件....... 后面再补充。
ts
// 花盆
import { createApp } from 'vue'
// 根 其他.vue 文件都叫组件
import App from './App.vue'
// 把 花插在花盆里 创建一个应用把应用的跟组件传入并挂载到 id为app的容器里,也就是idnex.html 中的 div容器中
// 把VDOM 插入到真实的 DOM 里
createApp(App).mount('#app')
重点是要有导出有路由就导路由
vue
<script lang="ts"> export default { name: "App" } </script> <template>一般组件
封装ts代码的
用于SPA页面的导航路由。
路由组件
Options 类型的 API ,数据,方法,计算属性等,是分散在: data、methods、computed中的,若想新增或者修改一个需求,就需要分别修改 data、methods、computed,不便于维护和复用。
用函数的方式把所有功能都缝合在一起,我喜欢这种模块化。封装封装再封装!
语法糖罢了,省的return了。要注意的地方是执行的生命周期和设置name名称的时候要安装vite-plugin-vue-setup-extend
现在对于ref的理解就是 ----> 1.响应式 2.里面有很多个_xxx的值和一个value,你只需要用asd.value就行了
这个也是响应式但是如果声明对象car = reactive({brand: "大奔",price: 100})用这个就行了,取值直接 car.price 就好了。
宏观看:
ref 用来定义: 基本数据类型、对象数据类型
reactive 用来定义: 对象数据类型
区别:
1.ref 创建变量必须使用.value(可以使用volar插件自动添加.value)。
2.reactive重新分配一个新对象(a: reactive = {a1: 1,a2:2} b: {b1: 1,b2:2},如果把b的值给a,直接 =赋值的话,会失去响应式),会失去响应式。(可以使用Object.assign(a,b)去整体换)。
3.ref可以整体替换这个b: {b1: 1,b2:2}吗?统一回答,可以!因为改的是ref的value值
使用原则:
1.若需要一个基本数据类型的响应式数据,必须使用ref
2.若需要一个响应式对象,层级不深,ref、reactive都可以。
3。若需要一个响应式对象,且层级较深,推荐使用reactive
在解构ref和reactive时使他们的属性具有响应式。
computed函数默认是只有 get(),也就是只读,如果想要修改computed的值记得写set()方法。
监视数据的变化
在vue3中watch只能见识一下四种数据:
1.ref定义的数据
2.reactive 定义的数据
3.函数返回的一个值(getter函数)
4.一个包含上述内容的数组
vue
import { watch } from "vue"
// watch的回调函数是结束监听 如果value是对象类型那么坚挺的是他的地址值,如果深度检测那么就要写deep: true
const myWatch = watch(value,(newValue,oldValue)=>{
// 写逻辑
console.log("变了",newValue,oldValue)
if(newValue > 10) {
// 结束监视
myWatch();
}
},{deep: true})
监视【ref】定义的【对象类型】数据,监视的是对象的地址值,若想监视对象内部属性的变化,需要动手开启深度监视 (deep: true)
watch的第一个参数是:被监视的数据
watch的第二个参数是:监视的回调
watch的第三个参数是:配置对象(deep、immediate等等……)
监视reactive定义的【对象类型】数据,且默认开启了深度监视。
监视ref或reactive定义的【对象类型】中的某个属性,注意点如下:
函数式的本质是 getter()
1.若该属性值不是【对象类型】,需要写成函数式。
vue
watch(()=>{return person.name},()=>{
console.log("proson变化了",newValue,oldValue)
})
2.若该属性式 依然是【对象类型】,可以直接变,也可以写成函数,建议写成函数。
结论:
函数一把梭!
监视的数据不能是字符串!如果必须要监听他就要写成函数式。
监视的是对象里的属性,那么最好写函数式,注意点:若是对象监视的式地址值,需要关注对象内部,需要手动开启深度监视,也就是说你如果监视reactive里的某个属性式个对象,如果你要监视这个对象里的属性,尽管这个最外层的数据是reactive类型(reactive默认开启深度监视),那你还得加deep:true。
监视上述多个数据
数组里放 很多个 函数式或地址。
函!数!一!把!梭!
官网: 立即执行一个函数,同时响应式的追踪其依赖,并在以来更改时重新执行这个函数。(人话就是函数里用到谁就监视谁!)
watch 和watchEffect
1.都能监听响应式数据的变化,不同的时监听数据变化的方式不同。
watch:要明确指出监视的数据
watchEffect:不用明确指出监视的数据(函数中用到的哪些属性,那就监视哪些属性)。
没有watchEffect用watch写,你就得一个一个监视。
vue
watch([temp,height],(value)=>{
let [newTemp,newHeight] = value
if(newTemp >= 60 || newHeight >= 80){
console.log("给服务器发请求!")
}
})
vue
watchEffect(()=>{
if(temp.value >= 60 || height.value >= 80){
console.log("给服务器发请求!")
}
})
作用:用于注册模板引用
- 用在普通DOM标签上,获取的是DOM节点。
- 用在组件标签上,获取的是组件标签的实例对象。
通常还可以用来取标签的DOM元素时解决与其他的标签id冲突。
概念:vue组件在创建时要经历一系列初始化步骤,在此过程中vue会在合适的时机,调用特定的函数,从而让开发者有机会在特定的阶段运行自己的代码,这些特定的函数统称为:生命周期狗子。
规律:
神明周期整体氛围四个阶段,分别是:创建、挂载、更新、销毁,每个阶段都有自己的两个钩子,一前一后。
vue3的生命周期
创建阶段:setup
挂载阶段:onBeforeMount、onMounted
更新阶段:onBeforeUpdate、onUpdated
销毁阶段:onBeforeUnmount、onUnmounted
理解为java中的封装,也是在vue3中组合式api的重点!
ts
// 创建一个路由器,并暴露出去
// 第一步:引入createRouter
import {createRouter,createWebHistory} from 'vue-router'
// 引入可能要呈现的组件
import Home from '@/components/Home.vue'
import News from '@/components/News.vue'
import About from '@/components/About.vue'
// 第二部: 创建路由
const router = createRouter({
history: createWebHistory(),
routes:[
// 一个一个的路由规则
]
})
// 暴露出去router
export default router
ts
// 引入 createApp 用于创建应用
import {createApp} from 'vue'
// 引入App根组件
import App from './App.vue'
// 引入路由器
import router from './router'
// 创建一个应用
const app = createApp(App)
// 使用路由器
app.use(router)
// 挂载整个应用到app容器中
app.mount('#app')
来个dmeo
vue
<template>
1.路由组件通常存放在pages或 views文件夹,一般组件通常存放在components文件夹。
2.通过点击导航,视觉效果上”消失“了的路由组件,默认是被销毁的,需要的时候再去挂载。
vue
<router-link active-class="likebocai" to="/home">主页</router-link>
<router-link active-class="likebocai" :to="{path: '/home'}">主页</router-link>
<router-link active-class="likebocai" :to="{name: 'zhuye'}">主页</router-link>
优点:URL更加美观,不带有#,更加接近传统的网站URL。
缺点: 后期项目上线,需要后端服务端配合处理路径问题,否者刷新会有404错误
ts
const router = createRouter({
history: createWebHistory(),// history模式
....
})如果用的nginx转发,解决404问题可以这样做
nginx
location / {
...
try_files uri uri/ /index.html;
}
优点: 兼容性更好,因为不需要服务器端处理路径。
缺点: URL带有#不太美观,且在SEO优化方面略差。
ts
const router = createRouter({
history:createWebHashHistory(),//hash模式
...
})
News.vue
vue
<RouterLink :to="/news/detail?id=${id}&title=${title}&content=${content}" /><RouterLink :to="{
// path:'news/detail'
name:'xiang',
query:{
id:id,
title:title,
content:content
}
}" />
接收参数: 重点是结构后失去响应式,所以必须加toRefs
Detail.vue
vue
<script lang="ts" setup> import {useRoute} from 'vue-router' const route = useRoute() const {query} = toRefs(route) </script> <template>@/router/index.ts
ts
// 创建一个路由器,并暴露出去
// 第一步:引入createRouter
import {createRouter,createWebHistory} from 'vue-router'
// 引入可能要呈现的组件
import Home from '@/components/Home.vue'
import News from '@/components/News.vue'
import About from '@/components/About.vue'
import Detail from '@/components/Detail.vue'
// 第二部: 创建路由
const router = createRouter({
history: createWebHistory(),
routes:[
...
{
name:'xinwen',
path:'/news',
component: News,
children:[
{
name:'xiang',
// /:content? 意思是可传可不传
path:'detail/:id/:title/:content?',
component: Detail
}
]
}
]
})
// 暴露出去router
export default router
News.vue
子路由:to里一定要根据 name 写 切不可用 path
vue
<RouterLink :to="/news/detail/${id}/${title}/${content}">{{title}}</RouterLink>
<RouterLink :to="{
name:'xiang',
params:{
id:id,
title:title,
content:content
}
}" >{{title}}</RouterLink>
Detail.vue
vue
<script lang="ts" setup> import {useRoute} from 'vue-router' const route = useRoute() </script> <template>备注1: 传递params参数时,若是使用to的三次想写法,必须使用name配置项,不能用path。
备注2: 传递params参数时,需要提前在规则(路由中)中占位。
ts
// 创建一个路由器,并暴露出去
// 第一步:引入createRouter
import {createRouter,createWebHistory} from 'vue-router'
// 引入可能要呈现的组件
import Home from '@/components/Home.vue'
import News from '@/components/News.vue'
import About from '@/components/About.vue'
import Detail from '@/components/Detail.vue'
// 第二部: 创建路由
const router = createRouter({
history: createWebHistory(),
routes:[
...
{
name:'xinwen',
path:'/news',
component: News,
children:[
{
name:'xiang',
// /:content? 意思是可传可不传
path:'detail/:id/:title/:content?',
component: Detail,
// 第一种写法:将路由收到的所有params参数作为props传给props传给路由组件
props:true
// 第二种写法: 函数写法。可以自己决定讲什么作为props作为props给路由组件
// 这种写法切记是用query,不然就采取第一种写法。
/* props(route){
return route.query
} */
// 第三种写法: 对象写法。可以自己决定讲什么作为props作为props给路由组件
// 不推荐,死值!
/* props(route){
a: 100,
b: 200,
c: 300
} */
}
]
}
]
})
// 暴露出去router
export default router
以上相当于
vue
props:true 之后 <Detail /> ==> <Detail id:=?? title=?? content=??/>
Detail.vue
vue
<script lang="ts" setup> defineProps(['id','title','content']) </script> <template><script lang="ts" setup> import {RouterView,RouterLink} from 'vue-router' </script>1.作用: 控制路由跳转时操作浏览器历史记录的模式。
2.浏览器的历史记录有两种写入方式:分别是push和replace
push时追加历史记录(默认值)。
replace是替换当前记录。
3.开启repalce模式:
下面例子:
vue
<template>
// active-class="likebocai" 被激活时的样式
<RouterLink replace to="/home" active-class="likebocai">首页</RouterLink>
<RouterLink replace to="/about" active-class="likebocai">关于</RouterLink>
<RouterLink replace to="/news" active-class="likebocai">新闻</RouterLink>
<RouterView></RouterView>
</template>
加上replace就会消除历史记录,无法前进或后退
没什么好说的,怎么写to就怎么写push/replace。
vue
{
path:'/',
redirect:'/home'
}
第一步:npm install pinia
第二步:操作src/main.ts
ts
import { createApp } from 'vue'
import App from './App.vue'
/* 引入createPinia,用于创建pinia */
import { createPinia } from 'pinia'
/* 创建pinia */
const pinia = createPinia()
const app = createApp(App)
/* 使用插件 */{}
app.use(pinia)
app.mount('#app')
ts
import {defineStore} from 'pinia'
import axios from 'axios'
import {nanoid} from 'nanoid'
import {reactive} from 'vue'
export const useTalkStore = defineStore('talk',()=>{
// talkList就是state
const talkList = reactive(
JSON.parse(localStorage.getItem('talkList') as string) || []
)
// getATalk函数相当于action
async function getATalk(){
// 发请求,下面这行的写法是:连续解构赋值+重命名
let {data:{content:title}} = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json')
// 把请求回来的字符串,包装成一个对象
let obj = {id:nanoid(),title}
// 放到数组中
talkList.unshift(obj)
}
return {talkList,getATalk}
})
概述:props是使用频率最高的一种通信方式,常用与 :父 ↔ 子。
父组件:
vue
<template>
子组件
vue
<template>
示例:
html
<template>
<Child @send-toy="saveToy"/>
</template>
vue
<template>
<button @click="emit('send-toy',toy)">按钮</button>
</template>
概述:与消息订阅与发布(pubsub)功能类似,可以实现任意组件间通信。
安装mitt
shell
npm i mitt
新建文件:src\utils\emitter.ts
javascript
// 引入mitt
import mitt from "mitt";
// 创建emitter
const emitter = mitt()
/*
// 绑定事件
emitter.on('abc',(value)=>{
console.log('abc事件被触发',value)
})
emitter.on('xyz',(value)=>{
console.log('xyz事件被触发',value)
})
setInterval(() => {
// 触发事件
emitter.emit('abc',666)
emitter.emit('xyz',777)
}, 1000);
setTimeout(() => {
// 清理事件
emitter.all.clear()
}, 3000);
*/
// 创建并暴露mitt
export default emitter
接收数据的组件中:绑定事件、同时在销毁前解绑事件:
typescript
import emitter from "@/utils/emitter";
import { onUnmounted } from "vue";
// 绑定事件
emitter.on('send-toy',(value)=>{
console.log('send-toy事件被触发',value)
})
onUnmounted(()=>{
// 解绑事件
emitter.off('send-toy')
})
【第三步】:提供数据的组件,在合适的时候触发事件
javascript
import emitter from "@/utils/emitter";
function sendToy(){
// 触发事件
emitter.emit('send-toy',toy.value)
}
注意这个重要的内置关系,总线依赖着这个内置关系
概述:实现 父↔子 之间相互通信。
前序知识 —— v-model的本质
vue
<input
type="text"
:value="userName"
@input="userName =(<HTMLInputElement>$event.target).value"
组件标签上的v-model的本质::moldeValue + update:modelValue事件。
vue
<AtguiguInput v-model="userName"/><AtguiguInput :modelValue="userName" @update:model-value="userName = $event"/>
AtguiguInput组件中:
vue
<template>
也可以更换value,例如改成abc
vue
<AtguiguInput v-model:abc="userName"/><AtguiguInput :abc="userName" @update:abc="userName = $event"/>
AtguiguInput组件中:
vue
<template>
如果value可以更换,那么就可以在组件标签上多次使用v-model
vue
<AtguiguInput v-model:abc="userName" v-model:xyz="password"/>