blog

TSコア知識のまとめとプロジェクト事例

最近、仕事が非常に忙しく、レビューサイクルも長くなっていますが、それでも週次レビューを遵守しています。今日、私はフロントエンドのプロジェクトで、アプリケーションを確認します、なぜ我々は学ぶ必要があると...

Apr 19, 2020 · 13 min. read
シェア

序文

最近、仕事が非常に忙しく、レビューサイクルも長くなっていますが、それでも週次レビューを遵守します。今日、私はフロントエンドのプロジェクトでtypescriptのアプリケーションをレビューしますが、なぜ我々はtypescriptを学ぶ必要があるとして、私は我々が自明だと思う、現在の主流のフレームワークvueとreactとtypescriptの使用のほとんどの関連する生態学的な内部構造は、その理由は、その静的な型チェックが大幅にコードの可読性と保守性を向上させることです。保守性が大幅に向上し、問題を見つけるのが非常に簡単だからです。以下はtypescriptの公式定義です:

TypeScriptはMicrosoftによって開発されたフリーでオープンソースのプログラミング言語で、ECMAScript 6標準をサポートするJavaScriptのスーパーセットです。大規模なアプリケーションの開発向けに設計されており、純粋なJavaScriptにコンパイルすることで、どのブラウザでも動作させることができます。

この記事では、typescriptを簡単にマスターできるように、tsのコアとなる知識と実践的な例を紹介します。

概要

正文

現在最も使われているプロジェクト開発はwebpackですが、tsの場合はコンパイルでts-loaderを設定できるのも非常に便利で、tsの学習の難易度を下げるために、vue-cli3やumiを使ってtsプロジェクトを直接ビルドすることで、より早くtsの開発を始められるようにするのがおすすめです。

コア・ナレッジ・ポイント

1.基本タイプ

TypeScriptはJavaScriptとほぼ同じデータ型をサポートし、さらに使いやすくするために便利な列挙型を提供しています。これらの型の使い方を簡単に説明します。

// ブール型
let isCookie:boolean = true
// 数値型
let myMoney:number = 12
// 文字列型
let name:string = ' '
// 配列型には2つの表現方法がある。[]まず最初に、このタイプの要素の配列を作成する。
let arr:number[] = [1,2,2]
// 配列型、配列ジェネリックを使う
let arr:Array<number> = [1,2,2]
// タプル型は、要素の数と型が既知の配列を表すことができる。
let xi: [string, number];
//  xi
xi = ['xu', 10]; //  
xi = [11, 'xu']; //  
// 列挙型、値の集合に親しみやすい名前をつけることができる
enum ActionType { doing, done, fail }
let action:ActionType = ActionType.done // 1
// any, どのような型でも表現でき、これらの値の型チェッカーをバイパスできる
let color:any = 1
color = 'red'
// void関数に戻り値がない場合、通常、戻り値の型は void
function getName(): void {
 console.log("This is my name");
}
// objectTypeは、非プリミティブ型、つまり数値、文字列、ブーリアン、シンボル、null、undefined以外の型を表す。
let a:object;
a = {props: 1}

これらはtypescriptでよく使われる型の一部であり、マスターしなければならない基本です。 また、typescriptの型アサーションはtsの警告を解決するための素晴らしいツールであることも付け加えておきます。

let arrLen: number = (someValue as Array<string>).length;
// window set property tsのエラーを解決する。
(window as any).name = 'xuxi'

2.インターフェイス

TypeScriptの基本原則の1つは、値が持つ構造を型チェックすることです。 TypeScriptにおけるインターフェースの役割は、これらの型に名前を付け、自分のコードやサードパーティのコードのためにコントラクトを定義することです。それでは、インターフェースの定義と使い方を見ていきましょう。

interface Product {
 name: string;
 size: number;
 weight: number;
}
let product1:Product = {
	name: 'machine1',
 size: 20,
 weight: 10.5
}

対応する属性が存在し、正しい型である限り、型チェッカは属性の順序をチェックしません。次に、オプショナル属性と読み取り専用属性を定義できます。 オプショナル属性は、インタフェースのいくつかのプロパティが必須ではないことを示します。読み取り専用属性は、読み取ることはできますが、割り当てることはできないインタフェースのプロパティです。 例を以下に示します。

interface Product {
 name: string;
 size?: number;
 readonly weight: number;
}

