JavaScript, который нужно знать для React


Это перевод оригинальной статьи JavaScript to Know for React от Kent C. Dodds.

Одна из вещей, которые мне больше всего нравятся в React по сравнению с другими фреймворками, которые я использовал, — это то, насколько вы не выходите за рамки обычного JavaScript при его использовании. Нет шаблона DSL (JSX компилируется в JavaScript), API компонентов стал проще с добавлением React Hooks, а фреймворк предлагает вам всего несколько абстракций за пределами основных проблем пользовательского интерфейса, которые он призван решать.

Поэтому изучение функций JavaScript действительно полезно для эффективного создания приложений с помощью React. Итак, вот несколько функций JavaScript, на изучение которых я рекомендую вам потратить некоторое время, чтобы вы могли максимально эффективно работать с React.

Прежде чем мы перейдем к некоторым синтаксическим вещам, еще одна вещь, которую очень полезно понять для React, — это концепция замыкания функции. Отличное описание этой концепции есть здесь: mdn.io/closure.

Хорошо, давайте перейдем к фичам JS, которые вы захотите знать для React.

Шаблонные литералы

Шаблонные строки похожи на обычные, но с суперсилами:

const greeting = 'Hello'
const subject = 'World'
console.log(`${greeting} ${subject}!`) // Hello World!

// this is the same as:
console.log(greeting + ' ' + subject + '!')

// in React:
function Box({className, ...props}) {
  return <div className={`box ${className}`} {...props} />
}
Войти в полноэкранный режим Выйти из полноэкранного режима

MDN: Шаблонные литералы

Сокращенные названия свойств

Это настолько привычно и полезно, что я делаю это, не задумываясь.

const a = 'hello'
const b = 42
const c = {d: [true, false]}
console.log({a, b, c})

// this is the same as:
console.log({a: a, b: b, c: c})

// in React:
function Counter({initialCount, step}) {
  const [count, setCount] = useCounter({initialCount, step})
  return <button onClick={setCount}>{count}</button>
}
Войти в полноэкранный режим Выйти из полноэкранного режима

MDN: Object initializer Новые нотации в ECMAScript 2015

Функции со стрелками

Функции со стрелками — еще один способ написания функций в JavaScript, хотя они имеют несколько семантических различий. К счастью для нас, в мире React нам не нужно так сильно беспокоиться о this, поскольку мы используем в нашем проекте хуки (а не классы). Но функция стрелки позволяет использовать более короткие анонимные функции и неявные возвраты, так что вы посмотрите и захотите использовать их больше в своем коде.

const getFive = () => 5
const addFive = a => a + 5
const divide = (a, b) => a / b

// this is the same as:
function getFive() {
  return 5
}
function addFive(a) {
  return a + 5
}
function divide(a, b) {
  return a / b
}

// in React:
function TeddyBearList({teddyBears}) {
  return (
    <ul>
      {teddyBears.map(teddyBear => (
        <li key={teddyBear.id}>
          <span>{teddyBear.name}</span>
        </li>
      ))}
    </ul>
  )
}
Войти в полноэкранный режим Выход из полноэкранного режима

