前言:vue3+typescript+pinia学习
介绍:什么是组合式API,组合式API的特点
Vue3提供两种组织代码逻辑的写法:
// 选项式API
<template>
<button @click="toggle">显示隐藏图片</button>
<img v-show="show" alt="Vue logo" src="./assets/logo.png" />
<hr />
计数器:{{ count }} <button @click="increment">累加</button>
</template>
<script>
export default {
data() {
return {
show: true,
count: 0,
};
},
methods: {
toggle() {
this.show = !this.show;
},
increment() {
this.count++;
},
},
};
</script>
// 组合式api
<template>
<button @click="toggle">显示隐藏图片</button>
<img v-show="show" alt="Vue logo" src="./assets/logo.png" />
<hr />
计数器:{{ count }} <button @click="increment">累加</button>
</template>
<script>
// ref 就是一个组合式API
import { ref } from 'vue';
export default {
setup () {
// 显示隐藏
const show = ref(true)
const toggle = () => {
show.value = !show.value
}
// 计数器
const count = ref(0)
const increment = () => {
count.value ++
}
return { show, toggle, count, increment }
}
};
</script>
概念:
setup
函数是组合式API
的入口函数setup
函数是 Vue3
特有的选项,作为组合式API的起点beforeCreate
之前执行this
不是组件实例,是 undefined
(今后在vue3的项目中几乎用不到 this
, 所有的东西通过函数获取)setup
返回通常使用它定义对象类型的响应式数据
原因:以前在 data 函数中返回对象数据就是响应式的,而现在 setup 中返回对象数据不是响应式的
<template>
<div>
<p>姓名:{{state.name}}</p>
<p>年龄:{{state.age}} <button @click="state.age++">一年又一年</button></p>
</div>
</template>
<script>
// 1. 从vue中导入reactive函数
import { reactive } from "vue";
export default {
setup() {
// 2. 创建响应式数据对象
const state = reactive({ name: 'tom', age: 18 })
// 3. 返回数据
return { state }
}
};
</script>
通常使用它定义响应式数据,不限类型
注意:使用 ref 创建的数据,js中需要 .value,template 中可省略
<template>
<div>
<p>
计数器:{{ count }}
<button @click="count++">累加1</button>
<!-- template中使用可省略.value -->
<button @click="increment">累加10</button>
</p>
</div>
</template>
<script>
// 1. 从vue中导入ref函数
import { ref } from "vue";
export default {
setup() {
// 2. 创建响应式数据对象
const count = ref(0);
const increment = () => {
// js中使用需要.value
count.value += 10;
};
// 3. 返回数据
return { count, increment };
},
};
</script>
reactive
可以转换对象成为响应式数据对象,但是不支持简单数据类型。ref
可以转换简单数据类型为响应式数据对象,也支持复杂数据类型,但是操作的时候需要 .value
。reactive
转成响应式数据,这样可以省去.value
其他一概使用 ref 。作用:简化 setup 固定套路代码 ,让代码更简洁
使用 setup 有几件事必须做:
// 不使用setup语法糖
<script>
export default { // 1. 默认导出配置选项
setup() { // 2. setup函数声明
const say = () => console.log('hi')
return { say } // 3. 返回模板需要数据与函数
}
}
</script>
// 使用setup语法糖)
<script setup>
const say = () => console.log('hi')
</script>
// 小案例
<script setup>
// 显示隐藏
const show = ref(true)
const toggle = () => {
show.value = !show.value
}
// 计数器
const count = ref(0)
const increment = () => {
count.value ++
}
</script>
<template>
<button @click="toggle">显示隐藏图片</button>
<img v-show="show" alt="Vue logo" src="./assets/logo.png" />
<hr />
计数器:{{ count }} <button @click="increment">累加</button>
</template>
unplugin-auto-import
使用方法:
unplugin-auto-import
//1.先引入
import AutoImport from "unplugin-auto-import/vite"
export default defineConfig({
plugins: [
vue(),
//2.然后配置
AutoImport({
imports: ['vue'],
dts: 'src/auto-import.d.ts'
}),
]
})
使用场景:当需要依赖一个数据得到新的数据,就使用计算属性
<script setup>
const list = ref([80,100,90,70,60])
const betterList = computed(()=>list.value.filter((item)=>item>=90))
setTimeout(()=>{
list.value.push(92,66)
},2000)
</script>
<template>
<div>
<p>分数{{list}}</p>
<p>优秀{{betterList}}</p>
</div>
</template>
使用watch函数监听数据的变化
大致内容:
watch(数据, 改变后回调函数)
watch([数据1, 数据2, ...], 改变后回调函数)
watch(()=>数据, 改变后回调函数)
watch(()=>数据, 改变后回调函数, {deep: true})
案例
// 1.使用 watch 监听一个响应式数据
<script setup>
const count = ref(0)
watch(count,()=>{
console.log('count改变了');
})
setTimeout(()=>{
count.value++
},2000)
</script>
<template>
<p>计数器:{{count}}</p>
</template>
// 2.使用 watch 监听多个响应式数据
<script setup>
const count = ref(0)
const user = reactive({
name: "tom",
info: {
gender: "男",
age: 18,
},
});
// 2. 监听多个响应式数据
// watch([数据1, 数据2, ...], 改变后回调函数)
watch([count, user], () => {
console.log("数据改变了");
});
// 2s改变数据
setTimeout(() => {
count.value++;
}, 2000);
// 4s改变数据
setTimeout(() => {
user.info.age++;
}, 4000);
</script>
<template>
<p>计数器:{{count}}</p>
<p>
姓名:{{ user.name }} 性别:{{ user.info.gender }} 年龄:{{ user.info.age }}
</p>
</template>
// 3.使用 watch 监听响应式对象数据中的一个属性(简单)
<script setup>
const user = reactive({
name: "tom",
info: {
gender: "男",
age: 18,
},
});
// 监听响应式对象数据的一个数据,简单类型
// watch(()=>数据, 改变后回调函数)
watch(() => user.name, () => {
console.log("数据改变了");
});
// 2s改变数据
setTimeout(() => {
user.name = 'jack';
}, 2000);
</script>
<template>
<p>
姓名:{{ user.name }} 性别:{{ user.info.gender }} 年龄:{{ user.info.age }}
</p>
</template>
// 4.使用 watch 监听响应式对象数据中的一个属性(复杂),配置深度监听
<script setup>
const user = reactive({
name: "tom",
info: {
gender: "男",
age: 18,
},
});
// 4. 监听响应式对象数据的一个数据,复杂类型
// watch(()=>数据, 改变后回调函数, {deep: true})
watch(() => user.info,() => {
console.log("数据改变了");
},
{
// 开启深度监听
deep: true,
}
);
// 2s改变数据
setTimeout(() => {
user.info.age = 60;
}, 2000);
</script>
<template>
<p>
姓名:{{ user.name }} 性别:{{ user.info.gender }} 年龄:{{ user.info.age }}
</p>
</template>
常用: onMounted
组件渲染完毕:发请求,操作dom,初始化图表…
<script setup>
// 生命周期函数:组件渲染完毕
// 生命周期钩子函数可以调用多次
onMounted(()=>{
console.log('onMounted触发了')
})
onMounted(()=>{
console.log('onMounted也触发了')
})
</script>
<template>
<div>生命周期函数</div>
</template>
元素上使用 ref 属性关联响应式数据,获取DOM元素
步骤:
const hRef = ref(null)
我是标题
hRef.value
<script setup>
// 1.创建ref
const hRef = ref(null)
//3.改变DOM数据
const clickFn = () => hRef.value.innerText = '我不是标题'
</script>
<template>
<div>
// 2.在模板中关联ref
<h1 ref="hRef">我是标题</h1>
<button @click="clickFn">操作DOM</button>
</div>
</template>
组件上使用 ref 属性关联响应式数据,获取组件实例
配合 defineExpose
暴露数据和方法,ref获取的组件实例才可以使用
的组件是默认关闭的,组件实例使用不到顶层的数据和函数。defineExpose
暴露给组件实例使用,暴露的响应式数据会自动解除响应式。注意:
defineProps
接收数据,这个数据只能在模板中渲染 defineProps({ 数据 })
script
中操作 props
属性,应该接收返回值 const props = defineProps({ 数据 })
通过provide和inject函数可以简便的实现跨级组件通讯
官方术语:依赖注入
在使用reactive创建的响应式数据被展开或解构的时候使用toRefs保持响应式
<script setup>
const user = reactive({ name: "tom", age: 18 });
const { name, age } = toRefs(user)
</script>
<template>
<div>
<p>姓名:{{ name }}</p>
<p>年龄:{{ age }} <button @click="age++">一年又一年</button></p>
</div>
</template>
<script setup>
import { onMounted,ref } from 'vue';
import axios from 'axios'
//可以看出vue3中的数据和逻辑是写在一起的,项目复杂后很方便
//定义学生列表数组
const list = ref([])
// 1.获取数据
const getList = async()=>{
const res = await axios.get('/list')
console.log(res);
list.value = res.data
}
onMounted(() => {
getList()
})
//2.删除数据
const delClick = async (id) => {
await axios.delete("/del?id=" + id)
getList()
}
</script>
<template>
<div class="app">
<el-table :data="list">
<el-table-column label="ID" prop="id"></el-table-column>
<el-table-column label="姓名" width="150" prop="name"></el-table-column>
<el-table-column label="籍贯" prop="place"></el-table-column>
<el-table-column label="操作" width="100" >
<template v-slot="row">
<el-button type="primary" link @click="delClick(row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<style>
.app {
width: 980px;
margin: 100px auto 0;
}
</style>
TS 是 JS 的超集,支持了JS 语法和扩展了类型语法。
注意:TS 需要编译才能在浏览器运行。
TS作用:在编译时进行类型检查提示错误
// JavaScript代码
// 没有明确的类型
let age = 18
// TypeScript代码
// 有明确的类型,可以指定age是number类型(数值类型)
let age: number = 18
使用tsc 编译 ts 代码
// npm 安装
npm i -g typescript
// yarn 安装
yarn global add typescript
// 查看版本
tsc -v
TS 常用类型:
简单类型:number string boolean null undefined
复杂类型:对象 数组 函数
联合类型、自定义类型(类型别名)、接口、元组、字面量类型、枚举、void、any、泛型 等
let age: number = 18;
let myName: string = '呵呵';
let isLoading: boolean = false;
let nullValue: null = null;
let undefinedValue: undefined = undefined;
let numbers: number[] = [1, 3, 5];
let strings: Array<string> = ['a', 'b', 'c'];
推荐使用: 类型[]
写法
let arr: (number | string)[] = [1, 'a', 3, 'b'];
// 这是什么类型?
let arr: number | string[];
arr=[1,'a'] //报错
arr=['a'] //不报错
arr=1 //不报错
// 表示arr是数字或者字符串类型的数组
type 类型别名 = 具体类型
type CustomArr = (number | string)[];
let arr: CustomArr = [1, 'a', 4];
type CustomArr = (number | string)[];
let arr: CustomArr = [1, 'a', 4];
let arr2: CustomArr = [2, 'b', 8];
两种写法:
// 函数声明
function add(num1: number, num2: number): number {
return num1 + num2;
}
// 箭头函数
const add2 = (a:number,b:number):number => a+b
const add3:(a:number,b:number)=>number =(a,b)=>a+b
//用type类型别名简化写法:
type AddFn = (a: number, b: number) => number;
const add3: AddFn = (a, b) => a+b
void
const say = (): void => {
console.log('hi');
};
void
const say = () => {
console.log('hi');
};
注意:
JS
中如果没有返回值,默认返回的是 undefined
void
和 undefined
在 TypeScript
中并不是一回事undefined
那返回值必须是 undefined
const add = (): undefined => {
return undefined;
};
?
即可(start?: number, end: number)
这样是不行的const fn = (n?: number) => {
// ..
};
fn(); // 不会报错
fn(10);
属性名: 类型
方法名(): 返回值类型
// 空对象
let person: {} = {};
// 有属性的对象
let person: { name: string; age: number } = {
name: '同学',
age: '18'
};
// 包含函数的对象
// 语法:函数名称():返回值类型
let obj: { sayHi(): void } = {
sayHi() {},
};
let obj: { sayHi:() => void } = {
sayHi() {},
};
const axios = (config: { url: string; method?: string }) => {};
// {} 会降低代码可阅读性,建议对象使用类型别名
// const axios = (config: { url: string; method?: string }) => {};
type Config = {
url: string;
method?: string;
};
const axios = (config: Config) => {};
接口声明是命名对象类型的另一种方式
interface
后面是接口名称,和类型别名的意思一样。
接口的每一行只能有一个属性或方法,每一行不需要加分号。
// 通过interface定义对象类型
interface Person {
name: string
age: number
sayHi: () => void;
}
// 使用类型
let person: Person = {
name: 'jack',
age: 19,
sayHi() {},
};
interface Point2D {
x: number;
y: number;
}
interface Point3D {
x: number;
y: number;
z: number;
}
extends
实现继承复用interface 接口A extends 接口B {}
interface Point2D {
x: number;
y: number;
}
// 继承 Point2D
interface Point3D extends Point2D {
z: number;
}
// 继承后 Point3D 的结构:{ x: number; y: number; z: number }
&
可以合并连接的对象类型,也叫:交叉类型// 使用 type 来定义 Point2D 和 Point3D
type Point2D = {
x: number;
y: number;
};
// 使用 交叉类型 来实现接口继承的功能:
// 使用 交叉类型 后,Point3D === { x: number; y: number; z: number }
type Point3D = Point2D & {
z: number;
};
type
不可重复定义;type Person = {
name: string;
};
// 标识符“Person”重复 Error
type Person = {
age: number;
};
interface
重复定义会合并 interface Person {
name: string;
}
interface Person {
age: number;
}
// 类型会合并,注意:属性类型和方法类型不能重复定义
const p: Person = {
name: 'jack',
age: 18,
};
在 TS 中存在类型推断机制,在没有指定类型的情况下,TS 也会给变量提供类型。
建议:
1.开发时,能省略类型注解的地方就省略,利用TS推断能力,提高开发效率。
2.在你还没有熟悉 ts 类型的时候建议都加上类型
3.如果不知道类型怎么写,可以把鼠标放至变量上,通过 Vscode 提示看到类型
// 变量 age 的类型被自动推断为:number
let age = 18;
// 函数返回值的类型被自动推断为:number
const add = (num1: number, num2: number) => {
return num1 + num2;
};
// : 'jack' 是字面量类型
let name: 'jack' = 'jack';
// : 18 是字面量类型
let age: 18 = 18;
// 报错:不能将类型“19”分配给类型“18”
age = 19;
let str1 = 'Hello TS';
const str2 = 'Hello TS';
// 通过类型推断发现,str1 类型是 string , str2 类型是 Hello TS
// 原因:str2 是 const 声明的,值只能是 Hello TS,所以类型只能是 Hello TS
// 使用自定义类型:
type Car = 'bc' | 'bm' | 'ad'
function pickCar(car: Car) {
console.log(car)
}
// 调用函数时,会有类型提示:
// 参数pickCar的值只能是 bc/bm/ad中的任意一个
pickCar('bc')
any
类型的作用是逃避 TS 的类型检查any
的使用越多,程序可能出现的漏洞越多,因此不推荐使用 any
类型,尽量避免使用。// 当变量的类型指定为 any的时候,不会有任何错误,也不会有代码提示,TS会忽略类型检查
let obj: any = { age: 18 }
obj.bar = 100
obj()
const n: number = obj
TS
更加明确一个值的类型,此时,可以使用类型断言来指定更具体的类型as
关键字实现类型断言,as
后面的类型是一个更加具体的类型// 假设
const img = document.getElementById('img')
img?.src // src会报错:类型“HTMLElement”上不存在属性“src”
// 进行类型断言
const img = document.getElementById('img') as HTMLImageElement
img?.src // 不报错了
泛型:是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
<类型参数>
就是泛型语法, 使用的时候传入具体的类型即可
是一个变量,可以随意命名,建议遵循大驼峰即可。类型别名
配合,在类型别名后加上泛型语法,然后类型别名内就可以使用这个类型参数类型别名type
上的应用// 用户信息返回数据类型
// 可以看到两个对象的msg和code类型相同,怎样提高复用性呢?
type UserResponse = {
msg:string
code:number
data:{
name:string
age:number
}
}
type GoodsResponse = {
msg:string
code:number
data:{
id:number
goodsName:string
}
}
// 使用泛型提高复用性
type Data<T> = {
msg:string
code:number
data:T
}
type UserResponse = Data<{name:string;age:number}>
type GoodsResponse = Data<{id:number;goodsName:string}>
// 使用类型别名type提高代码可读性
type Data<T> = {
msg:string
code:number
data:T
}
type User = {
name:string
age:number
}
type UserResponse = Data<User>
接口interface
上的应用<类型变量>
,那么,这个接口就变成了泛型接口,接口中所有成员都可以使用类型变量。// 对象,获取单个ID函数,获取所有ID函数,ID的类型肯定是一致的,但是可能是数字可能是字符串
interface IdFnObj<T> {
id: () => T;
ids: () => T[];
}
const obj: IdFnObj<number> = {
id() { return 1 },
ids() { return [1, 2] },
};
//修改泛型类型
const obj: IdFnObj<string> = {
id() { return '1' },
ids() { return ['1', '2'] },
};
const arr = [1, 2, 3];
// TS有自动类型推断,其实可以看做:const arr: Array = [1, 2, 3]
arr.push(4);
arr.forEach((item) => console.log(item));
, T是类型参数,是个类型变量,命名建议遵循大驼峰即可。// 函数的参数是什么类型,返回值就是什么类型
function getId<T>(id: T): T {
return id
}
let id1 = getId<number>(1)
// TS会进行类型推断,参数的类型作为泛型的类型 getId('2')
let id2 = getId('2')
typescript 配合 Vue3 composition-api 使用
// defineProps 的基本使用
const props = defineProps({
money: {
type: Number,
required: true
},
car: {
type: String,
required: false,
default: '宝马'
}
})
// defineProps 通过泛型参数来定义 props 的类型通常更直接:
const props = defineProps<{
money: number
car?: string
}>()
withDefaults
函数const props = withDefaults(defineProps<{
money: number;
car?: string;
}>(),{
car: '宝马'
})
响应式语法糖
解构 + defineProps
就行:const { money, car = "宝马" } = defineProps<{
money: number
car?: string
}>();
显式地选择开启
,因为它还是一个实验性特性。// 在vite.config.ts中
export default defineConfig({
plugins: [
vue({
reactivityTransform: true,
}),
],
});
defineEmits
的基本用法:const emit = defineEmits(['changeMoney', 'changeCar'])
defineEmits
通过泛型参数来定义,可以实现更细粒度的校验:const emit = defineEmits<{
(e: 'changeMoney', money: number): void
(e: 'changeCar', car: string): void
}>()
// const money = ref(10)
const money = ref(10)
<template>
<div>
<ul>
<li v-for="item in list" :key="item.id">{{item.name}}</li>
</ul>
</div>
</template>
<script setup lang='ts'>
import {ref} from 'vue'
// 类型别名: type类型别名 = 具体类型
// 数组类型:类型+[]写法
type Todo = {
id:number
name:string
done:boolean
}[]
const list = ref<Todo>([])
// 模拟从后台获取数据
// 复杂数据一般是后台返回数据,默认值是空,无法进行类型推导。
setTimeout(() => {
list.value = [
{id:1,name:'吃饭',done:false},
{id:2,name:'睡觉',done:true},
]
}, 1000)
// 推导得到的类型:{ title: string }
const book = reactive({ title: 'Vue3' })
官方:不推荐使用
reactive()
的泛型参数,因为底层和ref()
实现不一样。
// 我们想要的类型:{ title: string, year?: number }
type Book = {
title: string
year?: number
}
const book: Book = reactive({ title: 'Vue3' })
book.year = 2022
computed()
会从其计算函数的返回值上推导出类型// 类型推导
<script setup lang='ts'>
import { ref, computed } from 'vue'
const count = ref(100);
// 200(数字)
const doubleCount = computed(() => count.value * 2);
// 200.00(字符串)
// toFixed()方法四舍五入,返回值为字符串
const doubleMoney = computed<string>(() => (count.value * 2).toFixed(2));
</script>
const doubleMoney = computed<string>(() => (count.value * 2).toFixed(2));
<template>
<input type="text" @change="handleChange" />
</template>
<script setup lang="ts">
// 提示:参数“event”隐式具有“any”类型。
const handleChange = (event) => {
console.log(event.target.value)
}
</script>
<template>
// 鼠标触摸 @change,显示 (payload:Event) => void
// 说明传参类型为Event,Event是TS的内置类型
<input type="text" @change="changeFn" />
</template>
<script setup lang="ts">
// 参数“event”隐式具有“any”类型。
const changeFn = (event:Event) => {
// document.querySelector('input')看出event.target的类型为HTMLInputElement
// 类型断言为HTMLInputElement
console.log((event.target as HTMLInputElement).value)
}
</script>
模板 ref
需要通过一个显式指定的泛型参数,建议默认值 null
<template>
<input ref="input" />
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
// 使用泛型确定DOM的类型
const input = ref<HTMLInputElement| null>(null)
onMounted(() => {
// DOM获取完毕,渲染焦点
// 为了严格的类型安全,有必要在访问el.value时使用可选链或类型守卫
el.value?.focus()
})
</script>
<template>
<div>App组件</div>
<input type="text" ref="input" value="abc">
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
const input = ref< HTMLInputElement | null >(null)
onMounted(()=>{
// 1.可选链
console.log(input.value?.value);
})
</script>
if (input.value) {
console.log(input.value.value)
input.value.value = '123'
}
//非空断言! 表示确定某个标识符是有值的,跳过ts在编译阶段对它的检测
console.log(input.value!.value)
input.value!.value = '123'
项目中安装的第三方库里面都是打包后的JS代码,但是我们使用的时候却有对应的TS类型提示,这是为什么呢?
因为在第三方库中的JS代码都有对应的 TS类型声明文件
TS 中有两种文件类型:.ts 文件 .d.ts 文件作用是啥?
1 .ts
文件:
.js
文件,然后,执行代码2 .d.ts
文件:
只包含类型信息的类型声明文件
不会生成 .js
文件,仅用于提供类型信息,在.d.ts
文件中不允许出现可执行的代码,只用于提供类型
用途:为 JS
提供类型信息
TypeScript 给 JS 运行时可用的所有标准化内置 API 都提供了声明文件,这个声明文件就是 内置类型声明文件
const strs = ['a', 'b', 'c']
// 鼠标放在 forEach 上查看类型
strs.forEach
lib.es5.d.ts
类型声明文件中lib.dom.d.ts
情况1:库本身自带类型声明文件
node_modules/axios
可发现对应的类型声明文件。情况2:由 DefinitelyTyped 提供
@types/jquery
类型声明包@types/*
类型声明包后,TS 也会自动加载该类声明包,以提供该库的类型声明@types/*
.ts
文件中都用到同一个类型,此时可以创建 .d.ts
文件提供该类型,实现类型共享。操作步骤:
index.d.ts
类型声明文件。export
导出(TS
中的类型也可以使用 import/export
实现模块化功能)。.ts
文件中,通过 import
导入即可(.d.ts
后缀导入时,直接省略)。// src/types/data.d.ts
export type Person = {
id: number;
name: string;
age: number;
};
// App.vue
<script lang="ts" setup>
import { Person } from './types/data'
// 使用共享的类型 Person
const p: Person = {
id: 100,
name: 'jack',
age: 19
}
</script>
// 提取类型到 types 目录
// 频道对象
export type ChannelItem = {
id: number;
name: string;
};
// 频道接口响应数据
export type ChannelResData = {
data: {
channels: ChannelItem[];
};
message: string;
};
// 文章对象
export type ArticleItem = {
art_id: string;
aut_id: string;
aut_name: string;
comm_count: number;
cover: {
cover: number;
images: string[];
};
is_top: number;
pubdate: string;
title: string;
};
// 文章接口响应数据
export type ArticleResData = {
data: {
pre_timestamp: string;
results: ArticleItem[];
};
message: string;
};
<template>
<ChannelNav :channelId="channelId" @changeChannel="channelId = $event"></ChannelNav>
<ArticleList :channelId="channelId"></ArticleList>
</template>
<script setup lang="ts">
import ChannelNav from './components/ChannelNav.vue';
import ArticleList from './components/ArticleList.vue';
import { ref } from 'vue';
// 频道ID,用来设置高亮
// 在App组件定义数据,后续在ArticleList组件使用(父传子)
const channelId = ref(1)
</script>
<template>
<div class="channel-nav">
<nav class="list">
<a class="item" :class="{ active: channelId === item.id }" href="javascript:;"
v-for="item in getList" :key="item.id" @click="emit('changeChannel', item.id)">
{{ item.name }}
</a>
</nav>
</div>
</template>
<script setup lang='ts'>
import axios from 'axios'
import { onMounted, ref } from 'vue';
import { ChannelItem, ChannelResData } from '../types/data';
//父传子
defineProps<{ channelId: number }>()
//子传父
const emit = defineEmits<{
(e: 'changeChannel', id: number): void
}>()
// 获取文章列表
const getList = ref<ChannelItem[]>([])
onMounted(async () => {
// 使用TS的时候,axios()调用需要改为 axios.request(),才可以使用泛型
const res = await axios.request<ChannelResData>({
url: 'http://geek.itheima.net/v1_0/channels',
method:'GET'
})
getList.value = res.data.data.channels
})
</script>
<template>
<div class="article-list">
<div class="article-item" v-for="item in articles" :key="item.art_id">
<p class="title">{{ item.title }}</p>
<img v-for="(image, index) in item.cover.images" :key="index" class="img" :src="image" alt="" />
<div class="info">
<span>{{ item.aut_name }}</span>
<span>{{ item.comm_count }}评论</span>
<span>{{ item.pubdate }}</span>
</div>
</div>
</div>
</template>
<script setup lang='ts'>
import axios from 'axios'
import { ref, watch } from 'vue'
import { ArticleItem, ArticleResData } from '../types/data'
// 父传子
const props = defineProps<{ channelId: number }>()
const articles = ref<ArticleItem[]>([])
watch(
() => props.channelId,
async () => {
// 使用TS的时候,axios()调用需要改为 axios.request(),才可以使用泛型
const res = await axios.request<ArticleResData>({
url: 'http://geek.itheima.net/v1_0/articles',
params: {
channel_id: props.channelId,
timestamp: Date.now()
}
})
articles.value = res.data.data.results
},
{ immediate: true }
)
</script>
const {money,car='xxx'} = defineProps<{
money:number
car?:string
}>()
const emit = defineEmits<{
{e:'changeChannel',id:number}:void
}>()
const count = ref(0)
const list = ref<TodoItem[]>([])
const book = reactive({title:'xxx'})
//推导不正确,需要加类型,变量后类型注解
const book: {title:string,year?:number} = reactive({title:'xxx'})
const double = computed(()=>count.value*2)
const money = computed<string>(()=>(count.value*2).toFixed(2))
// $event是啥? 分情况
// 情况1:绑定的事件是原生JS事件,$event就是原生JS对象的event
// 情况2:组件自定义事件的$enevt就是emit('事件名','传参or数据')中的传参or数据
// input @change="handleChange($event)"
const handleChange = (event:Event) => {
(event.target as HTMLInputElement).value
}
const dom = ref<HTMLInputElement | null>(null)
onMounted(() => {
//非空断言
//dom.value拿到的就是input
dom.value!.value
})
<input ref="dom">
Pinia中文文档
Pinia
是一个状态管理工具,它和 Vuex
一样为 Vue
应用程序提供共享状态管理能力。Vue3
一样,它实现状态管理有两种语法:选项式API
与 组合式API
,我们学习组合式API
语法。Vue2
也支持 devtools
,当然它也是类型安全的,支持 TypeScript
Pinia的数据流转图
Vuex
一个仓库嵌套模块,结构复杂。Vuex
需要记忆太多的API
。总结:通过 const useXxxStore = defineStore('id',函数)
创建仓库得到使用仓库的函数
pinia
yarn add pinia
# 或者使用 npm
npm install pinia
main.ts
中引入注册// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'
//创建pinia插件
const pinia = createPinia()
const app = createApp(App);
app.use(pinia)
app.mount('#app')
src/store
下新建一个counter.ts
文件// counter.ts
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
//创建仓库
//参数1:id是仓库的唯一标识,一般和文件名字一致,方便找到对应的仓库
//参数2:storeSetup类似vue3的setup。作用:函数中定义数据和函数返回,他们就是store的数据和函数
//参数3:options? 额外配置
export const useCounterStore = defineStore('counter', () => {
//state
const count = ref(100)
//getters
const doubleCount = computed(() => count.value * 2)
//mutations
const update = () => count.value++
//actions
const asyncUpdate = () => {
setTimeout(() => {
count.value += 1000
}, 1000);
}
return { count, doubleCount, update, asyncUpdate }
})
<template>
<div>
<p>APP组件{{ store.count }} {{ store.doubleCount }}</p>
<button @click="store.update()">改Count</button>
<button @click="store.asyncUpdate()">异步改Count</button>
</div>
</template>
<script setup lang='ts'>
import { useCounterStore } from './store/counter';
// 使用仓库
const store = useCounterStore()
</script>
store
提供的数据时候,发现数据是没有响应式的。storeToRefs
解决解构仓库状态丢失响应式的问题import { storeToRefs } from 'pinia'
const store = useCounterStore()
const { count, doubleCount } = storeToRefs(store)
import { createApp } from 'vue'
import './styles/index.css'
// 1. 导入创建pinia的函数
import { createPinia } from 'pinia'
import App from './App.vue'
// 2. 创建pinia插件实例
const pinia = createPinia()
const app = createApp(App);
// 3. 使用插件
app.use(pinia)
app.mount('#app')
// 提取类型到 types 目录
// 频道对象
export type ChannelItem = {
id: number;
name: string;
};
// 频道接口响应数据
export type ChannelResData = {
data: {
channels: ChannelItem[];
};
message: string;
};
// 文章对象
export type ArticleItem = {
art_id: string;
aut_id: string;
aut_name: string;
comm_count: number;
cover: {
cover: number;
images: string[];
};
is_top: number;
pubdate: string;
title: string;
};
// 文章接口响应数据
export type ArticleResData = {
data: {
pre_timestamp: string;
results: ArticleItem[];
};
message: string;
};
// channel 仓库,提供频道ID数据和修改频道ID的函数
import {defineStore} from 'pinia'
import {ref} from 'vue'
export const useChannelStore = defineStore('channel',()=>{
//频道ID数据
const channelId = ref(0)
//修改频道ID的函数
const changeChannel = (id:number) => {
channelId.value = id
}
return {channelId,changeChannel}
})
<template>
<!-- 频道导航组件 -->
<ChannelNav></ChannelNav>
<!-- 文章列表组件 -->
<ArticleList></ArticleList>
</template>
<script setup lang="ts">
// vue3中只需引入就能使用子组件,无需注册
import ChannelNav from './components/ChannelNav.vue';
import ArticleList from './components/ArticleList.vue';
</script>
<!-- 频道导航组件 -->
<template>
<div class="channel-nav">
<nav class="list">
<a class="item" :class="{ active: store.channelId === item.id }" href="javascript:;"
v-for="item in getList" :key="item.id" @click="store.changeChannel(item.id)">
{{ item.name }}
</a>
</nav>
</div>
</template>
<script setup lang='ts'>
import axios from 'axios'
import { onMounted, ref } from 'vue';
import { useChannelStore } from '../store/channel';
import { ChannelItem, ChannelResData } from '../types/data';
// 定义文章列表数组
const getList = ref<ChannelItem[]>([])
onMounted(async () => {
// 使用TS的时候,axios()调用需要改为 axios.request(),才可以使用泛型
const res = await axios.request<ChannelResData>({
url: 'http://geek.itheima.net/v1_0/channels',
method:'GET'
})
getList.value = res.data.data.channels
})
// 使用名为 useChannelStore的仓库
const store = useChannelStore()
</script>
<!-- 文章列表组件 -->
<template>
<div class="article-list">
<div class="article-item" v-for="item in articles" :key="item.art_id">
<p class="title">{{ item.title }}</p>
<img v-for="(image, index) in item.cover.images" :key="index" class="img" :src="image" alt="" />
<div class="info">
<span>{{ item.aut_name }}</span>
<span>{{ item.comm_count }}评论</span>
<span>{{ item.pubdate }}</span>
</div>
</div>
</div>
</template>
<script setup lang='ts'>
import axios from 'axios'
import { ref, watch } from 'vue'
import { useChannelStore } from '../store/channel';
import { ArticleItem, ArticleResData } from '../types/data'
// 使用名为 useChannelStore的仓库
const store = useChannelStore()
const articles = ref<ArticleItem[]>([])
watch(
() => store.channelId,
async () => {
// 使用TS的时候,axios()调用需要改为 axios.request(),才可以使用泛型
const res = await axios.request<ArticleResData>({
url: 'http://geek.itheima.net/v1_0/articles',
method: 'GET',
params: {
channel_id: store.channelId,
timestamp: Date.now()
}
})
articles.value = res.data.data.results
console.log(res.data.data);
},
{ immediate: true }
)
</script>