Создание большого проекта TypeScript+Vue + подробное объяснение

Vue.js
Создание большого проекта TypeScript+Vue + подробное объяснение

написать впереди

Перед запуском игры сначала вставьте URL-адрес документа.

Руководство на китайском машинописном языке

vue-property-decorator

vuex-class

vue-class-component

Давайте сначала познакомимся со следующими гаджетами

vue-property-decorator

Здесь одностраничный компонент написан с использованиемvue-property-decoratorбиблиотека, которая полностью зависит отvue-class-component,СлишкомvueОфициально рекомендованная библиотека.

В одном компоненте страницы, в@Component({})написать внутриprops,dataТакие, как звонить крайне неудобно, иvue-property-decoratorОн содержит 8 декораторов для решения таких задач, это:

  • @EmitУкажите выброс события, вы можете использовать этот модификатор, или вы можете использовать его напрямуюthis.$emit()
  • @InjectУкажите внедрение зависимостей
  • @Mixinsинъекция миксина
  • @Modelуказать модель
  • @PropУкажите опору
  • @ProvideУкажите предостав
  • @WatchУказать часы
  • @Component export from vue-class-component

дать 🌰

import {
  Component, Prop, Watch, Vue
} from 'vue-property-decorator'

@Component
export class MyComponent extends Vue {
  dataA: string = 'test'
  count = 0
  
  @Prop({ default: 0 }) private propA!: number
  @Prop({ default: () => [10, 20, 30, 50] }) private propB!: number[]
  @Prop({ default: 'total, sizes, prev, pager, next, jumper' }) private propC!: string
  @Prop({ default: true }) private propD!: boolean,
  @prop([String, Boolean]) propE: string | boolean;
  
  @Emit('reset')
  resetCount() {
    this.count = 0
  }
  @Emit()
  returnValue() {
    return 10
  }
  @Emit()
  onInputChange(e) {
    return e.target.value
  }
  
  // watcher
  @Watch('child')
  onChildChanged (val: string, oldVal: string) {}
  @Watch('person', { immediate: true, deep: true })
  onPersonChanged (val: Person, oldVal: Person) {}

  // 其他修饰符详情见上面的 github 地址,这里就不一一做说明了
}

После разбора становится

export default {
  data () {
    return {
      dataA: 'test'
    }
  },
  props: {
    propA: {
	  type: Number
    },
    propB: {
      type: Array,
      default: [10, 20, 30, 50]
    },
    propC: {
      type: String,
      default: 'total, sizes, prev, pager, next, jumper'
    },
    propD: {
      type: String,
      default: 'total, sizes, prev, pager, next, jumper'
    },
    propE: {
      type: [String, Boolean]
  },
    
  watch: {
    'child': {
      // handler:其值是一个回调函数。即监听到变化时应该执行的函数。
      handler: 'onChildChanged',
      // immediate:其值是true或false;immediate:true代表如果在 wacth
      // 里声明了之后,就会立即先去执行里面的handler方法,如果为
      // false就跟我们以前的效果一样,不会在绑定的时候就执行
      immediate: false,
      // deep:其值是true或false;确认是否深入监听。deep的意思就是深入观
      // 察,监听器会一层层的往下遍历,给对象的所有属性都加上这个监听器(
      // 受现代 JavaScript 的限制 (以及废弃 Object.observe),Vue 
      // 不能检测到对象属性的添加或删除)
      deep: false
    },
    'person': {
      handler: 'onPersonChanged',
      immediate: true,
      deep: true
    }
  },
  methods: {
     resetCount() {
      this.count = 0
      this.$emit('reset')
    },
    
    returnValue() {
      this.$emit('return-value', 10)
    },
    
    onInputChange(e) {
      this.$emit('on-input-change', e.target.value, e)
    }
 
    onChildChanged (val, oldVal) {},
    onPersonChanged (val, oldVal) {}
  }
}

Вот два часто используемых модификатора!``?, !и необязательные аргументы?относительно,!означает принудительный синтаксический анализ (то есть сказатьtypescriptКомпилятор, у меня должно быть значение здесь), вызовите его, когда будете писать?typescriptможет указывать на то, чтоundefined

@Emit

@EmitФункция декоратора вызовет событие, эквивалентное названию ее функции (верблюжий регистр будет преобразован в горизонтальную полосу) после запуска, и ее функция будет передана в$emit

  • @Emit()Если никакие параметры не передаются, то имя события, которое оно запускает, является именем функции, которую оно изменяет.
  • @Emit(name: string), которому передается строка, строка является именем запускаемого события

@Watch

watchЭто объект, а у объекта есть ключи и значения.

  • Первыйhandler: значение которого является функцией обратного вызова. То есть функция, которая должна выполняться при обнаружении изменения.
  • Второйdeep: чье значениеtrueилиfalse; Подтвердите, нужно ли слушать глубоко.deepСмысл в том, чтобы наблюдать глубоко, слушатель будет проходить слой за слоем вниз, и добавлять этот слушатель ко всем свойствам объекта (с учетом современныхJavaScriptограничения (и устаревшиеObject.observe),VueДобавление или удаление свойств объекта не может быть обнаружено)
  • Третийimmediate: чье значениеtrueилиfalse;immediate:trueпредставляет, если вwacthПосле того, как он будет объявлен в, он немедленно выполнит метод обработчика внутри, если онfalseКак и наш предыдущий эффект, он не будет выполняться при привязке

@WatchЕго очень просто использовать, принимая первый параметр в качестве имени отслеживаемого свойства, а второе свойство — в качестве необязательного объекта.@WatchДекорированная функция — это функция, которая должна выполняться после прослушивания изменения свойства.@WatchИмя функции украшенной функции отличается от указанного выше.onStateChangedСтрого названный, он разнообразен, вы можете назвать его как хотите, конечно, возможность следовать стандартизированному именованию сделает ваш код более читабельным.

@Minxins

// myMixin.ts
import { Vue, Component } from 'vue-property-decorator';
declare module 'vue/types/vue' {
    interface Vue {
        mixinValue: string;
    }
}
@Component
export default class myMixins extends Vue {
    mixinValue: string = 'Hello World!!!'
}

Цитировать

import { Vue, Component, Prop } from 'vue-property-decorator';
import MyMixin from './myMixin.js'

@Component({
    mixins: [MyMixin]
})
export default class extends Vue{
    created(){
        console.log(mixinValue) // => Hello World!!!
    }
}

mixinДругой способ написания появится ниже.

@Model

@Model装饰器Позволяет настроить v-модель на компоненте, получая два параметра:

  • событие: строковое имя события.
  • options: Constructor | Constructor[] | PropOptions совпадает с первым параметром @Prop.
import { Vue, Component, Model } from 'vue-property-decorator'

@Component
export default class MyInput extends Vue {
  @Model('change', { type: String, default: 'Hello world!!!' }) readonly value!: string
}

Эквивалентно

<template>
  <input
    type="text"
    :value="value"
    @change="$emit('change', $event.target.value)"
  />
</template>

export default {
  model: {
    prop: 'value',
    event: 'change'
  },
  props: {
    value: {
      type: String,
      default: 'Hello world!!!'
    }
  }
}

@Provide @Inject

@Provideобъявить значение и использовать его в другом месте@InjectReceive, который мало используется в реальных проектах, обычно используется для того, чтобы не зависеть от какой-либо сторонней библиотеки управления состоянием (например,vuexНаписание компонента

@Ref(refKey?: string)

@RefДекоратор принимает необязательный параметр, который является ссылкой на элемент или подкомпонент. Если этот параметр не указан, в качестве параметра будет использоваться имя свойства после декоратора.

import { Vue, Component, Ref } from 'vue-property-decorator'
import { Form } from 'element-ui'

@Componentexport default class MyComponent extends Vue {
  @Ref() readonly loginForm!: Form
  @Ref('changePasswordForm') readonly passwordForm!: Form

  public handleLogin() {
    this.loginForm.validate(valide => {
      if (valide) {
        // login...
      } else {
        // error tips
      }
    })
  }
}

Эквивалентно

export default {
  computed: {
    loginForm: {
      cache: false,
      get() {
        return this.$refs.loginForm
      }
    },
    passwordForm: {
      cache: false,
      get() {
        return this.$refs.changePasswordForm
      }
    }
  }
}

среда сборки

Создать проект

? Please pick a preset:(使用上下箭头)
 ◯ default (babel, eslint)        //默认配置
❯◉ Manually select features       //手动选择
? Check the features needed for your project:
 ◉ Babel                                    // javascript转译器
 ◉ TypeScript                               // 使用 TypeScript 书写源码
 ◯ Progressive Web App (PWA) Support        // 渐进式WEB应用
 ◉ Router                                   // 使用vue-router
 ◉ Vuex                                     // 使用vuex
 ◉ CSS Pre-processors                       // 使用css预处理器
❯◉ Linter / Formatter                       // 代码规范标准
 ◯ Unit Testing                             // 单元测试
 ◯ E2E Testing                              // e2e测试

использовать или нетclassСинтаксис стилизованного компонента: Перед использованием:home = new Vue()Создайте экземпляр VUE и используйте его:class home extends Vue{}

? Use class-style component syntax? (Y/n) Y

// 使用Babel与TypeScript一起用于自动检测的填充
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? (Y/n) Y

// 路由
? Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n) Y

// 预处理器
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): (Use arrow keys)
❯◉ Sass/SCSS (with dart-sass)    // 保存后编译
 ◯ Sass/SCSS (with node-sass)    // 实时编译 
 ◯ Less
 ◯ Stylus

