TypeScript React Starter

这个快速入门指南将教你如何使用React链接TypeScript。 最后,将会得到:css

  • 一个使用React和TypeScript的项目html

  • TSLint项目检查node

  • JestEnzyme进行测试,react

  • Redux流程管理webpack

咱们将使用 create-react-app工具快搭建一个应用程序。git

咱们假设您已经在使用Node.js和npm。 您可能还想了解React的基础知识。github

  • 安装 create-react-appweb

咱们将使用create-react-app,由于它为React项目设置了一些有用的工具和规范默认值。 这只是一个命令行实用工具,用于建立新的React项目。typescript

npm install -g create-react-app
  • 建立咱们的项目npm

咱们建立一个新的项目,项目名称为 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存储咱们的linter,TSLint将使用的设置。

  • package.json包含咱们的依赖关系,以及咱们想要运行的用于测试,预览和部署应用程序命令的一些快捷方式。

  • public包含静态资源,好比咱们计划部署到的HTML页面或图像。 您能够删除该文件夹中除index.html以外的任何文件。

  • src包含咱们的TypeScript和CSS代码。 index.tsx是咱们文件的入口点,是强制性的。


  • 运行项目

运行项目只需简单的一个命令:

npm run start

这将运行咱们的package.json指定的启动脚本,并将生成一个服务器,当咱们保存文件时从新加载页面。一般,服务器运行在http://localhost:3000,自动为你打开。

这能够经过容许咱们快速预览更改来收紧迭代循环。

  • 测试项目

测试也是经过一个简单的命令:

npm run test

此命令运行Jest,这是一个很是有用的测试实用程序,针对扩展名以.test或.spec.ts结尾的全部文件。 像npm run start命令同样,Jest会在检测到更改后当即自动运行。 若是你愿意,你能够并行运行npm run start和npm run test,以便您能够预览更改并同时测试。

  • 建立生产构建

当以npm运行启动运行项目时,咱们没有最终构建优化版本。 一般,咱们但愿咱们传送给用户的代码尽量快和小。 某些优化如缩小能够实现这一点,但每每须要更多的时间。 咱们称这样的“生产”构建(而不是开发版本)。

运行生产构建,只需运行

npm run build

这将分别在./build/static/js和./build/static/css中建立优化的JS和CSS构建。

大多数时间您不须要运行生产版本,但若是您须要测量相似于应用程序最终大小,这将很是有用。

  • 建立组件

咱们将写一个Hello组件。该组件将以咱们要打招呼的内容命名(咱们称之为name),以及任意感叹号的数值(enthusiasmLevel)跟踪。

当咱们写一些代码好比<Hello name =“Daniel”enthusiasmLevel = {3} />时,组件将会渲染为<div> Hello Daniel !!! </ div>。若是没有指定enthusiasmLevel,组件就会给enthusiasmLevel 一个默认值。若是enthusiasmLevel为0或负数,则应该会出错。

Hello.tsx 代码以下:

// src/components/Hello.tsx

import * as React from 'react';

export interface Props {
  name: string;
  enthusiasmLevel?: number;
}

function Hello({ name, enthusiasmLevel = 1 }: Props) {
  if (enthusiasmLevel <= 0) {
    throw new Error('You could be a little more enthusiastic. :D');
  }

  return (
    <div className="hello">
      <div className="greeting">
        Hello {name + getExclamationMarks(enthusiasmLevel)}
      </div>
    </div>
  );
}

export default Hello;

// helpers

function getExclamationMarks(numChars: number) {
  return Array(numChars + 1).join('!');
}

请注意,咱们定义了一个名为Props的类型,该类型指定了组件将要执行的属性。 name是一个必需的string类型,而enthusiasmLevel是一个任意数字(你能够从name 后面的?看出来)。

咱们还写了Hello做为无状态函数组件(SFC)。具体来讲,Hello是一个使用Props对象的功能,并对其进行重构。若是咱们的Props对象中没有enthusiasmLevel值,那么默认值为1。

函数是React容许咱们制做组件的两个主要方式之一。若是咱们想要,咱们能够把它写成一个类,以下所示:

class Hello extends React.Component<Props, object> {
  render() {
    const { name, enthusiasmLevel = 1 } = this.props;

    if (enthusiasmLevel <= 0) {
      throw new Error('You could be a little more enthusiastic. :D');
    }

    return (
      <div className="hello">
        <div className="greeting">
          Hello {name + getExclamationMarks(enthusiasmLevel)}
        </div>
      </div>
    );
  }
}

