Содержание
- Функциональный компонент - Function component.
- Деструктуризация свойств - Destructuring props.
- Атрибуты распространения JSX - JSX spread attributes.
- Объединение деструктуризированных props с другими значениями - Merge destructured props with other values.
- Условный рендеринг - Conditional rendering
- Children types
- Массив как дети - Array as children
- Функции как дети - Function as children.
- Рендеринг свойств - Render prop.
- Передача через детей - Children pass-throught.
- Прокси-компонент - Proxy component.
- Компонент стиля - Style component.
- Переключатель событий - Event switch.
- Компонент шаблона (макета) - Layout component.
- Контейнерный компонент - Container component.
- Компонент высшего порядка - Higher-order component.
- Подъем состояния - State hoisting.
- Контролируемый ввод - Controlled input .
Function component - Функциональный компонент.
Function components Функциональный компонент - это самый простой способ объявить повторно используемые компоненты.
Это просто функции.function Greeting() { return <div>Hi there!</div>; }Собираем
props
из первого аргумента функции:
function Greeting(props) { return <div>Hi {props.name}!</div>; }Определите любое количество локальных переменных, чтобы делать то, что вам нужно в ваших функциональных компонентах.
function Greeting(props) { let style = { fontWeight: "bold", color: context.color }; return <div style={style}>Hi {props.name}!</div>; }Установка значений по умолчанию для любых
props
с использованием параметров по умолчанию - defaultProps
.
function Greeting(props) { return <div>Hi {props.name}!</div>; } Greeting.defaultProps = { name: "Guest" };
Destructuring props - Деструктуризация свойств.
Назначение деструктуризации (Destructuring assignment)- это функция JavaScript. Она была добавлена в язык ES2015.
Так что это может показаться не знакомым.
Подумайте об этом как о противоположном от буквального назначения.
let person = { name: "chantastic" }; let { name } = person;
Работает с массивами.
let things = ["one", "two"]; let [first, second] = things;
Назначение деструктуризации часто используется в функциональных компонентах.
Эти декларации компонентов ниже эквивалентны.
function Greeting(props) { return <div>Hi {props.name}!</div>; } function Greeting({ name }) { return <div>Hi {name}!</div>; }
Существует синтаксис для сбора оставшихся параметров объекта.
Этот синтаксис называется параметром rest
( Rest parameters
) и выглядит следующим образом.
Spread (...
) используется для разделения коллекций на отдельные элементы, а rest
, наоборот, для соединения отдельных значений в массив.
function Greeting({ name, ...restProps }) { return <div>Hi {name}!</div>; }
Эти три точки (...
) принимают все остальные свойства и назначают их объектам restProps
.
Итак, что вы делаете с restProps
, когда у вас есть это?
Продолжай читать...
Атрибуты распространения JSX - JSX spread attributes.
Атрибуты распространения - это функция JSX.
Это синтаксис для предоставления свойств объекта в качестве атрибутов JSX.
Следуя примеру деструктуризации параметров - Destructuring props, Мы можем распространятьrestProps
над нашим <div>
.
function Greeting({ name, ...restProps }) { return <div {...restProps}>Hi {name}!</div>; }
Это делает Greeting
супер гибким.
Мы можем передавать атрибуты DOM в Greeting
и надеяться, что они будут переданы в div
.
<Greeting name="Fancy pants" className="fancy-greeting" id="user-greeting" />Избегайте пересылки не DOM
props
компонентам.
Деструктуризация является популярной, поскольку она дает вам возможность разделить props, специфичные для компонентов, от DOM / платформы специфических атрибутов.
function Greeting({ name, ...platformProps }) { return <div {...platformProps}>Hi {name}!</div>; }
Объединение деструктуризированных props с другими значениями.
Компоненты - это абстракции. Хорошие абстракции допускают расширение.function MyButton(props) { return <button className="btn" {...props} />; }Это отлично работает, пока мы не попытаемся распространить его на другой класс.
<MyButton className="delete-btn">Delete...</MyButton>
В этом случае delete-btn
заменяет btn
.
Вопросы по Атрибутам распространения JSX
В этом случае, props.className
переопределяет className
в нашем компоненте.
Мы можем изменить порядок, но теперь className
никогда не будет ничем иным, как btn
.
function MyButton(props) { return <button {...props} className="btn" />; }Нам нужно использовать присваивание деструктуризации для получения входящего
className
и слияния с базовым className
.
Мы можем сделать это, просто добавив все значения в массив и присоединив их к пробелу.
function MyButton({ className, ...props }) { let classNames = ["btn", className].join(" "); return <button className={classNames} {...props} />; }Чтобы защитить от отображения
undefined
в качестве имени класса,
Используйте значения по умолчанию - default values.
function MyButton({ className="", ...props }) { let classNames = ["btn", className].join(" "); return <button className={classNames} {...props} />; }
Условный рендеринг.
Вы не можете использовать выраженияif / else
внутри деклараций компонентов.
Таким образом, условный (тернарный) оператор
conditional (ternary) operator и оценка короткого замыкания Short-circuit evaluation- ваши друзья.
if
{ condition && <span>Rendered when `truthy`</span>; }
unless
{ condition || <span>Rendered when `falsy`</span>; }
if-else
{ condition ? ( <span>Rendered when `truthy`</span> ) : ( <span>Rendered when `falsy`</span> ); }
Children types.
React может отображатьchildren
- детей из большинства типов.
В большинстве случаев это либо array
- массив, либо string
- строка.
String
<div>Hello World!</div>
String
<div>{["Hello ", <span>World</span>, "!"]}</div>
Массив как дети - Array as children.
Предоставление массива в качестве детей является очень распространенным явлением. Это как списки в React. Мы используемmap ()
для создания массива React Elements для каждого значения в массиве.
<ul> {["first", "second"].map(item => ( <li>{item}</li> ))} </ul>Это эквивалентно предоставлению литералa массива
array
.
<ul>{[<li>first</li>, <li>second</li>]}</ul>Этот шаблон можно комбинировать с деструктурированием, атрибутами распространения JSX и другими компонентами для некоторого серьезного усиления.
<ul> {arrayOfMessageObjects.map(({ id, ...message }) => ( <Message key={id} {...message} /> ))} </ul>
Функции как дети - Function as children.
Компоненты React не поддерживают функции какchildren
-дети. Однако Render prop - это шаблон для создания компонентов, которые выполняют функции как дети.
Рендеринг свойств - Render prop.
Вот компонент, который использует обратный вызов - render callback. Это не полезно, но для начала это простая иллюстрация.
const Width = ({ children }) => children(500);
Компонент вызывает
children
как функцию с некоторым количеством аргументов. Здесь это число 500
.
Чтобы использовать этот компонент, мы даем ему Функции как дети - Function as children.
<Width>{width => <div>window is {width}</div>}</Width>
Мы получаем этот результат.
<div>window is 500</div>
С помощью этой настройки мы можем использовать эту ширину для принятия решений.
<Width> {width => (width > 600 ? <div>min-width requirement met!</div> : null)} </Width>
Если мы планируем использовать это условие несколько раз, то мы можем определить другие компоненты для инкапсуляции повторно используемой логики.
const MinWidth = ({ width: minWidth, children }) => ( <Width>{width => (width > minWidth ? children : null)}</Width> );
Очевидно, что статический компонент
Width
не полезен, но тот, который смотрит в окно браузера. Вот пример реализации.
class WindowWidth extends React.Component { constructor() { super(); this.state = { width: 0 }; } componentDidMount() { this.setState( { width: window.innerWidth }, window.addEventListener("resize", ({ target }) => this.setState({ width: target.innerWidth }) ) ); } render() { return this.props.children(this.state.width); } }Многие разработчики предпочитают компоненты Higher Order Components - более высокого порядка для такого типа функциональности. Это вопрос предпочтения.
Children pass-through
Вы можете создать компонент, предназначенный для применения контекста и отображения его дочерних элементов.class SomeContextProvider extends React.Component { getChildContext() { return { some: "context" }; } render() { // how best do we return `children`? } }Вы столкнулись с решением. Оберните
children
в <div /> или верните (return) children
напрямую. В первом случае, это даст дополнительную разметку (которая может разорвать некоторые таблицы стилей). В другом, приведет к бесполезным ошибкам.
// option 1: extra div return <div>{children}</div>; // option 2: unhelpful errors return children;Лучше рассматривать
children
как непрозрачный тип данных. Реакт предоставляет React.Children
для работы с children
надлежащим образом.
return React.Children.only(this.props.children);
Прокси-компонент. Proxy component.
(Я не уверен, имеет ли это название смысл) Кнопки доступны везде в веб-приложениях. И каждая из них должна иметь атрибутtype
, установленный как "button".
<button type="button">
Написание этого атрибута сотни раз подвержено ошибкам. Мы можем написать компонент более высокого уровня для прокси-props
для нижнего уровня button
компонента.
const Button = props => <button type="button" {...props}>Мы можем использовать
Button
вместо button
и гарантировать, что атрибут type
постоянно применяется везде.
<Button /> // <button type="button"><button> <Button className="CTA">Send Money</Button> // <button type="button" class="CTA" >Send Money</button>
Компонент стиля - Style component.
Это прокси-компонент, применяемый к практике стиля. Скажем, у нас есть кнопка. Он использует классы для стилизации как "primary" кнопки.<button type="button" className="btn btn-primary">Мы можем генерировать этот результат, используя пару одноцелевых компонентов.
import classnames from "classnames"; const PrimaryBtn = props => <Btn {...props} primary />; const Btn = ({ className, primary, ...props }) => ( <button type="button" className={classnames("btn", primary && "btn-primary", className)} {...props} /> );Это может помочь визуализировать это.
PrimaryBtn() ↳ Btn({primary: true}) ↳ Button({className: "btn btn-primary"}, type: "button"}) ↳ '<button type="button" class="btn btn-primary"></button>'Используя эти компоненты, все они приводят к одному и тому же результату.
<PrimaryBtn /> <Btn primary /> <button type="button" className="btn btn-primary" />Это может быть огромным благом для поддержания стиля. Он изолирует все проблемы стиля до одного компонента.
Переключатель событий - Event switch.
При написании обработчиков событий принято применять соглашение об именахhandle {eventName}
.
handleClick(e) { /* do something */ }
Для компонентов, которые обрабатывают несколько типов событий, эти имена функций могут повторяться. Сами имена могут не обеспечить большую ценность, поскольку они просто проксируют другие действия / функции.
handleClick() { require("./actions/doStuff")(/* action stuff */) } handleMouseEnter() { this.setState({ hovered: true }) } handleMouseLeave() { this.setState({ hovered: false }) }Подумайте о написании одного обработчика событий для своего компонента и включении
event.type
.
handleEvent({type}) { switch(type) { case "click": return require("./actions/doStuff")(/* action dates */) case "mouseenter": return this.setState({ hovered: true }) case "mouseleave": return this.setState({ hovered: false }) default: return console.warn(`No case for event type "${type}"`) } }В качестве альтернативы, для простых компонентов вы можете вызвать импортированные действия / функции непосредственно из компонентов, используя функции стрелок.
<div onClick={() => someImportedAction({ action: "DO_STUFF" })}Не беспокойтесь об оптимизации производительности, пока у вас не возникнут проблемы. Серьезно не надо.
Компонент шаблона (макета) - Layout component.
Компоненты шаблона приводят к некоторой форме статического элемента DOM. Возможно, их не потребуется часто обновлять, если вообще когда-либо придется. Рассмотрим компонент, который создает дваchildren
рядом - бок о бок.
<HorizontalSplit leftSide={<SomeSmartComponent />} rightSide={<AnotherSmartComponent />} />Мы можем агрессивно оптимизировать этот компонент. Хотя
HorizontalSplit
будет родительским - parent
для обоих компонентов, он никогда не станет их владельцем. Мы можем сказать, что он никогда не обновится, не прерывая жизненный цикл компонентов внутри.
class HorizontalSplit extends React.Component { shouldComponentUpdate() { return false; } render() { <FlexContainer> <div>{this.props.leftSide}</div> <div>{this.props.rightSide}</div> </FlexContainer>; } }
Контейнерный компонент - Container component.
«Контейнер делает выборку данных, а затем отображает соответствующий субкомпонент. Вот и все».
Джейсон Бонта
Учитывая этот многократный компонент
CommentList
.
const CommentList = ({ comments }) => ( <ul> {comments.map(comment => ( <li> {comment.body}-{comment.author} </li> ))} </ul> );Мы можем создать новый компонент, ответственный за выборку данных и отображение компонента функции
CommentList
.
class CommentListContainer extends React.Component { constructor() { super() this.state = { comments: [] } } componentDidMount() { $.ajax({ url: "/my-comments.json", dataType: 'json', success: comments => this.setState({comments: comments}); }) } render() { return <CommentList comments={this.state.comments} /> } }Мы можем писать разные контейнеры для разных контекстов приложений.
Компонент высшего порядка - Higher-order component.
Функция более высокого порядка (higher-order function)- это функция, которая принимает и / или возвращает функцию. Это несложно. Итак, что такое компонент более высокого порядка?Если вы уже используете container components, это просто общие контейнеры, завернутые в функцию.
Начнем с нашего компонента
Greeting
.
const Greeting = ({ name }) => { if (!name) { return <div>Connecting...</div>; } return <div>Hi {name}!</div>; };Если он получит
props.name
, он будет отображать эти данные. В противном случае он скажет, что это «Подключение ...». Теперь для бит более высокого порядка.
const Connect = ComposedComponent => class extends React.Component { constructor() { super(); this.state = { name: "" }; } componentDidMount() { // this would fetch or connect to a store this.setState({ name: "Michael" }); } render() { return <ComposedComponent {...this.props} name={this.state.name} />; } };Это просто функция, которая возвращает компонент, который отображает компонент, который мы передали в качестве аргумента.
Последний шаг, нам нужно обернуть наш компонент
Greeting
в Connect
.
const ConnectedMyComponent = Connect(Greeting);Это мощный шаблон для обеспечения выборки и предоставления данных для любого количества функциональных компонентов.
Подъем состояния - State hoisting.
Функциональный компонент не содержит состояния (как следует из названия).События - это изменения в состоянии. Их данные должны быть переданы родительским элементам компонента контейнера с состоянием.
Это называется «подъемом состояния» (State hoisting). Это достигается путем передачи обратного вызова из компонента контейнера в дочерний компонент.
class NameContainer extends React.Component { render() { return <Name onChange={newName => alert(newName)} />; } } const Name = ({ onChange }) => ( <input onChange={e => onChange(e.target.value)} /> );
Name
получает обратный вызов onChange
от NameContainer
и вызывает события.
Приведенное выше предупреждение
alert
представляет собой краткую демонстрацию, но она не меняет состояние. Давайте изменим внутреннее состояние NameContainer
.
class NameContainer extends React.Component { constructor() { super(); this.state = { name: "" }; } render() { return <Name onChange={newName => this.setState({ name: newName })} />; } }
С помощью предоставленного обратного вызова состояние переносится - поднимается (hoisted) в контейнер, где оно используется для обновления локального состояния. Это устанавливает хорошую четкую границу и максимизирует возможность повторного использования компонента функции.
Этот шаблон не ограничен функциональными компонентами. Поскольку компоненты функций не имеют событий жизненного цикла, вы будете использовать этот шаблон и с классами компонентов.
Контролируемый ввод (Controlled input ) является важным шаблоном, который нужно знать для использования с подъемом состояния
(Лучше всего обрабатывать объект события в компоненте с состоянием)
Контролируемый ввод - Controlled input .
Трудно говорить об управляемых инпутах в резюме. Давайте начнем с неконтролируемого (нормального) ввода и пойдем оттуда.
<input type="text" />
Когда вы возитесь с этим вводом в браузере, вы видите ваши изменения. Это нормально.
Контролируемый ввод запрещает мутации DOM, которые делают это возможным. Вы устанавливаете значение ввода в компонентной области, а оно не изменяется в DOM-области.
<input type="text" value="This won't change. Try it." />
Очевидно, что статические инпуты не очень полезны для ваших пользователей. Итак, мы получаем значение от состояния state.
class ControlledNameInput extends React.Component { constructor() { super(); this.state = { name: "" }; } render() { return <input type="text" value={this.state.name} />; } }
Затем, изменение инпута - это вопрос изменения состояния компонента.
return ( <input value={this.state.name} onChange={e => this.setState({ name: e.target.value })} /> );Это контролируемый вход. Он обновляет DOM только после изменения состояния в нашем компоненте. Это бесценно при создании согласованных интерфейсов.
Если вы используете Function component - Функциональный компонент. для элементов формы, прочитайте об использовании подъема состояния - State hoisting. для перемещения нового состояния вверх по дереву компонентов.
Удачного кодирования!
Выражаю особую благодарность Flavio Copes, который вдохновил меня на этот труд.