// 代码格式化检测
? Pick a linter / formatter config: (Use arrow keys)
 ◯ ESLint with error prevention only     // 只进行报错提醒
 ◯ ESLint + Airbnb config                // 不严谨模式
 ◯ ESLint + Standard config              // 正常模式
 ◯ ESLint + Prettier                     // 严格模式
❯◉ TSLint(deprecated)                    // typescript格式验证工具

// 代码检查方式
? Pick additional lint features: (Press <space> to select, <a>
to toggle all, <i> to invert selection)
❯◉ Lint on save             // 保存检查
 ◯ Lint and fix on commit   // commit时fix

// 文件配置
? Where do you prefer placing config for Babel, ESLint, etc.? (
Use arrow keys)
  In dedicated config files // 配置在独立的文件中
❯ In package.json
  
// 保存上述配置,保存后下一次可直接根据上述配置生成项目
? Save this as a preset for future projects? (y/N) N

// 创建成功
🎉  Successfully created project vue-typescript-admin-demo.

yarn run serveПосле запуска проекта будет выдаваться куча необъяснимых ошибок, которые всеtslint.jsonЧерт возьми, просто настройте его и запустите снова

// tsconfig.json
Error: Calls to 'console.log' are not allowed.

Error: 去除行尾必加';'

Error: 禁止自动检测末尾行必须使用逗号,always总是检测,never从不检测,ignore忽略检测

"rules": {
    "no-console": false,
    "semicolon": [
        false,
        "always"
    ],
    "trailing-comma": [true, {
        "singleline": "never",
        "multiline": {
            "objects": "ignore",
            "arrays": "ignore",
            "functions": "never",
            "typeLiterals": "ignore"
        }
    }]
}

Пока весь проект работает нормально. Но... Это по-прежнему традиционный проект Vue. Мы хотим разработать реальный проект Vue+ts, поэтому его необходимо преобразовать. Подробная структура каталогов будет приложена после преобразования.

Структура каталогов

Это обновленная структура каталогов

├── public                          // 静态页面
├── scripts                         // 相关脚本配置
├── src                             // 主目录
    ├── assets                      // 静态资源
    ├── api                         // axios封装
    ├── filters                     // 过滤
    ├── lib                         // 全局插件
    ├── router                      // 路由配置
    ├── store                       // vuex 配置
    ├── styles                      // 样式
    ├── types                       // 全局注入
    ├── utils                       // 工具方法(全局方法等)
    ├── views                       // 页面
    ├── App.vue                     // 页面主入口
    ├── main.ts                     // 脚本主入口
    ├── registerServiceWorker.ts    // PWA 配置
├── tests                           // 测试用例
├── .editorconfig                   // 编辑相关配置
├── .npmrc                          // npm 源配置
├── .postcssrc.js                   // postcss 配置
├── babel.config.js                 // preset 记录
├── cypress.json                    // e2e plugins
├── f2eci.json                      // 部署相关配置
├── package.json                    // 依赖
├── README.md                       // 项目 readme
├── tsconfig.json                   // ts 配置
├── tslint.json                     // tslint 配置
└── vue.config.js                   // webpack 配置

В основном участвуетshims-tsx.d.tsа такжеshims-vue.d.tsдва файла

  • shims-tsx.d.ts, что позволяет использовать.tsxконец файла, вVueнаписано в проектеjsxкод
  • shims-vue.d.tsВ основном используетсяTypeScriptИдентифицировать.vueфайл, ts по умолчанию не поддерживает импорт.vueфайл, этот файл указывает ts импортировать.vueфайловый прессVueConstructor<Vue>иметь дело с.

существуетtslintДобавьте следующую конфигурацию

// tslint.json
// 不检测隐式类型
{
  "defaultSeverity": "none", // 值为warn时为警告
	"rules": {
		...
  }
  "arrow-parens": [
	false,
	"as-needed"
  ]
}

Конфигурация другого контента (необязательно)

