typescript - Exporterade variabler är skrivskyddade?


globals.ts:

export let TimeZone: number = 0;

app.js

import * as globals from "./globals";
globals.TimeZone = -5;

Den sista raden ger:

fel TS2540: Kan inte tilldela till 'TimeZone' eftersom det är en skrivskyddad egenskap.

Varför?

Svar #1

Importeringar är en endast-läsning vy av den exporterade bindningen i källmodulens miljö. Även om källbindningen är ändringsbar (som i ditt exempel), kan du inte använda den importerade vyn av den för att ändra den. Endast modulen som exporterar den kan ändra den.

Varför? För att en modul som importerar variabeln inte ska kunna gå in i källmodulen och ändra variabelns värde. Om källmodulen vill göra det möjligt för moduler som använder den att ändra värdet på den exporterade variabeln, kan den exponera en funktion för att göra det. (Eller exponera ett objekt med föränderliga egenskaper.)

Kom ihåg att moduler är delade mellan de olika moduler som importerar dem. Så om Modulerna A och B båda importerar Modul C, vill du inte att Modul A ändrar vad Modul B ser (även om Modul C kan göra det, eftersom det är en variabel, inte en konstant).¹

För vad det är värt, här är ett exempel (live-kopia på plunker):

index.html:

<!DOCTYPE html>
<html>

  <head>
  </head>

  <body>
    <p id="display">0</p>
    <script type="module" src="imp1.js"></script>
    <script type="module" src="imp2.js"></script>
  </body>

</html>

counter.js:

export let counter = 0;

export function increment() {
  ++counter;
}

imp1.js:

import { counter } from "./counter.js";

const display = document.getElementById("display");

setInterval(() => {
  // This module sees changes counter.js makes to the counter
  display.textContent = counter;
}, 100);

imp2.js:

import { counter, increment } from "./counter.js";

// Trying to set it directly fails
try {
  counter = 127;
} catch (e) {
  document.body.insertAdjacentHTML(
    "beforeend",
    "imp2: Failed to directly set counter to 127"
  );
}

setInterval(() => {
  // But it can ask counter.js to do it
  increment();
}, 200);

Jag bör nämna att även om du inte kan ändra bindningen (TimeZone i ditt exempel), kan ett objekt som du exporterar ändras i sitt tillstånd. Till exempel:

// In the exporting module
export const container = {
    TimeZone: 0,
};

// In the importing module
import { container } from "./module.js";
container.TimeZone = 42;

Det fungerar, och ändringen ses av vilken kod som helst som också importerar container. Ibland vill du försvara dig mot det, vilket du kan göra med Object.freeze (om källmodulen aldrig behöver ändra objektets egenskaper) eller en Proxy.

Men återigen, att ändra ett objekts tillstånd är inte samma sak som att ändra värdet på den binding som exporterades.


¹ "...om Modulerna A och B båda importerar Modul C vill du inte att Modul A ändrar vad Modul B ser..."

Okej, ibland vill du faktiskt göra det. :-) När du gör det, exportera ett objekt istället:

export const sharedObject = {/*...*/}; // Or a `Map`, or `Set`, or whatever

...och låta modulerna ändra dess egenskaper. Alla moduler importerar samma objekt, så ändringar av dess egenskaper ses av dem alla.

Relaterade artiklar