To Do List一直是我学习框架开始的地方,最早写To Do List这个demo是在初学Vue的时候。
时间过得很快,在我学习React的时候,我直接将这个Vue demo进行了改写。
现在我重新将这个demo从JavaScript改写成TypeScript,在此分享一些学习总结。
此次,仍然使用create-react-app脚手架创建React项目,按照TypeScript官网推荐,你可以使用以下命令完成项目创建。
# Install create-react-app
$ npm install -g create-react-app
# Create our new project
$ create-react-app my-app --scripts-version=react-scripts-ts
更多详细操作可以参见TypeScript-React-Starter。
泛型参数
泛型参数类型是用来约束类的成员/方法参数类型。
interface Component<P = {}, S = {}, SS = any> extends ComponentLifecycle<P, S, SS> { }
class Component<P, S> {
    constructor(props: P, context?: any);
    // We MUST keep setState() as a unified signature because it allows proper checking of the method return type.
    // See: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment-351013257
    // Also, the ` | S` allows intellisense to not be dumbisense
    setState<K extends keyof S>(
        state: ((prevState: Readonly<S>, props: P) => (Pick<S, K> | S | null)) | (Pick<S, K> | S | null),
        callback?: () => void
    ): void;
    forceUpdate(callBack?: () => void): void;
    render(): ReactNode;
    // React.Props<T> is now deprecated, which means that the `children`
    // property is not available on `P` by default, even though you can
    // always pass children as variadic arguments to `createElement`.
    // In the future, if we can define its call signature conditionally
    // on the existence of `children` in `P`, then we should remove this.
    props: Readonly<{ children?: ReactNode }> & Readonly<P>;
    state: Readonly<S>;
    context: any;
    refs: {
        [key: string]: ReactInstance
    };
}
查看Component源码你会发现泛型参数P对应的就是props,泛型参数S对应的就是state。
所以在写class组件时,都会以以下格式进行定义:
export default class App extends Component<IPropTypes, IStateTypes> {}
接口
来回顾一下,之前我们写React会使用prop-types来约束props的参数类型,但是现在我们可以放弃他,尽情发挥TypeScript的类型检查功能。
我们可以使用接口来约束props的类型,这同样也受用于state。
同样在写组件时,你会发现通用组件下会存在可选参数,那么写接口时就会使用到可选属性来解决这个问题。那么如果某些组件没有可选属性时候,你就不需要再使用defaultProps来设置props的默认值了,因为TypeScript的类型会直接抛出错误。
接口也提供导出导入功能,方便我们在开发的时候共用接口或者继承接口。
基础类型
期初我在定义数据类型的时候,不确定的我都会定义成any,当然我知道这很不优雅,也不是我的代码风格,所以我不会就此罢休。
我尝试着去把所有的不确定都换成确定。想想如果将所有的数据类型都定义成any,那么就失去了TypeScript的类型检查功能了,非常糟糕。
所以我在写泛型参数props和state时,如果存在没有属性的情况下,我会选择使用空对象类型{},使得我的程序更可控。
事件类型
在写到表单内容时,会有一些需要使用到event的地方,这次我使用了React提供的事件类型。
interface SyntheticEvent<T> {
    bubbles: boolean;
    /**
     * A reference to the element on which the event listener is registered.
     */
    currentTarget: EventTarget & T;
    cancelable: boolean;
    defaultPrevented: boolean;
    eventPhase: number;
    isTrusted: boolean;
    nativeEvent: Event;
    preventDefault(): void;
    isDefaultPrevented(): boolean;
    stopPropagation(): void;
    isPropagationStopped(): boolean;
    persist(): void;
    // If you thought this should be `EventTarget & T`, see https://github.com/DefinitelyTyped/DefinitelyTyped/pull/12239
    /**
     * A reference to the element from which the event was originally dispatched.
     * This might be a child element to the element on which the event listener is registered.
     *
     * @see currentTarget
     */
    target: EventTarget;
    timeStamp: number;
    type: string;
}
以上是SyntheticEvent<T>接口源码,React已经将原生事件需要部分进行了合成,在此基础上有更细化的接口:FocusEvent<T>、ChangeEvent<T>、KeyboardEvent<T>。
这里必须要很强势的安利一波VSCode,他让我的TypeScript开发体验变得如此之优雅,让我能够更好的学习TypeScript。
TSLint
代码舒适区这个问题,我选择适当的妥协,我选择适当的适应规则让我成长,而不是一味的寻找舒适区。
列举一下这个demo中我配置的TSLint:
"semicolon": [true, "never"]:一如既往,我选择不写分号。"object-literal-sort-keys": false:我关闭了短属性名提醒。"ordered-imports": false:我关闭了import的约束,使我可以导入CSS文件。
但是比如定义接口时必须以大写字母I打头的这类约束我并没有关闭,我认为这些是需要我去适应的。
遇到的一些问题
Issues Duplicate identifier with @types/node 10.0。
遇到此问题,之前我是将node版本降级至@types/node@9.6.7,但是目前@types/node@10.0.2已经修复该问题。
本demo会使用到react-dnd,但是使用TypeScript时,如果继续使用修饰器的写法,将无法继续进行下去。
本文中提及源码版本:@types/react@16.3.13
GitHub仓库: zongzi531/react-to-do-list
- 本文链接: https://zongzi531.github.io/2018/05/08/TypeScript%E5%88%9D%E5%AD%A6%E4%B9%8B%E8%B7%AF/
 - 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!