入门TypeScript React

核心提示本入门教程会教你如何连接TypeScript和React。你可以获得以下信息:一个TypeScript和React的项目使用TSLint做代码规范化 使用Jest和Enzyme做测试使用Redux做状态管理我们将使用create-react

本入门教程会教你如何连接Typescript和React。你可以获得以下信息:

  1. 一个Typescript和React的项目
  2. 使用TSLint做代码规范化
  3. 使用Jest和Enzyme做测试
  4. 使用Redux做状态管理

我们将使用create-react-app工具来做快速的部署。我们假设你已经在使用Node.js和npm。你可能需要对React的基础有一个简单的概念。

安装create-react-app

我们将使用create-react-app,因为它包含一些常用的有用的工具以及对React项目有意义的默认值。它其实就是一个用来创建和初始化React项目的命令行工具。

npm install -g create-react-app

创建自己的新项目

我们将创建一个叫做my-app的项目:

create-react-app my-app --scripts-version=react-scripts-ts

react-scripts-ts是一些调整的组合,使得你可以使用标准create-react-app项目流程并且加入Typescript。

现在你的项目应该是下面这个样子:

my-app/├─ .gitignore├─ node_modules/├─ public/├─ src/│ └─ ...├─ package.json├─ tsconfig.json└─ tslint.json

其中:

- ```tsconfig.json```包含我们项目中的Typescript的配置信息

- ```tslint.json```是我们的代码规范工具TSLint相关的配置

- ```package.json```包含我们的依赖项,以及一些用于测试、预览、部署等的快捷命令。

- ```public```包含静态资源,比如我们要部署的HTML页面和图片。你们可以删除除了index.html以外的任何文件。

- ```src```包含了我们Typescript和CSS的代码。index.tsx是我们的文件的入口。

运行我们的项目

程序的运行非常的简单:

npm run start

这个命令能够运行在```package.json```中的配置的start脚本。并且会启动一个server,用于在保存文件时候重新加载页面。server会在```http://localhost:3000```运行,而且会自动打开。这样大大提高了我们预览的效率。

测试项目

测试也同样是一个命令可以做到的:

npm run test

这个命令运行了Jest,Jest一个非常有用的测试工具。这个工具会针对所有以```.test.ts```

或者```.spect.ts```结尾的文件。和```npm run start```命令一样,Jest会在发现文件变动的时候自动运行。当然,如果你愿意,你可以依次运行```npm run start```和```npm run test```来预览和测试。

创建一个产品构建

当你用```npm run start```运行一个项目时,我们并没有得到一个优化后的构建。尤其是,我们希望这个代码在传递给用户的时候是尽量的小并且高效。一些优化点比如minification,能够很好的完成这样的目标,当然也会花去更多的时间。我们把这种带有优化的构建叫做产品构建(与开发构建相对)。

启动一个开发构建,只要运行:

npm run build

这样就能够创建一个优化后的构建,js和css经过优化后保存在```./build/static/js```和```./build/static/css```。

在绝大部分时间里你不需要运行产品构建,但这在你需要去衡量你的最后app产出的时候是很必要的。

创建一个组件

我们将去编写一个叫做```Hello```的组件。这个组件可以取一个用来打招呼的内容(称为```name```),并且可选地加上一个代表感叹号数量的数字(称为```enthusiasmLevel```)。

当我们写像``````的东西时,组件能够渲染类似``````的东西。如果```enthusiasmLevel```没有设置,组件默认加一个感叹号。如果```enthusiasmLevel```设置的是0或着负数,则会抛出异常。

我们这么写 ```Hello.tsx```:

// src/components/Hello.tsximport * as React from 'react';export interface Props { name: string; enthusiasmLevel : number;}function Hello { if { throw new Error; } return }

);}export default Hello;// helpersfunction getExclamationMarks { return Array.join;}

我们从```Props```来获取我们组件所需要的属性。```name```是一个必要的字符串,```enthusiasmLevel```是一个可选的数字(只要在名字后面加上一个``` ```)。

我们同时还要编写一个无状态的函数组件(SFC)叫做```Hello```。这是一个可以接收```Props```对象并且解析的的函数。如果```enthusiasmLevel```没有给,默认为1。

编写函数是React中建立组件的两种方式中的一种。如果我们愿意,我们可以将它包装成为一个类:

