深入理解TypeScript - react实践

 

 

初始化项目

这里使用create-react-app命令来创建项目:

$ sudo npm i create-react-app -g
$ create-react-app my-react-app --template typescript

 回车,就可以初始化一个react + TypeScript 本地项目。目录结构如下:

node_modules直接装好了,直接启动

cd react-ts

npm run start

项目正常启动。

理解声明文件

react-app-env.d.ts


/// <reference types="react-scripts" />

这个文件是 声明文件,用来声明全局变量使用的。具体参考文档:http://ts.xcatliu.com/

当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。

声明的变量一般都是以declare 开头,也可以是/// <reference /> 三斜线指令。声明文件中声明的变量,全局可以访问。

  • 三斜线指令的使用场景

在全局变量的声明文件中,是不允许出现 importexport 关键字的。一旦出现了,那么他就会被视为一个 npm 包或 UMD 库,就不再是全局变量的声明文件了。故当我们在书写一个全局变量的声明文件时,如果需要引用另一个库的类型,那么就必须用三斜线指令了。

如果把react-app-env.d.ts中的指令注释掉,看下会怎么?

App.tsx文件中会出现这样的提示;

找不到图片的声明文件,页面没有报错,可以正常运行,说明ts的语法错误不应该正常编译。

找到nodule_module文件中react-scripts中的类型声明文件,内容如下:

/// <reference types="node" />
/// <reference types="react" />
/// <reference types="react-dom" />

declare namespace NodeJS {
  interface ProcessEnv {
    readonly NODE_ENV: 'development' | 'production' | 'test';
    readonly PUBLIC_URL: string;
  }
}

declare module '*.bmp' {
  const src: string;
  export default src;
}

declare module '*.gif' {
  const src: string;
  export default src;
}

declare module '*.jpg' {
  const src: string;
  export default src;
}

declare module '*.jpeg' {
  const src: string;
  export default src;
}

declare module '*.png' {
  const src: string;
  export default src;
}

declare module '*.webp' {
    const src: string;
    export default src;
}

declare module '*.svg' {
  import * as React from 'react';

  export const ReactComponent: React.FunctionComponent<React.SVGProps<
    SVGSVGElement
  > & { title?: string }>;

  const src: string;
  export default src;
}

declare module '*.module.css' {
  const classes: { readonly [key: string]: string };
  export default classes;
}

declare module '*.module.scss' {
  const classes: { readonly [key: string]: string };
  export default classes;
}

declare module '*.module.sass' {
  const classes: { readonly [key: string]: string };
  export default classes;
}

可以看到这个文件主要声明了一些全局模块 。

declare module 扩展模块,也可用于在一个文件中一次性声明多个模块的类型

比如在tsx中引入以。svg结尾的文件, import logo from './logo.svg',就相当于引入了这样的一个模块。可以在任何地方引入,因为声明的模块是全局的。鼠标放上去,就会看到类型提示:

注意,三斜线指令必须放在文件的最顶端,三斜线指令的前面只允许出现单行或多行注释。

假如我在我的项目中需要使用第三方工具库lodash。在js,或者jsx文件中,我直接安装引入就可以了,但是在ts或者tsx文件中,不能这样做。

安装好lodash后,在App.tsx文件中引入,就会出现下面的提示:

提示要安装@type/lodash或者包含declare module 'lodash' 的声明文件。

现在声明文件中加上模块声明:

react-app-env.d.ts

declare module 'lodash' 

提示改变了。

再通过npm 引入类型包,先把声明文件中的声明语句去掉

tnpm install @types/lodash --save--dev

提示改变了:

这样就可以在项目中使用lodash带有的方法了。

如果react-app-env.d.ts文件中有声明lodash模块的语句,就会被优先使用,忽略node_modules中的声明。

大部分第三方库都带有自己的声明文件,可以通过 @type/module 方式安装。默认会自动识别。

相关的详细内容可以参考:http://ts.xcatliu.com/basics/declaration-files.html#declare-module

tsconfig.json配置文件

tsconfig.json包含了工程里TypeScript特定的选项,是TypeScript的配置文件,项目要想使用TypeScript需要增加这个文件。

基础配置可以查看 深入理解TypeScript - 认识TypeScript&配置详解 。

除了compilerOptions属性外,还可以配置另外两个属性来提高编译速度。这样就不会再去编译node_modules中的文件。

"include": [
    "src"
  ],
  "exclude": [
    "node_modules",
  ]

skipLibCheck