実際のシナリオでは、しばしばまた、属性名と属性値の型の不確実性の場合に遭遇し、このような状況は、しばしば第三SDKのアクセスまたはバックエンドの応答で発生し、この時間は、インデックス署名を使用して追加の属性と型を設定することができます、例は次のとおりです。

interface App {
 name: string;
 color?: number;
 [propName: string]: any;
}

インターフェースは、属性を持つ通常のオブジェクトに加えて、関数型を記述することができます。インターフェイスの呼び出しシグネチャを定義し、パラメータ・リストに各パラメー タの名前と型を記述する必要があります。例を以下に示します。

interface MyFunc {
 (value:string, type: number): boolean;
}
//  
let myLoveFront: MyFunc;
myLoveFront = function(value:string, type: number) {
 	return type > 1
}

vueやreactの開発では、再利用可能なコンポーネントやライブラリを記述するためにclassを使用することがよくあります。 答えはイエスです。しかし、クラスのインターフェースの定義は少し複雑で、クラスには2つの型があります。 クラスがインターフェイスを実装している場合、型チェックされるのはそのインスタンスだけです。コンストラクタはクラスの静的な部分に存在し、チェックされません。 この記述は非常に重要で、次の例のようにクラス・インタフェースを定義する際には、この特徴に注目することが重要です。

interface TickConstructor {
 new (hour: number, minute: number): TickInterface;
}
interface TickInterface {
 tick();
}
function createClock(ctor: ClockConstructor, hour: number, minute: number): TickInterface {
 return new ctor(hour, minute);
}
class DigitalClock implements TickInterface {
 constructor(h: number, m: number) { }
 tick() {
 console.log("xu xu");
 }
}
class MyTick implements TickInterface {
 constructor(h: number, m: number) { }
 tick() {
 console.log("tick tock");
 }
}
let digital = createClock(DigitalTick, 12, 17);
let analog = createClock(MyTick, 7, 32);

これらの主要なタイプのインターフェイスとその使い方をマスターすれば、基本的にtsについて学び始める準備はできています。

3.クラス

クラスインターフェイスの話題は上記でカバーしましたので、ここではクラスについて知っていることを説明します。 jsのクラスと同じように、typescriptのクラスにもpublic、private、protected修飾子があります。 その意味は以下の通りです。

  • public TypeScriptでは、メンバはデフォルトでpublicなので、アプリケーションで定義されたメンバに自由にアクセスできます。
  • private メンバが private とマークされると、宣言されたクラスの外部からはアクセスできなくなります。
  • protected は private に似ていますが、protected メンバは派生クラスでもアクセスできます。

具体的な事例は以下の通り。

class Person {
 protected name: string;
 constructor(name: string) { this.name = name; }
}
class Employee extends Person {
 private department: string;
 constructor(name: string, department: string) {
 super(name)
 this.department = department;
 }
 public getElevatorPitch() {
 return `Hello, my name is ${this.name} and I work in ${this.department}.`;
 }
}

クラス内のプロパティに readonly 修飾子を定義したり、静的プロパティを定義することも可能ですが、特筆すべきは抽象クラスだけです。

抽象クラスは、他の派生クラスの基本クラスとして使用されます。 通常、直接インスタンス化されることはありません。 インターフェイスとは異なり、抽象クラスにはメンバの実装の詳細を含めることができます。 abstract キーワードは、抽象クラスを定義したり、抽象クラス内で抽象メソッドを定義するために使用します。

抽象クラスのケースを簡単に説明すると、以下のようになります。

abstract class MyAbstract {
 constructor(public name: string) {}
 say(): void {
 console.log('say name: ' + this.name);
 }
 abstract sayBye(): void; // 派生クラスで実装しなければならない
}
class AccountingMyAbstract extends MyAbstract {
 constructor() {
 super(' '); // 派生クラスのコンストラクタで super()
 }
 sayBye(): void {
 console.log('Xiaoxiについての興味深い話.');
 }
 getOther(): void {
 console.log('loading...');
 }
}
let department: MyAbstract; // 抽象型への参照を作成できるようにする
department = new MyAbstract(); //  : 抽象クラスのインスタンスは作れない
department = new AccountingMyAbstract(); // 抽象サブクラスをインスタンス化して値を代入できるようにする
department.say();
department.sayBye();
department.getOther(); //  : 宣言された抽象クラスにはメソッドが存在しない

4.機能

