Если ваша задача — не просто научиться писать код, а понять, как стать тем, без кого поддержка и развитие проекта просто немыслимы, то этот текст для вас. Заодно поговорим о том, как помочь коллегам постичь дзен и досконально изучить структуру разрабатываемого приложения.
Всем привет, меня зовут Макс Кравец, я 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?
