Я пытался использовать глобальную переменную в приложении React для целей отладки и столкнулся с некоторыми проблемами, которые заставили меня понять, что я не понимаю, как работают глобальные переменные в javascript.
глобальный.js:
export let myGlobal = 0
IncButton.js:
import { Component } from 'react'
import { myGlobal } from './global'
class IncButton extends Component {
render() {
return (
<button onClick = {() => { myGlobal = myGlobal + 1 }}>increase</button>
)
}
}
export default IncButton
window.inc = () => {
myGlobal = myGlobal + 1
}
App.js:
import { myGlobal } from './global'
import IncButton from './IncButton'
function App() {
return (
<>
{myGlobal}
<IncButton />
</>
);
}
export default App;
Нажатие IncButton
вызывает ReferenceError: assignment to undeclared variable myGlobal
. Вызов inc() непосредственно из консоли вызывает ту же ошибку.
Использование let myGlobal = 0
в верхней части IncButton.js и удаление импорта устраняет ошибки, но, конечно, это приводит к тому, что App.js не видит тот же самый myGlobal. Я думаю, что импортированные переменные и переменные вне какой-либо функции или {}
должны быть в одной области видимости.
Поэтому я хотел бы знать: почему вышеизложенное не работает, как правильно разделить глобальную переменную между файлами, и добавляет ли React какие-либо дополнительные сложности к этому?
Примечание 1. Остальной код для приведенного выше примера — это просто код по умолчанию из create-react-app.
Примечание 2: я знаю, что использование глобальных переменных часто не очень хорошая идея.
Но поскольку модули es используют живые привязки, как объяснено в этом ответе, и поскольку несколько файлов могут импортировать один и тот же модуль, не должны ли в результате быть переменные, совместно используемые в файлах?
Требуется довольно много пояснений, чтобы объяснить, что происходит в вашем коде, но я постараюсь быть кратким.
глобальный.js
На самом деле это не глобальный, а модуль, и экспортированные элементы доступны только для чтения.
Вы можете заставить его работать, экспортируя объект:
export let myGlobal = { count: 0 };
И изменив обработчик onClick на
() => { myGlobal.count++ }
Но ваши компоненты не будут перерисовываться, поскольку они не являются частью состояния, и поэтому React не заметит изменения.
Если вы хотите разделить состояние между компонентами - вы должны либо поднять состояние, либо использовать контекстный API, как описано в этом ответе
Изменение на myGlobal = { count: 0 }
предотвращает появление ошибок. Но доступ к myGlobal.count
в App.js всегда показывает ноль, даже при нажатии кнопки для увеличения значения. Похоже, теперь есть 2 версии myGlobal
.
Как указано в ответе - App.js
не будет повторно отображаться, потому что myGlobal
не является частью состояния. Вы можете видеть, что значение разделяется, если вы принудительно выполняете повторную визуализацию App.js
.
В необработанном проекте javascript вы можете экспортировать глобальную переменную в файл и импортировать в любое место в других файлах. Это то, что вы сделали.
Но в React, если вы хотите использовать глобальное состояние, вы должны использовать контекстный API. Ваш код не запускается, потому что myGlobal не объявлен как состояние. Таким образом, React не может отслеживать его состояние.
Правильный метод такой:
const {useState, useContext} = React;
// you can create context outside of React component
const CounterContext = React.createContext(0);
// export default CounterContext;
// import CounterContext from 'global/counter-context';
const ChildComponent = () => {
const counter = useContext(CounterContext);
return (
<div className = "other-component">
<p>This is Child Component that consumes CounterContext value </p>
<p>Current counter is: {counter}</p>
</div>
);
};
// import CounterContext from 'global/counter-context';
const App = () => {
const [counter, setCounter] = useState(0);
return (
<div className = "app">
<CounterContext.Provider value = {counter}>
<p>This is Context Provider component. Global state is maintained here and you can consume the state value in any other child components</p>
<label>Click this button to increment global counter state: </label>
<button onClick = {() => setCounter((oldCounter) => (oldCounter + 1))}>
Increment
</button>
<ChildComponent />
</CounterContext.Provider>
</div>
);
};
class Root extends React.Component {
render() {
return (<App />);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
.app {
border: 1px solid red;
padding: 5px;
margin: 5px;
}
button {
margin-bottom: 5px;
margin-left: 5px;
}
.other-component {
border: 1px solid blue;
padding: 5px;
margin: 5px;
}
<script src = "https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src = "https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<div id = "root">
</div>
как правильно разделить глобальную переменную между файлами <-- Это невозможно сделать. Для этого вам нужно сохранить значение на сервере, обычно в базе данных. Хотя вы можете совместно использовать библиотеку JavaScript в файлах, все значения сбрасываются при загрузке каждой страницы, которая ее использует.