関数型についてはすでに説明しましたが、ここではオプショナル・パラメータの概念に焦点を当てます。 JavaScriptでは、すべてのパラメータはオプショナルであり、渡されることもあれば、渡されないこともあります。 TypeScriptでは、パラメータ名の横に?をパラメータ名の横につけます。 以下はその例です。

function createName(firstName: string, lastName?: string) {
 if (lastName)
 return firstName + " " + lastName;
 else
 return firstName;
}

オプションのパラメーターの後には必須のパラメーターを続けなければならないことに注意してください。

5.パンタイプ

ジェネリックスは再利用可能なコンポーネントを作成するために使用され、コンポーネントは複数のデータ型をサポートすることができます。 これにより、ユーザは独自のデータ型でコンポーネントを使用することができます。ジェネリックスはtypescriptで理解するのが難しいものの1つですが、サードパーティのコンポーネントライブラリのほとんどで使われているほど重要なものです。 簡単な例から始めましょう。

function iSay<T>(arg: T): T {
 return arg;
}
// 汎用関数を呼び出す
let come = iSay<number>(123); 

iSayに型変数Tを追加しました。 Tは、ユーザーから渡された型をキャプチャするのに役立ち、その型を使用できるようにします。T を返り値の型として使用します。これで、パラメータの型が戻り値の型と同じであることを知ることができます。 これにより、関数で使用された型に関する情報を追跡することができます。

また、以下の例のように、型全体ではなく型の一部としてジェネリック変数Tを使用することも可能です。

function iSay<T>(arg: T[]): T[] {
	console.log(arg.length)
 return arg;
}

関数型の定義と同様に、ジェネリック・インターフェースを定義し、ジェネリック・パラメータをインターフェイス全体のパラメータとして扱うことで、どのジェネリック型が使用されているかを明確にすることができます。 コード例は以下の通りです。