跳过所有声明文件的类型检查(*.d.ts),声明文件不做类型校验。

noImplicitAny

noImplicitAny:true,使用简单的any类型会发出警告,默认为false。这意味着,在编写代码时,您需要更多地关注类型,如果不指定类型,编译器就总是会「抱怨」。

props没有指定类型,默认是any类型,编译会报错。

noImplicitAny:false,依然会出现红色波浪线,但是编译不会报错。

               

显示的定义为any,报错将会消失:

 

allowSyntheticDefaultImports

为true,允许引入没有默认导出的模块。

如果为false,引入React就需要用下面的方式引入,这样所有的导出多加在了React对象上面:

import * as React from 'react';

函数组件

你可以使用 React.FunctionComponent 接口定义函数组件:

写一个简单的函数组件:

mport * as React from 'react';
import './App.css';

const App = (props) => {  //  Parameter 'props' implicitly has an 'any' type.
  return (
    <div className="App">
      Learn React { props.name}
    </div>)
}

export default App;

     这么写会发现编译报错,主要是因为tsconfig.json中配置不允许隐式的any类型,你可以显式定义为any, (props: any).。或者用接口来定义props类型:

interface IProps { 
  name: string;
}

(props: IProps) => ...这样就可以不会再出现编译报错了,但是index.tsx文件给出了提示:

这是因为再App组件props属性上,name属性是必须存在的,所以这里必须要传递name属性,但是有的时候我们在写一个组件的时候,有些props是可以选择传递或者不传递的,这个时候就主要这样来定义接口:

interface IProps { 
  name?: string;
}

一般情况下,props都需要哪些属性,开发人员都是很清楚的,所以只要把你需要的属性都定义在接口中,哪些是必须传递的,哪些是不必须传递的,都可以很明白的定义出来,这样如果一些必须传递的属性在使用的过程中漏传,编译器马上会给你指出来,这样就避免了在允许阶段的报错,提高了开发效率。

也可以用下面的方式来定义函数组件:

interface IProps { 
  name?: string;
}
const App: React.FunctionComponent<IProps> = props => {
  return (
    <div className="App">
      Learn React { props.name}
    </div>)
}

类组件

import * as React from 'react';
import './App.css';
interface IProps { 
  name?: string;
}
interface IState { 
  num: number;
}
class App extends React.Component<IProps, IState> {
  constructor(props: IProps) { 
    super(props);
    this.state = {
      num: 8
    }
  }
  render() { 
    return(
      <div className="App">
        Learn React { this.props.name}
      </div>)
  }
  
}

export default App;

<App />

props属性的值可以是字符串,也可以是JSX, 如果是JSX,类型为React.ReactNode:

import * as React from 'react';
import './App.css';
interface IProps { 
  header?: React.ReactNode;
}
const App: React.FunctionComponent<IProps> = props => {  //  Parameter 'props' implicitly has an 'any' type.
  return (
    <div className="App">
      Learn React { props.header}
    </div>)
}

export default App;
<App header={<h1>header</h1>} />

 react 类型声明文件提供了 React.ReactElement<T>,它可以让你通过传入 <T/>,来注解类组件的实例化结果。将App组件赋值给另外一个变量:

const otherApp: React.ReactElement<App> = <App />

泛型组件

如果说一个组件接收的参数是一个number类型的数组,可以这样来定义props类型:

type IProp = { items: number[] };

如果是sting类型的数组,可以这样来定义:

type IProp = { items: string[] };

 但是,有的时候传递的数组类型不固定,我们希望如果传递的是sting类型,items中类型是sting[],如果是number类型,items的类型是number[]。这就跟前面章节中讲到的泛型类似,所以这里就可以以这样的方式来定义一个泛型组件: 

// 一个泛型组件
type SelectProps<T> = { items: T[] };
class Select<T> extends React.Component<SelectProps<T>, any> {}

// 使用
const Form = () => <Select<string> items={['a', 'b']} />;

受控组件

写一个input的onChange事件,需要通过事件对象来获取value,怎样获取e的正确类型呢?

<input
            type="text"
            name="ming"
            value={this.state.ming}
            onChange={this.handleChange}
          />



  handleChange = (e) => {
    this.setState({
      xing: e.target.value
      // [e.target.name]: e.target.value
    })
  }

鼠标放置上去,会有类型提示: 

 

受控组件完整代码:

import React, { Component } from 'react'

type IProps = {}

type IState = {
  xing: string;
  ming: string;
  [key: string]: string;
}

