TypeScript

TS

TypeScript是Microsoft公司注册商标。https://zhongsp.gitbooks.io/typescript-handbook/content/

TypeScript具有类型系统,且是JavaScript的超集。 它可以编译成普通的JavaScript代码。 TypeScript支持任意浏览器,任意环境,任意系统并且是开源的。

  1. TypeScript 的定位是静态类型语言,在写代码阶段就能检查错误,而非运行阶段

  2. 类型系统是最好的文档,增加了代码的可读性和可维护性。

  3. 有一定的学习成本,需要理解接口(Interfaces)、泛型(Generics)、类(Classes)等

  4. ts最后被编译成js

创建TS项目

create-react-app my-app –template typescript

ts基础

1.基本类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 字符串
var name:string= 'sola'
name.substring(0,1)
// 数字
var age:number = 100
age.toFixed(1)
// 布尔
var show:boolean = true
// 多类型
var my:string|number = 123
// 任意类型
var myany:any = true

export default {}

2.数组

1
2
3
4
5
6
7
8
9
10
11
12
13
// 方式一
var list:string[] = ['1','2','3']

var list2:number[] = [1,2,3]

var list3:(number|string)[] = [1,2,'3']

// 方式二
var mylist1:Array<string> = ['a','b']

var myList2:Array<string|number> = [1,2,'qwer']

export default {}

3.对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 定义接口
interface IObj {
name:string,
age:number,
location?:string, // 可选属性
[propName:string]:any // 剩下不关心的数据用这种方式存储
}
var obj1:IObj = {
name:'sola',
age:11,
// location:'sy'
}



export default {}

4.函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 方式一
function test1(a:string,b:number,c?:boolean):string {
return a.substring(0,1) + b
}

test1('a',3,false)

// 方式二 接口
interface IFunc{
(a:string,b:string,c?:number):string
}

var myFunc2:IFunc = function (a: string,b:string,c?:number):string {
return a+b
}


interface IObj {
name:string,
age:number,
getName:(name:string)=>string
}

var obj:IObj = {
name:'sola',
age:18,
getName:(name:string) => {
return name
}
}
var res = obj.getName('sola')
export default {}

5.类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Bus {
public name = 'sola' // 共有属性
private list:any = [] // 私有属性,只有自己可以访问
protected age = 100 // 受保护的,孩子类可以访问
subscribe(cb: any){
this.list.push(cb)
}
dispatch(){
this.list.forEach((cb: () => any) => {
cb && cb()
})
}
}


export default {}

6.类+接口

类中 定义接口,用来实现类中所要实现的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
interface Ifunc {
getName:() => string,
// getAge:() => number
}
class A implements Ifunc{
getName(){
return "AAA"
}
}

class B implements Ifunc{
getName(){
return "CCC"
}
}

class C implements Ifunc{
getName(){
return 'DDD'
}
}

function init(obj:Ifunc) {
obj.getName()
}
var objA = new A()
var objB = new B()
var objC = new C()

init(objA)
init(objB)
init(objC)


export default {}

react class ts

1.state

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React, {Component} from 'react';
interface IState {
name:string
}
// 泛型中第一个参数约定属性,第二个约定状态
class App extends Component<any,IState> {
state:IState = {
name:'sola'
}
render() {
return (
<div>
app-{this.state.name.substring(0,1).toUpperCase() + this.state.name.substring(1)}
<button onClick={() => {
this.setState({
name:'jerry'
})
}}>click</button>
</div>
);
}
}

export default App;

2.prop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import React, {Component} from 'react';

class App extends Component {
render() {
return (
<div>
<Child name="sola"/>
</div>
);
}
}

interface IProps {
name:string
}
class Child extends Component<IProps, any>{
render() {
return <div>
child - {this.props.name}
</div>;
}
}


export default App;

3.ref

当在ts中使用ref的时候,需要通过类型断言的方式,来指定ref 的类型为HTMLInputElement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import React, {Component} from 'react';

interface IState {
text: string,
list:string[]
}

class App extends Component<any, IState> {
state = {
text:'',
list:[]
}
myRef = React.createRef<HTMLInputElement>()

render() {
return (
<div>
{/*<input value={this.state.text} onChange={(e) => {*/}
{/* this.setState({*/}
{/* text:e.target.value*/}
{/* })*/}
{/*}} type="text"/>*/}

<input type="text" ref={this.myRef}/>
<button onClick={() => {
console.log((this.myRef.current as HTMLInputElement).value);

this.setState({
list:[...this.state.list,(this.myRef.current as HTMLInputElement).value]
})
}}>click
</button>

{
this.state.list.map((item,index) =>
<li key={item}>{item}
<button onClick={() => {
let newList = this.state.list.concat()

var t = newList.splice(index,1)
console.log(t);
this.setState({
list:newList
})

}}>del</button>
</li>
)
}
</div>
);
}
}

export default App;

4.父子通信(子传父)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import React, {Component} from 'react';

class App extends Component {
state = {
isShow:true
}
render() {
return (
<div>
app
<Navbar title="首页" cb={() => {
this.setState({
isShow:!this.state.isShow
})
}}/>

{this.state.isShow && <Sidebar/>}
</div>
);
}
}
interface IProps {
title:string,
cb:() => void
}

class Navbar extends Component<IProps, any>{
render() {
return <div>
navbar-{this.props.title}
<button onClick={() => {
this.props.cb()
}
}>click</button>
</div>;
}
}


