umi+ts入门问题总结

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了umi+ts入门问题总结相关的知识,希望对你有一定的参考价值。

参考技术A React的变化很快,在我之前用习惯了直接hashHistory、browserHistory,而react-router4改变后就不习惯了,索性就直接使用了阿里的umijs。
下面是我学习umi过程中的一些心得。
学习前端框架,直接看官方文档是最好的学习方法 umi官方文档链接 ,但是总的来说,其实umi官方文档写的教程并不是很详细,它可能面向的是基础比较好的前端开发者的。但是,你的问题都是可以在其github项目的issue中找到解答的,我这里找了一个能解决我好多问题的网友总结的 issue链接 。
首先还是说一下umi的优点:我个人体会最显著的是,不需要你配置很多其他的参数,不需要你手动写react-router,因为其默认的pages下的文件夹名字就直接是路由的路径,也不需要你自己配置dll,因为内部直接内置了dll。同时umi默认使用dva代替react-redux,dva可以说是一个零api的数据流方案。
再一来说一说我只有redux-thunk和比较老版本react基础的在学习umi+dva+ts技术栈过程中碰到的几个问题:
1、effects和reducer的调用重名问题
一般来说,修改state的时候使用reducer,其他时候都用effects,如果名称一样,在dva@2后,只有effect会调用,所以两个不能重名
2、在有状态组件中,已经绑定mapStateToProps,但是this.props中的属性无法被读取
这个是ts的问题,我在stackoverflow上提的问题如链接: 我的提问 ,本质上是使用组件范型的方式解决问题。这一点在 Hello React and TypeScript 中也提到了。
3、less module使用问题
本来umi默认配置是支持less,但是我使用umi2.6.12自带的脚手架进行配置,根目录下的typings.d.ts中少了 declare module '*.less'; ,导致less无法作为模块导入。
4、antd Button控件报错
这个问题已经被修复,这里贴出我原本提问的链接: 我的提问 ,该问题已经被解决,只需要更改@types/react的版本就可以。
5、layout问题
其实我对umi框架的layout还不是很理解很熟悉,但是如果想要某些页面禁止使用一些layout,只需要判断 props.location.path 属性进行一些选择就可以了:

6、项目无法上传到git
我在 git commit 这一步的时候,会提示一些错误,但是不影响项目运行,其中比较烦的就是提示一些标签没有self-closing,我使用的antd的Menu.Item都让我自闭合那是不可能的,我怎么在Item中填我的Menu值呢?所以我采取了一种比较暴力的手段——每次上传项目的时候都禁用package.json文件的scripts属性中的 "precommit": "lint-staged" ,就可以上传成功了。可能有其他更好的手段解决,但是我图方便就这样进行了项目的上传。
7、路由跳转
这个其实不是我碰到的问题,但是umi的路由跳转真的很方便!我最常用的两种跳转方式有:一是使用 import router from 'umi/router ,然后直接router.push就可以,第二种是 import routerRedux from 'dva/router' ,然后routerRedux.push就可以进行跳转。这两种跳转可以说是满足了我做的项目的所有需求,不需要手动写一个Provider,也不需要react-router4后对于browserHistory的繁杂配置,直接可以进行解决。
8、props属性上不存在dispatch问题
这其实是一个ts的问题,在tsx文件中,props.dispatch是需要规定类型的,如果嫌麻烦可以在头上使用<any,any>的形式来表示比如: class xxx extends React.Component<any,any> 这样就可以解决props.dispatch问题了
这个问题还有个解决的方法,就是在tsconfig.json文件中,设置 "noImplicitAny":false ,这样就可以设置默认any类型了,但是这样对ts支持不太好,推荐上面的方法。

10分钟学会TypeScript,总结TS的常用特性

为什么要学习 TypeScript

前端老法师应该都知道,一路走来js有太多不完善的地方;呃,它是弱类型语言,它是解释型脚本,它入门其实很简单但深入挺难。想要知道为什么学习 TypeScript,那么我们首先要学习下什么是强类型、弱类型、静态类型、动态类型、类型系统。

强类型与弱类型(类型安全)

强类型,形参和实参的类型必须保持一致
强类型,不允许随意的隐式类型转换,而弱类型是允许的

弱类型的问题

缺失了类型系统的可靠性:

  • 一些类型异常要等到运行时才能发现
  • 类型不明确造成函数功能的改变
  • 对象索引器的错误用法
// 1. 异常需要等到运行时才能发现

const obj = {}

// obj.foo()

setTimeout(() => {
  obj.foo()
}, 1000000)

// =========================================

