От автора: в последнее время у меня был период жизни работы с React. И в моем путешествии мне было трудно найти хороший стиль написания кода, чтобы держать JSX и JS в чистом и понятном виде. Я придумывал свои собственные руководства по стилю, которыми хотел бы поделиться. Возможно, они окажутся полезны для вас и, конечно, не стесняйтесь делиться подобными рекомендациями в комментариях ниже.
Правило № 1: деструктурируйте props
Одной из моих любимых функций ES6 является деструктурирование. Она позволяет присваивать свойства объекта менее сложным переменным. Давайте посмотрим на пример.
Скажем, у нас есть собака, которую мы хотим отобразить как div с классом, названным в честь его породы. Внутри div есть предложение, которое отмечает цвет собаки и говорит нам, что это хорошая собака или плохая собака.
1 2 3 4 5 |
class Dog extends Component { render () { return <div className={this.props.breed}>My {this.props.color} dog is {this.props.isGoodBoy ? "good" : "bad"}</div>; } } |
Технически здесь делается все, что мы хотим, но это просто похоже на большой блок кода, который действительно представляет собой только три переменные и один HTML-тег.
Мы можем разбить его, присвоив все свойства props локальным переменным.
1 2 3 |
let breed = this.props.breed; let color = this.props.color; let isGoodBoy = this.props.isGoodBoy; |
Используя ES6, мы можем поместить его в одну точную конструкцию следующим образом:
1 |
let { breed, color, isGoodBoy } = this.props; |
Чтобы все было точно, мы вставляем тернарного оператора (подробнее об этом позже) в свою собственную переменную, и вуаля.
1 2 3 4 5 6 7 |
class Dog extends Component { render () { let { breed, color, isGoodBoy } = this.props; let identifier = isGoodBoy ? "good" : "bad"; return <div className={breed}>My {color} dog is {identifier}</div>; } } |
Намного легче читается.
Правило № 2: один тег, одна строка
У всех нас был такой момент, когда хотелось взять функцию и сделать ее пищей для операторов и крошечными именами параметров, чтобы создать какую-то угашенную, сверхбыструю, нечитаемую полезную функцию. Однако когда вы создаете бесструктурный компонент в React, то можете довольно легко сделать то же самое, оставаясь чистым.
1 2 3 4 5 6 |
class Dog extends Component { render () { let { breed, color, goodOrBad } = this.props; return <div className={breed}>My {color} dog is {goodOrBad}</div>; } } |
VS.
1 |
let Dog = (breed, color, goodOrBad) => <div className={breed}>My {color} dog is {goodOrBad}</div>; |
Если все, что вы делаете, создаёт базовый элемент и помещает свойства в HTML-тег, то не беспокойтесь о том, чтобы сделать так много функций и оберток, чтобы получить совершенно отдельный класс. Одна строка кода будет работать.
Вы даже можете проявить креативность с некоторыми функциями расширения ES6, когда будете передавать объект параметрам. Использование this.props.content будет автоматически помещать строку между тегами open и close.
1 2 3 4 5 6 7 8 |
let propertiesList = { className: "my-favorite-component", id: "myFav", content: "Hello world!" }; let SimpleDiv = props => <div {... props} />; let jsxVersion = <SimpleDiv props={propertiesList} />; |
Когда используются функции расширения:
Когда не требуются тернарные операторы
Когда передаются только атрибуты и содержимое HTML-тегов
Когда может использоваться повторно
Когда функция расширения не используется:
При динамических свойствах
Если требуются свойства массива или объекта
Если есть средство рендеринга, для которого требуются вложенные теги
Правило № 3: Правило трёх
Если у вас есть три или более свойства, то помещайте их на отдельные строки, как в экземпляре, так и в функции render.
Было бы неплохо иметь только одну строку свойств:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class GalleryImage extends Component { render () { let { imgSrc, title } = this.props; return ( <figure> <img src={imgSrc} alt={title} /> <figcaption> <p>Title: {title}</p> </figcaption> </figure> ); } } |
Но взгляните на это:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class GalleryImage extends Component { render () { let { imgSrc, title, artist, clas, thumbnail, breakpoint } = this.props; return ( <figure className={clas}> <picture> <source media={`(min-width: ${breakpoint})`} srcset={imgSrc} /> <img src={thumbnail} alt={title} /> </picture> <figcaption> <p>Title: {title}</p> <p>Artist: {artist}</p> </figcaption> </figure> ); } } |
Или рендер:
1 |
<GalleryImage imgSrc="./src/img/vangogh2.jpg" title="Starry Night" artist="Van Gogh" clas="portrait" thumbnail="./src/img/thumb/vangogh2.gif" breakpoint={320} /> |
Здесь слишком много кода для чтения. Поместите каждое свойство на следующую строку для чистого и понятного вида:
1 2 3 4 5 6 |
let { imgSrc, title, artist, clas, thumbnail, breakpoint } = this.props; |
а также:
1 2 3 4 5 6 7 |
<GalleryImage imgSrc="./src/img/vangogh2.jpg" title="Starry Night" artist="Van Gogh" clas="landscape" thumbnail="./src/img/thumb/vangogh2.gif" breakpoint={320} /> |
Правило № 4: Слишком много свойств?
Управление свойствами сложно на любом уровне, но с деструктуризацией ES6 и структурным подходом React существует довольно много способов привести в порядок внешний вид множества свойств.
Предположим, мы создаем приложение сопоставления, в котором есть список сохраненных адресов и координата GPS для вашего текущего местоположения.
Текущая информация пользователя о местоположении и близости к избранному адресу должна быть в родительском компоненте приложения следующим образом:
1 2 3 4 5 6 7 8 9 10 |
class App extends Component { constructor (props) { super(props); this.state = { userLat: 0, userLon: 0, isNearFavoriteAddress: false }; } } |
Итак, мы создаём адрес и хотим отметить, как близко к нему находимся. Так мы передаем по крайней мере два свойства из приложения.
В приложении render ():
1 2 3 4 |
<Address ... // Information about the address currentLat={this.state.userLat} currentLong={this.state.userLon} /> |
В функции рендеринга для адресного компонента:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
render () { let { houseNumber, streetName, streetDirection, city, state, zip, lat, lon, currentLat, currentLon } = this.props; return ( ... ); } |
Уже сейчас мы видим, каким громоздким становится код. Если взять два набора информации и разложить их на свои собственные объекты, то всё станет намного более управляемым.
В приложении constructor ():
1 2 3 4 5 6 7 |
this.state = { userPos: { lat: 0, lon: 0 }, isNearFavoriteAddress: false }; |
До приложения render ():
1 2 3 4 5 6 7 8 9 10 11 |
let addressList = []; addressList.push({ houseNumber: "1234", streetName: "Street Rd", streetDirection: "N", city: "City", state: "ST", zip: "12345", lat: "019782309834", lon: "023845075757" }); |
В приложении render ():
1 |
<Address addressInfo={addressList[0]} userPos={this.state.userPos} /> |
В функции рендеринга для адресного компонента
1 2 3 4 5 6 7 8 9 10 11 12 |
render () { let { addressInfo, userPos } = this.props; let { houseNumber, streetName, streetDirection, city, state, zip, lat, lon } = addressInfo; return ( ... ); } |
Так намного чище. В React тоже есть несколько отличных способов гарантировать, что свойства объекта существуют и имеют определенный тип, используя PropTypes , чего мы обычно не можем сделать в JavaScript, но который просто является отличной OOP вещью.
Правило №5: Динамические рендеринги — Отображение массивов
Довольно часто в HTML мы пишем одни и те же основные части кода снова и снова, просто с несколькими ключевыми отличиями. Вот почему был создан React. Вы создаете объект со свойствами, возвращающими сложный динамический блок HTML, без необходимости писать каждую его часть повторно.
JavaScript уже имеет отличный способ делать списки подобной информации: массивы!
React использует функцию .map() для упорядочивания массивов, используя один параметр из массивов в виде key.
1 2 3 4 5 6 7 8 |
render () { let pokemon = [ "Pikachu", "Squirtle", "Bulbasaur", "Charizard" ]; return ( <ul> {pokemon.map(name => <li key={name}>{name}</li>)} </ul> ); } |
Вы даже можете использовать наши удобные функции распространения, чтобы сбрасывать весь список параметров с помощью объекта Object.keys()(имеется в виду, что нам все еще нужен key).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
render () { let pokemon = { "Pikachu": { type: "Electric", level: 10 }, "Squirtle": { type: "Water", level: 10 }, "Bulbasaur": { type: "Grass", level: 10 }, "Charizard": { type: "Fire", level: 10 } }; return ( <ul> {Object.keys(pokemon).map(name => <Pokemon key={name} {... pokemon[name]} />)} </ul> ); } |
Правило №6: Динамический рендеринг — тернарные операторы React
В React вы можете использовать операторы для выполнения условного рендеринга точно так же, как объявление переменной. В правиле №1 мы рассмотрели это, указав, была ли наша собака хорошей или плохой. Не обязательно создавать целую строку кода, чтобы решить однословную разницу в предложении, но когда он становится крупным блоком кода, трудно найти те маленькие ? и те же : .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class SearchResult extends Component { render () { let { results } = this.props; return ( <section className="search-results"> {results.length > 0 && results.map(index => <Result key={index} {... results[index] />) } {results.length === 0 && <div className="no-results">No results</div> } </section> ); } } |
Или в настоящем тернарном стиле
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class SearchResult extends Component { render () { let { results } = this.props; return ( <section className="search-results"> {results.length > 0 ? results.map(index => <Result key={index} {... results[index] />) : <div className="no-results">No results</div> } </section> ); } } |
Даже с нашим аккуратным отображением результатов вы можете видеть, как скобки гнездятся довольно плотно. Теперь представьте, если рендер имеет не одну линию. Он может довольно быстро стать нечитаемым. Рассмотрим альтернативу:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class SearchResult extends Component { render () { let { results } = this.props; let outputJSX; if (results.length > 0) { outputJSX = ( <Fragment> {results.map(index => <Result key={index} {... results[index] />)} </Fragment> ); } else { outputJSX = <div className="no-results">No results</div>; } return <section className="search-results">{outputJSX}</section>; } } |
В конечном счете, длина кода примерно одинакова, но есть одно ключевое различие: в первом примере мы быстро переключаемся между двумя разными синтаксисами, делая визуальный анализ обременительным и сложным, тогда как второе — просто обычный JavaScript с присвоением значений в одном согласованном языке и однострочном возврате функции в другом.
Правилом в этой ситуации является то, что если вы вносите в ваш объект JSX JavaScript более двух слов (например object.property), это должно быть сделано до вызова return.
Заключение
Комбинация синтаксиса может стать беспорядочной, и в самой очевидной ситуации, я видел, что мой код сходит с рельсов. Вот основные понятия, почему это происходит, могут быть применены к любой ситуации, которая здесь не была рассмотрена:
Используйте функции ES6. Шутки в сторону. Есть много фантастических функций, которые могут облегчить и ускорить вашу работу, тем более, ручную.
Пишите JSX только с правой стороны = или return.
Иногда вам будет нужен JavaScript в вашем JSX. Если JavaScript не подходит для одной строки (например, функции .map() или тернарного оператора), то это должно быть сделано заранее.
Если ваш код начинает выглядеть примерно так (<{${()}
} />), то вы, вероятно, зашли слишком далеко. Возьмите самый низкий уровень за пределами текущего утверждения и напишите код перед ним.
Автор: Daniel Jauch
Источник: //css-tricks.com/
Редакция: Команда webformyself.