class Hello extends React.Component { render { const { name, enthusiasmLevel = 1 } = this.props; if { throw new Error; } return }

); }}

当我们的组件实例有一些状态时,类是很有用的。 但是在这个例子中我们并不需要考虑状态 - 实际上我们将它指定为```React.Component ```中的对象,所以编写SFC往往会更短。 当创建可以在库之间共享的通用UI元素时,本地组件状态在演示级别更有用。 对于我们的应用程序的生命周期,我们将重新审视应用程序如何使用Redux管理状态。

我们已经编写了我们的组件,让我们开始研究index.tsx并且并且用``````替换``````的渲染。

首先我们需要在文件头部进行引用:

import Hello from `./components/Hello`;

然后更改```render```调用:

ReactDOM.render as HTMLElement);

类型断言

我们在本节中将要指出的最后一件事就是```document.getElementById('root')as HTMLElement```。 这种语法称为类型断言,有时也称为类型转换。当您比类型检查器更了解表达式的真实类型是什么的时,这是一种告诉Typescript很有用的方式。

在这种情况下我们需要这样做的原因是```getElementById```的返回类型是```HTMLElement | null```。 简单来说,当```getElementById```找不到具有给定ID的元素时,返回```null```。 我们假设```getElementById```实际上会成功,所以我们需要使用as语法来说服它的```Typescript```。

```Typescript```还有一个后缀的“bang”语法(```!```),它从先前的表达式中去除了```null```和```undefined```。 所以我们可以编写```document.getElementById('root')!```但是在这种情况下,我们想要更加明确。

添加样式

使用我们的设置对组件进行样式很简单。为了调整我们的Hello组件,我们可以在```src/components/Hello.css```创建一个CSS文件。

.hello { text-align: center; margin: 20px; font-size: 48px; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;}.hello button { margin-left: 25px; margin-right: 25px; font-size: 40px; min-width: 50px;}

create-react-app使用的工具(即Webpack和各种加载器)使我们能够导入我们感兴趣的样式。当我们的构建运行时,任何导入的```.css```文件将被连接到输出文件中。 所以在```src/components/Hello.tsx```中,我们将添加以下导入脚本:

import './Hello.css';

使用Jest做测试 

我们对```Hello```模块做了以下假设:

  • 当我们写下类似``````,模块需要渲染出```

    Hello Daniel!!!

    ```。
  • 如果```enthusiasmLevel```没有给出来,模块需要默认地显示一个感叹号。
  • 如果```enthusiasmLevel```是```0```或者负数,则需要抛出异常。

我们也以用这些假设来为我们的模块写一些测试用例。

当然首先,我们需要安装Enzyme。Enzyme是一个在React生态中比较通用的工具,用于测试模块的工作情况。在默认情况下,我们的应用包含一个叫做jsdom的库,使得我们可以模拟DOM并且在没有浏览器的情况下测试它的运行时行为。基于jsdom的Enzyme的功能是相似的,但是它能够更方便地生成对我们组件的查询。

我们接下来安装Enzyme,将它作为开发时的依赖。

npm install -D enzyme @types/enzyme react-addons-test-utils

请注意,我们安装了```enzyme```包以及```@types/enzyme```。```enzyme```包是指包含实际运行的Javascript代码的包,而types/enzyme是包含声明文件(.d.ts文件)的包,以便Typescript可以了解如何使用Enzyme。 您可以在这里了解更多关于@types。

我们还需要安装```react-addons-test-utils```。这是```enzyme```包要求安装的。

我们现在已经完成```enzyme```的安装。让我们开始编写我们的测试。我们先建立一个叫做```src/components/Hello.test.tsx``` 的文件,与我们之前的```Hello.tsx```文件相关联。

// src/components/Hello.test.tsximport * as React from 'react';import * as enzyme from 'enzyme';import Hello from './Hello';it => { const hello = enzyme.shallow; expect.text).toEqual});it => { const hello = enzyme.shallow; expect.text).toEqual});it => { const hello = enzyme.shallow; expect.text).toEqual;});it => { expect => { enzyme.shallow; }).toThrow;});it => { expect => { enzyme.shallow; }).toThrow;});

这是非常基础的一些测试,但是你能够发现所有需要的信息。

添加状态管理

在这一点上,如果您正在使用React for获取数据一次并显示它,您可以考虑自己完成。 但是,如果您正在开发更具互动性的应用程序,那么您可能需要添加状态管理。