В приведенном выше примере следует обратить внимание на открытие и закрытие круглых скобок (. Это распространенный способ использования возможностей неявного возврата стрелочной функции при работе с JSX.

MDN: Arrow Functions

Деструктуризация

Декструкрутизация это, пожалуй, моя самая любимая фича в JavaScript. Я деструктуризирую объекты и массивы постоянно (и если вы используете useState, то вы тоже, вот так). Мне очень нравится их декларативность.

// const obj = {x: 3.6, y: 7.8}
// makeCalculation(obj)

function makeCalculation({x, y: d, z = 4}) {
  return Math.floor((x + d + z) / 3)
}

// this is the same as
function makeCalculation(obj) {
  const {x, y: d, z = 4} = obj
  return Math.floor((x + d + z) / 3)
}

// which is the same as
function makeCalculation(obj) {
  const x = obj.x
  const d = obj.y
  const z = obj.z === undefined ? 4 : obj.z
  return Math.floor((x + d + z) / 3)
}

// in React:
function UserGitHubImg({username = 'ghost', ...props}) {
  return <img src={`https://github.com/${username}.png`} {...props} />
}
Войти в полноэкранный режим Выйти из полноэкранного режима

MDN: Деструктуризация присвоения

Обязательно прочитайте эту статью на MDN. Вы точно узнаете что-то новое для себя. Когда закончите, попробуйте отрефакторить этот код, используя только одну строчку с деструктуризацией:

function nestedArrayAndObject() {
  // refactor this to a single line of destructuring...
  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 {} = info // <-- replace the next few `const` lines with this
  const title = info.title
  const protagonistName = info.protagonist.name
  const enemy = info.protagonist.enemies[3]
  const enemyTitle = enemy.title
  const enemyName = enemy.name
  return `${enemyName} (${enemyTitle}) is an enemy to ${protagonistName} in "${title}"`
}
Enter fullscreen mode Exit fullscreen mode

Параметры по умолчанию

Это еще одна фича, которую я использую постоянно. Это действительно мощный способ декларативного выражения значений параметров по умолчанию для ваших функций.

// add(1)
// add(1, 2)
function add(a, b = 0) {
  return a + b
}

// is the same as
const add = (a, b = 0) => a + b

// is the same as
function add(a, b) {
  b = b === undefined ? 0 : b
  return a + b
}

// in React:
function useLocalStorageState({
  key,
  initialValue,
  serialize = v => v,
  deserialize = v => v,
}) {
  const [state, setState] = React.useState(
    () => deserialize(window.localStorage.getItem(key)) || initialValue,
  )

  const serializedState = serialize(state)
  React.useEffect(() => {
    window.localStorage.setItem(key, serializedState)
  }, [key, serializedState])

  return [state, setState]
}
Войти в полноэкранный режим Выход из полноэкранного режима

MDN: Параметры по умолчанию

Rest/Spread

Синтаксис ... можно воспринимать как своего рода синтаксис «коллекций», работающий с коллекциями значений. Я постоянно его использую и вам также настоятельно рекомендую изучить как и где его применять. На самом деле, он имеет разные значения в зависимости от контекста, поэтому изучение нюансов будет вам полезным.

const arr = [5, 6, 8, 4, 9]
Math.max(...arr)
// is the same as
Math.max.apply(null, arr)

const obj1 = {
  a: 'a from obj1',
  b: 'b from obj1',
  c: 'c from obj1',
  d: {
    e: 'e from obj1',
    f: 'f from obj1',
  },
}
const obj2 = {
  b: 'b from obj2',
  c: 'c from obj2',
  d: {
    g: 'g from obj2',
    h: 'g from obj2',
  },
}
console.log({...obj1, ...obj2})
// is the same as
console.log(Object.assign({}, obj1, obj2))

function add(first, ...rest) {
  return rest.reduce((sum, next) => sum + next, first)
}
// is the same as
function add() {
  const first = arguments[0]
  const rest = Array.from(arguments).slice(1)
  return rest.reduce((sum, next) => sum + next, first)
}

// in React:
function Box({className, ...restOfTheProps}) {
  const defaultProps = {
    className: `box ${className}`,
    children: 'Empty box',
  }
  return <div {...defaultProps} {...restOfTheProps} />
}
Войти в полноэкранный режим Выход из полноэкранного режима

MDN: Синтаксис распространения

MDN: Параметры отдыха

ES Модули Модули

Если вы создаете приложение с помощью современных инструментов, оно, вероятно, поддерживает модули. Поэтому стоит узнать, как работает синтаксис, поскольку любое приложение даже небольшого размера, вероятно, потребует использования модулей для повторного использования и организации кода.

export default function add(a, b) {
  return a + b
}

/*
 * import add from './add'
 * console.assert(add(3, 2) === 5)
 */

export const foo = 'bar'

/*
 * import {foo} from './foo'
 * console.assert(foo === 'bar')
 */

export function subtract(a, b) {
  return a - b
}

export const now = new Date()

/*
 * import {subtract, now} from './stuff'
 * console.assert(subtract(4, 2) === 2)
 * console.assert(now instanceof Date)
 */

// dynamic imports
import('./some-module').then(
  allModuleExports => {
    // the allModuleExports object will be the same object you get if you had
    // used: import * as allModuleExports from './some-module'
    // the only difference is this will be loaded asynchronously which can
    // have performance benefits in some cases
  },
  error => {
    // handle the error
    // this will happen if there's an error loading or running the module
  },
)

// in React:
import React, {Suspense, Fragment} from 'react'

// dynamic import of a React component
const BigComponent = React.lazy(() => import('./big-component'))
// big-component.js would need to "export default BigComponent" for this to work
Войти в полноэкранный режим Выход из полноэкранного режима

MDN: импорт
MDN: export

В качестве альтернативы — я выступал с докладом об этом синтаксисе и вы можете посмотреть его здесь

Тернарный оператор

Обожаю тернарные операторы. Они невероятно декларативные. Особенно в JSX.

const message = bottle.fullOfSoda
  ? 'The bottle has soda!'
  : 'The bottle may not have soda :-('

// is the same as
let message
if (bottle.fullOfSoda) {
  message = 'The bottle has soda!'
} else {
  message = 'The bottle may not have soda :-('
}

// in React:
function TeddyBearList({teddyBears}) {
  return (
    <React.Fragment>
      {teddyBears.length ? (
        <ul>
          {teddyBears.map(teddyBear => (
            <li key={teddyBear.id}>
              <span>{teddyBear.name}</span>
            </li>
          ))}
        </ul>
      ) : (
        <div>There are no teddy bears. The sadness.</div>
      )}
    </React.Fragment>
  )
}
Войти в полноэкранный режим Exit fullscreen mode

Я понимаю, что тернарные операторы могут вызывать отвращение у некоторых людей, которым пришлось терпеть попытки понять их, прежде чем появился prettier и отформатировал наш код. Если вы еще не используете prettier, я настоятельно рекомендую вам это сделать. Prettier сделает ваши тернарные операторы намного легче для чтения.

MDN: Условный (тернарный) оператор

Методы массивов

Массивы фантастичны, и я часто использую их методы, в частности следующие:

  • find
  • some
  • every
  • включает
  • карта
  • фильтр
  • уменьшить

Вот несколько примеров:

const dogs = [
  {
    id: 'dog-1',
    name: 'Poodle',
    temperament: [
      'Intelligent',
      'Active',
      'Alert',
      'Faithful',
      'Trainable',
      'Instinctual',
    ],
  },
  {
    id: 'dog-2',
    name: 'Bernese Mountain Dog',
    temperament: ['Affectionate', 'Intelligent', 'Loyal', 'Faithful'],
  },
  {
    id: 'dog-3',
    name: 'Labrador Retriever',
    temperament: [
      'Intelligent',
      'Even Tempered',
      'Kind',
      'Agile',
      'Outgoing',
      'Trusting',
      'Gentle',
    ],
  },
]

dogs.find(dog => dog.name === 'Bernese Mountain Dog')
// {id: 'dog-2', name: 'Bernese Mountain Dog', ...etc}

dogs.some(dog => dog.temperament.includes('Aggressive'))
// false

dogs.some(dog => dog.temperament.includes('Trusting'))
// true

dogs.every(dog => dog.temperament.includes('Trusting'))
// false

dogs.every(dog => dog.temperament.includes('Intelligent'))
// true

dogs.map(dog => dog.name)
// ['Poodle', 'Bernese Mountain Dog', 'Labrador Retriever']

dogs.filter(dog => dog.temperament.includes('Faithful'))
// [{id: 'dog-1', ..etc}, {id: 'dog-2', ...etc}]

dogs.reduce((allTemperaments, dog) => {
  return [...allTemperaments, ...dog.temperament]
}, [])
// [ 'Intelligent', 'Active', 'Alert', ...etc ]

// in React:
function RepositoryList({repositories, owner}) {
  return (
    <ul>
      {repositories
        .filter(repo => repo.owner === owner)
        .map(repo => (
          <li key={repo.id}>{repo.name}</li>
        ))}
    </ul>
  )
}
Войти в полноэкранный режим Выход из полноэкранного режима

MDN: Array

Оператор нулевого слияния (??)

Если значение равно null или undefined, вероятно, вы захотите задать дефолтное значение:

// here's what we often did for this:
x = x || 'some default'

// but this was problematic for numbers or booleans where "0" or "false" are valid values

// So, if we wanted to support this:
add(null, 3)

// here's what we had to do before:
function add(a, b) {
  a = a == null ? 0 : a
  b = b == null ? 0 : b
  return a + b
}

// here's what we can do now
function add(a, b) {
  a = a ?? 0
  b = b ?? 0
  return a + b
}

// in React:
function DisplayContactName({contact}) {
  return <div>{contact.name ?? 'Unknown'}</div>
}
Enter fullscreen mode Exit fullscreen mode

MDN: Нуллишный коалесцирующий оператор

Оператор опциональной последовательности

Также известный как «Оператор Элвиса», он позволяет безопасно получать доступ к свойствам и вызывать функции, которые могут существовать или не существовать. Перед этим оператором мы использовали хаки, которые полагались на логику true/false.

// what we did before optional chaining:
const streetName = user && user.address && user.address.street.name

// what we can do now:
const streetName = user?.address?.street?.name

// this will run even if options is undefined (in which case, onSuccess would be undefined as well)
// however, it will still fail if options was never declared,
// since optional chaining cannot be used on a non-existent root object.
// optional chaining does not replace checks like if (typeof options == "undefined")
const onSuccess = options?.onSuccess

// this will run without error even if onSuccess is undefined (in which case, no function will be called)
onSuccess?.({data: 'yay'})

// and we can combine those things into a single line:
options?.onSuccess?.({data: 'yay'})

// and if you are 100% certain that onSuccess is a function if options exists
// then you don't need the extra ?. before calling it. Only use ?. in situations
// where the thing on the left might not exist.
options?.onSuccess({data: 'yay'})

// in React:
function UserProfile({user}) {
  return (
    <div>
      <h1>{user.name}</h1>
      <strong>{user.bio?.short ?? 'No bio provided'}</strong>
    </div>
  )
}
Войти в полноэкранный режим Exit fullscreen mode

Предостережение по этому поводу: если вы обнаружите, что используете ?. слишком часто в вашем коде, посмотрите, откуда происходят эти значения, и убедитесь, что они каждый раз возвращают те значения, которые должны.

MDN: Необязательное цепочное соединение

Промежутки и async/await

Это большая тема, и вам может потребоваться немного практики и времени, чтобы поработать с промежутками и хорошо разобраться. Они есть везде в экосистеме JavaScript и благодаря тому, что React укоренился в ней, там они также везде (на самом деле, сам React использует промиссы внутри библиотеки).

Промиссы помогают управлять асинхронным кодом, их возвращают многие DOM API, а также сторонние библиотеки. Синтаксис Async/await — это специальный синтаксис для работы с промиссами. Они идут рука об руку.

function promises() {
  const successfulPromise = timeout(100).then(result => `success: ${result}`)

  const failingPromise = timeout(200, true).then(null, error =>
    Promise.reject(`failure: ${error}`),
  )

  const recoveredPromise = timeout(300, true).then(null, error =>
    Promise.resolve(`failed and recovered: ${error}`),
  )

  successfulPromise.then(log, logError)
  failingPromise.then(log, logError)
  recoveredPromise.then(log, logError)
}

function asyncAwaits() {
  async function successfulAsyncAwait() {
    const result = await timeout(100)
    return `success: ${result}`
  }

  async function failedAsyncAwait() {
    const result = await timeout(200, true)
    return `failed: ${result}` // this would not be executed
  }

  async function recoveredAsyncAwait() {
    try {
      const result = await timeout(300, true)
      return `failed: ${result}` // this would not be executed
    } catch (error) {
      return `failed and recovered: ${error}`
    }
  }

  successfulAsyncAwait().then(log, logError)
  failedAsyncAwait().then(log, logError)
  recoveredAsyncAwait().then(log, logError)
}

function log(...args) {
  console.log(...args)
}

function logError(...args) {
  console.error(...args)
}

// This is the mothership of all things asynchronous
function timeout(duration = 0, shouldReject = false) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (shouldReject) {
        reject(`rejected after ${duration}ms`)
      } else {
        resolve(`resolved after ${duration}ms`)
      }
    }, duration)
  })
}