// 2. 函数功能可能发生改变

function sum (a, b) {
  return a + b
}

console.log(sum(100, 100))
console.log(sum(100, \'100\'))

// =========================================

// 3. 对象索引器的错误用法

const obj = {}

obj[true] = 100 // 属性名会自动转换为字符串

console.log(obj[\'true\'])

强类型的优势

  • 更早的暴露错误,可提前在语法阶段
  • 代码编写更智能,编码更准确(智能提示,开发工具能更有效的推断)
  • 重构更牢靠
  • 减少了代码层面的不必要的类型判断
// 1. 强类型代码错误更早暴露

// 2. 强类型代码更智能,编码更准确

function render (element) {
  element.className = \'container\'
  element.innerHtml = \'hello world\'
}

// =================================

// 3. 重构更可靠

const util = {
  aaa: () => {
    console.log(\'util func\')
  }
}

// =================================

// 4. 减少了代码层面的不必要的类型判断

function sum (a, b) {
  if (typeof a !== \'number\' || typeof b !== \'number\') {
    throw new TypeError(\'arguments must be a number\')
  }

  return a + b
}

静态类型与动态类型(类型检查)

  • 静态类型:一个变量声明时它的类型就是明确的,声明过后不允许在修改
  • 动态类型:运行阶段才明确变量的类型,而且可以修改(或者说变量没有类型,变量存放的值有类型)

Flow工具:javascript的类型检查器

官网:https://flow.org/en/docs/types/
手册:https://www.saltycrane.com/cheat-sheets/flow-type/latest/

注意事项:

1、Flow工具可以在代码编写过后检查类型错误;
2、通常将源码写在src目录,编译后移除类型注解到dist目录用于生产环境;

安装/初始化Flow工具

1、安装Flow工具:yarn add flow-bin --dev(安装Flow)
2、初始化Flow工具:yarn flow init(初始化Flow,生成.flowconfig文件)

如何使用Flow工具

1、在js文件顶部添加一行:@flow
2、在命令行执行命令:yarn flow

// @flow

function sum (a: number, b: number) {
  return a + b
}

sum(100, 100)

// sum(\'100\', \'100\')

// sum(\'100\', 100)

Flow工具移除类型注解

1、方法一:执行命令:yarn flow-remove-types ./ -d dist
2、方法二:使用babel插件来转换源码,需要安装3个babel插件,然后使用babel命令:

@babel/core
@babel/cli
@babel/preset-flow

.babelrc文件

{
  "presets": ["@babel/preset-flow"]
}

3、方法二:在命令行使用babel命令:yarn babel src -d dist

vscode 插件:直接代码编写页面展示语法错误
插件:Flow Language Support,可以在js文件保存后自动检查错误

TypeScript语言的特点

Typescript 特点

Typescript = (js、es6+、类型系统) => 编译成js
Typescript 是js的超集
Typescript 功能更强大,生态更健全、完善
Angular、Vue3.0使用TS开发
属于渐进式,边学边写

Typescript 缺点

多了很多概念:类型、泛型、枚举等
小型项目,项目初期 Typescript 会增加一些成本

TypeScript配置文件

安装ts

yarn add typescript --dev

编译ts

yarn tsc index.ts

初始化ts配置项:生成 tsconfig.json 文件

yarn tsc --init

tsconfig.json文件只有在运行整个项目的时候才能生效:yarn tsc

"target": "es5", // 编译成es版本: \'ES3\' (default), \'ES5\', \'ES2015\', \'ES2016\', \'ES2017\', \'ES2018\', \'ES2019\', \'ES2020\', or \'ESNEXT\'.
"module": "commonjs", // 使用哪种模块加载方式:\'none\', \'commonjs\', \'amd\', \'system\', \'umd\', \'es2015\', \'es2020\', or \'ESNext\'.
"sourceMap": true, // 源码映射,方便调试
"outDir": "./", // 代码输出目录
"rootDir": "./", // 源码输出目录

TS原始类型

/**
 * 基础数据类型的使用
 */
const a:string = "string"

const b:number = 100 //NaN Infinity

const c:boolean = true //false

//非严格模式下string、number、boolean都可以为空
//严格模式下会报错
const t:string = null

//非严格模式下可以是null、undefined
//严格模式下只能是undefined
const d:void = undefined
const e:null = null
const f:undefined = undefined

//默认情况下使用Symbol()会报错
//symbol类型只能在es2015,es6+中才能使用;Promise也有相同问题,也是es2015中引入
//可以在tsconfig.json配置文件lib中添加ES2015、DOM(解决console报错问题)
//"lib": ["ES2015","DOM"], 
const g:symbol = Symbol()

TS强制显示中文的错误消息

命令行显示中文:

yarn tsc --locale zh-CN

vscode配置显示中文:

setting 里面搜索:typescript local,第一个选项选择:zh-CN

TS Object类型,泛指对象、函数、数组

  • Object类型,指除了原始类型外的其它类型
  • 对象属性类型限制,可以使用类似对象字面量的方式,但最好用接口
const foo: object = function(){}

const obj: {a: number, b: string} = {a: 1, b: \'s\'}

TS数组类型

数组类型的声明

const arr1: Array<number> = [1,2,3]
const arr2: number[] = [1,2,3]

TS元组类型

在数组中使用不同类型的元素

const triple: [string, number] = [\'s\',18]

// const name: string = triple[0]
// const age: number = triple[1]

const [name, age] = triple

TS枚举类型,enum

通常用于表示不同的状态,它有两个好处:
1、可以给一组数值去上一个更好的名字
2、只会存在几个固定的值

// 标准的数字枚举,可以不赋值从0开始计数,后面自动+1
enum PostStatus {
  Draft = 0,
  Unpublished = 1,
  Published = 2
}

// 数字枚举,枚举值自动基于前一个值自增+1
enum PostStatus {
  Draft = 6,
  Unpublished, // => 7
  Published // => 8
}

// 字符串枚举,用的不多
enum PostStatus {
  Draft = \'aaa\',
  Unpublished = \'bbb\',
  Published = \'ccc\'
}

// const常量枚举,不会侵入编译结果
// 侵入编译结果,即编译后为双向键值对,也可以通过索引访问值:PostStatus[0] // => Draft
const enum PostStatus {
  Draft,
  Unpublished,
  Published
}

const post = {
  title: \'Hello TypeScript\',
  content: \'TypeScript is a typed superset of JavaScript.\',
  status: PostStatus.Draft // 3 // 1 // 0
}

TS函数类型

函数声明

// 形参和实参的个数必须一致
// 使用 ? 添加可选参数
// 使用 = 添加默认参数
// 可选参数、默认参数都需要放在最后
// ...reset放到最后,接受任意个数的参数
function func1 (a: number, b: number = 10, ...rest: number[]): string {
  return \'func1\'
}

func1(100, 200)

func1(100)

func1(100, 200, 300)

函数表达式

// 普通函数表达式,TS可以推断出来返回值类型
const func2 = function (a: number, b: number): string {
  return \'func2\'
}

// 函数作为参数,使用箭头函数
// const func2 = (a, b) => "func2"
const func2: (a: number, b: number) => string = function (a: number, b: number): string {
  return \'func2\'
}

TS任意类型:any,不安全

TS隐式类型推断

let age = 18 // number => 推断为number类型

// age = \'string\' //报错,已经推断为number类型

let foo // 推断为any类型

foo = 100

foo = \'string\'

TS类型断言

// 假定这个 nums 来自一个明确的接口
const nums = [110, 120, 119, 112]

const res = nums.find(i => i > 0)

// const square = res * res

const num1 = res as number // 方法一:as

const num2 = <number>res // 方法二:JSX 下不能使用

TS接口,interface

约定对象中有哪些成员,以及成员的类型
接口约束的成员,对象中就必须要有这些成员

interface Post {
  title: string
  content: string
  subtitle?: string // 可选成员,可有可无
  readonly summary: string // 只读成员,初始化后不能在修改
}

const hello: Post = {
  title: \'Hello TypeScript\',
  content: \'A javascript superset\',
  summary: \'A javascript\'
}

// hello.summary = \'other\'

// ----------------------------------

interface Cache {
  [prop: string]: string // prop为动态属性,prop为属性名(自定)
}

const cache: Cache = {}

cache.foo = \'value1\'
cache.bar = \'value2\'

TS类

基本使用继承了ES6中class的用法,但需要明确类中的成员有哪些以及类型

class Person {
  name: string // = \'init name\'
  age: number
  
  constructor (name: string, age: number) {
    this.name = name
    this.age = age
  }

  sayHi (msg: string): void {
    console.log(`I am ${this.name}, ${msg}`)
  }

类的访问修饰符
public
private 不允许外部访问,只允许在类的内部访问成员
protected 不允许外部访问,只允许在子类中访问成员

在构造函数前也能使用private,只能通过static静态函数去生城实例

class Person {
  public name: string // = \'init name\'
  private age: number
  protected gender: boolean
  
  constructor (name: string, age: number) {
    this.name = name
    this.age = age
    this.gender = true
  }

  sayHi (msg: string): void {
    console.log(`I am ${this.name}, ${msg}`)
    console.log(this.age)
  }
}

class Student extends Person {
  private constructor (name: string, age: number) {
    super(name, age)
    console.log(this.gender)
  }

  static create (name: string, age: number) {
    return new Student(name, age)
  }
}

const tom = new Person(\'tom\', 18)
console.log(tom.name)
// console.log(tom.age)
// console.log(tom.gender)

const jack = Student.create(\'jack\', 18)

类的只读属性
属性只能在声明时或者构造函数中初始化,两者选其一

class Person {
  public name: string // = \'init name\'
  private age: number
  // 只读成员
  protected readonly gender: boolean
  
  constructor (name: string, age: number) {
    this.name = name
    this.age = age
    this.gender = true
  }

  sayHi (msg: string): void {
    console.log(`I am ${this.name}, ${msg}`)
    console.log(this.age)
  }
}

const tom = new Person(\'tom\', 18)
console.log(tom.name)
// tom.gender = false

类与接口
接口可以约束类中的成员函数
一个接口尽量只实现一个功能

// 接口可以约束类中的成员函数
// 参数、返回值
interface Eat {
  eat (food: string): void
}

interface Run {
  run (distance: number): void
}

// implements关键字
// 约束过后类中必须要有接口中的成员,否则报错
class Person implements Eat, Run {
  eat (food: string): void {
    console.log(`优雅的进餐: ${food}`)
  }

  run (distance: number) {
    console.log(`直立行走: ${distance}`)
  }
}

class Animal implements Eat, Run {
  eat (food: string): void {
    console.log(`呼噜呼噜的吃: ${food}`)
  }

  run (distance: number) {
    console.log(`爬行: ${distance}`)
  }
}

TS抽象类(可以参考C++的理解)

约束子类中必须要有某个成员(类似于接口)
不包含成员的具体实现

abstract class Animal {
  eat (food: string): void {
    console.log(`呼噜呼噜的吃: ${food}`)
  }

  abstract run (distance: number): void
}

class Dog extends Animal {
  run(distance: number): void {
    console.log(\'四脚爬行\', distance)
  }

}

const d = new Dog()
d.eat(\'嗯西马\')
d.run(100)

TS泛型

不指定类型,调用的时候再指定具体的类型
以下实例中,T的类型可以根据情况指定具体的类型,但所有T应该保持一致


function createNumberArray (length: number, value: number): number[] {
  const arr = Array<number>(length).fill(value)
  return arr
}

function createStringArray (length: number, value: string): string[] {
  const arr = Array<string>(length).fill(value)
  return arr
}

function createArray<T> (length: number, value: T): T[] {
  const arr = Array<T>(length).fill(value)
  return arr
}

// const res = createNumberArray(3, 100)
// res => [100, 100, 100]

const res = createArray<string>(3, \'foo\')

TS函数声明,类型声明模块

  • 在ts中引用第三方模块时,如果不存在类型声明,可以尝试安装一个类型声明模块
  • 例如:lodash,可以安装一个:@types/lodash
  • 如果没有类型声明模块,只能使用declare 去声明模块类型
  • 目前有些模块已经集成了类型声明文件(没有报错就表示有?例如:qs模块)
import { camelCase } from \'lodash\'
import qs from \'query-string\'
// qs内部已经集成了类型声明
qs.parse(\'?key=value&key2=value2\')

// declare function camelCase (input: string): string

const res = camelCase(\'hello typed\')

TS断言推断等简写

  • 变量! => 变量结尾加上感叹号,有值断言
elm = oldVnode.elm!
// ===>
if(oldVnode.elm)
  elm = oldVnode.elm
else 
  return
  • 变量 as 类型 => 变量后面 as 某种类型,类型断言
  • 变量? => 变量结尾加上问号,有值判断
let init = hook?.init
//===>
let init = hook?hook.init:undefined

特别鸣谢:拉勾教育前端高薪训练营

以上是关于umi+ts入门问题总结的主要内容,如果未能解决你的问题,请参考以下文章

TS 报错 “umi“没有导出的成员‘xxx‘

TS 报错 “umi“没有导出的成员‘xxx‘

《umi+ts+antd Pro 珠峰课程》

10分钟学会TypeScript,总结TS的常用特性

umi中TS报错:TS7026: JSX element implicitly has type ‘any’…

解决umi项目引入React无智能提示,报错“React”指 UMD 全局,但当前文件是模块。请考虑改为添加导入。ts(2686)的问题。