TypeScript-ന്റെ പുതിയ const type parameter സിന്റാക്സ് ഉപയോഗിച്ച്, കോൾ ചെയ്യുന്നവർ എല്ലായിടത്തും as const ചേർക്കേണ്ടി വരാതെ തന്നെ ഒരു ഫങ്ക്ഷന് ലിറ്ററൽ ടൈപ്പുകൾ (literal types) അതേപടി നിലനിർത്താൻ സാധിക്കും. ഇത് ടൈപ്പ് വൈഡ്‌നിംഗ് (type-widening) ബഗുകൾ ഉണ്ടാകാനുള്ള പ്രധാന കാരണത്തെ ഒഴിവാക്കുന്നു.

ജനറിക് കോഡുകളെ ബാധിക്കുന്ന വൈഡ്‌നിംഗ് പ്രശ്നം

ഒരു ജനറിക് ഫങ്ക്ഷൻ ഒരു ഒബ്‌ജക്റ്റ് ലിറ്ററൽ സ്വീകരിക്കുമ്പോൾ, കംപൈലർ അതിലെ ലിറ്ററൽ പ്രോപ്പർട്ടികളെ അവയുടെ വിപുലമായ പ്രിമിറ്റീവ് ടൈപ്പിലേക്ക് (primitive type) മാറ്റുന്നു (widen ചെയ്യുന്നു).

function call<T>(arg: T) {}
call({ method: "GET" })   // T is inferred as { method: string }

"GET" എന്ന ലിറ്ററൽ string ആയി മാറുന്നു. ഡിസ്ക്രിമിനേറ്റഡ് യൂണിയനുകൾ (discriminated unions) അല്ലെങ്കിൽ ടെംപ്ലേറ്റ്-ലിറ്ററൽ എക്സ്ട്രാക്ഷൻ (template-literal extraction) പോലുള്ള കൃത്യമായ വാല്യൂവിൽ ആശ്രയിക്കുന്ന ഡൗൺസ്ട്രീം കോഡുകൾ ഇതിനാൽ തകരാറിലാകുന്നു, കാരണം ടൈപ്പിന് ഇനി ആ കൃത്യമായ ലിറ്ററൽ ലഭ്യമല്ല. ഇതിന് പരിഹാരമായി ഡെവലപ്പർമാർ കോൾ സൈറ്റിൽ { method: "GET" } as const എന്ന് എഴുതി കുറെ കാലമായി ഉപയോഗിക്കുന്നു. ഇത് കംപൈലറോട് ലിറ്ററൽ നിലനിർത്താൻ നിർദ്ദേശിക്കുന്നുണ്ടെങ്കിലും, ഈ പരിഹാരം ഫങ്ക്ഷന്റെ നിർവചനത്തിലല്ല (definition), മറിച്ച് കോൾ ചെയ്യുന്ന ആളുടെ കയ്യിലാണ്.

Const type parameters: ഒരു സിഗ്നേച്ചർ ലെവൽ പരിഹാരം

ഒരു ടൈപ്പ് പാരാമീറ്ററിൽ നൽകുന്ന പുതിയ const മോഡിഫയർ, ആ ജനറിക് ആർഗ്യുമെന്റിന് ഏറ്റവും ഇടുങ്ങിയ (narrowest possible) ടൈപ്പ് കണ്ടെത്താൻ കംപൈലറോട് നിർദ്ദേശിക്കുന്നു. ഒരു ഫങ്ക്ഷനെ function foo<const T>(arg: T) എന്ന് പ്രഖ്യാപിക്കുന്നത് വഴി, കോൾ ചെയ്യുന്നയാൾ as const എന്ന് എഴുതിയതുപോലെ T പ്രവർത്തിക്കും.

  • String, number, boolean ലിറ്ററലുകൾ അവയുടെ കൃത്യമായ വാല്യൂസ് തന്നെ നിലനിർത്തുന്നു (string-ന് പകരം "GET").
  • അറേകൾ (Arrays) ഓരോ എലമെന്റും കൃത്യമായ ടൈപ്പുള്ള റീഡ്‌ഓൺലി ട്യൂപ്പിൾസായി (readonly tuples) മാറുന്നു.
  • ഒബ്‌ജക്റ്റുകൾ എല്ലാ നെസ്റ്റിംഗ് ലെവലുകളിലും ലിറ്ററൽ ടൈപ്പുകൾ നിലനിർത്തിക്കൊണ്ട് ഡീപ്‌ലി റീഡ്‌ഓൺലി (deeply readonly) സ്ട്രക്ചറുകളായി മാറുന്നു.