// tslint.json
{
  "defaultSeverity": "warning",
  "extends": [
    "tslint:recommended"
  ],
  "linterOptions": {
    "exclude": [
      "node_modules/**"
    ]
  },
  "rules": {
    "quotemark": false, // 字符串文字需要单引号或双引号。
    "indent": false, // 使用制表符或空格强制缩进。
    "member-access": false, // 需要类成员的显式可见性声明。
    "interface-name": false, // 接口名要求大写开头
    "ordered-imports": false, // 要求将import语句按字母顺序排列并进行分组。
    "object-literal-sort-keys": false, // 检查对象文字中键的排序。
    "no-consecutive-blank-lines": false, // 不允许连续出现一个或多个空行。
    "no-shadowed-variable": false, // 不允许隐藏变量声明。
    "no-trailing-whitespace": false, // 不允许在行尾添加尾随空格。
    "semicolon": false, // 是否分号结尾
    "trailing-comma": false, // 是否强象添加逗号
    "eofline": false, // 是否末尾另起一行
    "prefer-conditional-expression": false, // for (... in ...)语句必须用if语句过滤
    "curly": true, //for if do while 要有括号
    "forin": false, //用for in 必须用if进行过滤
    "import-blacklist": true, //允许使用import require导入具体的模块
    "no-arg": true, //不允许使用 argument.callee
    "no-bitwise": true, //不允许使用按位运算符
    "no-console": false, //不能使用console
    "no-construct": true, //不允许使用 String/Number/Boolean的构造函数
    "no-debugger": true, //不允许使用debugger
    "no-duplicate-super": true, //构造函数两次用super会发出警告
    "no-empty": true, //不允许空的块
    "no-eval": true, //不允许使用eval
    "no-floating-promises": false, //必须正确处理promise的返回函数
    "no-for-in-array": false, //不允许使用for in 遍历数组
    "no-implicit-dependencies": false, //不允许在项目的package.json中导入未列为依赖项的模块
    "no-inferred-empty-object-type": false, //不允许在函数和构造函数中使用{}的类型推断
    "no-invalid-template-strings": true, //警告在非模板字符中使用${
    "no-invalid-this": true, //不允许在非class中使用 this关键字
    "no-misused-new": true, //禁止定义构造函数或new class
    "no-null-keyword": false, //不允许使用null关键字
    "no-object-literal-type-assertion": false, //禁止object出现在类型断言表达式中
    "no-return-await": true, //不允许return await
    "arrow-parens": false, //箭头函数定义的参数需要括号
    "adjacent-overload-signatures": false, //  Enforces function overloads to be consecutive.
    "ban-comma-operator": true, //禁止逗号运算符。
    "no-any": false, //不需使用any类型
    "no-empty-interface": true, //禁止空接口 {}
    "no-internal-module": true, //不允许内部模块
    "no-magic-numbers": false, //不允许在变量赋值之外使用常量数值。当没有指定允许值列表时,默认允许-1,0和1
    "no-namespace": [true, "allpw-declarations"], //不允许使用内部modules和命名空间
    "no-non-null-assertion": true, //不允许使用!后缀操作符的非空断言。
    "no-parameter-reassignment": true, //不允许重新分配参数
    "no-reference": true, // 禁止使用/// <reference path=> 导入 ,使用import代替
    "no-unnecessary-type-assertion": false, //如果类型断言没有改变表达式的类型就发出警告
    "no-var-requires": false, //不允许使用var module = require("module"),用 import foo = require('foo')导入
    "prefer-for-of": true, //建议使用for(..of)
    "promise-function-async": false, //要求异步函数返回promise
    "max-classes-per-file": [true, 2], // 一个脚本最多几个申明类
    "variable-name": false,
    "prefer-const": false // 提示可以用const的地方
  }
}

Мир внезапно становится чистым~~~ Друзья, у которых есть острая потребность, могут открыть его сами, предпосылка в том, что они должны быть хорошо настроеныtslintправила, иначе это все равно немного больно, в конце концовwarnВыглядеть более неудобно. прощание

./src/config/index.ts

/** 
 * 线上环境
 */
export const ONLINEHOST: string = 'https://xxx.com'

/** 
 * 测试环境
 */
export const QAHOST: string = 'http://xxx.com'

/** 
 * 线上mock
 */
export const MOCKHOST: string = 'http://xxx.com'

/** 
 * 是否mock
 */
export const ISMOCK: boolean = true

/**
 * 当前的host  ONLINEHOST | QAHOST | MOCKHOST
 */
export const MAINHOST: string = ONLINEHOST

/**
 * 请求的公共参数
 */
export const conmomPrams: any = {}

/**
 * @description token在Cookie中存储的天数,默认1天
 */
export const cookieExpires: number = 1

./src/utils/common.ts

// 下载js-cookie
cnpm i js-cookie --S
cnpm install @types/js-cookie --D

import Cookies from 'js-cookie'
import { cookieExpires } from '@/config' // cookie保存的天数

/**
 * @Author: asheng
 * @msg: 存取token
 * @param {string} token
 */
export const TOKEN_KEY: string = 'token'
export const setToken = (token: string) => {
  Cookies.set(TOKEN_KEY, token, { expires: cookieExpires || 1 })
}
export const getToken = () => {
  const token = Cookies.get(TOKEN_KEY)
  if (token) {
    return token
  } else {
    return false
  }
}

/**
 * @param {String} url
 * @description 从URL中解析参数
 */
export const getParams = (url: string) => {
  const keyValueArr = url.split('?')[1].split('&')
  let paramObj: any = {}
  keyValueArr.forEach(item => {
    const keyValue = item.split('=')
    paramObj[keyValue[0]] = keyValue[1]
  })
  return paramObj
}

/**
 * 判断一个对象是否存在key,如果传入第二个参数key,则是判断这个obj对象是否存在key这个属性
 * 如果没有传入key这个参数,则判断obj对象是否有键值对
 */
export const hasKey = (obj: any, key: string | number) => {
  if (key) {
    return key in obj
  } else {
    const keysArr = Object.keys(obj)
    return keysArr.length
  }
}

/**
 * @msg: 获取系统当前时间
 * @param {string} fmt 时间格式 具体看代码
 * @return: string
 */
export const getDate = (fmt: any) => {
  let time = ''
  const date = new Date()
  const o: any = {
    "M+": date.getMonth() + 1, // 月份 
    "d+": date.getDate(), // 日 
    "H+": date.getHours(), // 小时 
    "m+": date.getMinutes(), // 分 
    "s+": date.getSeconds(), // 秒 
    "q+": Math.floor((date.getMonth() + 3) / 3), // 季度 
    "S": date.getMilliseconds() // 毫秒 
  }
  if (/(y+)/.test(fmt)) {
    time = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length))
  }
  for (const k in o) {
    if (new RegExp("(" + k + ")").test(fmt)) {
      time = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)))
    }
  }
  return time
}

/**
 * @msg: 获取系统当前时间
 * @param {string} date 时间
 * @param {string} fmt 时间格式
 * @return: string
 */
export const formatDate = (date: any, fmt: string) => {
  let time = ''
  const o: any = {
    "M+": date.getMonth() + 1, // 月份 
    "d+": date.getDate(), // 日 
    "H+": date.getHours(), // 小时 
    "m+": date.getMinutes(), // 分 
    "s+": date.getSeconds(), // 秒 
    "q+": Math.floor((date.getMonth() + 3) / 3), // 季度 
    "S": date.getMilliseconds() // 毫秒 
  }
  if (/(y+)/.test(fmt)) {
    time = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length))
  }
  for (const k in o) {
    if (new RegExp("(" + k + ")").test(fmt)) {
      time = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)))
    }
  }
  return time
}

// copy in the 'fx-fuli' utils
/**
 * 校验手机号是否正确
 * @param phone 手机号
 */

export const verifyPhone = (phone: string | number) => {
  const reg = /^1[34578][0-9]{9}$/
  const _phone = phone.toString().trim()
  let toastStr = _phone === '' ? '手机号不能为空~' : !reg.test(_phone) && '请输入正确手机号~'
  return {
    errMsg: toastStr,
    done: !toastStr,
    value: _phone
  }
}

export const verifyStr = (str: string | number, text: string) => {
  const _str = str.toString().trim()
  const toastStr = _str.length ? false : `请填写${text}~`
  return {
    errMsg: toastStr,
    done: !toastStr,
    value: _str
  }
}

// 截取字符串
export const sliceStr = (str: any, sliceLen: number) => {
  if (!str) { return '' }
  let realLength = 0
  const len = str.length
  let charCode = -1
  for (let i = 0; i < len; i++) {
    charCode = str.charCodeAt(i)
    if (charCode >= 0 && charCode <= 128) {
      realLength += 1
    } else {
      realLength += 2
    }
    if (realLength > sliceLen) {
      return `${str.slice(0, i)}...`
    }
  }

  return str
}