当咱们的组件实例有一些状态时,类是颇有用的。可是在这个例子中咱们并不须要考虑状态 - 实际上咱们将它指定为React.Component <Props,object>中的对象,因此编写SFC每每会更短。当建立能够在库之间共享的通用UI元素时,本地组件状态在演示级别更有用。对于咱们的应用程序的生命周期,咱们将从新审视应用程序如何使用Redux管理通常状态。

如今咱们已经编写了咱们的组件,让咱们来看看index.tsx,并用<Hello ... />的渲染替换咱们的<App />渲染。

首先咱们在文件的顶部引入它:

import Hello from './components/Hello';

而后更改咱们的渲染调用:

ReactDOM.render(
  <Hello name="TypeScript" enthusiasmLevel={10} />,
  document.getElementById('root') as HTMLElement
);
  • 键入断言

咱们在本节中将要指出的最后一件事就是将document.getElementById('root')做为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 name="Daniel" enthusiasmLevel={3} />时,组件应该渲染为<div>Hello Daniel!!!</div>。

  • 若是没有指定enthusiasmLevel,组件应该默认显示一个感叹号。

  • 若是enthusiasmLevel为0或否认,则应该会出错。

咱们能够根据这些要求为咱们的组件编写一些测试。

但首先,咱们来安装Enzyme。Enzyme是React生态系统中的经常使用工具,能够更容易地编写测试,以肯定组件的运行方式。默认状况下,咱们的应用程序包含一个名为jsdom的库,容许咱们模拟DOM并在没有浏览器的状况下测试其运行时行为。Enzyme创建在jsdom上,使得对组件进行某些查询变得更加容易。

咱们来安装它做为一个开发时间的依赖。

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.tsx

import * as React from 'react';
import * as enzyme from 'enzyme';
import Hello from './Hello';

it('renders the correct text when no enthusiasm level is given', () => {
  const hello = enzyme.shallow(<Hello name='Daniel' />);
  expect(hello.find(".greeting").text()).toEqual('Hello Daniel!')
});

it('renders the correct text with an explicit enthusiasm of 1', () => {
  const hello = enzyme.shallow(<Hello name='Daniel' enthusiasmLevel={1}/>);
  expect(hello.find(".greeting").text()).toEqual('Hello Daniel!')
});

it('renders the correct text with an explicit enthusiasm level of 5', () => {
  const hello = enzyme.shallow(<Hello name='Daniel' enthusiasmLevel={5} />);
  expect(hello.find(".greeting").text()).toEqual('Hello Daniel!!!!!');
});

it('throws when the enthusiasm level is 0', () => {
  expect(() => {
    enzyme.shallow(<Hello name='Daniel' enthusiasmLevel={0} />);
  }).toThrow();
});

it('throws when the enthusiasm level is negative', () => {
  expect(() => {
    enzyme.shallow(<Hello name='Daniel' enthusiasmLevel={-1} />);
  }).toThrow();
});

这些测试是很是基本的,但你应该能领会到其中的要点。

  • 添加状态管理

在这一点上,若是您正在使用React获取数据并显示它,您能够考虑本身完成。可是,若是您正在开发更具互动性的应用程序,那么您可能须要添加状态管理。

  • 经常使用状态管理

React是一个用于建立可组合视图的有用库。可是,React并无任何在应用程序之间同步数据的功能。就React组件而言,数据经过您在每一个元素上指定的props向下流过其子项。

因为React自己不提供内部的状态管理支持,因此React社区使用像Redux和MobX这样的库。

Redux依赖于经过集中和不可变的数据存储来同步数据,而且该数据的更新将触发咱们的应用程序的从新渲染。状态经过发送必须由称为reducers的函数处理的显式操做消息以不变的方式更新。因为明确的性质,一般更容易理解一个行为将如何影响你的程序的状态。

MobX依赖于功能反应模式,其中状态经过可观测量包裹并做为道具传递。经过简单地将状态标记为可观察来完成任何观察者的状态彻底同步。做为一个很好的奖励,该库已经在TypeScript中编写。

二者都有不一样的优势和权衡。通常来讲,Redux每每会看到更普遍的使用,因此为了本教程的目的,咱们将专一于添加Redux;可是,你应该感到鼓舞去开发这二者。

如下部分可能具备陡峭的学习曲线。咱们强烈建议您经过其文档熟悉Redux

  • 设置动做来源

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

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

  • 安装 Redux