ഈ നിയന്ത്രണം ഫങ്ക്ഷൻ സിഗ്നേച്ചറിൽ തന്നെ ഉള്ളതുകൊണ്ട്, കോൾ ചെയ്യുന്ന എല്ലാവർക്കും ഇത് ഓട്ടോമാറ്റിക്കായി ഗുണകരമാകുന്നു; ഒരു കാസ്റ്റ് (cast) മറന്നുപോയാലും അത് ടൈപ്പ് സുരക്ഷിതത്വത്തെ ബാധിക്കില്ല.

എന്തുകൊണ്ട് ഇത് പഴയ as const രീതിയെക്കാൾ മികച്ചതാണ്

as const എന്നത് ഒരു കോൾ സൈഡ് (caller-side) പരിഹാരമാണ്. ഒരു ജനറിക് ഫങ്ക്ഷൻ ഉപയോഗിക്കുന്ന ഓരോരുത്തരും അസെർഷൻ (assertion) ചേർക്കാൻ മറക്കരുത്. ഒരു തവണ പോലും ഇത് വിട്ടുപോയാൽ ടൈപ്പ് സേഫ്റ്റി നഷ്ടപ്പെടും. എന്നാൽ const type parameter ഈ ഉത്തരവാദിത്തം API ഡിസൈനിലേക്ക് തന്നെ മാറ്റുന്നു: "നിങ്ങൾ നൽകുന്ന ഏത് ഡാറ്റയുടെയും ഏറ്റവും ഇടുങ്ങിയ രൂപമാണ് എനിക്ക് വേണ്ടത്" എന്ന് ഫങ്ക്ഷൻ പ്രഖ്യാപിക്കുന്നു, കംപൈലർ അത് നടപ്പിലാക്കുകയും ചെയ്യുന്നു.

ജനറിക് ബിൽഡറുകൾ, കോൺഫിഗറേഷൻ ഫാക്ടറികൾ, അല്ലെങ്കിൽ ഒരു ഫീൽഡിന്റെ ലിറ്ററൽ വാല്യൂ ടൈപ്പ് ലോജിക് നിയന്ത്രിക്കുന്ന ഏത് API എന്നിവ നൽകുന്ന ലൈബ്രറികൾക്കും യൂട്ടിലിറ്റികൾക്കും ഈ മാറ്റം വളരെ പ്രധാനമാണ്. ഡൗൺസ്ട്രീം കോഡുകളെ നിയന്ത്രിക്കാതെ തന്നെ കൃത്യമായ ഇൻഫറൻസ് (inference) ഉറപ്പാക്കാൻ ലൈബ്രറി ഓതറിന് ഇതിലൂടെ സാധിക്കും.

പ്രയോജനപ്പെടുന്ന യഥാർത്ഥ സാഹചര്യങ്ങൾ

  • Configuration builders – എൻവയോൺമെന്റ് പേരുകൾ ("dev" | "prod") ലിറ്ററലുകളായി തന്നെ നിലനിൽക്കുന്നു, ഇത് അധിക കാസ്റ്റുകൾ ഇല്ലാതെ തന്നെ ഡിസ്ക്രിമിനേറ്റഡ്-യൂണിയൻ ചെക്കുകൾ സാധ്യമാക്കുന്നു.
  • API route definitions – പാത്ത് സ്ട്രിംഗുകൾ കൃത്യമായി നിലനിൽക്കുന്നു, ഇത് ടെംപ്ലേറ്റ്-ലിറ്ററൽ ടൈപ്പുകൾ ഉപയോഗിച്ച് പാരാമീറ്ററുകൾ എക്സ്ട്രാക്ട് ചെയ്യാൻ സഹായിക്കുന്നു ("/users/:id"\/users/${string}``).
  • State-machine helpers – മെത്തേഡ് ചെയിനിംഗിലൂടെ സ്റ്റേറ്റ് ഐഡന്റിഫയറുകൾ ഫിക്സഡ് ലിറ്ററലുകളായി നിലനിൽക്കുന്നു, ഇത് അബദ്ധവശാൽ സ്റ്റേറ്റ് മാച്ച് ആകാതിരിക്കുന്നത് തടയുന്നു.

ഓരോ സാഹചര്യത്തിലും, const പാരാമീറ്റർ ആവർത്തിച്ചുള്ള as const ബോയിലർപ്ലേറ്റ് ഒഴിവാക്കുകയും സൂക്ഷ്മമായ ബഗുകൾ ഉണ്ടാകാനുള്ള സാധ്യത കുറയ്ക്കുകയും ചെയ്യുന്നു.

satisfies ഓപ്പറേറ്ററുമായുള്ള സംയോജനം

