← JavaScript/Опциональная цепочка ?.#69 из 383← ПредыдущийСледующий →+20 XP
Полезно по теме:Гайд: как учить JavaScriptПрактика: JS базаПрактика: async и сетьТермин: Closure

Опциональная цепочка ?.

Реальная проблема: данные из API бывают неполными

Вы получаете данные пользователя из GitHub API. Одни пользователи указали компанию, другие — нет. Если написать user.organization.login, код упадёт с ошибкой для пользователей без организации. Раньше приходилось писать длинные защитные проверки. Опциональная цепочка решает это элегантно.

Что решает ?.

Вместо цепочки &&-проверок оператор ?. безопасно «идёт» по цепочке свойств — и если встречает null или undefined, возвращает undefined вместо ошибки.

На основе предыдущих уроков

  • «Объекты» — доступ к свойствам объекта
  • «Логические операторы» — && для защитных проверок (старый способ)
  • «?? нулевое слияние» — комбинируется с ?. для задания значения по умолчанию
  • Проблема без ?.

    const user = { name: 'Алексей' }  // нет поля address
    
    // Падает с ошибкой:
    console.log(user.address.city)
    // TypeError: Cannot read properties of undefined (reading 'city')
    
    // Старый способ — многословно:
    const city = user && user.address && user.address.city && user.address.city.toUpperCase()

    Оператор ?.

    // Новый способ — чисто и коротко:
    const city = user?.address?.city?.toUpperCase()
    // Если user.address === undefined → вернёт undefined, без ошибки

    Варианты применения

    Доступ к свойству:

    user?.profile?.avatar  // если user или profile — null/undefined → undefined

    Вызов метода:

    user?.getAvatar?.()    // если метода нет — не упадёт, вернёт undefined

    Доступ к элементу массива:

    arr?.[0]               // если arr — null/undefined → undefined
    users?.[0]?.name       // первый пользователь, если массив не пуст

    Динамические ключи:

    const key = 'name'
    user?.[key]            // user?.name через динамический ключ

    Комбинация с ??

    // ?. возвращает undefined если путь оборван
    // ?? задаёт значение по умолчанию вместо undefined/null
    const city = user?.address?.city ?? 'Не указан'
    const plan = user?.subscription?.plan ?? 'free'
    const count = data?.items?.length ?? 0

    Типичные ошибки

    Ошибка 1: ?. слева от присваивания

    // Сломано — нельзя использовать ?. слева от =
    user?.address = { city: 'Москва' }  // SyntaxError!
    
    // Исправлено:
    if (user) user.address = { city: 'Москва' }

    Ошибка 2: использование ?. везде без нужды

    // Избыточно — если user всегда существует, ?. здесь лишний
    function greet(user) {
      return user?.name  // user точно есть — параметр функции
    }
    
    // Лучше явно:
    function greet(user) {
      return user.name
    }

    Ошибка 3: путаница с || и ??

    // || вернёт правый операнд при любом falsy: 0, '', false
    const count = user?.items?.length || 0  // если length = 0 → вернёт 0 — OK здесь
    const name  = user?.profile?.name  || 'Аноним'  // если name = '' → 'Аноним'!
    
    // ?? вернёт правый только при null/undefined
    const name  = user?.profile?.name  ?? 'Аноним'  // '' останется ''

    В реальных проектах

  • Ответы API: response?.data?.user?.email — типично при работе с REST/GraphQL
  • DOM: document.querySelector('.btn')?.addEventListener(...) — если элемент может отсутствовать
  • React: props.user?.avatar ?? defaultAvatar
  • Конфигурация: config?.database?.host ?? 'localhost'
  • Примеры

    Обработка разных форматов ответа GitHub API

    // Симуляция ответов API — разные пользователи имеют разные поля
    const users = [
      {
        login: 'torvalds',
        name: 'Linus Torvalds',
        company: 'Linux Foundation',
        location: 'Portland, OR',
        plan: { name: 'pro', seats: 1 },
      },
      {
        login: 'ghost',
        name: null,
        company: null,
        location: null,
        plan: null,
      },
      {
        login: 'newbie',
        name: 'Начинающий',
        // нет company, location, plan
      },
    ]
    
    function formatUser(user) {
      return {
        login:    user.login,
        name:     user.name     ?? 'Без имени',
        company:  user.company  ?? 'Не указана',
        city:     user.location?.split(',')[0] ?? 'Не указан',
        plan:     user.plan?.name ?? 'free',
        seats:    user.plan?.seats ?? 1,
      }
    }
    
    users.forEach(u => {
      const info = formatUser(u)
      console.log(`@${info.login}: ${info.name} | ${info.company} | план: ${info.plan}`)
    })
    // @torvalds: Linus Torvalds | Linux Foundation | план: pro
    // @ghost: Без имени | Не указана | план: free
    // @newbie: Начинающий | Не указана | план: free
    
    // Безопасный вызов метода
    const firstUser = users[0]
    const upperCity = firstUser.location?.toUpperCase?.() ?? 'НЕТ'
    console.log(upperCity)  // 'PORTLAND, OR'

    Опциональная цепочка ?.

    Реальная проблема: данные из API бывают неполными

    Вы получаете данные пользователя из GitHub API. Одни пользователи указали компанию, другие — нет. Если написать user.organization.login, код упадёт с ошибкой для пользователей без организации. Раньше приходилось писать длинные защитные проверки. Опциональная цепочка решает это элегантно.

    Что решает ?.

    Вместо цепочки &&-проверок оператор ?. безопасно «идёт» по цепочке свойств — и если встречает null или undefined, возвращает undefined вместо ошибки.

    На основе предыдущих уроков

  • «Объекты» — доступ к свойствам объекта
  • «Логические операторы» — && для защитных проверок (старый способ)
  • «?? нулевое слияние» — комбинируется с ?. для задания значения по умолчанию
  • Проблема без ?.

    const user = { name: 'Алексей' }  // нет поля address
    
    // Падает с ошибкой:
    console.log(user.address.city)
    // TypeError: Cannot read properties of undefined (reading 'city')
    
    // Старый способ — многословно:
    const city = user && user.address && user.address.city && user.address.city.toUpperCase()

    Оператор ?.

    // Новый способ — чисто и коротко:
    const city = user?.address?.city?.toUpperCase()
    // Если user.address === undefined → вернёт undefined, без ошибки

    Варианты применения

    Доступ к свойству:

    user?.profile?.avatar  // если user или profile — null/undefined → undefined

    Вызов метода:

    user?.getAvatar?.()    // если метода нет — не упадёт, вернёт undefined

    Доступ к элементу массива:

    arr?.[0]               // если arr — null/undefined → undefined
    users?.[0]?.name       // первый пользователь, если массив не пуст

    Динамические ключи:

    const key = 'name'
    user?.[key]            // user?.name через динамический ключ

    Комбинация с ??

    // ?. возвращает undefined если путь оборван
    // ?? задаёт значение по умолчанию вместо undefined/null
    const city = user?.address?.city ?? 'Не указан'
    const plan = user?.subscription?.plan ?? 'free'
    const count = data?.items?.length ?? 0

    Типичные ошибки

    Ошибка 1: ?. слева от присваивания

    // Сломано — нельзя использовать ?. слева от =
    user?.address = { city: 'Москва' }  // SyntaxError!
    
    // Исправлено:
    if (user) user.address = { city: 'Москва' }

    Ошибка 2: использование ?. везде без нужды

    // Избыточно — если user всегда существует, ?. здесь лишний
    function greet(user) {
      return user?.name  // user точно есть — параметр функции
    }
    
    // Лучше явно:
    function greet(user) {
      return user.name
    }

    Ошибка 3: путаница с || и ??

    // || вернёт правый операнд при любом falsy: 0, '', false
    const count = user?.items?.length || 0  // если length = 0 → вернёт 0 — OK здесь
    const name  = user?.profile?.name  || 'Аноним'  // если name = '' → 'Аноним'!
    
    // ?? вернёт правый только при null/undefined
    const name  = user?.profile?.name  ?? 'Аноним'  // '' останется ''

    В реальных проектах

  • Ответы API: response?.data?.user?.email — типично при работе с REST/GraphQL
  • DOM: document.querySelector('.btn')?.addEventListener(...) — если элемент может отсутствовать
  • React: props.user?.avatar ?? defaultAvatar
  • Конфигурация: config?.database?.host ?? 'localhost'
  • Примеры

    Обработка разных форматов ответа GitHub API

    // Симуляция ответов API — разные пользователи имеют разные поля
    const users = [
      {
        login: 'torvalds',
        name: 'Linus Torvalds',
        company: 'Linux Foundation',
        location: 'Portland, OR',
        plan: { name: 'pro', seats: 1 },
      },
      {
        login: 'ghost',
        name: null,
        company: null,
        location: null,
        plan: null,
      },
      {
        login: 'newbie',
        name: 'Начинающий',
        // нет company, location, plan
      },
    ]
    
    function formatUser(user) {
      return {
        login:    user.login,
        name:     user.name     ?? 'Без имени',
        company:  user.company  ?? 'Не указана',
        city:     user.location?.split(',')[0] ?? 'Не указан',
        plan:     user.plan?.name ?? 'free',
        seats:    user.plan?.seats ?? 1,
      }
    }
    
    users.forEach(u => {
      const info = formatUser(u)
      console.log(`@${info.login}: ${info.name} | ${info.company} | план: ${info.plan}`)
    })
    // @torvalds: Linus Torvalds | Linux Foundation | план: pro
    // @ghost: Без имени | Не указана | план: free
    // @newbie: Начинающий | Не указана | план: free
    
    // Безопасный вызов метода
    const firstUser = users[0]
    const upperCity = firstUser.location?.toUpperCase?.() ?? 'НЕТ'
    console.log(upperCity)  // 'PORTLAND, OR'

    Задание

    Интернет-магазин получает данные о заказе из API — некоторые поля могут отсутствовать. Напиши функцию getOrderInfo(order), которая возвращает объект с полями: customerName (имя покупателя или 'Гость'), city (город доставки или 'Самовывоз'), promoDiscount (скидка промокода или 0), firstItemName (название первого товара или 'Нет товаров'). Используй ?. и ??.

    Подсказка

    customerName: order?.customer?.name ?? "Гость". firstItemName: order?.items?.[0]?.name ?? "Нет товаров". promoDiscount: order?.promo?.discount ?? 0

    Загружаем среду выполнения...
    Загружаем AI-помощника...