/**
 * JSON 克隆
 * @param {Object | Json} jsonObj json对象
 * @return {Object | Json} 新的json对象
 */
export function objClone(jsonObj: any) {
  let buf: any
  if (jsonObj instanceof Array) {
    buf = []
    let i = jsonObj.length
    while (i--) {
      buf[i] = objClone(jsonObj[i])
    }
    return buf
  } else if (jsonObj instanceof Object) {
    buf = {}
    for (let k in jsonObj) {
      buf[k] = objClone(jsonObj[k])
    }
    return buf
  } else {
    return jsonObj
  }
}

1. Используйте Webpack умело

WebpackЭто основа для реализации нашей предварительной разработки проекта, но на самом деле его полезность гораздо больше.WebpackПриходите и помогите нам сделать что-то автоматизированное. Сначала нам нужно понятьrequire.context()этоAPI

require.context()

ты можешь использовать этоrequire.context()Функции создают свои собственные контексты. Он позволяет вам передать каталог для поиска, флаг, указывающий, следует ли искать в подкаталогах, и регулярное выражение для сопоставления файлов.

ФактическиWebpackпутем разбораrequire()вызов для извлечения следующей информации:

Directory: ./template
Regular expression: /^.*\.ejs$/

Затем для создания своего контекста, что это значит, мы можем отфильтровать нужные нам файлы этим методом и прочитать

/**
* @param directory 要搜索的文件夹目录不能是变量,否则在编译阶段无法定位目录
* @param useSubdirectories  是否搜索子目录
* @param regExp 匹配文件的正则表达式
* @return function 返回一个具有 resolve, keys, id 三个属性的方法
          resolve() 它返回请求被解析后得到的模块 id
          keys() 它返回一个数组,由所有符合上下文模块处理的请求组成。 
          id 是上下文模块里面所包含的模块 id. 它可能在你使用 module.hot.accept 的时候被用到
*/
require.context('.', useSubdirectories = false, regExp = /\.js$/)
// (创建了)一个包含了 demo 文件夹(不包含子目录)下面的、所有文件名以 `js` 结尾的、能被 require 请求到的文件的上下文。

Итак, вы думаете, что это абстрактно?Далее, давайте применим эту штучку.

2. Маршрутизация

дляVueМаршрутизация в , с которой все знакомы, похожа на декларативный файл конфигурации, который на самом деле очень лаконичен. Теперь давайте сделаем это более кратким

раздельная маршрутизация

router                           // 路由文件夹
  |__index.ts                    // 路由组织器:用来初始化路由等等
  |__common.ts                   // 通用路由:声明通用路由
  |__modules                     // 业务逻辑模块:所以的业务逻辑模块
        |__index.ts              // 自动化处理文件:自动引入路由的核心文件
        |__home.ts               // 业务模块home:业务模块

modules

modulesВ папке хранятся все наши модули бизнес-логики.Что касается того, как разделить модули бизнес-логики, я считаю, что у каждого, естественно, есть свой набор стандартов. Проходим упомянутый выше require.context()Затем напишите index.js, основную часть автоматизации.

const files: any = require.context('.', false, /\.ts/)

let configRouters: Array<any> = []

files.keys().forEach((key) => {
  if (key === './index.ts') {
    return
  }
  configRouters = configRouters.concat(files(key).default)
})

export default configRouters

common

commonОбработка маршрута В ​​нашем проекте есть много общедоступных маршрутов, которые необходимо обработать, например404Ах,503Ах, подождите, мы все в путиcommon.tsобработано в.

export default [
  {
    path: '/',
    name: 'Login',
    // redirect: '/Login',
    component: Login
  },
  {
    path: '*',
    name: 'Lost',
    component: () => import('@/views/404.vue')
  }
]

Инициализация маршрута Это наш последний шаг, используемый для инициализации маршрутизации нашего проекта.

import Vue from 'vue'
import Router from 'vue-router'
import ConfigRouters from './modules'
import Common from './common'
// 由于是网站开发,这个是进度条,具体开百度了解一下
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { getToken } from '@/utils/common'

Vue.use(Router)

const router = new Router({
  // mode: "history",
  // base: process.env.BASE_URL,
  scrollBehavior() {
    return { x: 0, y: 0 }
  },
  routes: ConfigRouters.concat(Common)
})

// 登陆页面路由 name
const LOGIN_PAGE_NAME = 'Login'

// 跳转之前
router.beforeEach((to, from, next) => {
  NProgress.start()
  const token = getToken()
  if (!token && to.name !== LOGIN_PAGE_NAME) {
    // 未登录且要跳转的页面不是登录页
    next({
      name: LOGIN_PAGE_NAME // 跳转到登录页
    })
  } else if (!token && to.name === LOGIN_PAGE_NAME) {
    // 未登陆且要跳转的页面是登录页
    next() // 跳转
  } else if (token && to.name === LOGIN_PAGE_NAME) {
    // 已登录且要跳转的页面是登录页
    next({
      name: 'Home' // 跳转到 index 页
    })
  } else {
    if (token) {
      next() // 跳转
    } else {
      next({
        name: LOGIN_PAGE_NAME
      })
    }
  }
})

router.afterEach(() => {
  NProgress.done() // finish progress bar
})
export default router

3. В полной мере используйте Nodejs

Оставь этоnodeНе использовать такую ​​хорошую вещь немного напрасно, так что давайте посмотримnodeЧто мы можем сделать, чтобы повысить нашу эффективность.

Есть такой сценарий, каждый раз при создании модуля приходится создавать новыйvueфайл и соответствующийrouterКонфигурация и большая часть новой страницы аналогичны, и вам придется копировать и вставлять другие страницы. Подумайте об этом немногоlow. Так как естьnodeМожем ли мы сделать этот беспорядок записи через узел? Давайте воплотим наши идеи в жизнь.

./scripts/template.js
const fs = require('fs')
const path = require('path')
const basePath = path.resolve(__dirname, '../src')

const dirName = process.argv[2]
const capPirName = dirName.substring(0, 1).toUpperCase() + dirName.substring(1)
if (!dirName) {
    console.log('文件夹名称不能为空!')
    console.log('示例:npm run tep ${capPirName}')
    process.exit(0)
}

/**
 * @msg: vue页面模版
 */
const VueTep = `<template>
  <div class="${dirName}-wrap">
    {{data.pageName}}
  </div>
</template>

<script lang="ts" src="./${dirName}.ts"></script>

<style lang="scss">
  @import './${dirName}.scss'
</style>

`

// ts 模版
const tsTep = `import { Component, Vue } from "vue-property-decorator"
import { Getter, Action } from "vuex-class"
import { ${capPirName}Data } from '@/types/views/${dirName}.interface'
// import {  } from "@/components" // 组件

@Component({})
export default class About extends Vue {
  // Getter
  // @Getter ${dirName}.author
    
  // Action
  // @Action GET_DATA_ASYN

  // data
  data: ${capPirName}Data = {
    pageName: '${dirName}'
  }

  created() {
    //
  }
    
  activated() {
    //
  }

  mounted() {
    //
  }

  // 初始化函数
  init() {
    //
  }
    
}
`

