मेरे पास पेड़ जैसी डेटा संरचना है। मैं प्रत्येक पेड़ नोड्स को नोड-रैपर के साथ लपेटना चाहता हूं, जो नोड में अतिरिक्त कार्यक्षमता जोड़ता है। लेकिन साथ ही मैं टाइपिंग को बरकरार रखना चाहता हूं। क्या कोई इसमें मेरी मदद कर सकता है?

यहां एक उदाहरण दिया गया है जो दिखा रहा है कि मैं क्या हासिल करना चाहता हूं

const a = { children: [], value: 12 } as Node<number>;
const b = { children: [], value: 'string' } as Node<string>;
const c = { children: [b, a], value: true } as Node<boolean>;

const root = { children: [c]; value: null } as Node<null>;

// wrap node data with some functions, but keep typings stable
const wrappedNode = wrapNode(root);

wrappedNode.value; // type should be => null
wrappedNode.children[0].value; // type should be => boolean
wrappedNode.children[0].children[0].value; // type should be => string
wrappedNode.children[0].children[1].value; // type should be => number

मेरा वर्तमान दृष्टिकोण इस तरह दिखता है:

interface Node<T, C extends Node<unknown, any[]>[]> {
  children: [...C];
  value: T;
}

interface WrapNode<T, C extends Node<unknown, any[]>[]> {
  children: WrapNode<any, [...C]>[];
  value: T;
  computeValue(): any;
}

function createNode<T, C extends Node<unknown, any[]>[]>(value: T, children: [...C]): Node<T, [...C]> {
  return {
    value,
    children,
  };
}

export function wrapNode<T, C extends Node<any, any>[]>(node: Node<T, C>): WrapNode<T, typeof node.children> {
  const value = node.value;

  return {
    ...node,
    computeValue: () => value,
    children: node.children.map(child => wrapNode(child)),
    //                          ^^^^^    ^^^^^^^^^^^^^^^
    //           types are:   C[number]  wrapNode<any, any>(n: Node<any, any>)
  };
}

const a = createNode(12, []);
const b = createNode('str', []);
const c = createNode(null, [b, a]);

const x = wrapNode(c);

x.value; // gives me type null, ok!
x.children[0].value; // gives me any :(
x.children[1].value; // gives me any too :(

क्या यह टाइपस्क्रिप्ट के साथ भी संभव है? मैं टाइपस्क्रिप्ट 4.0.2 का उपयोग कर रहा हूं, अगर इससे मदद मिलती है। अग्रिम में धन्यवाद :)

1
jlang 17 सितंबर 2020, 22:29

1 उत्तर

सबसे बढ़िया उत्तर

मुझे लगता है कि आप चाहते हैं कि आपका WrapNode इंटरफ़ेस इस तरह दिखे:

interface WrapNode<T, C extends Node<unknown, any[]>[]> {
    children: { [K in keyof C]:
        C[K] extends Node<infer CT, infer CC> ? WrapNode<CT, CC> : never
    }
    value: T,
    computeValue(): any,
}

यहां महत्वपूर्ण अंतर यह है कि children संपत्ति एक मैप्ड ऐरे/टुपल टाइप जो children के सांख्यिक सूचकांकों के माध्यम से चलता है और प्रत्येक तत्व का प्रकार प्राप्त करता है।

इस बात की बहुत अधिक संभावना नहीं है कि कंपाइलर वास्तव में सत्यापित कर सकता है कि कोई विशेष मान ऐसे मैप किए गए प्रकार के लिए असाइन करने योग्य है, इसलिए आप शायद अभिकथन लिखें

function wrapNode<T, C extends Node<unknown, any[]>[]>(node: Node<T, C>): WrapNode<T, C> {
    const value = node.value;
    return {
        ...node,
        computeValue: () => value,
        children: node.children.map(wrapNode) as any, // need to assert here
    };
}

और फिर सब कुछ वैसा ही काम करता है जैसा आप चाहते हैं, मुझे लगता है:

const x = wrapNode(c);
x.children[0].value.toUpperCase(); // okay
x.children[0].value.toFixed(2); // error! string can't do that
x.children[1].value.toFixed(2); // okay
x.children[1].value.toUpperCase(); // error! number can't do that

कोड के लिए खेल का मैदान लिंक

4
jcalz 17 सितंबर 2020, 23:35