interface SayLove {
	<T>(arg: T): T
}
// ジェネリック・パラメータはインターフェース全体のパラメータと考える
interface SayLoveArg<T> {
	(arg: T): T
}
// 汎用関数
function iSay<T>(arg: T): T {
 return arg;
let mySay1:SayLove = iSay
let mySay2:SayLoveArg<number> = iSay

ジェネリック・クラスを定義することも可能です。一般的な型を括弧で囲み、その後にクラス名を続けるだけです。 例を以下に示します。

class MyNumber<T> {
 year: T;
 compute: (x: T, y: T) => T;
}
let myGenericNumber = new MyNumber<number>();

クラスの型をより正確に制御するために、ジェネリック制約を定義することもできます。 例を以下に示します。

interface NumberControl {
	length: number
}
class MyObject<T extends NumberControl>(arg: T):T {
	console.log(arg.length)
 return arg
}

6.高度な類型

Typescriptの高度なタイピングは、以下のコアとなる知識に重点を置いています。

  • クロスタイプ
  • ユニオン型
  • ポリモーフィックな this型
  • インデックス型クエリ演算子
  • さくいんアクセスえんざんし
function extend<T, U>(first: T, second: U): T & U {
 let result = <T & U>{};
 for (let id in first) {
 (<any>result)[id] = (<any>first)[id];
 }
 for (let id in second) {
 if (!result.hasOwnProperty(id)) {
 (<any>result)[id] = (<any>second)[id];
 }
 }
 return result;
}

この場合、上記のコードの戻り値はTとUの型になります

結合型は、値が複数の型のいずれかになり得ることを示します。 それぞれの型は縦線で区切りますので、number | string | boolean は、次の例のように、値が数値、文字列、ブール値のいずれかになり得ることを意味します。

let name: string | number = 'xuxiaoxi'
function sayName(name: string):(string|number) { 
 return name 
}

注釈 値がユニオン型の場合は、ユニオン型のすべての型に共通するメンバにのみアクセスできます。

また、thisを返すべき独自のクラスを実装した後、クラスメソッドの連鎖をサポートしたいというニーズもよくありますが、タイプスクリプトではポリモーフィックなthis型を理解する必要があります。this型は、typescriptで知っておくべきポリモーフィズムの一種です。 これは、クラスやインターフェイスを囲むサブタイプを表します。 これは F-bounded polymorphism と呼ばれます。typescriptで連鎖をサポートするには、このように記述します。

class MyCalculator {
 public constructor(number = 0) { }
 public add(n: number): this {
 this.value += n;
 return this;
 }
 public multiply(n: number): this {
 this.value *= n;
 return this;
 }
 // ... その他 ...
}
let v = new MyCalculator(2).multiply(5).add(1);

次に知っておくべき点は、インデックス型クエリ演算子です。 これは一般的にkeyofで表されます。 任意の型 T に対して、keyof T の結果は、T で既知のパブリック属性の名前の和です。 例えば、インターフェース Animal を定義します。

interface Animal {
 cat: string;
 dog: string;
}
let AnimalProps: keyof Animal; // 'cat' | 'dog'

keyofのAnimalは'cat' | 'dog'と完全に互換性があります。 違いは、Animalに他の属性、例えば pig: stringを追加すると、keyof Animalは自動的に'cat' | 'dog' | 'pig'になるということです。

7.名前空間

名前空間は主に、他のオブジェクトとの名前の衝突を気にせずに型を記録できるようにコードを整理するために使用されます。 名前空間の使い方はとてもシンプルなので、ここではよりポピュラーなD3を例にして、コードは次のようになります。

declare namespace D3 {
 export interface Selectors {
 select: {
 (selector: string): Selection;
 (element: EventTarget): Selection;
 };
 }
 export interface Event {
 x: number;
 y: number;
 }
 export interface Base extends Selectors {
 event: Event;
 }
}
declare var d3: D3.Base;

8.サードパーティライブラリを使う上記の基本に慣れたら、typescriptをサポートするサードパーティライブラリの使い方を見てみましょう。 例えば、人気のあるlodashライブラリを使う場合、正しい使い方は以下のようになります。

// lodashと対応する型ファイルをインストールする
npm install --save lodash @types/lodash
// 使用するコードで
import * as _ from "lodash";
_.padStart("Hello xuxiaoxi!", 12, " ");
// global.d.ts
// グローバル変数の宣言
declare var name: string;
// グローバル関数
declare function say(name: string): void;
// 属性を持つオブジェクト
declare namespace myObj {
 function say(s: string): string;
 let money: number;
}
// 再利用可能なインターフェース
interface Animal {
	kind: string;
 age: number;
 weight?: number;
}
declare function findAnmiate(animal:Animal):void

もちろん、定義できる便利な宣言は他にもたくさんあるので、ここですべてを列挙することはしません。

React + Typescriptプロジェクトの実例

1.umiを使ってreact+typescriptプロジェクトをビルドします。

2.脱グローバリズム文書の定義

プロジェクトのsrcディレクトリにglobal.d.tsファイルを作成し、グローバル宣言とサードパーティライブラリとの互換性を処理します。

3.ツール・ライブラリを実装するためにtsを使用する srcディレクトリにutilsディレクトリを作成し、ツールや共通ライブラリを格納します。例えば、共通ツール関数としてutilsの下に新しいtool.tsを作成します。

/**
*  uuid
*/
const uuid = ():string => {
    let s:Array<any> = [];
    let hexDigits:string = "abcdef";
    for (let i = 0; i < 36; i++) {
        s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
    }
    s[14] = "4"; 
    s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); 
    s[8] = s[13] = s[18] = s[23] = "-";
    let uuid = s.join("");
    return uuid;
};
// 外部から導入できるtype.ts 
interface Params {
 [propName: string]: string | number
}
/**
 * reverseJson オブジェクトのキーと値のペアを逆にする
 * @param {object} obj 反転させるオブジェクト
 * @param {object} target 対象オブジェクトの反転
 */
const reverseJson = (obj:Params = {}, target:Params = {}):Params => {
 Object.keys(obj).forEach((key:string) => { target[obj[key]] = key })
 return target
}

上記はほんのいくつかの簡単なケースですが、完璧ではありません、あなたは対応するパッケージを達成するために、独自のニーズに応じてすることができます。

4.Reactコンポーネントでのtypescriptの使用

次回もこの章を実践して、実際のタイプスクリプト開発を具体的に理解していただきたいと思います。

究極

その他のおすすめ

Read next

機能モジュールの言及テストの前にやるべきことがいくつかある

要件フェーズと開発フェーズは最も重要であり、一つは設計であり、機能がどのように動作するかを定義し、もう一つは実装と実現であり、これらの2つのフェーズがよく制御されている場合、次のステップははるかにスムーズになります。以下では、テストの品質を確保するために何を準備すべきかの前に、テストステップの開発段階に焦点を当てます。 理論的には、開発者はテストが終われば、他の要求タスクに取り掛かることができます。 ただ...

Apr 19, 2020 · 5 min read