class Sidebar extends Component<any, any>{
render() {
return <div>
sidebar
</div>;
}

}

export default App;

react function ts

1.state

1
2
3
4
5
6
7
8
9
10
11
12
13
import React, {useState} from 'react';

function App() {
const [name,setName] = useState<string>("sola")
return (
<div>
app - {name.substring(0,1).toUpperCase()+name.substring(1)}
<button>click</button>
</div>
)
}

export default App

2.prop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React from 'react';

export default function App() {
return (
<div>
<Child name="aaa"/>
<Child1 name="bbb"/>
</div>
)
}
interface IProps {
name:string
}
function Child(props:IProps) {
return <div>child-{props.name}</div>
}

const Child1:React.FC<IProps> = (props) => {
return <div>child1</div>
}

3.ref

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React, {useRef, useState} from 'react';

export default function App() {
const myText = useRef<HTMLInputElement>(null)
const [list,setList] = useState<string[]>([])
return (
<div>
<input type="text" ref={myText}/>
<button onClick={() => {
console.log((myText.current as HTMLInputElement).value);
setList([...list,(myText.current as HTMLInputElement).value])
}}>click</button>
{list.map(item => <li key={item}>
{item}
</li>)}
</div>
)
}

4.父子通信(子传父)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import React, {useState} from 'react';

export default function App() {
const [isShow,setIsShow] = useState<boolean>(true)
return (
<div>
<Navbar cb={() => {
setIsShow(!isShow)
}}/>
{ isShow && <SideBar/>}
</div>
)
}
interface IProps {
title?:string
cb:() => void
}
function Navbar(props:IProps) {
return <div>
navbar- <button onClick={() => {
props.cb()
}
}>click</button>
</div>
}


function SideBar() {
return <div>
sidebar
</div>
}

react router ts

安装声明文件:npm i @types/react-router-dom

其他编码模式与之前是一样的,有一个点需要注意:在使用编程式路由导航的时候,需要引入接口配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import React, {Component} from 'react';
import axios from 'axios'
import {RouteComponentProps} from 'react-router-dom'
interface IItem {
filmId:number,
name:string
}
class Film extends Component<RouteComponentProps,any> {
state = {
list:[]
}
componentDidMount() {
axios({
url: "https://m.maizuo.com/gateway?cityId=110100&pageNum=1&pageSize=10&type=1&k=7605418",
headers: {
'X-Client-Info': '{"a":"3000","ch":"1002","v":"5.2.0","e":"1646462402616989231939585","bc":"110100"}',
'X-Host': 'mall.film-ticket.film.list'
}
}).then(res => {
this.setState({
list:res.data.data.films
})
})
}

render() {
return (
<div>
{
this.state.list.map((item:IItem) =>
<li onClick={() => {
this.props.history.push(`/detail/${item.filmId}`)
}} key={item.filmId}>{item.name}</li>
)
}
</div>
);
}
}

export default Film;

动态路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React, {Component} from 'react';
import {RouteComponentProps} from 'react-router-dom'
interface IParam {
myid:string
}
class Detail extends Component<RouteComponentProps<IParam>,any> {
render() {
return (
<div>
detail-{this.props.match.params.myid}
</div>
);
}
}

export default Detail;

react redux ts

ts中的redux,与之前也基本无大差异,只是新增了一些规定及约束,下面来看之前的业务场景:在Film组件中点击某个电影,跳转到电影详情Detail组件的时候,底部栏tabbar组件的状态需要改变,在Film组件里显示tabbar组件,在Detail组件中隐藏tabbar组件。

1.新建redux文件夹,该文件夹下创建store.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import {createStore} from 'redux'
// 通过ts指定派发action的类型
interface IAction {
type:string,
payload?:any
}
// 指定状态的类型
interface IState {
isShow:boolean
}
const reducer:any = (prevState:IState ={
isShow:true
},action:IAction) => {
const {type} = action
const newState:any = {...prevState}
switch (type) {
case "show":
newState.isShow = true
return newState
case "hide":
newState.isShow = false
return newState
default:return prevState
}
}

const store = createStore(reducer)
export default store

2.App组件中进行监听状态的变化并保存到状态(订阅)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import React, {Component} from 'react';
import IndexRouter from "./router/Index";
import store from "./redux/store";
class App extends Component {
state = {
isShow:(store.getState() as any).isShow
}
componentDidMount() {
console.log(store.getState());
store.subscribe(() => {
this.setState({
isShow:(store.getState() as any).isShow
})
})
}

render() {
return (
<div>
<IndexRouter></IndexRouter>
{
this.state.isShow && <ul>
<li>电影</li>
<li>影院</li>
<li>我的</li>
</ul>
}

</div>
);
}
}

export default App;

3.Detail组件中派发action(发布)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import React, {Component} from 'react';
import {RouteComponentProps} from 'react-router-dom'
import store from "../redux/store";
interface IParam {
myid:string
}
class Detail extends Component<RouteComponentProps<IParam>,any> {
componentDidMount() {
store.dispatch({
type:'hide'
})
}

componentWillUnmount() {
store.dispatch({
type:'show'
})
}

render() {
return (
<div>
detail-{this.props.match.params.myid}
</div>
);
}
}

export default Detail;

react antd ts

与正常使用无大差异,个别antd组件中,需要不同的ref,详细查看antd官网