TypeScriptで固定長配列を宣言する方法


104

TypeScript型に関する知識が不足していることを示すリスクがあります-次の質問があります。

このような配列の型宣言をすると...

position: Array<number>;

...任意の長さの配列を作成できます。ただし、特定の長さの数値を含む配列が必要な場合、つまり、x、y、zコンポーネントの3は、次のような固定長配列の型を作成できますか?

position: Array<3>

どんな助けや説明も感謝します!

回答:


164

JavaScript配列には、配列の長さを受け入れるコンストラクターがあります。

let arr = new Array<number>(3);
console.log(arr); // [undefined × 3]

ただし、これは単なる初期サイズであり、変更に制限はありません。

arr.push(5);
console.log(arr); // [undefined × 3, 5]

Typescriptには、特定の長さと型の配列を定義できるタプル型があります。

let arr: [number, number, number];

arr = [1, 2, 3]; // ok
arr = [1, 2]; // Type '[number, number]' is not assignable to type '[number, number, number]'
arr = [1, 2, "3"]; // Type '[number, number, string]' is not assignable to type '[number, number, number]'

18
タプルタイプは初期サイズのみをチェックするため、arr初期化後も無制限の「数値」をプッシュできます。
ベンジャミンズ2017

4
確かに、その時点ではまだ「何でも」というのは実行時のJavaScriptです。少なくともtypescriptトランスパイラーはこれをソースコードで少なくとも強制します
henryJack

6
たとえば50のような大きな配列サイズが必要な場合、50のよう[number[50]]に書き込む必要がないように、などの繰り返し型で配列サイズを指定する方法はあります[number, number, ... ]か?
ビクターザマニアン2018年

2
気にしないで、これに関する質問を見つけました。stackoverflow.com/questions/52489261/...
ビクターZamanian

1
@VictorZamanianちょうどあなたが知っているように、交差するという考え{length: TLength}はtypedを超えてもtypescriptエラーを提供しませんTLength。サイズが強制されたn長の型の構文はまだ見つかりません。
Lucas Morgan

22

タプルアプローチ:

このソリューションは、 タプルに基づく厳密なFixedLengthArray(ak.a. SealedArray)型シグネチャを提供します。

構文例:

// Array containing 3 strings
let foo : FixedLengthArray<[string, string, string]> 

これは、境界外のインデックスへのアクセスを防ぐこと考えると、最も安全なアプローチです。

実装:

type ArrayLengthMutationKeys = 'splice' | 'push' | 'pop' | 'shift' | 'unshift' | number
type ArrayItems<T extends Array<any>> = T extends Array<infer TItems> ? TItems : never
type FixedLengthArray<T extends any[]> =
  Pick<T, Exclude<keyof T, ArrayLengthMutationKeys>>
  & { [Symbol.iterator]: () => IterableIterator< ArrayItems<T> > }

テスト:

var myFixedLengthArray: FixedLengthArray< [string, string, string]>

// Array declaration tests
myFixedLengthArray = [ 'a', 'b', 'c' ]  // ✅ OK
myFixedLengthArray = [ 'a', 'b', 123 ]  // ✅ TYPE ERROR
myFixedLengthArray = [ 'a' ]            // ✅ LENGTH ERROR
myFixedLengthArray = [ 'a', 'b' ]       // ✅ LENGTH ERROR

// Index assignment tests 
myFixedLengthArray[1] = 'foo'           // ✅ OK
myFixedLengthArray[1000] = 'foo'        // ✅ INVALID INDEX ERROR

// Methods that mutate array length
myFixedLengthArray.push('foo')          // ✅ MISSING METHOD ERROR
myFixedLengthArray.pop()                // ✅ MISSING METHOD ERROR

// Direct length manipulation
myFixedLengthArray.length = 123         // ✅ READ-ONLY ERROR

// Destructuring
var [ a ] = myFixedLengthArray          // ✅ OK
var [ a, b ] = myFixedLengthArray       // ✅ OK
var [ a, b, c ] = myFixedLengthArray    // ✅ OK
var [ a, b, c, d ] = myFixedLengthArray // ✅ INVALID INDEX ERROR

(*)このソリューションを機能させるには、noImplicitAnytypescript 構成ディレクティブを有効にする必要があります(一般的に推奨される方法)


Array(ish)アプローチ:

このソリューションは、Array型の拡張として動作し、追加の2番目のパラメーター(配列の長さ)を受け入れます。タプルベースのソリューションほど厳格で安全ではありません。

構文例:

let foo: FixedLengthArray<string, 3> 

このアプローチは、宣言された境界外のインデックスにアクセスして値を設定することを妨げるものではないことに注意してください。

実装:

type ArrayLengthMutationKeys = 'splice' | 'push' | 'pop' | 'shift' |  'unshift'
type FixedLengthArray<T, L extends number, TObj = [T, ...Array<T>]> =
  Pick<TObj, Exclude<keyof TObj, ArrayLengthMutationKeys>>
  & {
    readonly length: L 
    [ I : number ] : T
    [Symbol.iterator]: () => IterableIterator<T>   
  }

テスト:

var myFixedLengthArray: FixedLengthArray<string,3>

// Array declaration tests
myFixedLengthArray = [ 'a', 'b', 'c' ]  // ✅ OK
myFixedLengthArray = [ 'a', 'b', 123 ]  // ✅ TYPE ERROR
myFixedLengthArray = [ 'a' ]            // ✅ LENGTH ERROR
myFixedLengthArray = [ 'a', 'b' ]       // ✅ LENGTH ERROR

// Index assignment tests 
myFixedLengthArray[1] = 'foo'           // ✅ OK
myFixedLengthArray[1000] = 'foo'        // ❌ SHOULD FAIL

// Methods that mutate array length
myFixedLengthArray.push('foo')          // ✅ MISSING METHOD ERROR
myFixedLengthArray.pop()                // ✅ MISSING METHOD ERROR

// Direct length manipulation
myFixedLengthArray.length = 123         // ✅ READ-ONLY ERROR

// Destructuring
var [ a ] = myFixedLengthArray          // ✅ OK
var [ a, b ] = myFixedLengthArray       // ✅ OK
var [ a, b, c ] = myFixedLengthArray    // ✅ OK
var [ a, b, c, d ] = myFixedLengthArray // ❌ SHOULD FAIL

1
ありがとう!ただし、エラーが発生することなく配列のサイズを変更することは可能です。
エデュアルド

1
var myStringsArray: FixedLengthArray<string, 2> = [ "a", "b" ] // LENGTH ERRORここでは2は3でなければならないようですか?
Qwertiy

配列の長さの変更を防ぐより厳密なソリューションで実装を更新しました
colxi

@colxi FixedLengthArrayから他のFixedLengthArrayへのマッピングを可能にする実装を持つことは可能ですか?私の意味する例:const threeNumbers: FixedLengthArray<[number, number, number]> = [1, 2, 3]; const doubledThreeNumbers: FixedLengthArray<[number, number, number]> = threeNumbers.map((a: number): number => a * 2);
Alex Malcolm

@AlexMalcolm私はmapその出力に一般的な配列の署名を提供することを恐れています。あなたの場合、おそらくnumber[]タイプ
colxi

5

実際、これは現在のtypescriptで実現できます。

type Grow<T, A extends Array<T>> = ((x: T, ...xs: A) => void) extends ((...a: infer X) => void) ? X : never;
type GrowToSize<T, A extends Array<T>, N extends number> = { 0: A, 1: GrowToSize<T, Grow<T, A>, N> }[A['length'] extends N ? 0 : 1];

export type FixedArray<T, N extends number> = GrowToSize<T, [], N>;

例:

// OK
const fixedArr3: FixedArray<string, 3> = ['a', 'b', 'c'];

// Error:
// Type '[string, string, string]' is not assignable to type '[string, string]'.
//   Types of property 'length' are incompatible.
//     Type '3' is not assignable to type '2'.ts(2322)
const fixedArr2: FixedArray<string, 2> = ['a', 'b', 'c'];

// Error:
// Property '3' is missing in type '[string, string, string]' but required in type 
// '[string, string, string, string]'.ts(2741)
const fixedArr4: FixedArray<string, 4> = ['a', 'b', 'c'];

1
要素の数が変数である場合、これをどのように使用しますか?数値タイプとしてNを、数値として「num」を指定した場合、const arr:FixedArray <number、N> = Array.from(new Array(num)、(x、i)=> i); 「型のインスタンス化は過度に深く、おそらく無限です」と私にくれます。
Micha Schwab

2
@MichaSchwab残念ながら、それは比較的小さな数でのみ機能するようです。それ以外の場合は、「再帰が多すぎます」と伝えます。同じことがあなたのケースにも当てはまります。私は徹底的にテストしていない:(。
トマシュGawel

私に戻ってきてくれてありがとう!可変長の解決策を見つけた場合は、お知らせください。
Micha Schwab
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.