# Однонаправленный поток данных. 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> ); } ```