状态管理综述

React是一个用于创建可管理视图的有用库。 但是,React并没有任何在应用程序之间同步数据的功能。 就React组件而言,数据流通过您在每个元素上指定的属性传递给后代。

由于React本身不提供内部的状态管理支持,所以React社区使用像Redux和MobX这样的库。

Redux依赖于通过集中和不可变的数据的存储的方式来同步数据,并且该数据的更新将触发我们的应用程序的重新渲染。 状态更新通过reducers的函数的显示处理。 由于组织明确,通常更容易理解一个行为将如何影响你的程序的状态。

MobX依赖于reactive模式,其中状态包装成可观察的并通过属性传递。 通过简单地将状态标记为可观察来完成任何观察者的状态完全同步。 作为一个很好的福利,该库已经通过Typescript编写完成。

两者都有不同的优点和权衡。 一般来说,Redux往往会看到更广泛的使用,所以为了本教程的目的,我们将专注于添加Redux; 但是,我们还是鼓励你应该同时了解两者。

以下部分可能具有陡峭的学习曲线。 我们强烈建议您通过其[文档]熟悉Redux。

为行为添加stage

添加Redux是没有意义的,除非我们的应用程序的状态发生变化。 我们需要一个可以触发更改的动作来源。 这可以是一个定时器,或者像UI中的某个按钮。

为了我们的目的,我们将添加两个按钮来控制我们的Hello组件的enthusiasm级别。

安装Redux

我们需要安装```redux```以及```react-redux```还有他们的依赖和types。

npm install -S redux react-redux @types/react-redux

这样我们就不需要安装```@types/redux```因为Redux的定义文件: IncrementEnthusiasm { return { type: constants.INCREMENT_ENTHUSIASM }}export function decrementEnthusiasm: DecrementEnthusiasm { return { type: constants.DECREMENT_ENTHUSIASM }}


我们创建了两种类型,描述增量动作和减量动作应该是什么样的。 我们还创建了一个类型(```EnthusiasmAction```)来描述存放增量或减量的地方。 最后,我们做了两个功能,实际上制造了我们可以使用的动作,而不是写出庞大的对象文字。

这里有明显的实例,当你在遇到事情的时候,可以随时查看像redux-actions这样的类库。

增加一个reducer

我们准备写下我们的第一个reducer!reducer只是通过创建我们应用程序状态的修改副本而产生更改的函数,但没有任何副作用。换句话说,它们就是我们所说的pure function。

我们的reducer将在src/reducers/index.tsx下。其功能是确保增量使```enthusiasm level```增加1,减量则是其减少1,但值不低于1。

// src/reducers/index.tsximport { EnthusiasmAction } from '../actions';import { StoreState } from '../types/index';import { INCREMENT_ENTHUSIASM, DECREMENT_ENTHUSIASM } from '../constants/index';export function enthusiasm: StoreState { switch { case INCREMENT_ENTHUSIASM: return { ...state, enthusiasmLevel: state.enthusiasmLevel + 1 }; case DECREMENT_ENTHUSIASM: return { ...state, enthusiasmLevel: Math.max }; } return state;

请注意,我们正在使用对象spread(```...state```),它允许我们创建一个浅状态的副本,同时替换```enthusiasmLevel```。重要的是,```enthusiasmLevel```属性需要放在最后,否则将被旧的属性所覆盖。

您可能想为您的reducer编写一些测试。 由于reducer是pure function,它们可以被传递任意数据。对于每个输入,reducer可以通过检查其新产生的状态进行测试。考虑研究Jest的toEqual方法来实现这一点。

制作一个容器

在使用Redux时,我们经常会写入组件以及容器。 组件通常与数据无关,并且主要在演示层面上工作。 容器通常包装组件并为他们提供显示和修改状态所需的任何数据。 您可以在丹·阿布拉莫夫的文章上更多地了解这一概念。

首先,让我门更新```src/components/Hello.tsx```,这样它就可以修改状态。我们将为```Props```添加两个可选的回调属性:```onIncrement```和```onDecrement```:

export interface Props { name: string; enthusiasmLevel : number; onIncrement : => void; onDecrement : => void;}

然后我们将这些回调绑定到我们添加到组件中的两个新按钮上。