// scss 模版
const scssTep = `@import "@/assets/scss/variables.scss";

.${dirName}-wrap {
  width: 100%;
}
`

// interface 模版
const interfaceTep = `// ${dirName}.Data 参数类型
export interface ${capPirName}Data {
  pageName: string
}

// VUEX ${dirName}.State 参数类型
export interface ${capPirName}State {
  data?: any
}

// GET_DATA_ASYN 接口参数类型
// export interface DataOptions {}

`

// vuex 模版
const vuexTep = `import { ${capPirName}State } from '@/types/views/${dirName}.interface'
import { GetterTree, MutationTree, ActionTree } from 'vuex'
import * as ${capPirName}Api from '@/api/${dirName}'

const state: ${capPirName}State = {
  ${dirName}: {
   author: undefined
  }
}

// 强制使用getter获取state
const getters: GetterTree<${capPirName}State, any> = {
  author: (state: ${capPirName}State) => state.${dirName}.author
}

// 更改state
const mutations: MutationTree<${capPirName}State> = {
  // 更新state都用该方法
  UPDATE_STATE(state: ${capPirName}State, data: ${capPirName}State) {
    for (const key in data) {
      if (!data.hasOwnProperty(key)) { return }
      state[key] = data[key]
    }
  }
}

const actions: ActionTree<${capPirName}State, any> = {
  UPDATE_STATE_ASYN({ commit, state: ${capPirName}State }, data: ${capPirName}State) {
    commit('UPDATE_STATE', data)
  },
  // GET_DATA_ASYN({ commit, state: LoginState }) {
  //   ${capPirName}.getData()
  // }
}

export default {
  state,
  getters,
  mutations,
  actions
}

`

// api 接口模版
const apiTep = `import Api from '@/utils/request'

export const getData = () => {
  return Api.getData()
}

`

fs.mkdirSync(`${basePath}/views/${dirName}`) // mkdir

process.chdir(`${basePath}/views/${dirName}`) // cd views
fs.writeFileSync(`${dirName}.vue`, VueTep) // vue 
fs.writeFileSync(`${dirName}.ts`, tsTep) // ts
fs.writeFileSync(`${dirName}.scss`, scssTep) // scss

process.chdir(`${basePath}/types/views`); // cd types
fs.writeFileSync(`${dirName}.interface.ts`, interfaceTep) // interface

process.chdir(`${basePath}/store/module`); // cd store
fs.writeFileSync(`${dirName}.ts`, vuexTep) // vuex

process.chdir(`${basePath}/api`); // cd api
fs.writeFileSync(`${dirName}.ts`, apiTep) // api

process.exit(0)
./scripts/component.js
const fs = require('fs')
const path = require('path')
const basePath = path.resolve(__dirname, '../src')

const dirName = process.argv[2]
const capPirName = dirName.substring(0, 1).toUpperCase() + dirName.substring(1)
if (!dirName) {
    console.log('文件夹名称不能为空!')
    console.log('示例:npm run tep ${capPirName}')
    process.exit(0)
}

/**
 * @msg: vue页面模版
 */
const VueTep = `<template>
  <div class="${dirName}-wrap">
    {{data.pageName}}
  </div>
</template>

<script lang="ts" src="./${dirName}.ts"></script>

<style lang="scss">
  @import './${dirName}.scss'
</style>

`

// ts 模版
const tsTep = `import { Component, Vue } from "vue-property-decorator"
import { Getter, Action } from "vuex-class"
import { ${capPirName}Data } from '@/types/views/${dirName}.interface'
// import {  } from "@/components" // 组件

@Component({})
export default class About extends Vue {
  // Getter
  // @Getter ${dirName}.author
    
  // Action
  // @Action GET_DATA_ASYN

  // data
  data: ${capPirName}Data = {
    pageName: '${dirName}'
  }

  created() {
    //
  }
    
  activated() {
    //
  }

  mounted() {
    //
  }

  // 初始化函数
  init() {
    //
  }
    
}
`

// scss 模版
const scssTep = `@import "@/assets/scss/variables.scss";

.${dirName}-wrap {
  width: 100%;
}
`

// interface 模版
const interfaceTep = `// ${dirName}.Data 参数类型
export interface ${capPirName}Data {
  pageName: string
}

// VUEX ${dirName}.State 参数类型
export interface ${capPirName}State {
  data?: any
}

// GET_DATA_ASYN 接口参数类型
// export interface DataOptions {}

`

// vuex 模版
const vuexTep = `import { ${capPirName}State } from '@/types/views/${dirName}.interface'
import { GetterTree, MutationTree, ActionTree } from 'vuex'
import * as ${capPirName}Api from '@/api/${dirName}'

const state: ${capPirName}State = {
  ${dirName}: {
   author: undefined
  }
}

// 强制使用getter获取state
const getters: GetterTree<${capPirName}State, any> = {
  author: (state: ${capPirName}State) => state.${dirName}.author
}

// 更改state
const mutations: MutationTree<${capPirName}State> = {
  // 更新state都用该方法
  UPDATE_STATE(state: ${capPirName}State, data: ${capPirName}State) {
    for (const key in data) {
      if (!data.hasOwnProperty(key)) { return }
      state[key] = data[key]
    }
  }
}

const actions: ActionTree<${capPirName}State, any> = {
  UPDATE_STATE_ASYN({ commit, state: ${capPirName}State }, data: ${capPirName}State) {
    commit('UPDATE_STATE', data)
  },
  // GET_DATA_ASYN({ commit, state: LoginState }) {
  //   ${capPirName}.getData()
  // }
}

export default {
  state,
  getters,
  mutations,
  actions
}

`

// api 接口模版
const apiTep = `import Api from '@/utils/request'

export const getData = () => {
  return Api.getData()
}

`

fs.mkdirSync(`${basePath}/views/${dirName}`) // mkdir

process.chdir(`${basePath}/views/${dirName}`) // cd views
fs.writeFileSync(`${dirName}.vue`, VueTep) // vue 
fs.writeFileSync(`${dirName}.ts`, tsTep) // ts
fs.writeFileSync(`${dirName}.scss`, scssTep) // scss

process.chdir(`${basePath}/types/views`); // cd types
fs.writeFileSync(`${dirName}.interface.ts`, interfaceTep) // interface

process.chdir(`${basePath}/store/module`); // cd store
fs.writeFileSync(`${dirName}.ts`, vuexTep) // vuex

process.chdir(`${basePath}/api`); // cd api
fs.writeFileSync(`${dirName}.ts`, apiTep) // api

process.exit(0)

использовать

cnpm run tep index
cnpm run tep login

Мы реализуем эту функцию главным образом посредствомNodeизfsа такжеprocess, если вам интересно, вы можете изучить его подробно.

Сначала мы должны написать нашnodeСкрипт, вот более простая версия. Нечего проверять папки или файлы, просто реализовать нашу идею:

В-четвертых, управление состоянием Vuex

vuex-module-decorators

традиционныйvuexсуществуетvue+tsЭто не работает внутри проекта,vue 2.0пара версийtsСама совместимость не особенно дружелюбна, поэтому для достижения эффекта управления состоянием здесь следует сослаться на дополнительную библиотеку классов.vuex-module-decorators, который основан наvue-class-componentРасширение сделано, он предоставляет ряд декораторов, так что проект в сочетании с vue + ts может выполнять роль управления состоянием.

Сначала посмотрим на структуру каталогов модульного управления, который будет завершен

