震惊!!!某前端小白竟手写出.......

225 阅读5分钟

今日本前端小白给各位大佬带来如何重第一步开始搭建出一个由Vue3、Typescript与Vant组件库的H5项目,本文有不足之处还望各位掘金大佬们不要手下留情,请狠狠的践踏我吧!!!

iq3kxzl42xxiq3kxzl42xx.png

今日白熊镇楼

Vue3 + TS + Vant项目

项目起步

1.项目搭建

使用 create-vue 脚手架创建项目。

pnpm create vue
# or
npm init vue@latest
# or
yarn create vue
✔ Project name: … patients-h5-100Add TypeScript? … No / `Yes`
✔ Add JSX Support? … `No` / Yes
✔ Add Vue Router for Single Page Application development? … No / `Yes`
✔ Add Pinia for state management? … No / `Yes`
✔ Add Vitest for Unit Testing? … `No` / Yes
✔ Add Cypress for both Unit and End-to-End testing? … `No` / Yes
✔ Add ESLint for code quality? … No / `Yes`
✔ Add Prettier for code formatting? … No / `Yes`
​
Scaffolding project in /Users/zhousg/Desktop/patient-h5-100...
​
Done. Now run:
​
  cd patient-h5-100
  pnpm install
  pnpm lint
  pnpm dev

2.Eslint的配置

修改.eslintrc.cjs文件,补充配置项

{
    rules: {
        'prettier/prettier': [
            'warn',
            {
                singleQuote: true,
                semi: false,
                printWidth: 80,
                trailingComma: 'none',
                endOfLine: 'auto'
            }
        ],
        'vue/multi-word-component-names': [
            'warn',
            {
                ignores: ['index']
            }
        ],
        'vue/no-setup-props-destructure': ['off']
    }
}
  • 格式:单引号,没有分号,行宽度 80 字符,省略最后一个逗号,换行字符串自动(系统不一样换行符号不一样)。
  • Vue 组件需要大驼峰命名,除去 index 之外,App 是默认支持的。
  • 允许对 props 进行解构,因为我们会开启解构保持响应式的语法糖。
# 修复格式
pnpm lint
{
    // 省略其他
    "editor.codeActionsOnSave": {
        "source.fixAll": true,
    }
}

3.husky配置

安装

pnpm dlx husky-init && pnpm install

修改 .husky/pre-commit 文件

pnpm lint

测试效果

  1. 故意调整代码格式导致不符合eslint规则,例如多添加一些空行
  2. git add . git commit -m "xxx" 。会发现,提交时,会自动运行pnpm lint。

4.路由配置

配置路由

import {
    createRouter,
    createWebHistory
} from 'vue-router'// createRouter 创建路由实例
// createWebHistory() 是开启history模块
// createWebHashHistory() 是开启hash模式// vite 的配置 import.meta.env.BASE_URL 是路由的基准地址,默认是 ’/‘
// https://vitejs.dev/guide/build.html#public-base-path
// 如果将来你部署的域名路径是:http://xxx/my-path/user
// vite.config.ts 添加配置 base: my-path,路由这就会加上 my-path 前缀了const router = createRouter({
    history: createWebHistory(import.meta.env.BASE_URL),
    routes: []
})
​
export default router

如何创建实例方式

createRouter()

如何设置路由模式

createWebHistory()
// or
createWebHashHistory()

import.meta.env. BASE_URL 值来自哪里?

# vite.config.ts 的 base 属性的值

base的作用

# 项目的基础路径前缀,默认是 /

5.pinia设置与创建持久化

a.pinia设置

import type {
    User
} from '@/types/user'
import {
    defineStore
} from 'pinia'
import {
    ref
} from 'vue'export const useUserStore = defineStore('cp-user', () => {
    // 用户信息
    const user = ref < User > ()
    // 设置用户,登录后使用
    const setUser = (u: User) => {
        user.value = u
    }
    // 清空用户,退出后使用
    const delUser = () => {
        user.value = undefined
    }
    return {
        user,
        setUser,
        delUser
    }
})

Pinia 存储这个数据的意义?

答:数据共享,提供给项目中任何位置使用。

如果存储了数据,刷新页面后数据还在吗?

答:不在,现在仅仅是存储在了 JS 内存中,需要进行本地存储(持久化)。

b.数据持久化

使用 pinia-plugin-persistedstate 实现 Pinia 仓库状态持久化,且完成测试。

安装

pnpm i pinia-plugin-persistedstate
# or
npm i pinia-plugin-persistedstate
# or
yarn add pinia-plugin-persistedstate

配置

 {
      persist: true
  }

c.抽取Pinia代码

import { createPinia } from 'pinia'
import persist from 'pinia-plugin-persistedstate'import useUserStore from './user'export const useStore = () => {
  return {
    userStore: useUserStore()
  }
}
// 创建 Pinia 实例
const pinia = createPinia()
// 使用 Pinia 插件
pinia.use(persist)
// 导出 Pinia 实例,给 main 使用
export default pinia