// in React:
function GetGreetingForSubject({subject}) {
  const [isLoading, setIsLoading] = React.useState(false)
  const [error, setError] = React.useState(null)
  const [greeting, setGreeting] = React.useState(null)

  React.useEffect(() => {
    async function fetchGreeting() {
      try {
        const response = await window.fetch('https://example.com/api/greeting')
        const data = await response.json()
        setGreeting(data.greeting)
      } catch (error) {
        setError(error)
      } finally {
        setIsLoading(false)
      }
    }
    setIsLoading(true)
    fetchGreeting()
  }, [])

  return isLoading ? (
    'loading...'
  ) : error ? (
    'ERROR!'
  ) : greeting ? (
    <div>
      {greeting} {subject}
    </div>
  ) : null
}
Войти в полноэкранный режим Выйти из полноэкранного режима

MDN: Promise

MDN: async функция

MDN: await

Итоги

Конечно, существует много полезных фич в JS для создания приложений на React, но эти одни из моих любимых и я замечаю, что использую их очень часто. Надеюсь, они вам пригодятся.

Если у вас будет желание углубиться в эту тему, у меня есть воркшоп, который я проводил и записал во время работы в PayPal. Возможно, он будет вам полезен: ES6 and Beyond Workshop at PayPal

Успехов!

Оцените статью
devanswers.ru
Добавить комментарий