.
├─ src/        
│   ├─ store/
│   ├─── modules/
│   │ 		├─ app.ts 
│   │ 		├─ user.ts
│   ├─── index.ts   
import Vue from 'vue'
import Vuex from 'vuex'
import { IAppState } from './modules/app'
import { IUserState } from './modules/user'

Vue.use(Vuex)

export interface IRootState {
    app: IAppState
    user: IUserState
}

// Declare empty store first, dynamically register all modules later.
export default new Vuex.Store<IRootState>({})

Эквивалентно

import Vue from 'vue'
import Vuex from 'vuex'
import app from './modules/app'
import user from './modules/user'
Vue.use(Vuex)

const store = new Vuex.Store({
  modules: {
    app,
    user
  }
})

export default store

Таким образом, прототип модульного управления состоянием завершен. Напротив, меняется только грамматический стиль, а остальные изменения невелики. Самое большое изменение в управлении состоянием версии ts отражено в каждой функциональной функции.

Сначала взгляните на исходную конфигурацию vuex, знакомую с дорогой.

export default new Vuex.Store({
    state: {
    },
    mutations: {
    },
    actions: {
    },
    modules: {
    }
});

Для того, чтобы выглядеть менее многословно, перейдите непосредственно к редакцииtsВерсия управления состоянием, вы можете иметь интуитивное сравнение

// user.ts
import { VuexModule, Module, Action, Mutation, getModule } from 'vuex-module-decorators'
import store from '@/store'

export interface IUserState {
    id_token: string
}

@Module({ dynamic: true, store, name: 'user' })
class User extends VuexModule implements IUserState {
    public id_token = ''
    
    @Mutation
    private SET_TOKEN(token: string) {
        this.id_token = token
    }
    
    @Action
    public async Login(params: any) {
        this.SET_TOKEN(`token!!!`)
    }
}

export const UserModule = getModule(User)

Module

определитьmodules, используйте декоратор напрямую@ModuleПримечание. В оригинальном vuex также есть файл с именемModuleкласс, но это не декоратор, так что не путайте его с

@Module({ dynamic: true, store, name: 'user' })

Как видно из вышеизложенного, мы определяемmodulesИспользуется не только декоратор, но и значение параметра, которое указывает на то, что он используется в виде пространства именmodule, как и выше, здесьnamespacedзначениеuser

подробныйvuexОписание пространства имен, вы можете ссылатьсяvuexПространства имен

Кромеnamespaced, мы видим, что есть еще одно значение параметраstore, то есть весьvuexмодульныйstore

import store from '@/store'

Если вы удалите его, браузер сообщит о следующей ошибке

state

Здесь всеstateатрибут, потому что добавилtslintбудет добавленоpublicМодификация, другие варианты использования аналогичны

Getters

оригинальныйgettersфункция вычисления, здесь соответствующая дажеgetметод, то есть

@Module
export default class UserModule extends VuexModule {
  countsNum = 2020
  
  get calculatCount() {
    return countsNum / 2
  }
}

Эквивалентно

export default {
  state: {
    countsNum: 2
  },
  getters: {
    calculatCount: (state) => state.countsNum / 2
  }
}

Mutations

@Mutation
private SET_TOKEN(token: string) {
    this.token = token
}

@Mutation
...

Эквивалентно

mutations: {
    SET_TOKEN: (state, token) => {
        state.token = token
    },
    ...
}

проиллюстрировать: Разница между ними на самом деле синтаксический сахар, примитивныйMutationСинхронные методы определены вmutationsвнутри, покаtsверсия каждогоMutationдобавить декоратор@Mutationретушь

Уведомление: когда-то использовал@MutationПосле декорирования функцииthisКонтекст указывает на текущийstate, так и хочется процитироватьstateзначение, вы можете напрямуюthis.tokenПросто посетите.

MuationФункция не может бытьasyncФункции также не могут быть определены с помощью стрелочных функций, потому что коду необходимо повторно привязать контекст выполнения во время выполнения.

Action

@Action
public async Login(userInfo: { username: string, password: string}) {
    ...
    this.SET_TOKEN(data.accessToken)
}

Эквивалентно

actions: {
    async Login({ commit }, data) {
        ...
        commit('SET_TOKEN', data.accessToken)
    }
}

проиллюстрировать: асинхронная функцияActionи функция синхронизацииMutationМетоды использования аналогичны, разница в том, что один синхронный, а другой асинхронный, если проводится различие

Уведомление:

  • Если нужно вactionДля задач/функций, выполнение которых в функции занимает много времени, рекомендуется определить задачу как асинхронную функцию *(асинхронные методы)*
  • Никогда не используйте функцию стрелки => для определения функции действия, потому что во время выполнения требуется динамическое связывание.thisконтекст

vuex+tsКонфигурация версии успешно построена.Далее применим ее к проекту.Вот модуль страницы входа для ознакомления.

import {
  VuexModule,
  Module,
  Action,
  Mutation,
  getModule
} from 'vuex-module-decorators'
import { login } from '@/api/users' //调用api方法
import store from '@/store'

//声明user模块的state变量类型
//export interface 只是对一个东西的声明(不能具体的操作)
//export class 导出一个类 类里面可有参数 ,函数,方法(干一些具体的事情)
export interface IUserState {
  id_token: string
}

@Module({ dynamic: true, store, name: 'user' })
class User extends VuexModule implements IUserState {
  public id_token = ''

  @Mutation
  private SET_TOKEN(token: string) {
    //同步存储id_token变量
    this.id_token = token
  }

  @Action
  public async Login(params: any) {
    let { mobilePhone, password } = params
    const { data } = await login({ mobilePhone, password })
    this.SET_TOKEN(`Bearer ${data.id_token}`)
  }
}

export const UserModule = getModule(User)

существуетloginпозвони на странице

import { UserModule } from '@/store/modules/user'

await UserModule.Login({
  ...this.loginForm,
  router: this.$router
})

Передача объекта маршрутизации в качестве параметра предназначена для вынесения суждений на основе различных состояний ответа.Когда запрос выполнен успешно, вы можете напрямую использовать переданные параметры объекта маршрутизации для перехода на страницу.

router.push('/')

Уведомление: Этот шаг фактически вызываетvuexизActionоперация, т.е. оригиналthis.$store.commit('action'),Но когдаvuex+tsВ проекте вызовите асинхронную функциюAction, не нужно использоватьthis.$store.commit('action')Этот метод после обращения к модулю напрямую вызывает внутреннююActionМетод просто отличный, такой же, синхронныйMutationЕго еще так называют. Все благодаряvuex-module-decoratorsИнкапсуляция библиотеки классов Хорошо, позвониActionзадние грубые волосыMutationСинхронная работа, экономияtokenтокен, потому что все запросы после входа в систему будутtokenзначение вheader头запрос в КромеvuexУправление состоянием, мы также можем комбинировать классы инструментов в проектеjs-cookieИспользуется вместе для управления значениями различных переменных, конкретное использование ничем не отличается от исходной версии, самое главное, что в процессе установки библиотеки классов вам необходимо установить скомпилированную версию для разработки.

yarn add js-cookie // dependencies yarn add @types/js-cookie --dev // devDependencies(必装)

будь осторожен

используется здесьvuex-moduleа такжеvuex-classЕсть еще много различий.В следующем содержании использованиеvuex-class.

