• 第五章、Vue3高级


    二十一、项目搭建规范

    1、创建项目
    • 命令
    vue create vue3-ts
    
    • 选项
    ? Please pick a preset: 
        - Manually select features
    ? Check the features needed for your project: 
        - Choose Vue version, Babel, TS, Router, Vuex, CSS Pre-processors, Linter
    ? Choose a version of Vue.js that you want to start the project with 
        - 3.x
    ? Use class-style component syntax? 
        - No
    ? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? 
        - Yes
    ? Use history mode for router? (Requires proper server setup for index fallback in production) 
        - No
    ? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): 
        - Sass/SCSS (with dart-sass)
    ? Pick a linter / formatter config: 
        - Prettier
    ? Pick additional lint features: 
        - Lint on save
    ? Where do you prefer placing config for Babel, ESLint, etc.? 
        - In dedicated config files
    ? Save this as a preset for future projects? 
        - No
    ? Pick the package manager to use when installing dependencies: 
        - NPM
    
    2、代码规范
    a、.editorconfig(编辑器编辑风格)
    # http://editorconfig.org
    
    root = true
    
    [*] # 表示所有文件适用
    charset = utf-8 # 设置文件字符集为 utf-8
    indent_style = space # 缩进风格(tab | space)
    indent_size = 2 # 缩进大小
    end_of_line = lf # 控制换行类型(lf | cr | crlf)
    trim_trailing_whitespace = true # 去除行首的任意空白字符
    insert_final_newline = true # 始终在文件末尾插入一个新行
    
    [*.md] # 表示仅 md 文件适用以下规则
    max_line_length = off
    trim_trailing_whitespace = false
    
    b、prettier(代码格式化风格)
    • 安装
    npm i -D prettier
    
    • .prettierrc
    {
      "useTabs": false,
      "tabWidth": 2,
      "printWidth": 80,
      "singleQuote": true,
      "trailingComma": "none",
      "semi": false
    }
    
    * useTabs:使用tab缩进还是空格缩进,选择false
    * tabWidth:tab是空格的情况下,是几个空格,选择2个
    * printWidth:当行字符的长度,推荐80,也有人喜欢100或者120
    * singleQuote:使用单引号还是双引号,选择true,使用单引号
    * trailingComma:在多行输入的尾逗号是否添加,设置为 `none`
    * semi:语句末尾是否要加分号,默认值true,选择false表示不加
    
    • .prettierignore
    /dist/*
    .local
    .output.js
    /node_modules/**
    
    **/*.svg
    **/*.sh
    
    /public/*
    
    • package.json
    {
      "scripts": {
        "prettier": "prettier --write ."
      }
    }
    
    c、eslint(代码风格检测工具)
    • 安装
    npm i -D eslint-plugin-prettier eslint-config-prettier
    
    • .eslintrc.js
    module.exports = {
      root: true,
      env: {
        node: true
      },
      extends: [
        'plugin:vue/vue3-essential',
        'eslint:recommended',
        '@vue/typescript/recommended',
        '@vue/prettier',
        '@vue/prettier/@typescript-eslint',
        // 解决eslint和prettier的冲突
        'plugin:prettier/recommended'
      ],
      parserOptions: {
        ecmaVersion: 2020
      },
      rules: {
        'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
        'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
        '@typescript-eslint/no-var-requires': 'off',
        '@typescript-eslint/no-explicit-any': 'off',
        '@typescript-eslint/explicit-module-boundary-types': 'off',
        '@typescript-eslint/no-non-null-assertion': 'off'
      }
    }
    
    d、git Husky(提交前代码格式化风格)
    • 命令
    ################
    # 1、用于拦截git命令
    # 2、项目三处变化:
    #     - package.json多一个依赖({"devDependencies": {"husky": ""}})
    #     - 项目下多一个.husky目录
    #     - package.json多一个脚本({"scripts": {"prepare": "husky install"}})
    ################
    npx husky-init && npm install
    
    • .husky/pre-commit
    #!/usr/bin/env sh
    . "$(dirname -- "$0")/_/husky.sh"
    
    npm run lint
    
    e、commitizen(提交信息格式化风格)
    • 安装
    npm i -D commitizen
    
    • 命令
    ################
    # 1、项目两处变化:
    #     - package.json多一个依赖({"devDependencies": {"cz-conventional-changelog": ""}})
    #     - package.json多一个配置
    #       ({"config": {"commitizen": {"path": "./node_modules/cz-conventional-changelog"}}})
    ################
    npx commitizen init cz-conventional-changelog --save-dev --save-exact
    
    • package.json(代码的git提交操作使用此脚本)
    {
      "scripts": {
        "commit": "cz"
      }
    }
    
    • 选项
    ? Select the type of change that you're committing: 
        - 提交的类型
    ? What is the scope of this change (e.g. component or file name): (press enter to skip) 
        - 提交的范围
    ? Write a short, imperative tense description of the change (max 87 chars):
        - 提交的简短描述信息
    ? Provide a longer description of the change: (press enter to skip)
        - 提交的详细描述信息
    ? Are there any breaking changes? 
        - No
    ? Does this change affect any open issues? 
        - No
    
    提交的类型 作用
    feat 新增特性 (feature)
    fix 修复 Bug(bug fix)
    docs 修改文档 (documentation)
    style 代码格式修改(white-space, formatting, missing semi colons, etc)
    refactor 代码重构(refactor)
    perf 改善性能(A code change that improves performance)
    test 测试(when adding missing tests)
    build 变更项目构建或外部依赖(例如 scopes: webpack、gulp、npm 等)
    ci 更改持续集成软件的配置文件和 package 中的 scripts 命令,例如 scopes: Travis, Circle 等
    chore 变更构建流程或辅助工具(比如更改测试环境)
    revert 代码回退
    f、commitlint(提交信息风格检测工具)
    • 安装
    npm i -D @commitlint/config-conventional @commitlint/cli
    
    • commitlint.config.js
    module.exports = {
      extends: ['@commitlint/config-conventional']
    }
    
    • 命令
    ################
    # 1、项目一处变化:
    #     - .husky下多一个commit-msg文件
    ################
    npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"
    
    3、项目结构
    a、vue.config.js
    const path = require('path')
    
    module.exports = {
      // 1、方式一: 通过Vue CLI提供的选项来配置(推荐)
      outputDir: 'dist',
      publicPath: '/',
      // 2、方式二: 通过configureWebpack(与webpack配置选项一致)
      // 2.1、对象:合并webpack配置
      configureWebpack: {
        resolve: {
          alias: {
            components: '@/components'
          }
        }
      },
      // 2.2、函数:修改webpack配置
      /*
      configureWebpack: (config) => {
        config.resolve.alias = {
          '@': path.resolve(__dirname, 'src'),
          components: '@/components'
        }
      },*/
      // 3、方式三:通过chainWebpack(与webpack配置选项一致)
      // 3.1、函数:修改webpack配置
      /*
      chainWebpack: (config) => {
        config.resolve.alias
          .set('@', path.resolve(__dirname, 'src'))
          .set('components', '@/components')
      }*/
    }
    
    b、vue-router
    • 安装
    npm i -S vue-router@next
    
    • src/router/index.ts
    import { createRouter, createWebHashHistory } from 'vue-router'
    import type { RouteRecordRaw } from 'vue-router'
    
    const routes: Array<RouteRecordRaw> = [
      {
        path: '/',
        redirect: '/home'
      },
      {
        path: '/home',
        component: () => import('../views/Home.vue')
      },
      {
        path: '/about',
        component: () => import('../views/About.vue')
      }
    ]
    
    const router = createRouter({
      history: createWebHashHistory(),
      routes
    })
    
    export default router
    
    • src/main.ts
    import { createApp } from 'vue'
    import App from './App.vue'
    import router from './router'
    
    const app = createApp(App)
    app.use(router)
    app.mount('#app')
    
    • src/App.vue
    <template>
      <div>
        <router-link to="/home">Home</router-link>
        <router-link to="/about">About</router-link>
      </div>
      <router-view />
    </template>
    
    c、vuex
    • 安装
    npm i -S vuex@next
    
    • src/store/index.ts
    import { createStore, useStore as useVuexStore } from 'vuex'
    import user, { IUserState } from './modules/user'
    
    export interface IRootState {
      user: IUserState
    }
    
    export default createStore<IRootState>({
      modules: {
        user
      }
    })
    
    export function useStore() {
      return useVuexStore<IRootState>()
    }
    
    • src/store/modules/user.ts
    import { IRootState } from '@/store'
    
    export interface IUserState {
      token: string
      userInfo: {
        id: string
        nickname: string
      }
    }
    
    const user: Module<IUserState, IRootState> = {
      namespaced: true,
      state() {
        return {
          token: '',
          userInfo: {
            id: '',
            nickname: ''
          }
        }
      },
      mutations: {},
      actions: {}
    }
    export default user
    
    • src/main.ts
    import { createApp } from 'vue'
    import App from './App.vue'
    import store from './store'
    
    const app = createApp(App)
    app.use(store)
    app.mount('#app')
    
    • src/App.vue
    <template>
      <div></div>
    </template>
    
    <script lang="ts">
    import { defineComponent } from 'vue'
    import { useStore } from '@/store'
    
    export default defineComponent({
      setup() {
        const store = useStore()
        console.log(store.state.user.token)
        return {}
      }
    })
    </script>
    
    <style lang="scss" scoped></style>
    
    d、element-plus
    • 安装
    npm i -S element-plus
    
    • src/main.ts
    import { createApp } from 'vue'
    import App from './App.vue'
    import ElementPlus from 'element-plus'
    import 'element-plus/dist/index.css'
    
    const app = createApp(App)
    app.use(ElementPlus)
    app.mount('#app')
    
    • src/App.vue
    <template>
      <el-button type="primary">黄婷婷</el-button>
    </template>
    
    e、axios
    • 安装
    npm i -S axios
    
    • 知识点
    * Promise<any>指定了泛型,则resolve(res)和.then(res=>{})的res就是any类型
    * axios配置默认值优先级:请求的config参数 > 实例的defaults属性
    * axios实例多个拦截器会合并,执行顺序是从后往前
    
    • src/utils/request.ts
    import axios from 'axios'
    import type { AxiosRequestConfig } from 'axios'
    import Cookies from 'js-cookie'
    
    const service = axios.create({
      baseURL: process.env.VUE_APP_BASE_API,
      timeout: 5000
    })
    
    service.interceptors.request.use(
      (config) => {
        // 1、请求头添加令牌
        const token = Cookies.get('token')
        if (token && config.headers) {
          config.headers.Authorization = `Bearer ${token}`
        }
        return config
      },
      (error) => {
        return Promise.reject(error)
      }
    )
    
    service.interceptors.response.use(
      (response) => {
        // 2、处理后端接口统一响应结果的状态码
        switch (response.data.code) {
          case 999:
            console.log('错误')
            break
          default:
        }
        return response
      },
      (error) => {
        // 3、处理http的状态码
        switch (error.response.status) {
          case 401:
            console.log('未认证或令牌过期')
            break
          case 403:
            console.log('无权限访问拒绝')
            break
          default:
        }
        return Promise.reject(error)
      }
    )
    
    interface IResponseBody<T = any> {
      code: number
      data: T
      message: string
    }
    
    export default function <T = any>(
      config: AxiosRequestConfig
    ): Promise<IResponseBody<T>> {
      return service(config).then((res) => {
        return res.data
      })
    }
    
    • src/api/user.ts
    import request from '@/utils/request'
    
    export function login(data: any) {
      return request({
        url: '/user/login',
        method: 'post',
        data
      })
    }
    
    • src/App.vue
    <template>
      <div></div>
    </template>
    
    <script lang="ts">
    import { defineComponent } from 'vue'
    import { login } from '@/api/user'
    
    export default defineComponent({
      setup() {
        login({}).then((res) => {
          console.log(res)
        })
        return {}
      }
    })
    </script>
    
    <style lang="scss"></style>
    
    f、环境变量
    * 模式:
        - 脚本命令:vue-cli-service serve(默认--mode development)
          引用文件:.env.development
          NODE_ENV:process.env.NODE_ENV = "development"
        - 脚本命令:vue-cli-service build(默认--mode production)
          引用文件:.env.production
          NODE_ENV:process.env.NODE_ENV = "production"
        - 脚本命令:vue-cli-service build --mode staging(手动指定)
          引用文件:.env.staging
          NODE_ENV:process.env.NODE_ENV = "staging"
    * NODE_ENV变量无需手动赋值
    * 其他变量应以VUE_APP_开头才会自动注入到代码中
    * .env优先级小于.env.development或.env.production等
    
    g、tsconfig.json
    {
      "compilerOptions": {
        // 目标代码:babel编译(esnext),tsc编译(es5)
        "target": "esnext",
        // 目标代码模块化方案:es模块化方案(esnext),模块化方案混合使用(umd)
        "module": "esnext",
        // ts严格模式
        "strict": true,
        // jsx处理:不转化处理(preserve)
        "jsx": "preserve",
        // 导入功能辅助
        "importHelpers": true,
        // 解析模块方式:后缀名顺序查找文件(node)
        "moduleResolution": "node",
        // 跳过第三方库的类型检测
        "skipLibCheck": true,
        // 源代码模块化方案:模块化方案混合使用
        "esModuleInterop": true,
        "allowSyntheticDefaultImports": true,
        // 是否生成映射文件
        "sourceMap": true,
        // 文件解析路径
        "baseUrl": ".",
        // 解析类型
        "types": [
          "webpack-env"
        ],
        // 路径别名(与webpack配置对应)
        "paths": {
          "@/*": [
            "src/*"
          ]
        },
        // 基础类型库
        "lib": [
          "esnext",
          "dom",
          "dom.iterable",
          "scripthost"
        ]
      },
      "include": [
        "src/**/*.ts",
        "src/**/*.tsx",
        "src/**/*.vue",
        "tests/**/*.ts",
        "tests/**/*.tsx"
      ],
      "exclude": [
        "node_modules"
      ]
    }
    
    h、shims-vue.d.ts
    /* eslint-disable */
    /**
     * 1、声明.vue文件:.vue文件导出的component对象的类型是DefineComponent<{}, {}, any>
     * 2、.vue文件defineComponent函数:
     *     - 源码:function defineComponent(options) {return options}
     *     - 作用:可以使options具有类型限制
     */
    declare module '*.vue' {
      import type { DefineComponent } from 'vue'
      const component: DefineComponent<{}, {}, any>
      export default component
    }
    

    二十二、项目实战细节

    1、获取组件实例的类型
    • 父组件
    <template>
      <div>
        <son-component ref="sonComponentRef"></son-component>
        <el-button @click="clickHandler">减1按钮</el-button>
      </div>
    </template>
    
    <script lang="ts">
    import { defineComponent, ref } from 'vue'
    import SonComponent from './son-component.vue'
    
    export default defineComponent({
      components: { SonComponent },
      setup() {
        const sonComponentRef = ref()
        type SonComponentType = InstanceType<typeof SonComponent>
    
        const clickHandler = () => {
          /**
           * 1、函数签名以new关键字为前缀:
           *     - var BankAccount: new() => BankAccount;
           *       则BankAccount函数只能以new BankAccount()方式调用
           * 2、InstanceType获取构造函数的实例类型:
           *     - InstanceType<typeof SonComponent>;
           *       获取子组件实例的类型
           */
          const sonComponentInstance: SonComponentType = sonComponentRef.value
          // 3、组件实例中的属性并不是ref对象,而是proxy对象,所以不需要.value
          sonComponentInstance.person.age--
        }
    
        return {
          sonComponentRef,
          clickHandler
        }
      }
    })
    </script>
    
    • 子组件
    <template>
      <div>{{ person }}</div>
      <el-button @click="clickHandler">加1按钮</el-button>
    </template>
    
    <script lang="ts">
    import { defineComponent, ref } from 'vue'
    
    export default defineComponent({
      setup() {
        const person = ref({
          name: '黄婷婷',
          age: 18
        })
    
        const clickHandler = () => {
          person.value.age++
        }
    
        return {
          person,
          clickHandler
        }
      }
    })
    </script>
    
    2、确定props的类型
    <template>
      <div></div>
    </template>
    
    <script lang="ts">
    import { defineComponent, PropType } from 'vue'
    
    interface IPerson {
      username: string
      age: number
    }
    
    export default defineComponent({
      props: {
        persons: {
          type: Array as PropType<IPerson[]>,
          required: true
        }
      },
      setup(props) {
        props.persons.forEach((item) => {
          console.log(item.username)
        })
        return {}
      }
    })
    </script>
    
    <style lang="scss" scoped></style>
    
    3、setup顶层编写方式中的props用法
    <template>
      <div ref="echartsDivRef" :style="{  width, height: height }"></div>
    </template>
    
    <script lang="ts" setup>
    import * as echarts from 'echarts'
    import { EChartsOption } from 'echarts'
    import { ref, onMounted, withDefaults, defineProps } from 'vue'
    
    const props = withDefaults(
      defineProps<{
        options: EChartsOption // required: true
        width?: string
        height?: string
      }>(),
      {
         '100%', // defaults: '100%'
        height: '100%'
      }
    )
    
    const echartsDivRef = ref<HTMLDivElement>()
    
    onMounted(() => {
      const echartsInstance = echarts.init(echartsDivRef.value!, 'light', {
        renderer: 'svg'
      })
      echartsInstance.setOption(props.options)
    })
    </script>
    
    4、浏览器的重绘和重排(回流)
    * 重绘和回流:
        - 重绘:比如颜色的改变,不会影响后续节点
        - 回流:比如布局的改变,会影响后续节点,影响性能的关键因素
    * css优化:
        - transform替代position
        - 避免使用table布局
        - visibility: hidden替代display: none
        - 样式尽可能设置在DOM树的末端,限制回流的范围,减少影响的节点
        - 避免使用包含选择器(div > span)和css表达式(calc())
    * js优化:
        - 避免频繁操作DOM(虚拟节点)
        - 避免频繁读取会引发回流的属性(offset)
    
    5、echarts使用canvas或者svg渲染
    * canvas:大数据量的图表、视觉特效
    * svg(推荐):内存占用低、浏览器缩放不会模糊(矢量图)
    
    6、setup顶层编写方式中子组件暴露属性和方法的用法
    <template>
      <div></div>
    </template>
    
    <script lang="ts" setup>
    import { defineExpose, ref } from 'vue'
    
    const person = ref({ name: '黄婷婷', age: 18 })
    const clickHnadler = () => {
      console.log('黄婷婷')
    }
    
    // 父组件ref方式调用子组件方法和获取子组件属性
    defineExpose({
      person,
      clickHnadler
    })
    </script>
    

    二十三、项目打包和自动化部署

    1、centos7安装dnf
    yum install epel-release -y
    yum install dnf -y
    # 查看是否安装成功
    dnf
    
    2、dnf方式安装java
    dnf search java-1.8
    dnf install java-1.8.0-openjdk.x86_64 -y
    # 查看是否安装成功
    java
    
    3、安装jenkins
    • dnf方式安装jenkins(失败)
    cd /etc/yum.repos.d/
    wget http://pkg.jenkins-ci.org/redhat-stable/jenkins.repo
    rpm --import http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key
    vi jenkins.repo
    # baseurl=http://pkg.jenkins.io/redhat,保存退出
    dnf install jenkins -y
    
    • rpm方式安装jenkins
    # https://mirrors.tuna.tsinghua.edu.cn/jenkins/redhat/:清华大学开源软件镜像站,下载jenkins软件包
    dnf install daemonize -y
    rpm -ivh jenkins-2.332-1.1.noarch.rpm
    
    • 启动服务
    # 启动服务(默认端口号8080)
    systemctl start jenkins
    # 查看状态
    systemctl status jenkins
    # 开机自启
    systemctl enable jenkins
    
    4、dnf方式安装nginx
    • 安装
    dnf install nginx -y
    
    • 启动服务
    # 启动服务(默认端口80)
    # index页面目录:/usr/share/nginx/html/
    # nginx配置文件:vi /etc/nginx/nginx.conf
    systemctl start nginx
    # 查看状态
    systemctl status nginx
    # 开机自启
    systemctl enable nginx
    
    5、dnf方式安装git
    dnf install git -y
    
    6、jenkins配置与使用
    • jenkins初始化
    * 按提示输入密码
    * 安装推荐的插件
    
    • jenkins安装nodejs插件
    * 系统管理 -> 
      插件管理 -> 
      可选插件 -> 
      搜索nodejs并选择 ->
      download now and install after restart ->
      安装完成后重启jenkins(空闲时)
    * 系统管理 -> 
      全局工具配置 -> 
      nodejs安装 -> 
      设置别名选择版本(自动安装应是选中状态) ->
      保存
    
    • jenkins配置用户
    # 默认用户是jenkins,访问一些文件会有权限问题
    vi /etc/sysconfig/jenkins
    # 设置JENKINS_USER="root"
    # 重启jenkins
    systemctl restart jenkins
    
    • jenkins任务
    * 新建任务 ->
      输入任务名 ->
      构建一个自由风格的软件项目 ->
      确定
    * 描述
    * 源码管理 ->
      git ->
      仓库URL ->
      添加凭证 ->
      指定分支
    * 构建触发器(手动则跳过此步) ->
      定时构建
    * 构建环境 ->
      Provide Node & npm bin/ folder to PATH
    * 构建 ->
      执行shell
    * 保存  
    
    • shell
    # shell
    pwd
    node -v
    npm -v
    npm install 
    npm run build
    pwd
    echo '构建成功'
    ls
    rm -rf /usr/share/nginx/html/* 
    cp -rf ./dist/* /usr/share/nginx/html/
    
    • 定时构建的触发器规则
    #每半小时构建一次OR每半小时检查一次远程代码分支,有更新则构建
    H/30 * * * *
    #每两小时构建一次OR每两小时检查一次远程代码分支,有更新则构建
    H H/2 * * *
    #每天凌晨两点定时构建
    H 2 * * *
    #每月15号执行构建
    H H 15 * *
    #工作日,上午9点整执行
    H 9 * * 1-5
    #每周1,3,5,从8:30开始,截止19:30,每4小时30分构建一次
    H/30 8-20/4 * * 1,3,5
    
  • 相关阅读:
    Java设计模式--命令模式
    linux 挂载windows盘
    C# 对含有向量偏移的明文进行AES加解密
    Vue修仙之旅之Vue初尝
    Cookie的Secure属性
    Webserver信息泄露的解决方案--使用StripHeaders模块删除不必要的header
    window自定义事件
    vue typescript .eslintrc.js
    css word-break: break-word;无效
    vscode vue 片段
  • 原文地址:https://www.cnblogs.com/linding/p/16286797.html
Copyright © 2020-2023  润新知