typescript - ¿Las variables exportadas son solo de lectura?
globals.ts:
export let TimeZone: number = 0;
app.js
import * as globals from "./globals";
globals.TimeZone = -5;
La última línea da:
error TS2540: No se puede asignar a 'TimeZone' porque es una propiedad de solo lectura.
¿Por qué?
Respuesta #1
Las importaciones son una vista de solo lectura del enlace exportado en el entorno del módulo de origen. Incluso si el enlace de origen es mutable (como en tu ejemplo), no puedes usar la vista importada de este para modificarlo. Solo el módulo que lo exporta puede modificarlo.
¿Por qué? Porque un módulo que importa la variable no debería poder acceder al módulo de origen y cambiar el valor de la variable. Si el módulo de origen quiere permitir que los módulos que lo usan cambien el valor de la variable exportada, puede exponer una función para hacerlo. (O exponer un objeto con propiedades mutables.)
Recuerda que los módulos se comparten entre los distintos módulos que los importan. Así que si los Módulos A y B importan el Módulo C, no querrás que el Módulo A modifique lo que el Módulo B ve (incluso si el Módulo C puede hacerlo, porque es una variable, no una constante).¹
Para lo que vale, aquí hay un ejemplo (copia en vivo en 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);
Debo señalar que, aunque no puedes modificar la vinculación (TimeZone en tu ejemplo), si exportas un objeto, el estado de ese objeto puede ser modificado. Por ejemplo:
// In the exporting module
export const container = {
TimeZone: 0,
};
// In the importing module
import { container } from "./module.js";
container.TimeZone = 42;
Eso funciona, y el cambio es visto por cualquier código que también importe container. A veces quieres protegerte contra eso, lo cual podrías hacer con Object.freeze (si el módulo fuente nunca necesita cambiar las propiedades del objeto) o un Proxy.
Pero de nuevo, cambiar el estado de un objeto no es lo mismo que cambiar el valor de la vinculación que fue exportada.
¹ "...si los Módulos A y B ambos importan el Módulo C, no quieres que el Módulo A modifique lo que el Módulo B ve..."
Bueno, está bien, a veces sí quieres hacer eso. :-) Cuando lo hagas, exporta un objeto en su lugar:
export const sharedObject = {/*...*/}; // Or a `Map`, or `Set`, or whatever
...y hacer que los módulos modifiquen sus propiedades. Todos los módulos importan el mismo objeto, por lo que las modificaciones a sus propiedades son vistas por todos ellos.