У некоторых людей могут возникнуть вопросы, зачем вводитьvuex-module, при использованииvuex-class. . . Когда проект был первоначально построен, он использовалvuex-class, недавно узналvuex-module, Запиши это.

специфическийvuex-classИспользование статьи, документ размещен в начале статьи, вы также можете обратиться к методу написания следующего гнезда.

vuex-class

vuex-classосновывается наVue,Vuex,vue-class-componentбиблиотека иvue-property-decoratorНапример, он также предоставляет 4 модификатора иnamespace, решеноvuexсуществует.vueНеудобство использования файла.

  • @State
  • @Getter
  • @Mutation
  • @Action
  • namespace

Здесь я больше не буду объяснятьState,Getter...

Давайте сначала посмотрим на структуру каталогов модульного управления, которое будет завершено.

.
├─ src/        
│   ├─ store/
│   ├─── modules/
│   │ 	├─ user.ts
│   │ 	├─ index.ts
│   ├─── index.ts   
// ./store/modules/user.ts
import { GetterTree, MutationTree, ActionTree } from 'vuex'
interface LoginState {
  [key: string]: any
}

const state: LoginState = {
  user_id: '1', // 用户id
  authority: 1, // 开户权限
  token: ''
}

// 强制使用getter获取state
const getters: GetterTree<LoginState, any> = {
  getUserId: (state: LoginState) => state.user_id,
  getToken: (state: LoginState) => state.token
}

// 更改state
const mutations: MutationTree<LoginState> = {
  // 更新state都用该方法
  UPDATE_STATE(state: LoginState, data: LoginState) {
    Object.keys(data).forEach((item) => {
      state[item] = data[item]
    })
  }
}

const actions: ActionTree<LoginState, any> = {
  UPDATE_STATE_ASYN({ commit, state: LoginState }, data: LoginState) {
    commit('UPDATE_STATE', data)
  }
}

export default {
  namespaced: true,
  state,
  getters,
  mutations,
  actions
}

// ./store/modules/index.ts
import { ModuleTree } from 'vuex'
const files: any = require.context('.', false, /\.ts$/)

// 这里为了方便演示,重复写了一个接口。
// 个人可放置到 * 目录下,来同一管理接口
interface LoginState {
  [key: string]: any
}

let modules: ModuleTree<any> = {}

files.keys().forEach((key) => {
  if (key === './index.ts') {
    return
  }
  modules[key.replace(/(\.\/|\.ts)/g, '')] = files(key).default
})

export default modules

организованный./store/modules/*файл, то пришло время «использовать». . .

// ./store/index.ts
import Vue from 'vue'
import Vuex from 'vuex'
import modules from './modules'

Vue.use(Vuex)
export default new Vuex.Store({
  modules
})

мы вlogin.vueИспользуйте Канкан

...
import { State, Action, namespace } from 'vuex-class'

// 在上面 ./store/modules/user.ts 并使用了命名空间,这里直接使用就行啦
const usreModel = namespace('user')

@Component({})
export default class Login extends Vue {
  // 窝是这样使用
  @usreModel.State((state) => state.user_id) user_id
  @usreModel.State((state) => state.authority) authority
  // 调用user/actions中的UPDATE_STATE_ASYN
  @usreModel.Action('UPDATE_STATE_ASYN') UPDATE_STATE_ASYN
  ...
}

(гнездо - терминатор)...

На официальном сайте Laikangkangvuex-class(Ссылаться на)

import Vue from 'vue'
import Component from 'vue-class-component'
import {
  State,
  Getter,
  Action,
  Mutation,
  namespace
} from 'vuex-class'

const someModule = namespace('path/to/module')

@Component
export class MyComp extends Vue {
  @State('foo') stateFoo
  @State(state => state.bar) stateBar
  @Getter('foo') getterFoo
  @Action('foo') actionFoo
  @Mutation('foo') mutationFoo
  @someModule.Getter('foo') moduleGetterFoo

  // If the argument is omitted, use the property name
  // for each state/getter/action/mutation type
  @State foo
  @Getter bar
  @Action baz
  @Mutation qux

  created () {
    this.stateFoo // -> store.state.foo
    this.stateBar // -> store.state.bar
    this.getterFoo // -> store.getters.foo
    this.actionFoo({ value: true }) // -> store.dispatch('foo', { value: true })
    this.mutationFoo({ value: true }) // -> store.commit('foo', { value: true })
    this.moduleGetterFoo // -> store.getters['path/to/module/foo']
  }
}

Уведомление

Здесь еще раз подчеркивается, чтоvuex-classа такжеvuex-moduleЭто две разные вещи, никогда не используйте их одновременно, чтобы избежать человеческих ошибок.

5. Миксины

Если у нас есть большое количество страниц таблицы, если вы посмотрите внимательно, вы обнаружите, что многие вещи можно использовать повторно, такие как разбиение по страницам, высота таблицы, метод загрузки,laodingДекларации и еще куча всего. Разберем простой общий миксинindex.js

import { Provide, Vue } from 'vue-property-decorator'
import Component from 'vue-class-component'
// 这里使用的是vuex-class,与上面的vuex-module不同,请注意
import { namespace } from 'vuex-class'
import moment from 'moment'

const usreModel = namespace('user')

@Component
export default class MyMixin extends Vue {
  @Provide() public loading: boolean = false
  @Provide() public form: any
  @Provide() public data: Array<any> = []
  @Provide() public pagination: any = {
    defaultPageSize: 6,
    showQuickJumper: true,
    hideOnSinglePage: false
  }

  @usreModel.State(state => state.user_id) user_id
  @usreModel.State(state => state.authority) authority
  
  formatDate(value, format = 'YYYY-MM-DD HH:mm') {
    if (value) {
      return moment(value).format(format)
    }
  }
}

mixinsиспользовать

import Component, { mixins } from 'vue-class-component'
import { Vue, Provide } from 'vue-property-decorator'
import MyMixin from '@/mixins'

@Component
export default class Home extends mixins(MyMixin) {
  @Provide() private columns: Object = Columns
  @Provide() private search: string = ''
}

Его можно использовать в обычном режимеloding,formи т. д. метод данных

Уведомление: Глобальные миксины нужно использовать с осторожностью, если в этом нет необходимости, я все равно не рекомендую их использовать.

6. Пакет аксиом

существуетvueВ проекте, взаимодействуя с фоном для получения данных, мы обычно используемaxiosбиблиотека, основанная наpromiseизhttpбиблиотека, которая работает на стороне браузера иnode.jsсередина. Он имеет множество отличных функций, таких как перехват запросов и ответов, отмена запросов, преобразованиеjson, Защита на стороне клиента от XSRF и т. д. Поэтому наш Юда тоже решительно отказался от своей официальной библиотеки.vue-resourceтехническое обслуживание, напрямую рекомендуем нам использоватьaxiosбиблиотека. если это правильноaxiosЕсли вы не знаете, вы можете двигатьсяaxiosдокументация.

Установить

npm install axios; // 安装axios

представлять

Обычно я буду в проектеsrcкаталог, создать новыйapiпапку и создайте новую внутриapi.tsс однимrequestConfig.tsдокумент.api.tsфайл для инкапсуляции нашегоaxios,requestConfig.tsИспользуется для унифицированного управления нашим интерфейсом.

// src/api/api.ts
import axios, { AxiosResponse, AxiosRequestConfig } from 'axios'
// config文件夹往后会出现,这里就不说明了
import { MAINHOST, ISMOCK, QAHOST, conmomPrams } from '@/config' 
// 接口
import requestConfig from './requestConfig'
// 获取存储在 cookies 的 token
import { getToken, removeToken } from '@/utils/common'
// 这里我使用了 antd ,大家根据自己的UI来使用
import { message } from 'ant-design-vue'
// 路由
import router from '@/router'
// 下面两个是加解密文件,因为用的是http,为了安全考虑,使用到这两个。(可忽略)
import apiEncrypt from '@/utils/apiEncrypt'
import apiDecrypt from '@/utils/apiDecrypt'

declare type Methods = 'GET' | 'OPTIONS' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'TRACE' | 'CONNECT'

declare interface Datas {
  method?: Methods
  [key: string]: any
}

// 根据环境,切换请求不同的url
const baseURL = process.env.NODE_ENV === 'production' ? MAINHOST : QAHOST//QAHOST

class HttpRequest {
  public queue: any // 请求的url集合
  public hide: any
  public constructor() {
    this.queue = {}
  }
  destroy(url: string) {
    delete this.queue[url]
    if (!Object.keys(this.queue).length) {
      // 关闭loding
      setTimeout(this.hide, 0)
    }
  }
  interceptors(instance: any, url?: string) {
    // 请求拦截
    instance.interceptors.request.use(
      (config: AxiosRequestConfig) => {
        // 添加全局的loading...
        if (!Object.keys(this.queue).length) {
          // show loading
          this.hide = message.loading('加载中..', 0)
        }
        if (url) {
          this.queue[url] = true
        }
        return config
      },
      (error: any) => {
        console.error(error)
      }
    )
    // 响应拦截
    instance.interceptors.response.use(
      (res: AxiosResponse) => {
        if (url) {
          this.destroy(url)
        }
        let { data, status } = res
        data = apiDecrypt(data)
        if (status === 200 && ISMOCK) {
          return data.result
        } // 如果是mock数据,直接返回
        if (status === 200 && data && data.code === 200) {
          return data.result
        } // 请求成功
        res.data = data
        return requestFail(res) // 失败回调
      },
      (error: any) => {
        if (url) {
          this.destroy(url)
        }
        message.error('服务器出错')
        console.error(error)
      }
    )
  }
  async request(options: AxiosRequestConfig) {
    const instance = axios.create()
    await this.interceptors(instance, options.url)
    return instance(options)
  }
}

// 请求失败
const requestFail = (res: AxiosResponse) => {
  let errStr = '网络繁忙!'

  if (res.data.code) {
    switch (res.data.code) {
      // 401: 未登录
      // 未登录则跳转登录页面,并携带当前页面的路径
      // 在登录成功后返回当前页面,这一步需要在登录页操作。
      case 401:
        router.replace({
          path: '/'
        })
        removeToken()
        break
      // 403 token过期
      // 登录过期对用户进行提示
      // 清除本地token和清空vuex中token对象
      // 跳转登录页面
      case 403:
        // 清除token
        // store.commit('loginSuccess', null);
        // 跳转登录页面,并将要浏览的页面fullPath传过去,登录成功后跳转需要访问的页面
        router.replace({
          path: '/'
        })
        removeToken()
        // localStorage.removeItem('token')
        break
      // 404请求不存在
      case 404:
        ...      
        break
    }
  }
  console.error({
    code: res.data.errcode || res.data.code,
    msg: res.data.errMsg || errStr
  })

  if (typeof res.data.errMsg === 'object') {
    res.data.errMsg = '服务器错误'
  }
  message.error(res.data.errMsg || errStr)
  return null
}

// 合并axios参数
const conbineOptions = (_opts: any, data: Datas, method: Methods): AxiosRequestConfig => {
  let opts = _opts
  if (typeof opts === 'string') {
    opts = { url: opts }
  }
  const _data = { ...conmomPrams, ...opts.data, ...data }
  const options = {
    method: opts.method || data.method || method || 'GET',
    url: opts.url,
    headers: { Authorization: `Bearer${getToken()}` },// 这个需要与后端配合,让后端去除掉Bearer,加上这个是为了(安全考虑)
    baseURL,
    timeout: 10000
  }
  const c = apiEncrypt(_data) // 加密数据
  return options.method !== 'GET' ? Object.assign(options, { data: c }) : Object.assign(options, { params: _data })
}

const HTTP = new HttpRequest()

/**
 * 抛出整个项目的api方法
 */