function Hello { if { throw new Error; } return } </p> <p> <button onClick={onDecrement}>-</button> <button onClick={onIncrement}>+</button> </p> </p> );}

一般来说,在点击相应按钮时触发```onIncrement```和```onDecrement```的一些测试是一个好主意。 给你组件测试留下很好空间。

现在我们的组件已更新,我们已经准备好将其包装到一个容器中。我们创建一个名为src/containers/Hello.tsx的文件,并开始使用以下imports。

import Hello from '../components/Hello';import * as actions from '../actions/';import { StoreState } from '../types/index';import { connect, Dispatch } from 'react-redux';


这里最关键的两块是原始的```Hello```组件和react-redux的```connect```函数。```connect```可以使用以下两个函数实际地将原始的```Hello```组件转化为一个容器:

  • ```mapStateToProps```,可以将当前store的数据作为消息传给我们组件需要shape。
  • ```mapDispatchToProps```,可以创建回调属性来使用```dispatch```函数将行为推送到我们的store。

我们的应用状态由两个属性组成:languageName和enthusiasmLevel。另一方面,我们的Hello组件预计会有一个name和enthusiasmLevel。 mapStateToProps将从store获取相关数据,并在必要时对我们组件的属性进行调整。 我们继续:

export function mapStateToProps { return { enthusiasmLevel, name: languageName, }}

请注意,```mapStateToProps```仅创建```Hello```组件期望的属性中的2个。也就是说,我们仍然希望通过```onIncrement```和```onDecrement```回调。 ```mapDispatchToProps```是一个采用调度程序功能的函数。此调度程序功能可以将操作传递到我们的存储中进行更新,因此我们可以创建一对可以根据需要调用调度程序的回调函数。

export function mapDispatchToProps { return { onIncrement: => dispatch), onDecrement: => dispatch), }}

最后,我们准备好调用```connect```。```connect```将首先使用```mapStateToProps```和```mapDispatchToProps```,然后返回另一个可以用来包装组件的函数。我们生成的容器由以下代码行定义:

export default connect;

当我们完成的时候,我们的文件是这样的:

// src/containers/Hello.tsximport Hello from '../components/Hello';import * as actions from '../actions/';import { StoreState } from '../types/index';import { connect, Dispatch } from 'react-redux';export function mapStateToProps { return { enthusiasmLevel, name: languageName, }}export function mapDispatchToProps { return { onIncrement: => dispatch), onDecrement: => dispatch), }}export default connect;

创建一个store

让我门从新看一下```src/index/tsx```。为了将这些全部整合到一起,我们需要创建一个store和其初始状态。并且和我们所有的reducer一起设置好:

mport { createStore } from 'redux';import { enthusiasm } from './reducers/index';import { StoreState } from './types/index';const store = createStore;

```store```就像你猜测的一样,我们用于存储应用的全局状态的对象。

然后我们将调换我们对```./src/components/Hello```和```./src/containers/Hello```的使用。并且使用react-redux的```Provider```来连接我们的属性和容器。我们import下面这些:

import Hello from './containers/Hello';import { Provider } from 'react-redux';

然后将```store```传递给```Provider```的属性。

ReactDOM.render as HTMLElement);

请注意,```Hello```不再需要属性,因为我们使用我们的连接功能来适应我们封装的```Hello```组件的属性的应用程序的状态。

Eject

如果在任何时候,您需要配置自定义create-react-app应用配置,您可以随时选择退出并获取所需的各种配置选项。 例如,如果您想添加一个Webpack插件,可能需要利用create-react-app提供的“弹出”功能。

npm run eject

你就完成了!

作为一个heads up,你可能想要在运行弹出之前提交所有的工作。您无法撤销eject命令,因此选择退出是永久性的,除非您可以在运行eject之前从提交中恢复。

后续步骤

create-react-app有很多好东西。 其中大部分记录在为我们的项目生成的默认README.md中,因此可以快速阅读。

如果您还想了解有关Redux的更多信息,您可以查看官方网站的文档。 MobX也一样。

你想要在某个时候弹出,你可能需要更多地了解Webpack。 您可以在这里查看我们的React&Webpack演练。

在某些时候你可能需要路由。有几个解决方案,react-router可能是Redux项目中最受欢迎的,并且通常与react-router-redux起使用。

 
友情链接
鄂ICP备19019357号-22