title: vue(TS)+路由器 date: 2025-01-28 12:00:00 tags: - 前端 categories: - 前端
Vue3-第二部分
这里是代码中出现TS的,后面是路由器
现在先上代码,步步分析。
eg1-props的使用
步步分析代码(先理解,再实践)
框架
先分析main.ts
常规出现,就是创建与引入
// 引入createApp用于创建应用 import {createApp} from 'vue' // 引入App根组件 import App from './App.vue' createApp(App).mount('#app')
分析组件内的person.vue
模版部分
<template> <div class="person"> <ul> <li v-for="p in list" :key="p.id"> { {p.name} } -- { {p.age} } </li> </ul> </div> </template>
功能说明:
1. 渲染数据列表:
• list 是通过 props 传递给 Person 组件的。
• 使用 v-for 循环遍历 list 数组,动态生成 <li> 列表项。
• 每个 li 显示每个对象的 name 和 age。
2. 绑定唯一的 key:
• 使用 :key="p.id" 为每个列表项绑定唯一的 key,提高渲染效率。
脚本部分
<script lang="ts" setup name="Person"> import { withDefaults } from 'vue' import { type Persons } from '@/types' // 接收list + 限制类型 + 限制必要性 + 指定默认值 withDefaults(defineProps<{list?: Persons}>(), { list: () => [{ id: 'ausydgyu01', name: '康师傅·王麻子·特仑苏', age: 19 }] }) </script>
上面的import是什么?
一般是导入工具与类型
• withDefaults:
• 用于为 defineProps 定义的 props 设置默认值。它接收两个参数:
• defineProps 的返回值(包含 props 的类型约束)。
• 一个对象,用来指定每个 prop 的默认值。
• Persons:
• 从 @/types 导入的类型别名,表示一个由多个 person 对象组成的数组,符合以下结构:
也就是如果我要用到 prop的时候用
知识点解析:
1. defineProps:
• Vue 3 提供的 API,用于定义组件接收的 props。
• defineProps<{list?: Persons}>():
• 定义了一个可选的 list 属性,类型为 Persons(数组,每个元素是一个符合 PersonInter 的对象)。
2. withDefaults:
• 用来为可选 props(如 list?)设置默认值。
• 默认值为:
[{ id: 'ausydgyu01', name: '康师傅·王麻子·特仑苏', age: 19 }]
再分析index.ts
// 定义一个接口,用于限制person对象的具体属性 export interface PersonInter { id: string, name: string, age: number, } // 一个自定义类型 export type Persons = PersonInter[]
1. 接口 PersonInter:
• 定义了 person 对象的结构,强制要求每个对象包含以下属性:
• id:字符串,唯一标识。
• name:字符串,人员姓名。
• age:数字,人员年龄。
2. 类型别名 Persons:
• 定义了一个数组类型,数组的每个元素都必须符合 PersonInter 接口。
App.vue解析
<template> <!-- 务必看懂下面这一行代码 --> <!-- <h2 a="1+1" :b="1+1" c="x" :d="x" ref="qwe">测试</h2> --> <Person a="哈哈" /> </template>
静态属性:a = "1 + 1"是一个普通的字符串,直接作为属性值
动态属性:b = "1 + 1"是一个动态表达式,结果会被计算后作为属性值
ref="qwe",绑定DOM引用,可以在JavaScript中通过ref操作这个DOM元素
2. 子组件 <Person /> 的使用:
• <Person /> 是导入的子组件,代表 person.vue 文件。
• a="哈哈" 是传递给 <Person /> 的一个普通属性。
脚本部分
<script lang="ts" setup name="App"> import Person from './components/Person.vue' import { reactive } from 'vue' import { type Persons } from '@/types' let x = 9 let personList = reactive<Persons>([ { id: 'asudfysafd01', name: '张三', age: 18 }, { id: 'asudfysafd02', name: '李四', age: 20 }, { id: 'asudfysaf)d03', name: '王五', age: 22 } ]) </script>
1. 引入 Person 组件:
• import Person from './components/Person.vue' 引入 person.vue,使得 <Person /能够在模板中使用。
-
定yi响应式数据:
-
reactive 的作用:
• 使得 personList 成为响应式数据。当 personList 或其内部的对象属性发生变化时,Vue 会自动更新视图。
整体逻辑总结
Index.ts 定义了personInter接口和Person类型,用来约束person数据结构
Person.vue 接收list作为props,通过withDefaults为list设置默认值
渲染list数据。动态生成列表
App.vue
定义一个响应式数据personalist,并可以通过props传递给pweson.vue
vue2生命周期
<template> <div class="person"> <h2>当前求和为:{ { sum } }</h2> <button @click="add">点我sum+1</button> </div> </template>
功能说明:
1. 数据绑定:
• { { sum } } 用来动态展示变量 sum 的值。
• 每次点击按钮,sum 的值增加 1。
2. 事件绑定:
• @click="add":绑定按钮点击事件,触发 add 方法,更新 sum 的值。
<script lang="ts" setup name="Person"> import { ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue' // 数据 let sum = ref(0) // 方法 function add() { sum.value += 1 } // 创建 console.log('创建') // 挂载前 onBeforeMount(() => { // console.log('挂载前') }) // 挂载完毕 onMounted(() => { console.log('子---挂载完毕') }) // 更新前 onBeforeUpdate(() => { // console.log('更新前') }) // 更新完毕 onUpdated(() => { // console.log('更新完毕') }) // 卸载前 onBeforeUnmount(() => { // console.log('卸载前') }) // 卸载完毕 onUnmounted(() => { // console.log('卸载完毕') }) </script>
app.vue
<template> <Person v-if="isShow" /> </template>
1. <Person v-if="isShow" /:
• 条件渲染子组件 Person。
• 当 isShow 为 true 时,<Person / 会被挂载。
• 当 isShow 为 false 时,<Person /会被卸载。
脚本部分
<script lang="ts" setup name="App"> import Person from './components/Person.vue' import { ref, onMounted } from 'vue' let isShow = ref(true) // 挂载完毕 onMounted(() => { console.log('父---挂载完毕') }) </script>
功能解析:
1. 引入子组件:
• Person 是一个子组件,来自 ./components/Person.vue。
2. 响应式数据:
• let isShow = ref(true):定义了一个响应式布尔值 isShow,控制 <Person /> 的显示和隐藏。
3. 生命周期钩子:
• onMounted:在 App 组件挂载到 DOM 后执行。这里输出 父---挂载完毕,用于标记父组件挂载完成。
运行流程
-
父组件挂载(App)
• isShow 默认为 true。
• <Person / 被挂载到 DOM 中。
• 控制台输出:
父---挂载完毕 创建 子---挂载完毕
. 子组件更新(Person)
• 点击按钮时,sum.value 增加 1,触发子组件更新。
• 在更新阶段,执行以下生命周期钩子:
• onBeforeUpdate
• onUpdated
3. 子组件卸载(Person)
• 如果将 isShow 设置为 false(例如通过交互),<Person /> 会被卸载。
• 卸载阶段执行:
• onBeforeUnmount
• onUnmounted
5. 总结
person.vue 的功能
• 通过按钮点击实现 sum 的动态更新。
• 使用 Vue 3 的生命周期钩子监控组件的各个阶段,包括挂载、更新、卸载。
app.vue 的功能
• 使用 v-if 控制子组件 Person 的挂载和卸载。
• 父组件负责管理子组件的存在与否,同时通过生命周期钩子记录父组件的挂载阶段。
生命周期运行示意图
1. 父组件挂载:
• onMounted -> 输出:父---挂载完毕
2. 子组件挂载:
• 创建
• onBeforeMount(未输出)
• onMounted -> 输出:子---挂载完毕
3. 子组件更新:
• onBeforeUpdate
• onUpdated
4. 子组件卸载:
• onBeforeUnmount
• onUnmounted
对应页面
Eg2-hook自定义
<template> <div class="person"> <h2>当前求和为:{ { sum } },放大10倍后:{ { bigSum } }</h2> <button @click="add">点我sum+1</button> <hr> <img v-for="(dog,index) in dogList" :src="dog" :key="index"> <br> <button @click="getDog">再来一只小狗</button> </div> </template> <script lang="ts" setup name="Person"> import useSum from '@/hooks/useSum' import useDog from '@/hooks/useDog' const {sum,add,bigSum} = useSum() const {dogList,getDog} = useDog() </script> <style scoped> .person { background-color: skyblue; box-shadow: 0 0 10px; border-radius: 10px; padding: 20px; } button { margin: 0 5px; } li { font-size: 20px; } img { height: 100px; margin-right: 10px; } </style>
这个是person.vue
在模版部分,
• 图片渲染:
• 使用 v-for 循环 dogList,通过动态绑定 src 和 key 属性渲染小狗图片。
在脚本部分
• 引入逻辑模块:
• 从 useSum.ts 中引入了 sum、add 和 bigSum,负责数值的处理。
• 从 useDog.ts 中引入了 dogList 和 getDog,负责图片列表的管理和 API 请求。
• 使用组合式 API:
• 通过解构的方式,将逻辑解耦到独立的模块中,提高代码的可复用性。
在useDog.ts模块
import { reactive, onMounted } from 'vue' import axios from 'axios' export default function () { // 数据 let dogList = reactive([ 'https://images.dog.ceo/breeds/pembroke/n02113023_4373.jpg' ]) // 方法 async function getDog() { try { let result = await axios.get('https://dog.ceo/api/breed/pembroke/images/random') dogList.push(result.data.message) } catch (error) { alert(error) } } // 钩子 onMounted(() => { getDog() }) // 向外部提供东西 return { dogList, getDog } }
功能分析:
1. 响应式数据:
• 使用 reactive 定义了图片列表 dogList,默认包含一张图片。
2. API 请求方法:
• getDog 方法使用 Axios 请求 https://dog.ceo 提供的小狗图片 API。
• 将获取的图片 URL 推入 dogList 中。
• 通过 try-catch 捕获请求错误。
3. 生命周期钩子:
• 在组件挂载时 (onMounted) 自动调用 getDog,预加载一张小狗图片。
关键点:
• 合理使用 reactive 管理数组的响应式更新。
• 在组件加载时预先获取数据,优化用户体验。
在这里面有一个地方发送异步请求
zlet result = await axios.get('https://dog.ceo/api/breed/pembroke/images/random')
axios.get():
• 使用 axios 发起一个 HTTP GET 请求。
• 请求地址是 https://dog.ceo/api/breed/pembroke/images/random,这是一个提供随机小狗图片的 API。
• 返回的数据是一个包含 message 属性的 JSON 对象
await:
• await 暂停代码的执行,直到请求完成并返回结果。
• 返回值存储在 result 变量中,具体数据保存在 result.data 中。
在useSum.ts模块
import { ref, onMounted, computed } from 'vue' export default function () { // 数据 let sum = ref(0) let bigSum = computed(() => { return sum.value * 10 }) // 方法 function add() { sum.value += 1 } // 钩子 onMounted(() => { add() }) // 给外部提供东西 return { sum, add, bigSum } }
功能分析:
1. 响应式数据:
• 使用 ref 定义了单一响应式数据 sum。
• 使用 computed 定义了计算属性 bigSum,动态计算 sum 的 10 倍值。
2. 方法:
• add 方法使 sum 增加 1。
3. 生命周期钩子:
• 在 onMounted 中调用 add,在组件加载时使 sum 初始值为 1。
路由
在 Vue 3 中,路由管理通常通过 Vue Router 实现。路由的主要功能是实现页面的导航和组件的动态渲染。以下是关于路由的核心概念和代码实现的详细说明。
1. 什么是路由?
路由是一种通过 URL 映射组件或视图的方式。它允许用户在单页应用(SPA)中导航,而无需重新加载整个页面。
例如:
• /home 映射到 Home 组件。
• /about 映射到 About 组件。
页面组件
<template> <div class="home"> <img src="http://www.atguigu.com/images/index_new/logo.png" alt=""> </div> </template>
• 功能:展示一个居中的图片。
• 样式:通过 flex 布局将内容水平和垂直居中。
• 用途:作为首页内容。
<script setup lang="ts" name="Home"> </script>
• 作用:
• 表示该组件使用 Vue 3 的 <script setup> 语法。
• lang="ts" 表示代码使用 TypeScript,增加类型安全。
• name="Home" 给组件命名为 Home,方便调试和递归调用。
样式部分
<style scoped> .home { display: flex; justify-content: center; align-items: center; height: 100%; } </style>
• 作用:
• 定义组件的样式。
• scoped 表示样式只作用于当前组件,不影响其他组件。
• 细节解析:
• display: flex;:
• 使用 Flex 布局,使子元素容易居中对齐。
• justify-content: center;:
• 子元素水平居中。
• align-items: center;:
• 子元素垂直居中。
• height: 100%;:
• 根容器的高度设置为父级容器的 100%。
About.vue
<template> <div class="about"> <h2>大家好,欢迎来到尚硅谷直播间</h2> </div> </template> <script setup lang="ts" name="About"> </script> <style scoped> .about { display: flex; justify-content: center; align-items: center; height: 100%; color: rgb(85, 84, 84); font-size: 18px; } </style>
News.vue
<template> <div class="news"> <ul> <li><a href="#">新闻001</a></li> <li><a href="#">新闻002</a></li> <li><a href="#">新闻003</a></li> <li><a href="#">新闻004</a></li> </ul> </div> </template> <script setup lang="ts" name="News"> </script> <style scoped> /* 新闻 */ .news { padding: 0 20px; display: flex; justify-content: space-between; height: 100%; } .news ul { margin-top: 30px; list-style: none; padding-left: 10px; } .news li>a { font-size: 18px; line-height: 40px; text-decoration: none; color: #64967E; text-shadow: 0 0 1px rgb(0, 84, 0); } </style>
路由配置
// 创建一个路由器,并暴露出去 // 第一步:引入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:[ //一个一个的路由规则 { path:'/home', component:Home }, { path:'/news', component:News }, { path:'/about', component:About }, ] }) // 暴露出去router export default router
• createRouter:
• 创建一个路由实例。
• createWebHistory:
• 使用 HTML5 的历史记录模式。
• routes:
• 定义路由规则,每条规则对应一个路径和组件。
应用入口
// 引入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')
App.vue
模版部分
<template> <div class="app"> <h2 class="title">Vue路由测试</h2> <!-- 导航区 --> <div class="navigate"> <RouterLink to="/home" active-class="xiaozhupeiqi">首页</RouterLink> <RouterLink to="/news" active-class="xiaozhupeiqi">新闻</RouterLink> <RouterLink to="/about" active-class="xiaozhupeiqi">关于</RouterLink> </div> <!-- 展示区 --> <div class="main-content"> <RouterView></RouterView> </div> </div> </template>
<RouterLink:
• Vue Router 提供的导航组件,类似于 HTML 的 <a> 标签。
• to="/home":指定点击该链接时跳转的路由路径。
• active-class="xiaozhupeiqi":定义激活时的样式类名,当链接的路由匹配当前路径时会自动应用。
• 导航链接功能:
• 首页:跳转到 /home 路由。
• 新闻:跳转到 /news 路由。
• 关于:跳转到 /about 路由。
在展示区
<div class="main-content"> <RouterView></RouterView> </div>
• <RouterView :
• Vue Router 提供的内置组件,用于渲染当前路由匹配的组件。
• 根据用户点击的导航链接,<RouterView会动态切换为对应的组件内容,例如 Home.vue、News.vue 或 About.vue。
脚本部分
<script lang="ts" setup name="App"> import { RouterView, RouterLink } from 'vue-router' </script>
逐行解析
(1) lang="ts"
• 表示当前脚本部分使用 TypeScript,增强类型安全。
• 允许对变量、函数等进行类型声明。
(2) setup
• 使用 Vue 3 的组合式 API 的语法糖。
• 在 <script setup中定义的变量和方法,可以直接在模板中使用,无需显式返回。
(3) name="App"
• 为当前组件指定名称为 App。
• 在开发者工具(如 Vue DevTools)中调试时,可以看到组件名称为 App,便于区分。
(4) 引入 Vue Router 的组件
import { RouterView, RouterLink } from 'vue-router'
• RouterLink:用于定义路由导航链接。
• RouterView:用于动态渲染路由匹配的组件。
样式部分
.title { text-align: center; word-spacing: 5px; margin: 30px 0; height: 70px; line-height: 70px; background-image: linear-gradient(45deg, gray, white); border-radius: 10px; box-shadow: 0 0 2px; font-size: 30px; } .navigate { display: flex; justify-content: space-around; margin: 0 100px; } .navigate a { display: block; text-align: center; width: 90px; height: 40px; line-height: 40px; border-radius: 10px; background-color: gray; text-decoration: none; color: white; font-size: 18px; letter-spacing: 5px; }
xiaozhupeiqi 激活后呈现的
这个就是超链接<a的时候,未点击前呈现的
视频中没有讲这一部分,少了一个知识点的讲解就是routeLink->转换为<a标签
Query 参数-路由
Header.ts
<template> <h2 class="title">Vue路由测试</h2> </template> <script setup lang="ts" name="Header"> </script> <style scoped> .title { text-align: center; word-spacing: 5px; margin: 30px 0; height: 70px; line-height: 70px; background-image: linear-gradient(45deg, gray, white); border-radius: 10px; box-shadow: 0 0 2px; font-size: 30px; } </style>
About.ts
<template> <div class="about"> <h2>大家好,欢迎来到尚硅谷直播间</h2> </div> </template> <script setup lang="ts" name="About"> import {onMounted,onUnmounted} from 'vue' onMounted(()=>{ console.log('About组件挂载了') }) onUnmounted(()=>{ console.log('About组件卸载了') }) </script> <style scoped> .about { display: flex; justify-content: center; align-items: center; height: 100%; color: rgb(85, 84, 84); font-size: 18px; } </style>
Detail.ts
<template> <ul class="news-list"> <li>编号:{ { query.id } }</li> <li>标题:{ { query.title } }</li> <li>内容:{ { query.content } }</li> </ul> </template> <script setup lang="ts" name="About"> import {toRefs} from 'vue' import {useRoute} from 'vue-router' let route = useRoute() let {query} = toRefs(route) </script> <style scoped> .news-list { list-style: none; padding-left: 20px; } .news-list>li { line-height: 30px; } </style>
脚本部分
解析
(1) useRoute
• 定义:
• useRoute 是 Vue Router 提供的组合式 API,用于获取当前路由对象。
• 作用:
• 返回当前激活的路由信息,包括路径、参数、查询字符串等。
• 返回值:
• route 是一个响应式对象,包含当前路由的所有信息,例如:
{ path: "/news", query: { id: "123", title: "Vue Router", content: "这是一个简单的示例" }, params: { ... }, ... }
(2) toRefs
• 定义:
• toRefs 是 Vue 的组合式 API,用于将响应式对象的属性转换为独立的响应式引用。
• 作用:
• 将 route.query 转换为响应式引用,使得在模板中访问 query 的属性时,能够保持响应式更新。
• 代码作用:
let { query } = toRefs(route)
(3) 数据流程
• useRoute() 获取当前路由信息。
• 通过 toRefs(route) 解构出 query,用于动态绑定数据。
Params
<template> <div class="news"> <!-- 导航区 --> <ul> <li v-for="news in newsList" :key="news.id"> <!-- 第一种写法 --> <!-- <RouterLink :to="`/news/detail/${news.id}/${news.title}/${news.content}`">{ {news.title} }</RouterLink> --> <!-- 第二种写法 --> <RouterLink :to="{ name:'xiang', params:{ id:news.id, title:news.title, content:news.content } }" > { {news.title} } </RouterLink> </li> </ul> <!-- 展示区 --> <div class="news-content"> <RouterView></RouterView> </div> </div> </template> <script setup lang="ts" name="News"> import {reactive} from 'vue' import {RouterView,RouterLink} from 'vue-router' const newsList = reactive([ {id:'asfdtrfay01',title:'很好的抗癌食物',content:'西蓝花'}, {id:'asfdtrfay02',title:'如何一夜暴富',content:'学IT'}, {id:'asfdtrfay03',title:'震惊,万万没想到',content:'明天是周一'}, {id:'asfdtrfay04',title:'好消息!好消息!',content:'快过年了'} ]) </script> <style scoped> /* 新闻 */ .news { padding: 0 20px; display: flex; justify-content: space-between; height: 100%; } .news ul { margin-top: 30px; /* list-style: none; */ padding-left: 10px; } .news li::marker { color: #64967E; } .news li>a { font-size: 18px; line-height: 40px; text-decoration: none; color: #64967E; text-shadow: 0 0 1px rgb(0, 84, 0); } .news-content { width: 70%; height: 90%; border: 1px solid; margin-top: 20px; border-radius: 10px; } </style>
这个是new.vue
Pinia
Count.vue
模版部分
<template> <div class="count"> <h2>当前求和为:{ { sum } }</h2> <select v-model.number="n"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button @click="add">加</button> <button @click="minus">减</button> </div> </template>
• 使用 { { sum } } 动态绑定 sum 的值,显示当前的求和结果。
2. 选择操作数:
• 使用 <select 元素,让用户选择一个数字(1、2 或 3)。
• 通过 v-model.number="n" 双向绑定选中的值到变量 n,并将其转换为数值。
-
加减操作:
• 点击“加”按钮时调用 add 方法。
• 点击“减”按钮时调用 minus 方法。
脚本部分
<script setup lang="ts" name="Count"> import { ref } from "vue"; // 数据 let sum = ref(1) // 当前求和 let n = ref(1) // 用户选择的数字 // 方法 function add(){ sum.value += n.value } function minus(){ sum.value -= n.value } </script>
1. ref 定义响应式数据:
• sum:当前求和,初始值为 1。
• n:用户选择的数字,初始值为 1。
• 响应式数据会自动更新绑定到模板的内容。
2. 方法功能:
• add:将选中的数字 n.value 加到 sum.value 上。
• minus:从 sum.value 中减去 n.value。
<script setup lang="ts" name="LoveTalk"> import { reactive } from 'vue' import axios from "axios"; import { nanoid } from 'nanoid' // 数据 let talkList = reactive([ {id:'ftrfasdf01',title:'今天你有点怪,哪里怪?怪好看的!'}, {id:'ftrfasdf02',title:'草莓、蓝莓、蔓越莓,今天想我了没?'}, {id:'ftrfasdf03',title:'心里给你留了一块地,我的死心塌地'} ]) // 方法 async function getLoveTalk(){ // 发请求,下面这行的写法是:连续解构赋值+重命名 let {data:{content:title} } = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json') // 把请求回来的字符串,包装成一个对象 let obj = {id:nanoid(),title} // 放到数组中 talkList.unshift(obj) } </script>
在app.vue里面写
<template> <Count/> <br> <LoveTalk/> </template> <script setup lang="ts" name="App"> import Count from './components/Count.vue' import LoveTalk from './components/LoveTalk.vue' </script>
npm i pinia
import {createApp} from 'vue' import App from './App.vue' // 第一步:引入pinia import {createPinia} from 'pinia' const app = createApp(App) // 第二步:创建pinia const pinia = createPinia() // 第三步:安装pinia app.use(pinia) app.mount('#app')
小菠萝出来啦!!!
存储+读取数据
创建一个store文件夹
Count.ts
import {defineStore} from 'pinia' export const useCountStore = defineStore('count',{ // 真正存储数据的地方 state(){ return { sum:6 } } })
Lovetalk.ts
import {defineStore} from 'pinia' export const useTalkStore = defineStore('talk',{ // 真正存储数据的地方 state(){ return { talkList:[ {id:'ftrfasdf01',title:'今天你有点怪,哪里怪?怪好看的!'}, {id:'ftrfasdf02',title:'草莓、蓝莓、蔓越莓,今天想我了没?'}, {id:'ftrfasdf03',title:'心里给你留了一块地,我的死心塌地'} ] } } })
修改数据
import {defineStore} from 'pinia' export const useCountStore = defineStore('count',{ // actions里面放置的是一个一个的方法,用于响应组件中的“动作” actions:{ increment(value){ console.log('increment被调用了',value) if( this.sum < 10){ // 修改数据(this是当前的store) this.sum += value } } }, // 真正存储数据的地方 state(){ return { sum:6, school:'atguigu', address:'宏福科技园' } } })
import {defineStore} from 'pinia' import axios from 'axios' import {nanoid} from 'nanoid' export const useTalkStore = defineStore('talk',{ actions:{ async getATalk(){ // 发请求,下面这行的写法是:连续解构赋值+重命名 let {data:{content:title} } = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json') // 把请求回来的字符串,包装成一个对象 let obj = {id:nanoid(),title} // 放到数组中 this.talkList.unshift(obj) } }, // 真正存储数据的地方 state(){ return { talkList:[ {id:'ftrfasdf01',title:'今天你有点怪,哪里怪?怪好看的!'}, {id:'ftrfasdf02',title:'草莓、蓝莓、蔓越莓,今天想我了没?'}, {id:'ftrfasdf03',title:'心里给你留了一块地,我的死心塌地'} ] } } })
现在展开解释
整体功能
这段代码通过 Pinia 定义了一个 Store,用于管理一个情话列表 talkList,并提供了一个方法 getATalk 来向 API 请求新的情话并添加到 talkList 中。
1. 引入的依赖
import { defineStore } from 'pinia' import axios from 'axios' import { nanoid } from 'nanoid'
解析
1. defineStore:
• 从 pinia 中引入,用于定义一个新的 Store。
• Store 是状态管理的核心,用于存储和管理全局共享的状态。
2. axios:
• 用于发送 HTTP 请求。
• 这里通过 axios.get() 从 https://api.uomg.com/api/rand.qinghua 获取随机土味情话。
3. nanoid:
• 一个小型的 ID 生成工具。
• 用于为每条情话生成唯一的 ID,确保 talkList 中的每条情话都有一个独特的标识。
2. 定义 Store
export const useTalkStore = defineStore('talk', { ... })
解析
1. export const useTalkStore:
• 定义了一个 Store,命名为 useTalkStore。
• 这个名字的命名规则通常是 use 开头,以表明它是一个 Store。
2. defineStore('talk', {...}):
• 'talk' 是这个 Store 的唯一标识符,用于区分其他 Store。
• 第二个参数是 Store 的配置对象,包含 state 和 actions 等。
3. state:存储数据
state() { return { talkList: [ { id: 'ftrfasdf01', title: '今天你有点怪,哪里怪?怪好看的!' }, { id: 'ftrfasdf02', title: '草莓、蓝莓、蔓越莓,今天想我了没?' }, { id: 'ftrfasdf03', title: '心里给你留了一块地,我的死心塌地' } ] } }
解析
1. state:
• 一个函数,返回一个对象,这个对象定义了 Store 中的数据。
• 在这里,state 定义了一个情话列表 talkList。
2. talkList:
• 是一个数组,存储了情话的初始数据。
• 每条情话是一个对象,包含以下字段:
• id:情话的唯一标识符。
• title:情话的具体内容。
3. 响应式特性:
• Pinia 的 state 是响应式的。
• 当 talkList 数据发生变化时,绑定到 talkList 的视图会自动更新。
4. actions:定义方法
actions: { async getATalk() { let { data: { content: title } } = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json') let obj = { id: nanoid(), title } this.talkList.unshift(obj) } }
解析
1. actions:
• 定义了 Store 中的方法,通常用于处理复杂逻辑或修改状态。
• getATalk 是一个异步方法,用于从 API 获取新的情话并更新 talkList。
2. getATalk 的工作流程:
• 发送请求:
let { data: { content: title } } = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json')
• 使用 axios.get() 发送请求,从 API 获取情话。
• 解构赋值提取 content 字段,并将其重命名为 title。
• 创建新对象:
let obj = { id: nanoid(), title }
• 使用 nanoid() 生成一个唯一的 ID。
• 创建一个包含 id 和 title 的新情话对象。
• 更新 talkList:
this.talkList.unshift(obj)
• 使用 unshift 方法,将新情话添加到 talkList 的开头。
• 由于 talkList 是响应式的,更新数据后,绑定到 talkList 的 UI 会自动更新。
1. storeToRefs 的使用
storeToRefs 是 Pinia 提供的一个工具函数,主要用于从 Store 中提取状态(state)和 Getter 的响应式引用,确保解构后不会丢失响应性。
import { storeToRefs } from 'pinia' const store = useSomeStore() const { stateProp, getterProp } = storeToRefs(store)
作用
• 将 state 和 getter 转换为响应式 ref。
• 解构 Store 中的属性时,防止响应性丢失。
2. getters 的使用
Pinia 的 getters 是类似于 Vuex 中的计算属性,用于对 state 的值进行派生计算。
state: () => ({ talkList: [ { id: '1', title: '情话一' }, { id: '2', title: '情话二' } ] }), getters: { talkCount: (state) => state.talkList.length // 返回情话总数 } })
import { useTalkStore } from '@/stores/talkStore' const talkStore = useTalkStore() // 直接访问 getter console.log(talkStore.talkCount) // 输出情话总数
3. $subscribe 的使用
$subscribe 是 Pinia 提供的一个方法,用于监听 Store 中 state 和 action 的变化。
语法
store.$subscribe((mutation, state) => { console.log(mutation) // 包含 type 和 payload console.log(state) // 当前状态 })
4. store 组合式写法
Pinia 支持组合式 API(Composition API)风格的 Store 定义。
export const useTalkStore = defineStore('talk', () => { const talkList = ref([ { id: '1', title: '情话一' }, { id: '2', title: '情话二' } ]) const talkCount = computed(() => talkList.value.length) const addTalk = (id, title) => { talkList.value.push({ id, title }) } return { talkList, talkCount, addTalk } }) import { useTalkStore } from '@/stores/talkStore' const talkStore = useTalkStore() // 调用方法和访问属性 talkStore.addTalk('3', '情话三') console.log(talkStore.talkCount) // 输出 3