const Api = (() => {
  const apiObj: any = {}
  const requestList: any = requestConfig
  const fun = (opts: AxiosRequestConfig | string) => {
    return async (data = {}, method: Methods = 'POST') => {
      const newOpts = conbineOptions(opts, data, method)
      const res = await HTTP.request(newOpts)
      return res
    }
  }
  Object.keys(requestConfig).forEach((key) => {
    apiObj[key] = fun(requestList[key])
  })

  return apiObj
})()

export default Api as any

src/api/requestConfig
export default {
  getData: '/mock/5e23f600df5e86413d7f1486/example/upload', // 随机数据 来自 easy mock
}

несколько советов

  • если определено.d.tsфайл, перезапустите службу, чтобы ваша служба могла распознать определенный вами модуль, и перезапуститеvscodeПусть редактор тоже признает (действительно противно)
  • установите свойtsconfig, например, не забудьте поставитьstrictPropertyInitializationустановить какfalse, в противном случае вы должны задать ему начальное значение при определении переменной.
  • Десять миллионов на управление иерархией маршрутизации или даже обычная, когда нельзя экономить Рабочий уровень десять миллионов, чтобы определить тип обнаружения или перечисления, чтобы не только облегчить разработку, но и решить проблему при быстром позиционировании.
  • Использование в модуляхvuex, пожалуйста, используйте напрямуюrootGetters
  • Если вам нужно изменить тему библиотеки компонентов, откройте один файл для централизованного управления и не изменяйте каждый компонент в одном файле, иначе скорость компиляции будет беспокоить.
  • Чтобы иметь возможность повторно использовать вещи, разработанные другими людьми в команде, старайтесь не разрабатывать это во второй раз, иначе пустая трата времени может быть не просто временем разработки, но иcode reviewвремя

Порядок контекста TS в файле vue

  • data
  • @Prop
  • @State
  • @Getter
  • @Action
  • @Mutation
  • @Watch

крючки жизненного цикла

  • beforeCreate (сверху вниз в соответствии с крючками жизненного цикла)
  • created
  • beforeMount
  • mounted
  • beforeUpdate
  • updated
  • activated
  • deactivated
  • beforeDestroy
  • destroyed
  • errorCaptured (последний хук жизненного цикла)

крючок маршрутизации

  • beforeRouteEnter
  • beforeRouteUpdate
  • beforeRouteLeave

computed

methods


Поделиться не просто, если понравилось, не забудь нажать 💖! ! !

Те, кто только обращает внимание и не кликает💖 — хулиганы, и те, кто только собирает и не кликает 💖 — тоже хулиганы.

Конец 👍👍👍.


Ссылаться на

пакет axios (тыкать гнездо)

Ускорить скорость разработки проектов vue (тыкать в гнездо)

TypeScript + крупномасштабный проектный бой (тыкание гнезда)

Typescript+Vue крупномасштабная боевая система управления фоном (ковыряние в гнезде)

Учебное пособие по шаблону проекта сборки vue-cli3.0 (тыкать гнездо)