ഒരു വാല്യൂവിന്റെ ഒറിജിനൽ ലിറ്ററൽ വിവരങ്ങൾ നിലനിർത്തിക്കൊണ്ടുതന്നെ, അത് ഒരു സ്ട്രക്ചറൽ ടൈപ്പിന് അനുസൃതമാണോ എന്ന് satisfies ഓപ്പറേറ്റർ പരിശോധിക്കുന്നു. ഇവ രണ്ടും ഒരുമിച്ച് ഉപയോഗിക്കുന്നത് മികച്ച ഫലം നൽകുന്നു: const പാരാമീറ്ററുകൾ ഇടുങ്ങിയ ഇൻഫറൻസ് നൽകുന്നു, satisfies ആ വാല്യൂ ആവശ്യമായ രൂപത്തിലാണെന്ന് ഉറപ്പാക്കുന്നു.

function makeConfig<const C>(cfg: C) {
  // cfg is inferred with exact literals
}
const cfg = {
  env: "staging",
  ports: [8080, 8443],
} satisfies { env: string; ports: number[] };
makeConfig(cfg); // works, literals stay intact

എപ്പോൾ as const ഉപയോഗിക്കണം

ഫങ്ക്ഷന്റെ സിഗ്നേച്ചർ നിങ്ങളുടെ നിയന്ത്രണത്തിലായിരിക്കുമ്പോഴാണ് const പാരാമീറ്റർ കൂടുതൽ ഫലപ്രദമാകുന്നത്. ഈ മോഡിഫയർ ഇല്ലാത്ത തേർഡ് പാർട്ടി ഫങ്ക്ഷനുകളുമായാണ് നിങ്ങൾ ഇടപഴകുന്നതെങ്കിൽ, അല്ലെങ്കിൽ ഒരു ലോക്കൽ വേരിയബിളിന് വേണ്ടി മാത്രം ലിറ്ററൽ നിലനിർത്തണമെന്നുണ്ടെങ്കിൽ as const ആണ് ശരിയായ മാർഗ്ഗം. വിളിക്കുന്ന API മാറ്റാതെ തന്നെ ഒരു വാല്യൂ ഫ്രീസ് ചെയ്യാൻ ഇപ്പോഴും ഇത് ഉപയോഗിക്കാം.

ശ്രദ്ധിക്കേണ്ട കാര്യങ്ങൾ

ഈ ഫീച്ചർ പുതിയതായതുകൊണ്ട് ടൂളിംഗിലും കമ്മ്യൂണിറ്റി പാറ്റേണുകളിലും മാറ്റങ്ങൾ വരാം. ഓട്ടോകംപ്ലീറ്റ് (autocomplete), ക്വിക്ക്-ഫിക്സ് (quick-fix) നിർദ്ദേശങ്ങളിൽ പുതിയ സിന്റാക്സ് ലഭ്യമാക്കുന്ന രീതിയിൽ IDE പിന്തുണ മെച്ചപ്പെടുമെന്ന് പ്രതീക്ഷിക്കാം. ലൈബ്രറി മെയിന്റനർമാരുടെ മാറ്റങ്ങൾ ശ്രദ്ധിക്കുക: പലരും പബ്ലിക് ജനറിക്കുകളെ const പാരാമീറ്ററുകളിലേക്ക് മാറ്റാൻ തുടങ്ങും, ഇത് മുമ്പ് as const കാസ്റ്റുകളെ ആശ്രയിച്ചിരുന്ന കോഡുകളിൽ ബ്രേക്കിംഗ് ചേഞ്ചുകൾ (breaking changes) വരുത്തിയേക്കാം.

ചുരുക്കത്തിൽ: ലിറ്ററൽ പ്രിസർവേഷൻ നേരിട്ട് ഒരു ഫങ്ക്ഷന്റെ ടൈപ്പ് പാരാമീറ്ററുകളിൽ ഉൾപ്പെടുത്തുന്നതിലൂടെ, TypeScript-ന്റെ const type parameters വൈഡ്‌നിംഗ് എററുകൾ ഒഴിവാക്കുകയും സുരക്ഷാ ഉത്തരവാദിത്തം കോൾ ചെയ്യുന്ന ആളിൽ നിന്ന് API ഡിസൈനറിലേക്ക് തിരികെ നൽകുകയും ചെയ്യുന്നു. നിങ്ങൾ നിയന്ത്രിക്കുന്ന ഏതൊരു ജനറിക് എൻട്രി പോയിന്റിലും ഇവ ഉപയോഗിക്കുക; ലോക്കൽ വാല്യൂകൾക്കോ എക്സ്റ്റേണൽ APIകൾക്കോ വേണ്ടി മാത്രം as const മാറ്റിവെക്കുക.