统一导出:export * from './user'

在APP.vue中

import { useUserStore } from './stores'

统一导出是什么意思?

答:一个模块下的所有资源通过 index 进行导出。

6.Vant组件库使用

a.安装

# Vue3 项目,安装最新版 Vant
npm i vant
# 通过 yarn 安装
yarn add vant
# 通过 pnpm 安装
pnpm add vant

b.引入样式

//main.ts
import 'vant/lib/index.css'

c.按需引入

<script setup lang="ts">
    import {
        Button as VanButton
    } from 'vant'
</script><template>
    <van-button type="primary">按钮</van-button>
</template>

为什么不全局使用?

全局使用是全量加载,项目体积变大,加载慢。

d.自动按需加载

安装

pnpm add unplugin-vue-components -D

配置

import { fileURLToPath, URL } from 'node:url'import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// #1
import Components from 'unplugin-vue-components/vite'
// #2
import { VantResolver } from 'unplugin-vue-components/resolvers'// https://vitejs.dev/config/
export default defineConfig({
    plugins: [
        vue(),
        // #3 自动导入的插件
        Components({
            // #5 默认 true,开启自动生成组件的类型定义文件,而 vant 已经自带类型了,无需生成
            dts: false,
            // #4 main.ts 已经引入了所有的 vant 样式,不需要自动导入样式,只需要自动导入组件即可
            resolvers: [VantResolver({ importStyle: false })]
        })
    ],
    resolve: {
        alias: {
            '@': fileURLToPath(new URL('./src', import.meta.url))
        }
    }
})

7.移动端适配

安装

npm install postcss-px-to-viewport -D
# or
yarn add -D postcss-px-to-viewport
# or
pnpm add -D postcss-px-to-viewport

配置 postcss.config.js

// eslint-disable-next-line no-undef
module.exports = {
    plugins: {
        'postcss-px-to-viewport': {
            // 以设备宽度 375 为基准计算 vw 的值
            // 假如 100px 的 div
            // 375 宽度下,最终转换出的 vw 应该是:x / 100vw = 100px / 375px,x 等于 26.66vw
            // 而转换出来的 26.66vw 自然在不同的设备宽度下所表示的 div 宽度会不一样,例如 414 设备下
            // 26.66vw / 100vw = div 宽度 / 414px
            viewportWidth: 375,
        },
    },
};

8.请求实例封装

基本准备

import axios from 'axios'const instance = axios.create({
    // TODO 1. 基础地址,超时时间
})
​
instance.interceptors.request.use(
    (config) => {
        // TODO 2. 携带 token
        return config
    },
    (err) => Promise.reject(err)
)
​
instance.interceptors.response.use(
    (res) => {
        // TODO 3. 处理业务失败
        // TODO 4. 摘取核心响应数据
        return res
    },
    (err) => {
        // TODO 5. 处理 401 错误
        return Promise.reject(err)
    }
)
​
export default instance

基础封装

import { useUserStore } from '@/stores'
import router from '@/router'
import axios from 'axios'
import { showToast } from 'vant'// 1. 新 axios 实例,基础配置
const baseURL = 'https://xxx.xxx/'
const instance = axios.create({
    baseURL,
    timeout: 10000
})
​
// 2. 请求拦截器,携带 token
instance.interceptors.request.use(
    (config) => {
        const store = useUserStore()
        if (store.user?.token && config.headers) {
            config.headers['Authorization'] = `Bearer ${store.user?.token}`
        }
        return config
    },
    (err) => Promise.reject(err)
)
​
// 3. 响应拦截器,剥离无效数据,401 拦截
instance.interceptors.response.use(
    (res) => {
        // 后台约定,响应成功,但是 code 不是 10000,是业务逻辑失败
        if (res.data?.code !== 10000) {
            showToast(res.data?.message || '网络异常')
            return Promise.reject(res.data)
        }
        // 业务逻辑成功,返回响应数据,作为 axios 成功的结果
        return res.data
    },
    (err) => {
        if (err.response.status === 401) {
            // 删除用户信息
            const store = useUserStore()
            store.delUser()
            // 跳转登录,带上接口失效所在页面的地址,登录完成后回跳使用
            router.push(`/login?returnUrl=${router.currentRoute.value.fullPath}`)
        }
        return Promise.reject(err)
    }
)
​
export { baseURL, instance }

请求函数的封装

添加上泛型

const request = <T>(
    url: string,
    method: Method = 'get',
    submitData?: object
) => {
    // #2
    return instance.request<
        T,
        {
            code: string
            message: string
            data: T
        }
    >({
        url,
        method,
        [method.toLowerCase() === 'get' ? 'params' : 'data']: submitData
    })
}