
Доброго времени суток, друзья!
Представляю Вашему вниманию перевод статьи Kent Dodds «5 JavaScript Features I Couldn't Code Without».
Это мой первый перевод, так что буду рад любым замечаниям.
5 фич JavaScript, без которых я не мог бы писать код
Прежде чем мы начнем, позвольте мне немного рассказать о коде, который я пишу. Почти весь мой код состоит из Javascript, плюс немного HTML и CSS. Я пишу как клиентский, так и серверный JS. Я тестирую свой код. Я создаю и распространяю библиотеки с открытым исходным кодом, которые используются тысячами разработчиков по всему миру. Для фронтенда я использую React, для бэкенда — Express или бессерверные вычисления.
Вот 5 фич JS, без которых я не мог бы писать код. В произвольном порядке. Разумеется, «без которых я не мог бы писать код» — это гипербола. Это фичи, которые мне по-настоящему нравятся и используются мной постоянно.
1. Деструктуризация
Я использую эту фичу почти во всех файлах. Неважно, о React-компоненте идет речь или о функции, принимающей аргументы, деструктуризация — прикольная штука.
Вот несколько примеров:
const address = {
city: 'Salt Lake City',
state: 'UT',
zip: 84115,
coords: {
lat: 40.776608,
long: -111.920485,
},
}
// допустим, вы хотите сделать это:
const city = address.city
const state = address.state
const zip = address.zip
// используя деструктуризацию, вы можете сделать так:
const {city, state, zip} = address
Вот как это выглядит в React:
// без деструктуризации:
function UserName(props) {
return (
<div>
</div>
)
}
// с использованием деструктуризации:
function UserName() {
return (
<div>
</div>
)
}
// с использованием вложенной деструктуризации:
function UserName({name: {first, last}}) {
return (
<div>
</div>
)
}
Эта фича позволяет делать некоторые интересные вещи:
const info = {
title: 'Once Upon a Time',
protagonist: {
name: 'Emma Swan',
enemies: [
{name: 'Regina Mills', title: 'Evil Queen'},
{name: 'Cora Mills', title: 'Queen of Hearts'},
{name: 'Peter Pan', title: `The boy who wouldn't grow up`},
{name: 'Zelena', title: 'The Wicked Witch'},
],
},
}
// при правильном форматировании вложенная деструктуризация довольно проста,
// хотя не все разделяют мое мнение на этот счет
const {
title,
protagonist: {
name: name,
enemies: [, , , {title: enemyTitle, name: enemyName}],
},
} = info
console.log(`${enemyTitle} (${enemyName}) is an enemy to ${name} in "$5 JavaScript Features I Couldn't Code Without"`)
2. Модули
Это еще одна фича, которую я использую практически в каждом файле. До того как модули стали частью языка, приходилось использовать странные библиотеки и инструменты для работы с большими проектами. С модулями (и сборщиками, такими как Rollup или Webpack) у нас появилась отличная возможность делиться кодом с другими.
Вот парочка примеров:
// a.js
// экспорт
function add(a, b) {
return a + b
}
const foo = 'bar'
const theAnswer = 42
const theQuestion = 'who knows'
// синтаксис модулей позволяет экспортировать их во время создания, но
// мне нравится размещать все "экспорты" в одном месте
export default add
export {foo, theAnswer, theQuestion}
// b.js
// импорт
// 1. импортируем модуль
import './a'
// 2. импортируем по умолчанию
import add from './a'
// 3. импортируем `theAnswer` и `theQuestion` из './a'
import {theAnswer, theQuestion} from './a'
// 4. импортируем `theAnswer` и переименовываем его в `fortyTwo`
import {theAnswer as fourtyTwo} from './a'
// 5. импортируем `add` (по умолчанию) и `theQuestion`
import {default as add, theQuestion} from './a'
// 6. импортируем `add` и `theQuestion` без указания импорта по умолчанию
import add, {theQuestion} from './a'
// 7. импортируем все в единое "пространство имен" под названием `allTheThings`
import * as allTheThings from './a'
Если Вы хотите узнать больше о модулях, можете посмотреть мое видео на youtube — «More than you want to know about ES6 Modules».
3. Параметры по умолчанию
Я люблю и использую эту фичу все время. Это относится как к аргументам функции, так и к деструктуризации. Вот как это используется при деструктуризации объекта:
const bench = {type: 'Piano', adjustable: false}
const {legs = 4} = bench
// `The bench has ${legs} legs`
// -> The bench has 4 legs
// bench - скамья, leg - ножка
Обратите внимание, что у объекта bench нет свойства legs. Без использования синтаксиса параметров по умолчанию значением legs будет undefined.
Вы также можете использовать деструктурирующее присваивание с этой фичей:
const bench = {type: 'Piano', adjustable: false}
const {legs: legCount = 4} = bench
// `The bench has ${legCount} legs`
// -> The bench has 4 legs
Вот как это выглядит в списке параметров:
function getDisplayName(firstName = 'Unknown', lastName = 'Unknown') {
return `${firstName} ${lastName}`
}
// getDisplayName()
// -> Unknown Unknown
// getDisplayName('Andrew')
// -> Andrew Unknown
// getDisplayName(undefined, 'Yang')
// -> Unknown Yang
// getDisplayName('Andrew', 'Yang')
// -> Andrew Yang
Эта фича также позволяет делать некоторые довольно интересные вещи, поскольку значение справа от знака "=" вычисляется только при необходимости. Это означает, что Вы можете использовать ее для проверки наличия обязательных параметров:
function getCandy(
kind = requiredParam('kind'),
size = requiredParam('size'),
upperKind = kind.toUpperCase(),
callback = function noop() {},
) {
const result = {kind, size, upperKind}
callback(result)
return result
}
function requiredParam(argName) {
throw new Error(`${requiredParam} is required`)
}
// getCandy('twix', 'king')
// -> {kind: 'twix', size: 'king', upperKind: 'TWIX'}
// getCandy('twix')
// -> ошибка: 'size is required'
Некоторые считают этот код слишком сложным. Возможно, они правы. Но понимать, как это работает, очень круто!
О, а Вы заметили, что мы можем использовать предыдущие аргументы как часть параметров по умолчанию для следующих аргументов (как в случае с upperKind)? Здорово, правда?
4. Стрелочные функции
Я часто использую стрелочные функции. Мне нравятся функциональные выражения, но если, например, мне нужна анонимная функция обратного вызова (которой я не хочу придумывать имя), или я хочу получить неявные возвращаемые значения, то стрелочные функции — это как раз то, что мне нужно.
Вот несколько примеров использования стрелочных функций:
const divide = (a, b) => a / b
const getFive = () => 5
const identity = i => i
const asArray = (...args) => args
// обычно, я не именую стрелочные функции с несколькими аргументами
// (в таких случаях я использую обычные функциональные выражения),
// но Вы можете их именовать, если хотите:
const tryInvoke = (obj, fn, ...args) => {
try {
return obj[fn](...args)
} catch (e) {
return undefined
}
}
// для того, чтобы вернуть объект, необходимо заключить его в круглые скобки
const getObject = favoriteCandy => ()
// многострочные JSX (расширение JS в React) также должны быть заключены в круглые скобки
const MyComponent = () => (
<div>
Hello world! I am a function and I return <strong>JSX!</strong>
</div>
)
5. Промисы и async/await
JS является однопоточным и построен на системе событий (стеке вызовов). Я большой поклонник разговоров типа Что такое event loop? (с русскими субтитрами). Промисы и async/await являются отличными инструментами для управления этим. Большая часть моего кода является асинхронной и названные инструменты значительно упрощают мою работу. Честно говоря, промисы являются серьезной темой и требуют некоторого «привыкания», но они классные.
Я часто использую async/await в тестах и бэкенде. Вот пример асинхронного теста:
test('Can fill out a form across multiple pages', async () => {
mockSubmitForm.mockResolvedValueOnce({success: true})
const testData = {food: 'test food', drink: 'test drink'}
const {findByLabelText, findByText} = render(<App />)
user.click(await findByText(/fill.*form/i))
user.type(await findByLabelText(/food/i), testData.food)
user.click(await findByText(/next/i))
user.type(await findByLabelText(/drink/i), testData.drink)
user.click(await findByText(/review/i))
expect(await findByLabelText(/food/i)).toHaveTextContent(testData.food)
expect(await findByLabelText(/drink/i)).toHaveTextContent(testData.drink)
user.click(await findByText(/confirm/i, {selector: 'button'}))
expect(mockSubmitForm).toHaveBeenCalledWith(testData)
expect(mockSubmitForm).toHaveBeenCalledTimes(1)
user.click(await findByText(/home/i))
expect(await findByText(/welcome home/i)).toBeInTheDocument()
})
А вот пример использования async\await в Express:
async function getListItems(req, res) {
const listItems = await listItemsDB.query({ownerId: req.user.id})
res.json({listItems: await expandBookDataMultiple(listItems)})
}
Любопытно, что я не часто использую async/await в своем React-коде (по крайней мере, напрямую). Так происходит потому, что я стараюсь делать большую часть «асинхронной логики» вне моих компонентов. Поэтому если я, например, делаю что-то асинхронное при вызове useEffect в React, я ограничиваюсь одним вызовом асинхронной функции, поскольку считаю, что с промисами работать легче:
React.useEffect(() => {
getUser().then(
user => setState({status: 'success', error: null, user}),
error => setState({status: 'error', error, user: null}),
)
}, [])
Советую прочитать статью Anthony Chu «Async/Await in Node».
Заключение
Существует множество других фич, которые я регулярно использую и которые могли бы войти в этот список. Приведенные фичи — мои любимые, к ним я все время обращаюсь. Имеются также некоторые свежие дополнения к языку, которые еще не вошли в мою мышечную память. Сейчас самое время стать JS-разработчиком! Надеюсь, это статья была Вам полезной! Удачи!
grimalschi
Ведь пишем и без всего этого. Недавняя статья про капризных разработчиков как раз про это)
А вот async/await это невероятно удобно, и правда.
ameli_anna_kate
atomic1989
Async/await удобна там, где много взаимного вызова асинхронного кода. При этом проверки на ошибку производится а самом верхнем уровне. В клиентских приложениях скорее редкость. При написании веб служб встречается чаще
grimalschi
Можно и без :)
Zoolander
вместо стрелочных функций каждый раз сохраняете контекст в переменную или делаете bind(this)?
Я не подколоть, искренне интересуюсь. Для меня аргументом для использования стрелочных функций стало именно сохранение контекста без лишнего кода
scronheim
Плюсую, это довольно важная часть этой фичи
grimalschi
Сохраняю контекст
Keyten
bind(this), ещё всякие функции типа forEach поддерживают передачу this