tsconfig.json
「这是我参与2022首次更文挑战的第20天,活动详情查看:2022首次更文挑战」
TypeScript在编译为JavaScript的时候的配置文件
{
// 编译配置
"compilerOptions": {
// 目标代码 --- 编译后的代码使用esnext的语法
"target": "esnext",
// 目标代码需要使用的模块化方案 --- 编译后的代码使用esnext模块化
"module": "esnext",
// 开启所有的严格模式
"strict": true,
// 对jsx进行怎么样的处理 --- preserve表示不对jsx进行处理
"jsx": "preserve",
// 辅助的导入功能 ---- 如果用到了polyfill的时候,使用import导入的方式,而不是直接在文件中插入
"importHelpers": true,
// 按照node的方式去解析模块
"moduleResolution": "node",
// 跳过一些库的类型检测 (如axios等)
// 1. 提高性能 2. 避免多个库出现同名类型 从而发生冲突
"skipLibCheck": true,
// 以下2个选项 一般是一起使用的
// 目的是使ESM 和 CJS 可以一起混用
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
// 要不要生成映射文件(ts -> js)
"sourceMap": true,
// 是否可以解析json模块 --- 也就是是否允许将json文件作为一个独立的模块来引入
"resolveJsonModule": true,
// 文件路径在解析时, 基本URL(即基于那个路径进行解析,一般设计为当前路径即可)
"baseUrl": ".",
// 指定具体要解析使用的类型
"types": ["webpack-env"],
// 路径解析(类似于webpack alias) --- 方便ts对我们自己配置的路径别名进行解析
"paths": {
// 数组表示可以对应多个,但一般配置一个即可
"@/*": ["src/*"],
"components/*": ["src/components/*"]
},
// 可以指定在项目中可以使用哪些库的类型
"lib": ["esnext", "dom", "dom.iterable", "scripthost"],
// 在vite中的默认根路径是项目所在的文件夹
// 而tsc在进行解析的时候,默认的根路径是系统根路径
// 所以需要对两者的编译行为进行统一
"paths": {
// ./* --- 这里的相对路径是相对于tsconfig.json所在的文件夹而言的
"/*": ["./*"]
}
},
// 那些文件需要经过ts的解析
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
// 那些文件不需要经过TS的解析
// 例如在一个ts后缀文件中引入了第三方库
// 默认情况下会对引入的第三方进行类型检测
// 有的时候,这会损失性能,所以排除node_modules
// 但是TS依旧会对我们引入的类型,也就是我们所使用到的类型进行类型解析
"exclude": ["node_modules"]
}
shims-vue.d.ts
默认情况下Typescript只能对TS文件进行解析,是无法解析vue的SFC文件的,所以我们需要手动对SFC文件的类型进行声明
declare module '*.vue' {
// 引入类型 DefineComponent
import { DefineComponent } from 'vue'
// 引入component变量的类型为 DefineComponent的实例
const component: DefineComponent<{}, {}, any>
// 导出默认类型
export default component
}
normalize.css
默认情况下,界面会存在一些默认的样式,例如存在8px的margin和padding
为了避免这些默认样式对项目的uI产生影响,我们可以使用normalize.css来清除浏览器的一些默认样式
安装
> npm i normalize.css
main.ts
// 在全局进行使用
import 'normalize.css'
在Vuex中使用TS
store/types.ts
export interface IRootState {
name: string
age: number
}
store/index.ts
import { createStore } from 'vuex'
import login from './login/login'
import { IRootState } from './types'
// 在createStore的时候可以传入泛型 --- 该泛型对应的是state的类型
const store = createStore<IRootState>({
state() {
return {
name: 'Klaus',
age: 23
}
},
modules: {
login
}
})
export default store
store/login/login.ts
import { Module } from 'vuex'
import { ILoginState } from './types'
import { IRootState } from '../types'
interface IAccount {
name: string,
password: string
}
// Module是vuex提供了用于表示子模块的数据类型
// 需要传递两个泛型 --- 必传
// 第一个泛型 --- 当前模块的state的返回值类型
// 第二个泛型 --- 根state的返回值类型
const loginModule: Module<ILoginState, IRootState> = {
namespaced: true,
state() {
return {
token: ''
}
},
actions: {
async accountLoginAction({ commit }, payload: IAccount) {
// ... 这里放置登录的异步请求逻辑
}
}
}
export default loginModule
页面登录逻辑
主逻辑
import { defineStore } from 'pinia'
import Cookies from 'js-cookie'
import router from '/src/router/index'
import { login, getUserInfo, getMenu } from '../../../api/login'
import { IAccount, ILoginType, IUserInfo } from './types'
import { IResponseType } from '../../../types'
interface IState {
token: string
userInfo: IUserInfo | null
menus: []
}
export default defineStore('loginStore', {
state: (): IState => ({
token: '',
userInfo: null,
menus: []
}),
actions: {
async login(account: IAccount) {
// 登录获取token
const { data } = await login<IAccount, IResponseType<ILoginType>>(account)
const { token, id } = data
this.$state.token = token
Cookies.set('token', token)
// 登录后 获取用户信息
this.fetchUserInfo(id)
// 登录后 获取用户可操作的菜单列表
this.fetchMenu(id)
// 返回主页
router.push('/home')
},
async fetchUserInfo(id: number) {
const { data } = await getUserInfo<IResponseType<IUserInfo>>(id)
this.$state.userInfo = data
Cookies.set('userInfo', JSON.stringify(data))
},
// 获取动态菜单的方式有两种
// 1. 通过用户id去服务器获取对应的菜单 --- 这里使用的方式
// 2. 在本地定义好所有的路由对象,根据用户所对应的角色去获取所有可以访问的路由对象后 动态注册
async fetchMenu(id: number) {
const { data } = await getMenu(id)
this.$state.menus = data
Cookies.set('menus', JSON.stringify(data))
},
// 用户刷新后,是否已登录是通过在storage或者cookie中存储的数据来进行判断的
// 但是因为用户刷新了界面,所以vuex或pinia中的状态会被全部置空
// 因此需要重新进行设置
initStore() {
const token = Cookies.get('token')
const userInfo = Cookies.get('userInfo')
const menus = Cookies.get('menus')
if (token && userInfo && menus) {
/* eslint-disable no-param-reassign */
this.$patch(state => {
state.token = token
state.userInfo = JSON.parse(userInfo ?? '{}')
state.menus = JSON.parse(menus ?? '{}')
})
}
}
}
})
api.ts
// 对应的请求api
// 这里导入的api实际上是axios的实例对象
// 所以对应的get,post等方法调用的也是axios上对应的方法
// 对应的泛型也会传递给axios
import api from '../utils/api'
// Record<K, T> --- 生成一个新的对象类型,键的类型为K, 值的类型为T
// Record<string, never> --- 等价于 空对象
export function login<T, R>(account: T) {
return api.post<T, R>('/login', account)
}
export function getUserInfo<R>(id: number) {
return api.get<R>(`/users/${id}`)
}
export function getMenu<R>(id: number) {
return api.get<R>(`/role/${id}/menu`)
}
./types.ts
export interface IAccount {
name: string
password: string
}
export interface ILoginType {
id: number
name: string
token: string
}
// 对于后台返回的数据类型 一般情况来说比较复杂
// 推荐使用 json to ts之类的网站直接进行转换0.
interface Role {
id: number
name: string
intro: string
createAt: Date
updateAt: Date
}
interface Department {
id: number
name: string
parentId: number
createAt: Date
updateAt: Date
leader: string
}
export interface IUserInfo {
id: number
name: string
realname: string
cellphone: number
enable: number
createAt: Date
updateAt: Date
role: Role
department: Department
}
/src/type.ts
// 自定义的后台返回值类型
// data类型 不同请求所对应的返回值类型不同,所以定义为泛型 由调用者来决定具体的类型
export interface IResponseType<T> {
code: number
data: T
}
main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import router from './router'
import App from './App.vue'
import { useLoginStore } from './store/index'
import 'normalize.css'
const app = createApp(App)
app.use(createPinia())
app.use(router)
// 在全局调用store.initStore()
// 以确保 每次用户刷新页面的时候
// vuex或pinia中的数据都得到有效初始化
const store = useLoginStore()
store.initStore()
app.mount('#app')