В этой статье мы собираемся создать тумблер в стиле iOS с использованием компонентов React. К концу мы создадим простое демо-приложение React, которое использует наш компонент переключателя.
Для этого мы могли бы использовать сторонние библиотеки, но сборка с нуля позволяет нам лучше понять, как работает наш код, и позволяет полностью настраивать наш компонент.
Формы предоставляют основные средства для обеспечения взаимодействия с пользователем. Этот флажок традиционно используется для сбора двоичных данных — таких как « да» или « нет» , « истина» или « ложь» , включение или отключение , включение или выключение и т. Д. Несмотря на то, что при создании тумблеров некоторые современные конструкции интерфейса избегают полей формы, я буду придерживаться их здесь из-за их большей доступности.
Вот скриншот компонента, который мы будем строить:
Начиная
Мы можем начать с базового элемента HTML-поля ввода с установленными необходимыми свойствами:
<input type="checkbox" name="name" id="id" />
Чтобы обойти это, нам может понадобиться вложенный <div>
с class
, <label>
и сам элемент управления <input />
. Добавляя все, мы можем получить что-то вроде этого:
<div class="toggle-switch"> <input type="checkbox" class="toggle-switch-checkbox" name="toggleSwitch" id="toggleSwitch" /> <label class="toggle-switch-label" for="toggleSwitch"> Toggle Me! </label> </div>
Со временем мы можем избавиться от текста метки и использовать <label>
чтобы установить или снять флажок для элемента управления вводом. Внутри <label>
давайте добавим два <span>
которые помогут нам сконструировать держатель переключателя и сам переключатель:
<div class="toggle-switch"> <input type="checkbox" class="toggle-switch-checkbox" name="toggleSwitch" id="toggleSwitch" /> <label class="toggle-switch-label" for="toggleSwitch"> <span class="toggle-switch-inner"></span> <span class="toggle-switch-switch"></span> </label> </div>
Преобразование в компонент React
Теперь, когда мы знаем, что нужно ввести в HTML, все, что нам нужно сделать, — это преобразовать HTML в компонент React. Давайте начнем с основного компонента здесь. Мы сделаем это компонентом класса, а затем преобразуем его в хуки, так как новым разработчикам легче следить за state
чем useState
:
import React, { Component } from "react"; class ToggleSwitch extends Component { render() { return ( <div className="toggle-switch"> <input type="checkbox" className="toggle-switch-checkbox" name="toggleSwitch" id="toggleSwitch" /> <label className="toggle-switch-label" htmlFor="toggleSwitch"> <span className="toggle-switch-inner" /> <span className="toggle-switch-switch" /> </label> </div> ); } } export default ToggleSwitch;
На данный момент невозможно иметь несколько ползунков переключателей на одном и том же экране или одной странице из-за повторения id
. Мы могли бы использовать метод компонентной компоновки в React, но в этом случае мы будем использовать props
для динамического заполнения значений:
import React, { Component } from "react"; class ToggleSwitch extends Component { render() { return ( <div className="toggle-switch"> <input type="checkbox" className="toggle-switch-checkbox" name={this.props.Name} id={this.props.Name} /> <label className="toggle-switch-label" htmlFor={this.props.Name}> <span className="toggle-switch-inner" /> <span className="toggle-switch-switch" /> </label> </div> ); } } export default ToggleSwitch;
this.props.Name
будет динамически заполнять значения id
, name
и for
(обратите внимание, что в React JS это htmlFor
), так что вы можете передавать различные значения компоненту и иметь несколько из них на одной странице. Кроме того, <span>
не имеет конечного </span>
; вместо этого он закрывается в начальном теге, как <span />
, и это вполне нормально.
Стиль и CSS
Недавно я написал 8 способов стилевого оформления компонентов React , и здесь мы будем использовать SCSS (что я считаю лучшим способом). Поскольку наш файл SCSS уже включен через начальный скрипт index.js
, нам не нужно снова включать другой файл SCSS в сам компонент. Давайте сначала посмотрим, как делается базовый CSS. После этого мы внесем в него улучшения. Вот несколько вещей, которые мы будем делать со стилем:
- По умолчанию переключатель будет иметь ширину всего
75px
и выровнен по вертикалиinline-block
чтобы он был встроен в текст и не вызывал проблем с макетом. - Мы позаботимся о том, чтобы элемент управления не был доступен для выбора, чтобы пользователи могли его перетаскивать.
- Мы также будем скрывать исходный флажок ввода.
- Оба стиля
::after
и::before
должны быть стилизованы и сделаны как элементы, чтобы поместить их в DOM и стилизовать его. - Мы также добавим несколько CSS-переходов, чтобы они выглядели круто и анимировано.
.toggle-switch { position: relative; width: 75px; display: inline-block; vertical-align: middle; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; text-align: left; } .toggle-switch-checkbox { display: none; } .toggle-switch-label { display: block; overflow: hidden; cursor: pointer; border: 0 solid #ccc; border-radius: 20px; margin: 0; } .toggle-switch-inner { display: block; width: 200%; margin-left: -100%; transition: margin 0.3s ease-in 0s; } .toggle-switch-inner::before, .toggle-switch-inner::after { display: block; float: left; width: 50%; height: 34px; padding: 0; line-height: 34px; font-size: 14px; color: white; font-weight: bold; box-sizing: border-box; } .toggle-switch-inner:before { content: "Yes"; text-transform: uppercase; padding-left: 10px; background-color: #f90; color: #fff; } .toggle-switch-disabled { background-color: #ccc; cursor: not-allowed; } .toggle-switch-disabled::before { background-color: #ccc; cursor: not-allowed; } .toggle-switch-inner::after { content: "No"; text-transform: uppercase; padding-right: 10px; background-color: #ccc; color: #fff; text-align: right; } .toggle-switch-switch { display: block; width: 24px; margin: 5px; background: #fff; position: absolute; top: 0; bottom: 0; right: 40px; border: 0 solid #ccc; border-radius: 20px; transition: all 0.3s ease-in 0s; } .toggle-switch-checkbox:checked + .toggle-switch-label .toggle-switch-inner { margin-left: 0; } .toggle-switch-checkbox:checked + .toggle-switch-label .toggle-switch-switch { right: 0px; }
Если мы посмотрим на это, "Yes"
и "No"
могут быть отправлены сюда динамически из data-*
управления с использованием атрибутов data-*
в HTML5. Это необходимо, потому что это не всегда одинаковые значения "Yes"
и "No"
. Давайте сделаем это динамически сейчас:
.toggle-switch-inner::before { content: attr(data-yes); /* other styles */ } .toggle-switch-inner::after { content: attr(data-no); /* other styles */ }
Также было бы неплохо использовать уменьшенную версию switch без текста, поэтому давайте добавим немного CSS для него с минимальными размерами и удалим текст:
.toggle-switch.small-switch { width: 40px; } .toggle-switch.small-switch .toggle-switch-inner:after, .toggle-switch.small-switch .toggle-switch-inner:before { content: ""; height: 20px; line-height: 20px; } .toggle-switch.small-switch .toggle-switch-switch { width: 16px; right: 20px; margin: 2px; }
Что касается отзывчивости, мы должны изменить полный размер. Для хакерской версии мы будем использовать функцию scale
CSS. Здесь мы рассмотрели все адаптивные ширины устройств на основе Bootstrap:
@media screen and (max-width: 991px) { .toggle-switch { transform: scale(0.9); } } @media screen and (max-width: 767px) { .toggle-switch { transform: scale(0.825); } } @media screen and (max-width: 575px) { .toggle-switch { transform: scale(0.75); } }
Преобразовав приведенный выше код в SCSS, мы получим что-то вроде:
.toggle-switch { position: relative; width: 75px; display: inline-block; vertical-align: middle; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; text-align: left; &-checkbox { display: none; } &-label { display: block; overflow: hidden; cursor: pointer; border: 0 solid #ccc; border-radius: 20px; margin: 0; } &-inner { display: block; width: 200%; margin-left: -100%; transition: margin 0.3s ease-in 0s; &:before, &:after { display: block; float: left; width: 50%; height: 34px; padding: 0; line-height: 34px; font-size: 14px; color: white; font-weight: bold; box-sizing: border-box; } &:before { content: attr(data-yes); text-transform: uppercase; padding-left: 10px; background-color: #f90; color: #fff; } } &-disabled { background-color: #ccc; cursor: not-allowed; &:before { background-color: #ccc; cursor: not-allowed; } } &-inner:after { content: attr(data-no); text-transform: uppercase; padding-right: 10px; background-color: #ccc; color: #fff; text-align: right; } &-switch { display: block; width: 24px; margin: 5px; background: #fff; position: absolute; top: 0; bottom: 0; right: 40px; border: 0 solid #ccc; border-radius: 20px; transition: all 0.3s ease-in 0s; } &-checkbox:checked + &-label { .toggle-switch-inner { margin-left: 0; } .toggle-switch-switch { right: 0px; } } &.small-switch { width: 40px; .toggle-switch-inner { &:after, &:before { content: ""; height: 20px; line-height: 20px; } } .toggle-switch-switch { width: 16px; right: 20px; margin: 2px; } } @media screen and (max-width: 991px) { transform: scale(0.9); } @media screen and (max-width: 767px) { transform: scale(0.825); } @media screen and (max-width: 575px) { transform: scale(0.75); } }
Тематика в SCSS
Поскольку мы можем использовать переменные в SCSS, их использование становится проще. Добавление поддержки нескольких цветовых тем в наше приложение стало проще с помощью SCSS. Sass Theming: Nevernding Story объясняет некоторые из них. Мы будем использовать некоторые цветовые темы здесь и изменим все необработанные цвета на переменные. Первые три строки — это настраиваемый набор цветов, который помогает нам контролировать наш маленький контроль:
// Colors $label-color: #ccc; $toggle-color: #f90; $white: #fff; // Styles .toggle-switch { position: relative; width: 75px; display: inline-block; vertical-align: middle; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; text-align: left; &-checkbox { display: none; } &-label { display: block; overflow: hidden; cursor: pointer; border: 0 solid $label-color; border-radius: 20px; margin: 0; } &-inner { display: block; width: 200%; margin-left: -100%; transition: margin 0.3s ease-in 0s; &:before, &:after { display: block; float: left; width: 50%; height: 34px; padding: 0; line-height: 34px; font-size: 14px; color: white; font-weight: bold; box-sizing: border-box; } &:before { content: attr(data-yes); text-transform: uppercase; padding-left: 10px; background-color: $toggle-color; color: $white; } } &-disabled { background-color: $label-color; cursor: not-allowed; &:before { background-color: $label-color; cursor: not-allowed; } } &-inner:after { content: attr(data-no); text-transform: uppercase; padding-right: 10px; background-color: $label-color; color: $white; text-align: right; } &-switch { display: block; width: 24px; margin: 5px; background: $white; position: absolute; top: 0; bottom: 0; right: 40px; border: 0 solid $label-color; border-radius: 20px; transition: all 0.3s ease-in 0s; } &-checkbox:checked + &-label { .toggle-switch-inner { margin-left: 0; } .toggle-switch-switch { right: 0px; } } &.small-switch { width: 40px; .toggle-switch-inner { &:after, &:before { content: ""; height: 20px; line-height: 20px; } } .toggle-switch-switch { width: 16px; right: 20px; margin: 2px; } } @media screen and (max-width: 991px) { transform: scale(0.9); } @media screen and (max-width: 767px) { transform: scale(0.825); } @media screen and (max-width: 575px) { transform: scale(0.75); } }
Взаимодействия и JavaScript
Теперь давайте сосредоточимся на том, чтобы заставить работать основной компонент. Поскольку все в React происходит на лету, нам нужно использовать state
s для хранения информации о локальных компонентах. Просто дружеское напоминание: всякий раз, когда вы изменяете любое значение в состоянии компонента, запускается метод жизненного цикла render()
React JS. Имея это в виду, давайте построим наше состояние по умолчанию:
state = { checked: this.props.defaultChecked };
Все, что нам нужно, это просто checked
состояние. Это будет логическое значение, и оно будет получено из defaultChecked
компонента. Мы будем использовать static defaultProps
в нашем компоненте класса как запасной вариант. Это выглядит примерно так:
// Set text for rendering. static defaultProps = { Text: ["Yes", "No"] }
Поскольку большинство реквизитов должны быть установлены пользователем, а мы не можем использовать произвольные значения, всегда лучше прекратить рендеринг, если необходимые реквизиты не переданы. Это можно сделать с помощью простого JavaScript-оператора if
или троичного оператора ? :
? :
или короткое замыкание «и» &&
:
{this.props.id ? ( <!-- display the control --> ) : null}
Мы могли бы также добавить слушателей событий, используя реквизиты для компонента, такого как onChange
. Наряду с этим, мы также можем добавить еще несколько реквизитов для следующего:
-
id
(обязательный): этоid
который будет передан в элемент управления вводом флажка. Без этого компонент не будет отображаться. -
Text
(обязательно): если вы не используетеSmall
версию элемента управления, вам может потребоваться передать его в тумблер в виде массива из двух значений, которые означают текст для True и False. Примером может бытьText={["Yes", "No"]}
. -
Name
(необязательно): это будет текст метки ввода флажка, но мы обычно не будем его использовать. -
onChange
(необязательно): это будет напрямую передано в<input type="checkbox" />
. -
defaultChecked
(необязательно): это будет напрямую передано в<input type="checkbox" />
. -
Small
(необязательно): это логическое значение, которое отображает тумблер в небольшом режиме, когда текст не отображается. -
currentValue
(необязательно): это будет напрямую передано в<input type="checkbox" />
какdefaultValue
. -
disabled
(необязательно): это будет напрямую передано в<input type="checkbox" />
.
По мере роста нашего приложения мы можем обнаруживать множество ошибок при проверке типов. React имеет некоторые встроенные возможности проверки типов. Чтобы запустить проверку типов для реквизита для компонента, вы можете назначить специальное свойство propTypes
. Мы можем применить приведенный выше список реквизитов, используя библиотеку React’s PropType , которая является отдельной библиотекой, которая экспортирует диапазон валидаторов, которые можно использовать для проверки правильности полученных вами данных.
В этом примере мы используем следующее:
-
PropTypes.string.isRequired
: это строковое значение, обязательное и обязательное. -
PropTypes.string
: это строковое значение, но оно не является обязательным. -
PropTypes.func
: это реквизит, который принимает в качестве значения функцию, но это не обязательно. -
PropTypes.bool
: это логическое значение, но оно не является обязательным.
Вам необходимо импортировать библиотеку PropTypes, используя:
import PropTypes from "prop-types";
А после определения класса и перед оператором экспорта, если он у вас есть отдельно, мы определим PropTypes следующим образом.
ToggleSwitch.propTypes = { id: PropTypes.string.isRequired, Text: PropTypes.string.isRequired, Name: PropTypes.string, onChange: PropTypes.func, defaultChecked: PropTypes.bool, Small: PropTypes.bool, currentValue: PropTypes.bool, disabled: PropTypes.bool };
Со всеми вышеупомянутыми элементами наш компонент теперь выглядит так:
import React, { Component } from "react"; import PropTypes from "prop-types"; /* Toggle Switch Component Note: id is required for ToggleSwitch component to function. Name, currentValue, defaultChecked, Small and onChange're optional. Usage: <ToggleSwitch id="id" onChange={function (e) { console.log("Checkbox Current State: " + e.target.checked); }} /> */ class ToggleSwitch extends Component { state = { checked: this.props.defaultChecked }; onChange = e => { this.setState({ checked: e.target.checked }); if (typeof this.props.onChange === "function") this.props.onChange(); }; render() { return ( <div className={"toggle-switch" + (this.props.Small ? " small-switch" : "")} > <input type="checkbox" name={this.props.Name} className="toggle-switch-checkbox" id={this.props.id} checked={this.props.currentValue} defaultChecked={this.props.defaultChecked} onChange={this.onChange} disabled={this.props.disabled} /> {this.props.id ? ( <label className="toggle-switch-label" htmlFor={this.props.id}> <span className={ this.props.disabled ? "toggle-switch-inner toggle-switch-disabled" : "toggle-switch-inner" } data-yes={this.props.Text[0]} data-no={this.props.Text[1]} /> <span className={ this.props.disabled ? "toggle-switch-switch toggle-switch-disabled" : "toggle-switch-switch" } /> </label> ) : null} </div> ); } // Set text for rendering. static defaultProps = { Text: ["Yes", "No"] }; } ToggleSwitch.propTypes = { id: PropTypes.string.isRequired, Text: PropTypes.string.isRequired, Name: PropTypes.string, onChange: PropTypes.func, defaultChecked: PropTypes.bool, Small: PropTypes.bool, currentValue: PropTypes.bool, disabled: PropTypes.bool }; export default ToggleSwitch;
Модульное тестирование
Любой созданный код должен пройти модульное тестирование, по крайней мере, до базового уровня. На моем текущем рабочем месте мы используем модульное тестирование на основе снимков. Мы будем проверять следующие условия в компоненте. Мы используем библиотечный энзим от AirBNB для тестирования и в качестве стандартного тестера . При написании этих тестов мы проверяем наш компонент Toggle Switch:
- следует рендерить без сбоев
- должен соответствовать снимку
- должен произойти сбой, если идентификатор не указан
- должен сделать, если указан идентификатор
- следует отключить слайдер, если он содержит отключенные реквизиты
Вот полный ToggleSwitch.test.js
:
import React from "react"; import { shallow } from "enzyme"; import ToggleSwitch from "../components/ToggleSwitch"; const text = ["Yes", "No"]; const chkID = "checkboxID"; describe("Toggle Switch Component", () => { it("should render without crashing", () => { const ToggleSwitchComponent = shallow(<ToggleSwitch />); expect(ToggleSwitchComponent.html()).not.toHaveLength(0); }); it("should match snapshot", () => { const ToggleSwitchComponent = shallow(<ToggleSwitch />); expect(ToggleSwitchComponent).toMatchSnapshot(); }); it("should fail if id is not supplied", () => { const ToggleSwitchComponent = shallow(<ToggleSwitch />); expect(ToggleSwitchComponent.find("label")).toHaveLength(0); }); it("should render if id is supplied", () => { const ToggleSwitchComponent = shallow( <ToggleSwitch id={chkID} Text={text} /> ); expect(ToggleSwitchComponent.find("label")).not.toHaveLength(0); }); it("should disable switch slider if it contains disabled props", () => { const ToggleSwitchComponent = shallow( <ToggleSwitch id={chkID} Text={text} disabled={true} /> ); expect(ToggleSwitchComponent.find("#" + chkID).props().disabled).toBe(true); }); });
Резюме
Вы можете получить полный исходный код от praveenscience / ToggleSwitch: Реализация тумблера в React JS в качестве компонента многократного использования . Инструкции по использованию обновляются в репозитории GitHub.
Я надеюсь, что вы нашли эту статью полезной. Если у вас есть какие-либо комментарии или вопросы, пожалуйста, напишите мне в Twitter .