# Однонаправленный поток данных. ref. React-Transition-Group. Декларативная анимация в React
## Однонаправленный поток данных
Принципы:
- props
- не может быть изменён дочерними компонентами
- может изменить только тот компонент, который передавал эти props
- Взаимодействие с родителями производится через события: callback-функции
### Пример общения с дочерним компонентом
onSelect - вручную созданное событие. По нашей задумке, компонент обязуется вызывать переданную в onSelect функцию при выборе значения:
```jsx
<CurrencySelect
data={currencies}
label="Из"
defaultValue={fromCurrency}
onSelect={value => setCurrency(CURRENCY_FROM, value)}/>
```
Реализация компонента:
```jsx
import ClickOutside from 'react-click-outside';
import { useRef, useState, useEffect } from 'react';
import { Select } from 'antd';
const { Option } = Select;
export const CurrencySelect = props => {
const {
onSelect = f => f,
data = [],
label = 'Select value',
defaultValue
} = props;
const [open, setOpen] = useState(false);
const [value, setValue] = useState(defaultValue);
const selectRef = useRef();
useEffect(() => {
setValue(defaultValue);
onSelect(defaultValue);
}, [defaultValue]);
const onLabelClick = () => {
selectRef.current.focus();
}
const onChange = (value) => {
setValue(value);
onSelect(value);
selectRef.current.blur();
}
const showDropdown = () => {
setOpen(true);
};
const hideDropdown = () => {
setOpen(false);
};
const onKeyDown = e => {
if (e.code !== 'Tab') {
return;
}
hideDropdown();
}
return (
<ClickOutside onClickOutside={hideDropdown}>
<div className={S.wrapper}>
<div className={S.label} onClick={onLabelClick}>
{label}
</div>
<Select
className={S.select}
bordered={false}
value={value}
onChange={onChange}
onFocus={showDropdown}
onKeyDown={onKeyDown}
open={open}
ref={selectRef}
>
{data.map(({ currency }, index) =>
<Option value={currency} key={index}>{currency}</Option>
)}
</Select>
</div>
</ClickOutside>
);
}
```
- Обычно вы сами проектируете события
- На основе событий другого компонента можно создать свои собстенные
## ref'ы
ref позволяет обращаться к узлу DOM напрямую. Для работы с ref необходимо использовать хук useRef()
```jsx
import logo from './logo.svg';
import './App.scss';
import { Converter } from './components';
import { useEffect, useRef, useState } from 'react';
import { loadCurrencies } from './util';
import { CSSTransition } from 'react-transition-group';
function App() {
const [currencies, setCurrencies] = useState([]);
const [visible, setVisible] = useState(false);
const nodeRef = useRef();
useEffect(() => {
loadCurrencies()
.then(data => {
setCurrencies(data);
setVisible(true);
});
}, []);
return (
<CSSTransition nodeRef={nodeRef} in={visible} timeout={5000} classNames="app-root">
<div className="app" ref={nodeRef}>
<Converter currencies={currencies} />
</div>
</CSSTransition>
);
}
export default App;
```
Для получения доступа необходимо обратиться к свойству:
```js
nodeRef.current
```
## Вспоминаем переходы в CSS: transition
Переход (transition) - анимация плавного изменения значения CSS-свойств.
Ограничения:
- Только два состояния: начальное и конечное
- Для перехода необходимо начальное воздействие, которое поменяет значение свойства
- При пропадении воздействия, значение возвращается к исходному состоянию
Список CSS-свойств для переходов:
- transition-property - свойство, по которому производится переход
- transition-duration - длительность перехода
- transition-timing-function - функция времени (динамика изменений)
- transition-delay (задержка перед переходом)
Переходы поддерживают анимацию по нескольким свойствам:
```css
.box {
width: 100px;
height: 100px;
transition: 1s;
background: #000;
transition-property: margin-left, background-color;
transition-duration: 2s, 3s;
transition-delay: 0s, 2s;
}
.box:hover {
margin-left: 100px;
background: orange;
}
```
## Разница JS и CSS анимации
- Технически процессы выполняются одинаково
- CSS-переходы и анимации удобны в простых случаях, например реакции элементов UI
- В случае, если требуется контроль над состоянием анимации (приостановить, воспроизвести назад, анимация составных частей), удобнее всего это делать с помощью JS
## В чём принципиальная разница CSS transition и React-Transition-Group
- React Transition Group (RTG) производит анимацию, подключая в завсимости от состояния анимации (старт воздействия, начало анимации, процесс анимации, окончание)
- RTG управляется с помощью JS, что позволяет связать анимацию с возникновением событий
## Установка
```bash
yarn add react-transition-group
```
## Основные компоненты
- Transition - переходы для CSS in JS подхода
- CSSTransition - переходы на CSS классах
- SwitchTransition - вариант задания перехода на основе событий
- TransitionGroup - вспомогательный компонент для анимации списка элементов
## Базовый пример анимации через CSSTransition
\[Логика показана в компоненте App выше\]
Стили:
```css
.app-root-enter {
opacity: 0;
}
.app-root-enter-active {
opacity: 1;
transition: opacity 5s;
}
.app-root-exit {
opacity: 1;
}
```
## Состояния
По умолчанию, React Transition Group проходит через следующие состояния:
Переход из начала в конец:
- enter
- enterActive
- enterDone
Обратное движение перехода (обратная анимация, когда воздействие пропало):
- exit
- exitActive
- exitDone
### Описания состояний
1. В начале перехода элемент получает базовый класс и следом -active. За счёт этого будет работать переход в css, заданный через transition
2. По окончании анимации устанавливается класс done, а active убирается.
## Управление началом анимации
В Transition и CSSTransition задаётся через prop *in*. В SwitchTransition управляется программно
## Описание состояний Transition
Прямое направление:
- 'entering' - стартовое состояние перехода
- 'entered' - процесс перехода
Аналогично в обратную сторону:
- 'exiting'
- 'exited'
### Базовый пример работы
```jsx
import { Transition } from 'react-transition-group';
import { useRef } from 'react';
const duration = 300;
const defaultStyle = {
transition: `opacity ${duration}ms ease-in-out`,
opacity: 0,
}
const transitionStyles = {
entering: { opacity: 1 },
entered: { opacity: 1 },
exiting: { opacity: 0 },
exited: { opacity: 0 },
};
function Fade({ in: inProp }) {
const nodeRef = useRef(null);
return (
<Transition nodeRef={nodeRef} in={inProp} timeout={duration}>
{state => (
<div ref={nodeRef} style={{
...defaultStyle,
...transitionStyles[state]
}}>
I'm a fade Transition!
</div>
)}
</Transition>
);
}
```