前言 之前学习了用 react + redux 实现一个简易的 todolist Demo,感觉挺简单的,最近又自己看了些 typescript 的教程,打算用 typescript 重新实现一次,发现原来没那么容易.遇到好多的坑.记录一下
原生 JavaScript 组件写法
import React, { Component } from 'react';
class ComponentName extends Component {
render() {
return (
<div>hello World</div>
) }
}
export default ComponentName;
typescript 组件写法
interface IPorps{ name:string}
interface IState{ count:number}
class ComponentName extends Component<IProps,IState> {
render() {
return (
<div>hello World</div>
) }
}
export default ComponentName;
可以看到,与原生 js 写法不同,ts 组件Component 后面多了<IProps,IState> ,这里方括号表示的是泛型约束,里面的变量 IPorps, IState 是代表要传入组件的 props 和 state 必须要有与 IProps,IState 所约束类型的值,以及相同返回类型的同名方法, 简单的来说就是
interface mustHasLength {
length:number
}
function printArgLenth <T extend mustHasLength> (arg:T) : void {
console.log(arg.length)
}
let arr = [1,2,3],
objWidthOutLength = {
id:1,
name:'obj1'
},
objWidthLength = {
id:2,
name:'obj2',
length:3
}
console.log(printArgLength(arr)) // 输出 3
console.log(printArgLength(objWidthLength)) // 输出 3
console.log(printArgLength(objWidthOutLength)) // 报错
类型“{ id: number; name: string; }”的参数不能赋给类型“mustHasLength”的参数。
Property 'length' is missing in type '{ id: number; name: string; }' but required in type 'mustHasLength'.ts(2345)
从上面的代码可以看出泛型约束的作用,当参数arg含有 length这个属性时,编译会正常通过,输出 arg.length, 当参数 arg 不含有 length 这个属性时,编译会不通过,并且提示代码出错的地方,让我们更好思考代码组织和定义
在 ts 里面,类型检查是很严格的,如果传递给组件的类型和在方括号泛型声明的类型不匹配,编译都不会通过.在写组件之前,要想好这个组件需要用到什么属性和状态,然后再定义接口,把用到的 props,state 的类型都写上.
import React, { MouseEvent, ChangeEvent, PureComponent } from 'react'
import { TodosState, TodoTypes } from './types'
import { connect } from 'react-redux'
import { addTodo, change_input_value } from './actions'
interface PropsFromDispatch {
addTodo: typeof addTodo
change_input_value: typeof change_input_value
}
interface PropsFromState {
inputValue: string
data: TodoTypes[]
}
type AllProps = PropsFromDispatch & PropsFromState
class AddToto extends PureComponent<AllProps> {
render() {
return (
<div>
<input type='text' onChange={this.handleChange} />
<button type='button' onClick={this.handleSubmit}>
submit todo
</button>
</div>
)
}
handleSubmit = (e: MouseEvent): void => {
if (this.props.inputValue !== '') {
this.props.addTodo(this.props.inputValue)
}
}
handleChange = (e: ChangeEvent<HTMLInputElement>): void => {
let value = e.currentTarget.value
this.props.change_input_value(value)
}
}
const mapDispatchToProps = {
addTodo,
change_input_value
}
const mapStateToProps = (state: TodosState) => {
return {
inputValue: state.inputValue,
data: state.data
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(AddToto)
在我这个 AddTodo组件中,我使用了 react-redux 的 connect 方法把从 redux 中获取的属性和方法都挂载到组件的 props 上,所以定义了两个接口 PropsFromDispatch PropsFromState ,然后把它们结合的类型通过 AllProps 传递给组件,这样组件才能知道将来要使用的方法和属性是什么类型的.
types.ts
export interface TodoTypes {
id: number
name: string
}
export interface TodosState {
readonly inputValue: string
readonly data: TodoTypes[]
}
export interface TodoDispatch {
addTodo?: () => void
change_input_value?: () => void
deleteToDo?: () => void
}
export enum TodoActionType {
ADD_TODO = 'ADD_TODO',
DELETE_TODO = 'DELETE_TODO',
CHANGE_INPUT_VALUE = 'CHANGE_INPUT_VALUE'
}
这里分别定义了三个接口,TodoTypes,TodosState,TodoDispatch 以此来通知 reducer 即将用到的 state 的类型,以及使用枚举类型(enum)定义 redux 中的 行为标识(actionTypes).
reducer.ts
import { Reducer } from 'redux'
import { TodoActionType, TodosState } from './types'
const initialState: TodosState = {
inputValue: '',
data: [
{ id: 0, name: '睡觉觉' },
{ id: 1, name: '睡觉觉' },
{ id: 2, name: '睡觉觉' } ]
}
const reducer: Reducer<TodosState> = (state = initialState, action) => {
let newState = JSON.parse(JSON.stringify(state))
switch (action.type) {
case TodoActionType.ADD_TODO:
return addtodo()
case TodoActionType.CHANGE_INPUT_VALUE:
return changeInput()
case TodoActionType.DELETE_TODO:
return deleteTodo()
default:
return newState
}
}
export { reducer as todosReducer }
在 reducer.ts 中,使用到TodosState接口定义初始 state的类型,然后根据不同的行为标识(TodoActionType),返回不同函数运行,这三个函数都用于返回新的状态(state).
actions.ts
import { action } from 'typesafe-actions'
import { TodoActionType } from './types'
export const addTodo = (payload: string) => action(TodoActionType.ADD_TODO, payload)
export const deleteTodo = (payload: number) => action(TodoActionType.DELETE_TODO, payload)
export const change_input_value = (payload: string) => action(TodoActionType.CHANGE_INPUT_VALUE, payload)
在 actions.ts 中,用到了第三方库 typesafe-actions用于创建类型安全的 action
从上面三个文件可以看出,使用 typescript 对比原生写法,多了很多类型的定义或着说明,类型这给概念是很重要的,没有明确告诉编译器类型,编译都不会通过.
刚开始写 typescript 和 react 结合的时候很不习惯,基本上写几句就要 debug,各种编译不通过,在参考了网上其他人的项目后,再根据编译的提示,慢慢的了解了 typescript 的类型概念,基本上算是入门了 typescript.