Доброго времени суток, друзья!
Представляю Вашему вниманию перевод статьи 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(`$ ($) is an enemy to $ 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`
// -> 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 $ legs`
// -> The bench has 4 legs
Вот как это выглядит в списке параметров:
function getDisplayName(firstName = 'Unknown', lastName = 'Unknown') {
return `$ $`
}
// 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(`$ 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-разработчиком! Надеюсь, это статья была Вам полезной! Удачи!