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

相關文章
相關標籤/搜索