要添加Redux,咱们将首先安装redux和react-redux以及它们的类型做为依赖。

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

在这种状况下,咱们不须要安装@types/redux,由于Redux已经有本身的定义文件(.d.ts文件)。

  • 定义app状态

咱们须要定义Redux将存储的状态的形状。为此,咱们能够建立一个名为src/types/index.tsx的文件,该文件将包含整个程序中可能使用的类型的定义。

// src/types/index.tsx

export interface StoreState {
    languageName: string;
    enthusiasmLevel: number;
}

咱们的意图是languageName将是此应用程序编写的编程语言(即TypeScript或JavaScript),而enthusiasmLevel值也会改变。当咱们写第一个容器时,咱们会明白为何咱们故意使咱们的状态与咱们的props略有不一样。

  • 添加行为

咱们首先建立一组咱们的应用程序能够在src/constants/index.tsx中响应的消息类型。

// src/constants/index.tsx

export const INCREMENT_ENTHUSIASM = 'INCREMENT_ENTHUSIASM';
export type INCREMENT_ENTHUSIASM = typeof INCREMENT_ENTHUSIASM;


export const DECREMENT_ENTHUSIASM = 'DECREMENT_ENTHUSIASM';
export type DECREMENT_ENTHUSIASM = typeof DECREMENT_ENTHUSIASM;

这种常量/类型模式容许咱们以易于访问和可重构的方式使用TypeScript的字符串文字类型。

接下来,咱们将建立一组能够在src/actions/index.tsx中建立这些操做的动做和函数。

import * as constants from '../constants'

export interface IncrementEnthusiasm {
    type: constants.INCREMENT_ENTHUSIASM;
}

export interface DecrementEnthusiasm {
    type: constants.DECREMENT_ENTHUSIASM;
}

export type EnthusiasmAction = IncrementEnthusiasm | DecrementEnthusiasm;

export function incrementEnthusiasm(): IncrementEnthusiasm {
    return {
        type: constants.INCREMENT_ENTHUSIASM
    }
}

export function decrementEnthusiasm(): DecrementEnthusiasm {
    return {
        type: constants.DECREMENT_ENTHUSIASM
    }
}

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

这里有明显的样板代码,因此你能够随时查看像redux-actions这样的库当你有相关须要的话。

  • 添加reducer

咱们准备写下咱们的第一个减速机!减小器只是经过建立咱们应用程序状态的修改副本而产生更改的功能,但没有任何反作用。换句话说,它们就是咱们所说的纯功能

咱们的reducer将在src/reducers/index.tsx下。其功能是确保增量提升1点,减量下降1点,但enthusiasm level 不低于1。

// src/reducers/index.tsx

import { EnthusiasmAction } from '../actions';
import { StoreState } from '../types/index';
import { INCREMENT_ENTHUSIASM, DECREMENT_ENTHUSIASM } from '../constants/index';

export function enthusiasm(state: StoreState, action: EnthusiasmAction): StoreState {
  switch (action.type) {
    case INCREMENT_ENTHUSIASM:
      return { ...state, enthusiasmLevel: state.enthusiasmLevel + 1 };
    case DECREMENT_ENTHUSIASM:
      return { ...state, enthusiasmLevel: Math.max(1, state.enthusiasmLevel - 1) };
  }
  return state;
}

注意,咱们正在使用对象spread(... state),它容许咱们建立一个浅状态的副本,同时替换enthusiasmLevel。重要的是,enthusiasmLevel在最后,不然将被旧状态所覆盖。

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

  • 制做container

在使用Redux进行写入时,咱们常常会写入组件以及容器。组件一般与数据无关,而且主要在演示层面上工做。容器一般包装组件并为他们提供显示和修改状态所需的任何数据。您能够在丹·阿布拉莫夫的文章“展现和集装箱组件”上更多地了解这一律念。

首先让咱们更新src/components/Hello.tsx,以便它能够修改状态。咱们将向名为onIncrement和onDecrement的Props添加两个可选回调属性:

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

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

function Hello({ name, enthusiasmLevel = 1, onIncrement, onDecrement }: Props) {
  if (enthusiasmLevel <= 0) {
    throw new Error('You could be a little more enthusiastic. :D');
  }

  return (
    <div className="hello">
      <div className="greeting">
        Hello {name + getExclamationMarks(enthusiasmLevel)}
      </div>
      <div>
        <button onClick={onDecrement}>-</button>
        <button onClick={onIncrement}>+</button>
      </div>
    </div>
  );
}

