vue3+router+ts+pinia
ts
优势
静态类型检查,提前发现错误
良好的代码提示
需要编译才可以使用
准备工作
-
需要node环境
- npm下载npm i typescript
- 编译单个文件 tsc 文件名 -w (-w自动编译)
- 编译整个文件 tsc (需要tsconfig.json)
-
ts声明变量时会自动判断类型
数据类型
-
简单类型
number、string、boolean、null、undefined
-
复杂类型
对象、数组、函数
-
新增类型
联合类型 | 类型别名 type 接口interface 字面量类型 相当于常量 泛型 void 无返回值 any 任意类型,会影响别人 unknown 未知类型,只影响自己 元祖 [string,number] 固定长度,指定类型的数组
普通值
let num:number = 123
let str:string = 'abc'
let bool:boolean = true
对象
let obj:{
name:string,必须存在属性name
age?:number,可选属性age
[propName:string]:any],其他所有字符串属性都是可选的-propName 可以是任意名字
}
数组
let arr:number[] = [1,2,3]
let arr:(number|string)[] = ['a',1,2,3]
let arr:Array<number|string>=['a',1,2,3]
函数
// 普通函数
function fn1(n1: number, n2: number): number {
return n1 + n2
}
// 箭头函数
let fn2: (n1: number, n2: number) => number
fn2 = (n1, n2) => n1 + n2
// 或者
type fn = (n1: number, n2: number) => number
const fn3: fn = (n1, n2) => n1 + n2
// 箭头函数(整体注释)
const fn4 = (n1:number,n2:number) => number = (n1,n2) => n1 + n2
接口interface
接口是用于定义一个标准,都是抽象的属性和方法(也不完全是),类似于class类
可以重复声明,属性和方法会叠加、可以继承、可以嵌套
// 普通
interface Father {
name: string
age?: number
}
interface Father {
sex: boolean
}
const f: Father = {
name: 'yj',
sex:true
}
// 继承
interface Son extends Father {
kaoshi:number
}
const s:Son = {
name:'zz',
kaoshi:99
}
// 嵌套
interface Sun {
eye:number,
data:F
}
const sun: Sun = {
eye: 2,
data: {
name: 'yy',
age:99
}
}
接口实现
// 接口定义对象的结构,包含属性和方法声明
interface Person {
name: string; // 必需属性
age?: number; // 可选属性
greet(): void; // 抽象方法
sayHello(): string; // 抽象方法
walk?(): void; // 可选方法
}
// 实现接口
class Student implements Person {
name: string;
age?: number;
constructor(name: string, age?: number) {
this.name = name;
this.age = age;
}
greet() {
console.log("Hello, I'm " + this.name);
}
sayHello() {
return "Hello";
}
}
// 创建实例
const student = new Student("Alice", 25);
student.greet(); // 输出:Hello, I'm Alice
console.log(student.sayHello()); // 输出:Hello
类型type
不可以重复声明
type Father = {
name: string
age?: number
}
const f: Father = {
name: 'yj',
age: 19
}
type Son = Father & {
sex: boolean
}
const s: Son = {
name: 'zz',
age: 29,
sex: true
}
抽象类、抽象方法abstract
abstract class Person {
abstract sayHei():void // 这个抽象类,只能被继承,不能被实例化
}
let p = new Person() // 不允许,只能被继承
class Man extends Person{
sayHei(){} // 必须重写父类的方法,否则ts报错
} // 允许,可以被继承
修饰符 public 、private 、protected
class P {
private _name: string
public _age: number
protected _sex: number
constructor(_name:string,_age:number) {
this._name = _name
this._age = _age
}
go1(){
this.
}
}
let yj = new P('yj',99)
yj._name = 'zd2' // ts报错,提示,私有的_name属性无法控制
yj._age = 999 // ts不报错,公共属性可以修改
console.log(yj);
class P {
private name: string
public age: number
protected sex: number
constructor(name: string, age: number, sex: number) {
this.name = name
this.age = age
this.sex = sex
}
changeSex() {
this.sex = 999
}
}
class PPPP extends P {
changeSex() {
this.sex = 999
}
}
let yj = new P('yj',9,0)
let yjj = new PPPP('yj',9,0)
yj.name = 'yjj' // ts报错,提示,属性name是私有的,只能在当前类中修改
yj.age = 99
yj.sex = 1 // ts报错,提示,属性sex受到保护,只能在当前类和子类中修改
yj.changeSex()
yjj.changeSex()
断言(欺骗ts)
const link = document.qs('link') as HTMLAnchorElement
link.可访问链接元素身上的方法
泛型
// 对象和泛型
interface User {
token: string
id: number
}
interface List {
arr1: number[]
arr2: Array<number>
}
interface ResData<T> {
code: number
message: string
data: T
}
const userRes: ResData<User> = {
code: 100,
message: 'ok',
data: {
token: 'asd',
id: 1
}
}
const listRes: ResData<List> = {
code: 100,
message: 'ok',
data: {
arr1: [1],
arr2: [1]
}
}
// type和interface基本相同
// 函数和泛型
function Myjoin<T>(len: number, value: T): void {
const arr = []
for (let i = 0; i < len; i++) {
arr[i] = value
}
console.log(arr)
}
const res = Myjoin<string>(5, 'c')
const res2 = Myjoin<number>(3, 4)
pick挑选属性
// Pick 可以从一个对象类型中 取出某些属性
type Person = {
name: string
age: number
sex: 0|1
}
type PickPerson = Pick<Person, 'age'|'sex'>
// PickPerson === { age: string, sex: 0|1 }
###omit去除属性
// Omit 可以从一个对象类型中 排出某些属性
type Person = {
name: string
age: number
sex: 0|1
}
type OmitPerson = Omit<Person, 'age'|'sex'>
// OmitPerson === { name: string }
Partial属性转为可选
type Person = {
name:string
age?:number
sex?:0|1
}
type PartialPerson = Partial<Person>
// PartialPerson
{name? age? sex?}
Required属性转为必选
type Person = {
name:string
age?:number
sex?:0|1
}
type RequiredPerson = Required<Person>
// RequiredPerson
{name age sex}
keyof 提取所有属性
interface Person {
name: string;
age: number;
id: number;
}
// Person 所有键的联合类型
type Keys = keyof Person; // 等效于 "name" | "age" | "id"
// 相当于
interface Keys {
name: string;
age: number;
id: number;
}
enum 枚举
// 不需要直接写入常量,比如1234具体值(没有语义),而是用fx.单词还获取数字(有语义)
// 枚举具有默认值
enum fx {
left = 1,
right = 2,
top = 3,
bottom = 4
}
console.log(fx.right)
告诉ts是全局变量
/* global 变量名 */
后续的该变量名就不会提示引入错误,被TS认为是已经被全局引入过
tsconfig.json
ts的配置文件,直接放在根目录
{
"include":["./src/**/*"], // 需要编译的路径
"exclude":["./dist/**/*"], // 不需要编译的路径
"extends":"./config/base", //继承其他的规则
"files":["core.ts","sys.ts"], // 指定编译文件
"compilerOptions": {
"target": "ES6",//编译指定es版本 ts=>js
"module": "ESNext",//编译指定模式es6、commonjs
// "lib": [],
"outDir": "dist2", //编译保存的文件夹,
// "outFile": "./dist/app.js", //编译合并保存文件
"allowJs": true, //是否编译js文件(文件夹中除了ts之外可能还存在js文件,是否一起编译了)
"checkJs": true, //是否检查js代码符合规范
"removeComments": true, //是否移出注释
"noEmit": false ,// 是否生成编译后的文件
"noEmitOnError": true, //有错误时,不编译生成文件
"strict": true,//所有严格模式的总开关,开启后,所有相关属性全部打开和关闭(优先级没有单独写高)
"alwaysStrict": false,// 开启严格模式 use strict
"noImplicitAny": true,// 不允许出现默认any类型
"noImplicitThis": true, // 不允许出现不明确类型的this
"strictNullChecks": true // 严格的检验是否为空,万一什么东西没获取到
}
}
vue3 基础语法
reactive-响应式(适用于对象)
import { reactive } from "vue";
const state = reactive({ name: 'tom', age: 18 })
state.name++
{{state.name}}
ref-响应式(适用于简单数据)
import { ref } from "vue";
const num = ref(100)
num.value++
{{num}}
computed
import { ref, computed } from "vue";
let n1 = ref(10)
let n2 = ref(20)
const sum = computed(()=>{
return n1.value+n2.value
})
{{sum}}
watch
import { reactive, watch } from "vue";
const state = reactive({
n1:10,
n2:20
})
// 监听一个
watch(state.n1,(newV,oldV)=>{
console.log(newV,oldV)
})
// 监听多个
watch([state.n1,state.n2],(newV,oldV)=>{
console.log(newV,oldV)//newV、oldV都是数组
})
// 监听对象(深度监听)
watch(state,(newV,oldV)=>{
console.log(newV,oldV)//newV、oldV都是数组
},{
deep:true,
immediate:true
})
ref-获取DOM
// Vue js
import { ref } from 'vue'
const dom = ref(null)
dom.value.innerHTML = 换一个标题
// Vue html
<h1 ref="dom">我是标题</h1>
defineExpose
使用:暴露组件方法和属性—defineExpose({})
// fatherVue js
import { ref } from 'vue'
import HomeView from './views/HomeView.vue';
const dom = ref(null)
调用子级属性和方法时,必须先在子级中暴露
dom.value.conut++
dom.value.fn()
// fatherVue html
<HomeView ref="dom"/>
// sonVue js
const count = ref(0)
const fn = () => {
console.log('方法')
}
import { ref } from 'vue'
defineExpose({count,fn}) // 暴露属性和方法count和fn
defineProps
js使用:接收父组件的传递的属性—defineProps({})
// fatherVue js
import { ref } from 'vue'
import HomeView from './views/HomeView.vue';
const num = ref(1)
const state = ref({
name:'yj',age:19
})
// fatherVue html
<HomeView :fstate="state" :fnum="num"/>
// sonVue js
const count = ref(0)
const fn = () => {
console.log('方法')
}
import { ref } from 'vue'
// 接收属性----js
const props = defineProps({
fnum:Number,fstate:Object
})
// 接收属性----ts
const props = defineProps<{fnum:number,fstate:Object}>()
props.fnum
props.fstaet
//sonVue html
{{fnum}}---{{fstate}}
defineEmits
使用:接收父组件的传递的属性—defineEmits([])
// fatherVue js
import { ref } from 'vue'
import HomeView from './views/HomeView.vue';
const num = ref(1)
const cfnum = v => {
console.log(v);
num.value+=parseInt(v)
}
// fatherVue html
<HomeView @cfnum="cfnum" :fnum="num"/>
// sonVue js
defineProps({
fnum:Number
})
// 接收属性----js
const emit = defineEmits(['cfnum'])
// 接收属性----ts
const emit = defineEmits<{(e:'cfnum',fnum:number):void}>()
const cfnum = ()=>{
emit('cfnum','100')
}
//sonVue html
<button @click="cfnum">btn</button>
provide、inject跨组件传值
// yeyeVue
<script setup>
import { provide, ref } from 'vue';
import ParentCom from './ParentCom.vue';
// 1. app组件数据传递给child
const count = ref(0);
provide('count', count);
// 2. app组件函数传递给child,调用的时候可以回传数据
const updateCount = (num) => {
count.value += num;
};
provide('updateCount', updateCount);
</script>
<template>
<div
class="app-page"
style="border: 10px solid #ccc; padding: 50px; width: 600px"
>
app 组件 {{ count }} updateCount
<ParentCom />
</div>
</template>
// sunVue
<script setup>
const count = inject('count');
const updateCount = inject('updateCount');
</script>
<template>
<div class="child-page" style="padding: 50px; border: 10px solid #ccc">
child 组件 {{ count }} <button @click="updateCount(100)">修改count</button>
</div>
</template>
toRefs解构保持响应式
import { reactive, toRefs } from "vue";
const user = reactive({ name: "tom", age: 18 });
const { name, age } = toRefs(user)
router 路由
-
路由抽离单独文件 router/index.ts
-
router/index.ts引入路由
import { createRouter, createWebHistory } from 'vue-router'
-
router/index.ts 创建路由
const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: '/login', name: 'login', component: () => import('@/views/login/index.vue'), meta: { title: '登录' } } ] })
-
router/index.ts 路由拦截器
router.beforeEach((to) => { }) router.afterEach((to) => { })
-
暴露router,让vue注册
import router from './router' app.use(router)
composables
类似于vue2的mixins ,抽离公共复用的js
优势
- 多个mixins 时,好追溯,知道该方法属于谁
- 防止命名被覆盖
// 定义的composables函数 count.js import { onMounted, ref } from 'vue' export const useCount = () => { const num1 = ref(0) const num2 = ref(0) const sum = ref(0) const count = () => { sum.value = +num1.value + +num2.value console.log('ok') } onMounted(() => { console.log('可以调用生命周期函数') }) return { num1, num2, sum, count } }
// 使用 composables函数 A.vue 导入 import { useCount } from '@/composables/count.js' const { num1, num2, sum, count } = useCount() 使用 <input type="text" v-model.number.trim="num1" /> <input type="text" v-model.number.trim="num2" /> {{ num1 }} +{{ num2 }} = {{ sum }} <button @click="count">计算</button>
pinia 状态管理
-
下载
npm i pinia
-
引入、注册
// main.ts import { createPinia } from 'pinia' const pinia = createPinia() app.use(pinia)
-
定义数据状态(组合式api代码)
import { defineStore } from 'pinia' import { ref,computed } from 'vue' // useChannelStore 尽量这个格式起名字 // channel 为模块唯一id,和浏览器插件挂钩 // num 为原vuex的state // dbNum 为原vuex的getter // addNum、addNumDelay 为原vuex的mutation、action方法,但pinia方法中没有mutation,默认都是action,所以同步和异步为一个方法 export const useChannelStore = defineStore('channel',()=>{ const num = ref<number>(0) const addNum = (n:number) =>{ num.value+= n } const addNumDelay = (n:number) =>{ setTimeout(()=>{ num.value+= n },1000) } const dbNum = computed(()=>num.value*2) return { num,addNum,addNumDelay,dbNum } })
-
使用数据状态
import {useChannelStore} from '../store/chanelStore' import { storeToRefs } from 'pinia' const store = useChannelStore() const onBtn = ()=>{ store.addNum(10) } const onBtnDelay = ()=>{ store.addNumDelay(10) } // 直接解构使用num,dbNum不是响应式数据,使用storeToRefs方便解构后使用num和dbNum const { num,dbNum } = storeToRefs(store)