javascript - TypeScript: Kräva att två arrayer har samma längd?
Säg att jag gör en Tab + Panel-komponent som kallas TabsPanels. Jag vill säkerställa att jag får samma antal Tab- och Panel-komponenter, på följande sätt:
type TabsPanelsProps = {
tabs: Tab[];
panels: Panel[];
}
<TabsPanels
tabs={[<Tab/>, <Tab/>]}
panels={[<Panel/>]} // Error: tabs.length and panels.length do not match
/>
Finns det något sätt att göra detta? Om det fanns någon nytta-funktion som
PropsAreEqual<T, K1, K2, P>
where
T = type
K1 = key 1
K2 = key 2
P = the property to be equal
Det är uppenbarligen dåligt, men du förstår vad jag menar. Då skulle du kunna göra
PropsAreEqual<TabsPanelsProps, 'tabs', 'panels', 'length'>
Svar #1
Jag ser några svar med frågor kring att härleda längden på array-litteraler. Problemet är att när du skickar in en array-litteral till en funktion, vidgar kompilatorn den generellt till en array och tolkar den inte som en tuple med fix längd. Detta är ofta det man vill; ofta ändras arrays i längd. När du vill att kompilatorn ska se [1, 2] som ett par och inte som en array, kan du ge kompilatorn en ledtråd:
function requireTwoSameLengthArrays<
T extends readonly [] | readonly any[]
>(t: T, u: { [K in keyof T]: any }): void { }
Observera att den generiska typparametern Ts generiska begränsning är en union av en tom tupltyp [] och en arraytyp any[]. (Oroa dig inte för readonly; denna modifierare gör funktionen mer generell, inte mer specifik, eftersom string[] kan tilldelas till readonly string[] och inte tvärtom.) Att ha den tomma tupltypen i en union förändrar inte slagen av saker som T kan vara (eftersom any[] redan inkluderar den tomma tuplen []). Men det ger kompilatorn en ledtråd om att tupltyper är önskvärda.
Så kompilatorn kommer att härleda [1, 2] som [number, number] istället för som number[].
Genom att granska signaturen ovan ser du att u-argumentet är en mappad array-/tuple-typ. Om T är en tuple, är {[K in keyof T]: any} en tuple med samma längd som T.
Så låt oss se det i aktion:
requireTwoSameLengthArrays([1, 2], [3, 4]); // okay
requireTwoSameLengthArrays([1, 2], [3]); // error! property 1 is missing in [number]!
requireTwoSameLengthArrays([1, 2], [3, 4, 5]); // error! length is incompatible!
Hurra!
Observera att om kompilatorn redan har glömt längden på tuplen, kommer detta inte att fungera:
const oops = [1, 2]; // number[]
requireTwoSameLengthArrays(oops, [1, 2, 3]); // okay because both are of unknown length
Typen av oops härleds som number[], och att skicka in den i requireTwoSameLengthArrays() kan inte ångra den härledningen. Det är för sent. Om du vill att kompilatorn bara ska avvisa arrayer med helt okänd längd, kan du göra så här:
function requireTwoSameLengthTuples<
T extends (readonly [] | readonly any[]) & (
number extends T["length"] ? readonly [] : unknown
)>(t: T, u: { [K in keyof T]: any }): void { }
Detta är fulare, men det som det gör är att kontrollera om T har en längd på number istället för ett specifikt numeriskt litteral. Om så är fallet förhindrar det matchningen genom att kräva en tom tuple. Detta är lite konstigt, men det fungerar:
requireTwoSameLengthTuples([1, 2], [3, 4]); // okay
requireTwoSameLengthTuples([1, 2], [3]); // error! [number] not [any, any]
requireTwoSameLengthTuples([1, 2], [3, 4, 5]); // error! ]number, number, number]
requireTwoSameLengthTuples(oops, [1, 2, 3]); // error on oops!
// Types of property 'length' are incompatible.
Okej, hoppas det hjälper; lycka till!
Lekplatslänk till kod