通常来讲,在点击相应按钮时触发onIncrement和onDecrement的一些测试是一个好主意。给它一个镜头,以得到您的组件的写做测试的悬念。

如今咱们的组件已更新,咱们已经准备好将其包装到一个容器中。咱们建立一个名为src/containers/Hello.tsx的文件,并开始使用如下导入。

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功能。链接将可以实际使用咱们原来的Hello组件,并使用两个功能将其变成容器:

  • mapStateToProps将数据从当前存储区按部件形状组织所需。

  • mapDispatchToProps建立回调道具,以使用给定的调度功能将操做泵送到咱们的商店。

若是咱们记得,咱们的应用状态由两个属性组成:languageName和enthusiasmLevel。另外一方面,咱们的Hello组件预计会有一个名字和一个热情。 mapStateToProps将从商店获取相关数据,并在必要时对咱们组件的props进行调整。让咱们继续。

export function mapStateToProps({ enthusiasmLevel, languageName }: StoreState) {
  return {
    enthusiasmLevel,
    name: languageName,
  }
}

请注意,mapStateToProps仅建立Hello组件指望的属性四个中的2个。也就是说,咱们仍然但愿经过onIncrement和onDecrement回调。 mapDispatchToProps是一个采用调度程序功能的函数。此调度程序功能能够将操做传递到咱们的存储中进行更新,所以咱们能够建立一对能够根据须要调用调度程序的回调函数。

export function mapDispatchToProps(dispatch: Dispatch<actions.EnthusiasmAction>) {
  return {
    onIncrement: () => dispatch(actions.incrementEnthusiasm()),
    onDecrement: () => dispatch(actions.decrementEnthusiasm()),
  }
}

最后,咱们准备好调用connect。 connect将首先使用mapStateToProps和mapDispatchToProps,而后返回另外一个能够用来包装组件的函数。咱们生成的容器由如下代码行定义:

export default connect(mapStateToProps, mapDispatchToProps)(Hello);

完成后,咱们的文件应该以下所示:

// src/containers/Hello.tsx

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

export function mapStateToProps({ enthusiasmLevel, languageName }: StoreState) {
  return {
    enthusiasmLevel,
    name: languageName,
  }
}

export function mapDispatchToProps(dispatch: Dispatch<actions.EnthusiasmAction>) {
  return {
    onIncrement: () => dispatch(actions.incrementEnthusiasm()),
    onDecrement: () => dispatch(actions.decrementEnthusiasm()),
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Hello);
  • 建立store

咱们回到src/index.tsx。为了把这些都放在一块儿,咱们须要建立一个初始状态的store,并将其与全部的reducer进行配置。

import { createStore } from 'redux';
import { enthusiasm } from './reducers/index';
import { StoreState } from './types/index';

const store = createStore<StoreState>(enthusiasm, {
  enthusiasmLevel: 1,
  languageName: 'TypeScript',
});

正如您可能猜到的那样,咱们的store包含了咱们应用程序的全局状态。

接下来,咱们将使用./src/containers/Hello交换咱们对./src/components/Hello的使用,并使用react-redux的Provider将咱们的props与咱们的容器链接起来。咱们将导入每一个:

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

并将咱们的store经过Provider's的属性:
ReactDOM.render(
  <Provider store={store}>
    <Hello />
  </Provider>,
  document.getElementById('root') as HTMLElement
);

注意,Hello再也不须要props,由于咱们使用咱们的链接功能来适应咱们包装Hello组件pops应用程序的状态。

  • Ejecting

若是在任什么时候候,您以为create-react-app设置特定的自定义有些困难,您能够随时选择退出并获取所需的各类配置选项。例如,若是您想添加一个Webpack插件,可能须要利用create-react-app 提供的"eject"功能。

npm run eject

这样你就能够更好地进行工做了。

  • 推荐

create-react-app带有不少好东西。其中大部分记录在为咱们的项目生成的默认README.md中,所以能够快速阅读。

若是您还想了解有关Redux的更多信息,您能够查看官方网站的文档。 MobX也同样。

若是你想在某个时候eject,你可能须要更多地了解Webpack。您能够在这里查看咱们的React&Webpack

在某些时候你可能须要路由。有几个解决方案,可是react-router多是Redux项目中最受欢迎的,而且一般与react-router-redux一块儿使用。


原地址:https://zhuanlan.zhihu.com/p/27847933

相关文章
相关标签/搜索