Рассмотрим пример работы часов. Мы привязываем ReactDOM.render()изменить отображаемый вывод:
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(
element,
document.getElementById('root')
);
}
setInterval(tick, 1000);
В этом пример будет описано, как сделать Clock компонент действительно повторно используемым и инкапсулированным. Он будет настраивать свой собственный таймер и обновлять каждую секунду.
Мы можем начать с инкапсуляции того, как выглядят часы:
function Clock(props) {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {props.date.toLocaleTimeString()}.</h2>
</div>
);
}
function tick() {
ReactDOM.render(
<Clock date={new Date()} />,
document.getElementById('root')
);
}
setInterval(tick, 1000);
Тем не менее, это не соответствует критическому требованию: тот факт, что Clock настройка таймера и обновление пользовательского интерфейса каждую секунду должна быть деталью реализации Clock.
В идеале мы хотим написать это один раз и иметь Clock само-обновляемым:
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
Чтобы реализовать это, нам нужно добавить «состояние» (state) к Clock компоненту. Состояние похоже на реквизит, но оно является частным и полностью контролируется компонентом.
Преобразование функции в класс
Вы можете преобразовать функциональный компонент как Clock класс в пять шагов:
- Создайте класс ES6 с тем же именем, который расширяется React.Component.
- Добавьте к нему один пустой метод render()
- Переместите тело функции в render() метод
- Замените props с this.props внутри render()
- Удалите оставшееся пустое объявление функции
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
Clock теперь определяется как класс, а не функция. Это позволяет нам использовать дополнительные функции, такие как локальные состояния и перехваты жизненного цикла.
Добавление локального состояния в класс
Мы переместим его date из реквизита в три этапа:
1) Замените с this.props.date на this.state.date в render() методе:
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
2) Добавьте конструктор класса, который назначает начальную this.state:
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
Обратите внимание, как мы переходим props к базовому конструктору:
constructor(props) {
super(props);
this.state = {date: new Date()};
}
Компоненты класса всегда должны ссылаться на базовый конструктор props.
3) Снимите date с <Clock /> элемента:
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
Позднее мы добавим код таймера обратно к самому компоненту. Результат выглядит следующим образом:
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
Затем мы сделаем Clock настройку собственного таймера и обновим каждую секунду.
Добавление методов жизненного цикла в класс
В приложениях со многими компонентами очень важно высвобождать ресурсы, используемые компонентами при их уничтожении. Мы хотим настроить таймер всякий раз, когда Clock отображается в DOM в первый раз. Это называется «установка» (mounting) в React. Мы также хотим очистить этот таймер всякий раз, когда DOM, созданный Clock удаляется. Это называется «размонтирование» (unmounting) в React. Мы можем объявить специальные методы для класса компонентов для запуска некоторого кода, когда компонент монтирует и размонтирует:
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
}
componentWillUnmount() {
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
Эти методы называются «крючки жизненного цикла». На componentDidMount() крючок работает после компонентного выхода было оказано DOM. Это хорошее место для установки таймера:
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
Обратите внимание, как мы сохраняем идентификатор таймера this. Хотя this.props настроен самим Реактом и this.state имеет особое значение, вы можете добавлять дополнительные поля в класс вручную, если вам нужно сохранить что-то, что не участвует в потоке данных (например, идентификатор таймера).
Мы разрушим таймер в componentWillUnmount() крючке жизненного цикла:
componentWillUnmount() {
clearInterval(this.timerID);
}
Наконец, мы реализуем метод , называемый , tick() что Clock компонент будет работать каждый второй. Он будет использоваться this.setState() для планирования обновлений локального состояния компонента:
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
Правильное использование setState()
Есть три вещи, которые вы должны знать о setState():
Не изменять состояние напрямую
Например, это не приведет к повторному рендерингу компонента:
// Wrong
this.state.comment = 'Hello';
Вместо этого используйте setState():
// Correct
this.setState({comment: 'Hello'});
Единственным местом, где вы можете назначить, this.state является конструктор.
Обновления состояния могут быть асинхронными
React может выполнять несколько setState() вызовов в одном обновлении для производительности. Поскольку this.props и this.state могут быть обновлены асинхронно, вы не должны полагаться на свои значения для вычисления следующего состояния.
Например, этот код может не обновить счетчик:
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
Чтобы исправить это, используйте вторую форму, setState() которая принимает функцию, а не объект. Эта функция получит предыдущее состояние в качестве первого аргумента и реквизит во время обновления в качестве второго аргумента:
// Correct
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
Выше использована функция со стрелкой, но также можно использовать регулярные функции:
// Correct
this.setState(function(prevState, props) {
return {
counter: prevState.counter + props.increment
};
});
Обновления состояния объединены
Когда вы вызываете setState(), React объединяет объект, который вы указываете, в текущее состояние. Например, ваше состояние может содержать несколько независимых переменных:
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
Затем вы можете самостоятельно обновлять их с помощью отдельных setState() вызовов:
componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts
});
});
fetchComments().then(response => {
this.setState({
comments: response.comments
});
});
}
Слияние неглубокое, поэтому this.setState({comments}) оставляет this.state.posts нетронутыми, но полностью заменяет this.state.comments.
Потоки данных
Ни родительский, ни дочерний компоненты не могут знать, является ли какой-либо компонент состоятельным или неактивным, и им не важно, определено ли оно как функция или класс. Вот почему состояние часто называют локальным или инкапсулированным. Он недоступен для любого компонента, кроме того, который владеет и устанавливает его. Компонент может выбрать передачу своего состояния вниз в качестве реквизита его дочерних компонентов:
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
Это также работает для пользовательских компонентов:
<FormattedDate date={this.state.date} />
FormattedDate компонент будет получать date в его реквизите и не знал бы , пришел ли он из Clock от реквизита, или был набран вручную:
function FormattedDate(props) {
return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}
Это обычно называют потоком данных «сверху вниз» или «однонаправленным». Любое состояние всегда принадлежит определенному компоненту, и любые данные или пользовательский интерфейс, полученные из этого состояния, могут влиять только на компоненты «ниже» их в дереве.
Если вы представляете дерево компонентов как водопад реквизита, состояние каждого компонента похоже на дополнительный источник воды, который соединяет его в произвольной точке, но также течет вниз.
Чтобы показать, что все компоненты действительно изолированы, мы можем создать App компонент, который отображает три <Clock>:
function App() {
return (
<div>
<Clock />
<Clock />
<Clock />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
Каждый Clock устанавливает свой собственный таймер и обновляется независимо.
В приложениях React, независимо от того, является ли компонент устаревшим или неактивным, рассматривается как деталь реализации компонента, который может меняться со временем. Вы можете использовать компоненты без состояния в компонентах stateful, и наоборот.
0 комментариев