本入门教程会教你如何连接Typescript和React。你可以获得以下信息:
- 一个Typescript和React的项目
- 使用TSLint做代码规范化
- 使用Jest和Enzyme做测试
- 使用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```)。
当我们写像```
我们这么写 ```Hello.tsx```:
// src/components/Hello.tsximport * as React from 'react';export interface Props { name: string; enthusiasmLevel : number;}function Hello { if { throw new Error; } return }
我们从```Props```来获取我们组件所需要的属性。```name```是一个必要的字符串,```enthusiasmLevel```是一个可选的数字(只要在名字后面加上一个``` ```)。
我们同时还要编写一个无状态的函数组件(SFC)叫做```Hello```。这是一个可以接收```Props```对象并且解析的的函数。如果```enthusiasmLevel```没有给,默认为1。
编写函数是React中建立组件的两种方式中的一种。如果我们愿意,我们可以将它包装成为一个类:
class Hello extends React.Component
当我们的组件实例有一些状态时,类是很有用的。 但是在这个例子中我们并不需要考虑状态 - 实际上我们将它指定为```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起使用。
免责声明:本平台仅供信息发布交流之途,请谨慎判断信息真伪。如遇虚假诈骗信息,请立即举报
举报