export default class MyInput extends Component<IProps, IState> {

  constructor(props: IProps) {
    super(props);
    this.state = {
      xing: '',
      ming: ''
    }
  }



  handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    this.setState({
      // xing: e.target.value
     [e.target.name]: e.target.value
    })
  }

  render() {
    return (
      <div>
        <label htmlFor="">姓:
        <input type="text"
            value={this.state.xing}
            name="xing"
            onChange={this.handleChange}
          />
        </label>
        <label htmlFor="">
          名:
        <input
            type="text"
            name="ming"
            value={this.state.ming}
            onChange={this.handleChange}
          />

        </label>
        <p>
          {this.state.xing} {this.state.ming}
        </p>
      </div>
    )
  }
}

非受控组件

 

完整代码:

import React, { FormEvent } from 'react'

const UserForm = () => {

  let userRef: React.RefObject<HTMLInputElement> = React.createRef()

  let pwdInput: HTMLInputElement | null;

  let handleSubmit = (e: FormEvent) => {
    e.preventDefault();
    console.log('submit')
    console.log(userRef.current?.value)
    console.log(userRef.current!.value)
    console.log((userRef.current as HTMLInputElement).value)

    console.log(pwdInput?.value)
  }

  return (
    <form action="" onSubmit={handleSubmit}>

      username: <input ref={userRef}></input>

      pwd: <input ref={(node) => { return pwdInput = node; }}></input>

      <button type="submit">提交</button>


    </form>

  )
}

export default UserForm;

高阶组件

react提供了一个ComponentType类型来定义组件类型:

import React, { Component, ComponentType } from 'react'


class App extends Component {
  render() {
    return (
      <div>
        <h1>
          react
      </h1>
        <p>
          React.js 是一个构件用户界面的库
      </p>
      </div>
    )
  }
}


const withCopyright = (WrappedComponent: ComponentType) => {

  return class extends Component {
    render() {
      return (
        <>
          <WrappedComponent></WrappedComponent>
          <div>@copyright; 版权所有 NZ2020</div>
        </>
      )
    }
  }
}


const CopyrightApp = withCopyright(App);

export default CopyrightApp;

button点击


import React, { ReactNode } from 'react'

type IProps = {
  click(e: React.MouseEvent): void,
  children: ReactNode
}

const Button = (props: IProps) => {
  return (
    <button onClick={props.click}>{props.children}</button>
  )
}

export default Button;
<Button
    click={this.handleClick}
 >
    <span>button test</span>
</Button>

封装请求函数

src/utils/index.ts

这个函数返回了一个new Promise,就可以给这个函数一个Promise类型,鼠标放上去会看到类型提示,说是Promise类型需要接收一个 参数。这个参数代表的是resolve参数类型。可以定义个resolve参数类型接口。

初步实现代码如下: 

interface IResponse { 
    message: string;
    result: [];
    success: boolean;
}

function request(): Promise<IResponse> { 
    return new Promise((resolve, reject) => { 
        setTimeout(() => { 
            resolve({
                message: '',
                result: [],
                success: true,
            })
        })
    })
}
export default request

resolve参数一般result属性一般情况下不会固定,有时是个对象,有时是个数组,数组还会有不同的类型,所以Promise类型接口还提供了另外一个泛型,可以动态的定义result的类型:

interface IResponse<T> { 
    message: string;
    result: T;
    success: boolean;
}

type IParams = {
    url: string;
    method: 'get' | 'post',
    data?: any
}

function request(): Promise<IResponse<number[]>> { 
    return new Promise((resolve, reject) => { 
        setTimeout(() => { 
            resolve({
                message: '',
                result: [3,9],
                success: true,
            })
        })
    })
}
export default request

配置store

actions.ts


export type Payload = { 
    name: string;
}
export function addAction(params: Payload) { 
    return {
        type: 'ADD',
        payload: params
    };
}

reducer.ts

import { Reducer } from "redux"
import { Payload } from './actions'
interface State { 
    name: string
}
interface Action<T> { 
    type: string;
    payload: T
}

export const reducers: Reducer<State, Action<Payload>> = (state = {name: '麦乐'}, action) => {
    switch (action.type) {
        case "ADD":
            
            return {
                ...state,
                ...action.payload
            }
        default:
            return {
                ...state,
            }
    }
}

index.ts

import { createStore } from 'redux'
import { reducers } from './reducer'

const store = createStore(reducers)

export default store;

 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值