Если ваша задача — не просто научиться писать код, а понять, как стать тем, без кого поддержка и развитие проекта просто немыслимы, то этот текст для вас. Заодно поговорим о том, как помочь коллегам постичь дзен и досконально изучить структуру разрабатываемого приложения.
Всем привет, меня зовут Макс Кравец, я CEO IT-компании Holyweb, и сегодня хочу поделиться вредными советами о том, как стать незаменимым React-разработчиком. Поехали!
Волшебный спред-оператор
Вам поставили задачу написать компонент, приветствующий пользователя. Нет ничего легче — обращаемся к объекту, который хранит имя и фамилию, и добавляем их к приветствию:
export function Welcome({name, surname}) {
return (
<div>
Привет, {name} {surname}
</div>
)
}
Но на странице приветствие выводится в составе другого компонента, предположим, это хедер. А значит — нужно пробросить параметры. Как поступит новичок? Сядет и методично распишет все в коде родителя:
export function Header({ user }) {
return (
<div>
<Welcome
name={user.name}
surname={user.surname}
/>
<div>
Some other code...
</div>
</div>
);
}
В целом — это работает. Но такой код разберет любой другой новичок, ведь движение пропсов по древу компонентов прозрачно! Кроме того, параметров может быть не два, а два десятка. Представляете, сколько времени придется потратить, сколько строк кода написать?
Ловите лайфхак:
export function Header({ user }) {
return (
<div>
<Welcome {...user}/>
<div>
Some other code...
</div>
</div>
);
}
Магия спред-оператора — код стал проще, короче, не нужно расписывать каждый параметр. А главное — никто, кроме вас, теперь не может при беглом взгляде на хедер понять, какие именно поля из объекта user на самом деле нужны в компоненте приветствия.
Вот таким нехитрым способом можно и время сэкономить, и +100 в скилл «незаменимость» себе добавить — пусть только попробуют от вас избавиться, себе дороже выйдет!
Техника Props Drilling
Если писать код, беспокоясь только о реализации бизнес-логики, можно даже не заметить, как попадешь в ловушку.
Простейшая задача: пишем составной компонент, который выводит в банковском приложении информацию о счете клиента. Тип счета задается на верхнем уровне, а отрисовывает его компонент в глубине иерархии. Создаем контекст, определяем провайдер, присваиваем значение…
export const AccountType = createContext()
export function TopComponent() {
return (
<AccountType.Provider value="credit">
<MiddleComponent />
</AccountType.Provider>
);
}
export function MiddleComponent() {
return (
<LowComponent />
);
}
export function LowComponent() {
return (
<DeepComponent />
);
}
export function DeepComponent() {
const accountType = useContext(AccountType);
return (
<span> {accountType} account</span>
);
}
Код выше — воплощение цифрового эгоизма. Судите сами — вашему коллеге достаточно разобраться, где контекст провайдится, и где выводится. А вся остальная иерархия остается без внимания. Хорошо, когда все нужные компоненты выстроены в одном файле друг за дружкой, но где вы такое в реальном проекте видели?
На помощь приходит техника Props Drilling — просто объявите нужное свойство на самом верху древа компонентов и методично прокиньте до места использования.
export function TopComponent() {
const account = {
accountType: "credit"
}
return (
<MiddleComponent account={account} />
);
}
export function MiddleComponent({account}) {
return (
<LowComponent account={account} />
);
}
export function LowComponent({account}) {
return (
<DeepComponent account={account}/>
);
}
export function DeepComponent({account}) {
return (
<span> {account.accountType} account</span>
);
}
Бинго! Даже если компоненты разнесены по разным папкам проекта, ни один не останется без внимания ваших коллег в процессе поиска возможной ошибки. А чтобы не бросать и этот процесс на самотек и гарантированно заработать искреннюю благодарность соратников по работе, можно где-то в середине цепочки изменить или даже обнулить свойство:
…
export function MiddleComponent({account}) {
account.accountType = null
return (
<LowComponent account={account} />
);
}
…
Простыми компонентами никого не удивишь
Ну вот и пришло время для серьезной задачи: готовим компонент, который выводит данные карты пользователя приложения. При этом номер карты и CVV по умолчанию должны быть скрыты и отображаться при клике/тапе по соответствующему полю, ФИО — отображаться в виде «имя сокращено до одной буквы, фамилия полностью, все заглавными буквами». Кроме того, нужно предусмотреть возможность копирования номера карты и CVV в буфер обмена.
Сначала пройдем по простому пути, а потом разберем, в чем была ошибка. Напишем функцию, которая будет отправлять нужные данные в буфер обмена
const copyToClipBoard = data => {
const textField = document.createElement('textarea')
textField.innerText = data
document.body.appendChild(textField)
textField.select()
document.execCommand('copy')
textField.remove()
alert('Скопировано в буфер обмена')
}
добавим разбиение номера карты на группы
const formatNumber = cardNumber => {
let cNumToStr = ''
let it = 0
String(cardNumber).split('').forEach((s) => {
if (it % 4 === 0) {
cNumToStr = cNumToStr + ' '
}
cNumToStr = cNumToStr + s
it++
})
return cNumToStr
}
напишем компонент, который выводит номер карты, предусмотрев вариант скрытия первых трех групп чисел и возможность копирования данных
export function CardNumberComponent({cardNumber}) {
const [isShow, setIsShow] = useState(false)
const toggle = () => {setIsShow(!isShow)}
const showNumber = isShow
? formatNumber(cardNumber)
: "•••• " + formatNumber(cardNumber).substring(formatNumber(cardNumber).length - 4)
return (
<div>
<p>Номер карты</p>
<div onClick={toggle}> {showNumber} </div>
<button onClick={() => copyToClipBoard(cardNumber)}>копировать</button>
</div>
)
}
аналогичным образом разберемся с CVV
export function CardCVVComponent({cvv}) {
const [isShow, setIsShow] = useState(false)
const toggle = () => {setIsShow(!isShow)}
const showCVV = isShow
? cvv
: "•••"
return (
<div>
<p>CVV</p>
<div onClick={toggle}> {showCVV} </div>
<button onClick={() => copyToClipBoard(cvv)}>копировать</button>
</div>
)
}
Напишем компонент, который выводит срок действия карты
export function CardExpireComponent({expire}) {
return (
<div>
<p>Срок действия</p>
{expire.year}/{expire.month}
</div>
)
}
и данные владельца
export function CardUserNameComponent({user}) {
const userInfo = (user.name[0] + '. ' + user.surname).toUpperCase()
return (
<div>
<p>Владелец</p>
<div>{userInfo}</div>
<button onClick={() => copyToClipBoard(userInfo)}>копировать</button>
</div>
)
}
Ну и последним действием соберем все вместе
export function CardComponent() {
return (
<>
<CardNumberComponent cardNumber={cardInfo.cardNumber}></CardNumberComponent>
<CardExpireComponent expire={cardInfo.expire}></CardExpireComponent>
<CardCVVComponent cvv={cardInfo.cvv}></CardCVVComponent>
<CardUserNameComponent user={user}></CardUserNameComponent>
</>
)
}
Источниками данных будут служить объект информации о пользователе и объект информации о карте
const user = {
name: 'Ivan',
surname: 'Ivanov',
}
const cardInfo = {
cardNumber: "1234567812345678",
expire: {
year: 24,
month: 11
},
cvv: 123
}
Как говорится — получите, распишитесь. Но присмотритесь внимательно, что мы накодили в результате? Пара простых функций, несколько небольших компонентов. Срамота, да и только! Этим не похвастаешься перед коллегами, да и начальству не объяснишь, что задача была трудна и неплохо бы подумать о премии.
Выход — вспомнить о том, что нас просили сделать компонент, который выводит данные о карте. Один. Давайте исправим наш код.
export function CardComponent() {
const copyToClipBoard = data => {
const textField = document.createElement('textarea')
textField.innerText = data
document.body.appendChild(textField)
textField.select()
document.execCommand('copy')
textField.remove()
alert('Скопировано в буфер обмена')
}
const [isShowNumber, setIsShowNumber] = useState(false)
const [isShow, setIsShow] = useState(false)
const toggleNumber = () => {setIsShowNumber(!isShowNumber)}
const toggleCVV = () => {setIsShow(!isShow)}
const userInfo = (user.name[0] + '. ' + user.surname).toUpperCase()
let cNumToStr = ''
let it = 0
String(cardInfo.cardNumber).split('').forEach((s) => {
if (it % 4 === 0) {
cNumToStr = cNumToStr + ' '
}
cNumToStr = cNumToStr + s
it++
return cNumToStr
})
const showNumber = isShowNumber
? cNumToStr
: "•••• " + cNumToStr.substring(cNumToStr.length - 4)
const showCVV = isShow
? cardInfo.cvv
: "•••"
return (
<>
<div>
<p>Номер карты</p>
<div onClick={toggleNumber}> {showNumber} </div>
<button onClick={() => copyToClipBoard(cardInfo.cardNumber)}>копировать</button>
</div>
<div>
<p>Срок действия</p>
{cardInfo.expire.year}/{cardInfo.expire.month}
</div>
<div>
<p>CVV</p>
<div onClick={toggleCVV}> {showCVV} </div>
<button onClick={() => copyToClipBoard(cardInfo.cvv)}>копировать</button>
</div>
<div>
<p>Владелец</p>
<div>{userInfo}</div>
<button onClick={() => copyToClipBoard(userInfo)}>копировать</button>
</div>
</>
)
}
Согласитесь, выглядит гораздо солиднее? Мало того, что весь функционал в одном месте, так еще и можно быть уверенным — проще будет позвать вас, как автора, чем разобраться, что в этом компоненте к чему относится. А значит — смело можно добавлять +500 в скилл «незаменимость».
Что в итоге?
Разумеется, секретов у настоящего мастера много, и выше приведены далеко не все. Ищите сами, добавляйте свои варианты в комментарии. Но помните, что при этом главное — не перегнуть палку. А то можно стать настолько незаменимым, что никто и на работу взять не рискнет.
Если хотите продолжения, пишите в комментарии или мне в Телеграм, и мы выпустим продолжение подборки незаменимых советов для реакт-мастеров :)
Другие наши статьи:
Security с характером, или еще несколько слов о паттерне Singleton
Как выдать Золушку за принца и не сойти с ума. Паттерн Декоратор
Погружение во внедрение зависимостей (DI), или как взломать Матрицу
Прекратите изучать фреймворк, станьте JavaScript-разработчиком
DOM, который построил Chrome. Или не построил? Или не Chrome? Или не DOM?
Комментарии (10)
aavezel
07.02.2022 16:00+1Контексты можно тоже преопределять, притом хрен кто поймет в каком месте это поменяно. Особенно если использовать displayName по извращенному кодестайлу. И в отличии от props drilling, это точно хрен найдешь...
export function MiddleComponent() { return ( <AccountType.Provider value="debet"> <LowComponent account={account} /> </AccountType.Provider> ); }
а ещё можно провайдер завязать на функционал, который в зависимость берет туеву кучу параметров от родителя, который формируется на основе магии вуду индийского пошива.
а ещё можно каждый компонент обвязать 5-6 различными контекстами (каждый компонент конечно своим списком контекстов), каждый контекст из которых инициализируется в разных родительских компонентах...
я ещё знаю пару способов использования контекстов, которые заставят вашего разработчика сделать харакири, но их приводить не буду, так как запрещены Женевской конвенцией.
Alexandroppolus
08.02.2022 02:43Про способ с локальными контекстами было недавно - https://habr.com/ru/company/alfa/blog/647013/
Имхо, вполне съедобная штука. Есть паттерн, есть теория к нему, есть, в конце концов, код-ревью для подстраховки соблюдения дисциплины при использовании - значит инструмент можно использовать.
skeevy
07.02.2022 16:46+1почему нет совета использовать querySelector вместо ref? :) такое же тоже бывает
Maxim_from_HW Автор
07.02.2022 20:22+1Поздравляем, вы приняты на работу, занесите в отдел кадров ведомость "Проклятия коллег 2010-2020"))
Gibboustooth
08.02.2022 19:37const formatNumber = cardNumber => { let cNumToStr = '' let it = 0 String(cardNumber).split('').forEach((s) => { if (it % 4 === 0) { cNumToStr = cNumToStr + ' ' } cNumToStr = cNumToStr + s it++ }) return cNumToStr }
Отличная реализация форматирования! А то неопытный разработчик мог бы написать
const formatNumber = (cardNumber: string): string => cardNumber.match(/\d{4}/g).join(' ')
и немедленно вылететь с работы. Не за то ему платят, чтобы функции в одну строчку писать.
Кстати, еще один отличный совет: ни в коем случае, ни при каких обстоятельствах не использовать Typescript
insphoto
08.02.2022 22:40+1Видимо у автора наболело)
К сожалению правда жизни такова что очень многие разработчики пишут код как будто учились по вредным советам. Был у меня в работе один проект, который начинал один мастер пера, я один компонент дорабатывал три дня что бы отредактировать один алгоритм внутри. В итоге мне надоело, я его с нуля написал, оказалось проще код выкинуть
Frimko
Заголовок не верный для такой статьи. Предлагаю "Вредные рецепты и как схватить ненависть своих коллег".
з.ы. Смеялись с коллегами долго.
JustDont
Я так и не понял, то ли это "вредные советы", то ли еще что. Скажем первый совет про спред — весьма рабочий. При условии что проект на TS, и типы нормально описаны. А если на JS — то да, это уровень классического // happy debugging!
ivadey
Необязательно, вполне может хватить prop-types, в некоторых моментах результат может быть даже лучше чем на ts
P.S. PropTypes не замена и не альтернатива, просто ещё один инструмент, который порой очень удобен.
Dartess
Типы не спасут от того что так можно покинуть несуществующие пропсы в дом элементы.