Author: furrycat

07
Янв
2020

Чего ждать от JavaScript в 2020 году? 7 фич, которые вот-вот попадут в стандарт

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

Ура-ура! Сразу несколько приятных и полезных фич перешли на стадию завершения, а значит в 2020 году нас ждет много радостей. Наконец-то в стандарте появятся долгожданные опциональные последовательности и динамический импорт. Оставим их на сладкое, а сначала посмотрим, что еще приготовил разработчикам TC39.

Если вы не можете удержаться, загляните в репозиторий пропозалов на страничку Finished Proposals. Там вы найдете полный список предложений, достигших стадии 4.

String.prototype.matchAll

Представим, строка сопоставляется с подстрокой или регулярным выражением. Чтобы найти все совпадения, придется потрудиться. Ведь метод String.prototype.match для обычной регулярки возвращает информацию только о первом совпадении:

            let re = /ll(o|y)/;
let reg = /ll(o|y)/g;
let str = 'Hello Dolly';

str.match(re); 
        
Вызов метода String.prototype.match с обычным регулярным выражением

А для глобального регулярного выражения возвращается малоинформативный массив:

            str.match(reg); 
        
Вызов метода String.prototype.match с глобальным регулярным выражением

В прототипе RegExp есть подходящий метод exec. Для обычного выражения он работает так же, как String.prototype.match – ничего интересного:

            re.exec(str); 
        
Вызов метода RegExp.prototype.exeс для обычного регулярного выражения

С глобальными регулярками уже можно работать:

            reg.exec(str);
reg.exec(str);
reg.exec(str); 
        
Вызов метода RegExp.prototype.exeс для глобального регулярного выражения

Каждый вызов возвращает следующее совпадение, а когда строка кончится – null. Чтобы перебрать длинные строки, можно воспользоваться циклом while. Позиция для начала поиска хранится в свойстве lastIndex объекта регулярного выражения. Это не так уж очевидно, правда?

На смену этому подходу пришел удобный и интуитивно понятный метод String.prototype.matchAll. Он возвращает итератор:

            str.matchAll(re); 
        
Метод String.prototype.matchAll возвращает итератор

Вызов next() выводит каждое следующее значение:

Перебор совпадений с помощью метода next

Но можно воспользоваться уже имеющимися благами ES6 и сделать вот так:

            [...str.matchAll(reg)]; 
        
Деструктуризация итератора

Одной строчкой кода мы собрали полную информацию обо всех совпадениях в строке.

BigInt

До появления нового типа данных BigInt приходилось пользоваться типом Number. Самое большое число, которое можно было точно представить в JavaScript, равнялось 2⁵³-1 (константа Number.MAX_SAFE_INTEGER).

Теперь не обязательно ограничивать себя этой ничтожной величиной. Расправьте крылья и воспарите в пространстве по-настоящему больших чисел.

Чтобы создать значение с типом BigInt, добавьте к числу n или воспользуйтесь встроенной функцией BigInt().

            let bigInt = 4n
console.log(bigInt * bigInt) // 16n 
        
  1. Значение BigInt не может быть строго равно значению Number, но выражение 2n == 2 истинно.
  2. При делении результат всегда будет округляться до нуля (4n / 10n = 0n).
  3. Нельзя смешивать в одной операции BigInt и Number, так как неявное преобразование между этими типами не происходит (4n + 2).

globalThis

Получение доступа к глобальным объектам – это вечная головная боль в JavaScript. В каждой среде выполнения свои особенности, свой синтаксис, который нужно знать, чтобы ваш код можно было свободно портировать.

Обычно мы выходили из этого положения, создавая функцию getGlobal(), внутри которой перебирали все возможные варианты:

            const getGlobal = function() {
  if (typeof self !== 'undefined') { return self; }
  if (typeof window !== 'undefined') { return window; }
  if (typeof global !== 'undefined') { return global; }
  throw new Error('no global object found');
};

const globals = getGlobal(); 
        

После того как появился globalThis, можно с чистой совестью забыть об этом зоопарке. Например, в браузере в объекте globalThis вы найдете знакомый window:

Объект globalThis в браузере равен объекту window

Promise.allSettled

Если вам требуется дождаться выполнения сразу нескольких промисов, вы, конечно, обратитесь к методу Promise.all(). Но он принимает только успешно выполненные «обещания». Если где-то появится ошибка, продолжить работу не получится.

Новому методу Promise.allSettled() наплевать, как завершились ваши промисы – главное, что они отработали.

            const fulfilledPromise = Promise.resolve("success");
const rejectedPromise = Promise.reject("error")
const promises = [fulfilledPromise, rejectedPromise];

Promise.allSettled(promises)
  .then((results) => results.forEach((result) => console.log(result))); 
        

Динамический импорт

Еще одна новая фича JavaScript, которой вы наверняка будете пользоваться – динамический импорт. Теперь модули можно загружать прямо в рантайме – в зависимости от некоторого условия или просто отложенно, чтобы не загружать клиент. Достаточно вызвать функцию import(), передать указатель на нужный модуль, и получить ответный промис.

            import("some_module")
  .then(module => {
    module.doSomething();
  })
  .catch(err => {
    console.error(err.message);
  }); 
        

Или даже пользоваться шаблонными строками, чтобы динамически указать имя модуля:

            import(`${some_module}.js`)
  .then(module => {
    module.doSomething();
  })
  .catch(err => {
    console.error(err.message);
  }); 
        

Nullish coalescing

У вас есть некоторый объект. Нужно получить и вернуть значение одного из его свойств, если оно не равно null или undefined. В обратном случае возвратить значение по умолчанию. Как бы вы решили эту задачу?

Наверняка вы обратитесь к логическому оператору ИЛИ (||):

            const response = someResponse.properties.mayBeUndefined || 'Response'; 
        

Но есть небольшая проблема. Если в свойстве лежит пустая строка или ноль, мы все равно получим в ответе правую часть выражения ('Response'), так как это ложные значения:

            const someResponse = {properties: { mayBeUndefined: ''}}
const response = someResponse.properties.mayBeUndefined || 'Response';
console.log(response); // Response 
        

Оператор Nullish coalescing, или оператор объединения с null, позволяет решить эту задачу. Если свойство равно null или undefined, то вернется правое значение. Во всех остальных случаях – левое. Независимо от того, истинное оно или ложное.

            const someResponse = {properties: { mayBeUndefined: ''}}
const response = someResponse.properties.mayBeUndefined ?? 'Response';
console.log(response); // '' – пустая строка 
        

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

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

            const someObj = {
  property: 'prop',
  otherProperty: {
    name: 'prop2'
  }
};
const property = someObj.NotOtherProperty ? someObj.NotOtherProperty.name: undefined;
console.log(property); // undefined 
        

Нас спасут опциональные последовательности (англ. Optional Chaining). Достаточно поставить рядом с потенциально несуществующим свойством оператор ?. (вопросительный знак как символ сомнения и точка для стандартного доступа к свойству объекта).

            const someObj = {
  property: 'prop',
  otherProperty: {
    name: 'prop2'
  }
};
const property = someObj.NotOtherProperty?.name;
console.log(property); // undefined 
        

Результат тот же, а запись короче и понятнее!

Вы тоже не можете дождаться?

Если у вас чешутся руки использовать эти приятные возможности в работе, не ограничивайте себя! Многие из них уже воплощены в движках и практически для всех есть плагины babel. Наслаждайтесь 😉

Какая из новых фич JavaScript вам нравится больше всего?

07
Дек
2019

Redux и Vuex: правая и левая палочки Twix

Абсолютно разные создатели, совершенно разные фабрики и такие разные библиотеки для управления состоянием. Попробуй обе и реши – на чьей стороне ты?

Redux и Vuex – это две совершенно разные библиотеки или одно и то же в разной обертке? Пора разобраться в этой загадке. Начнем с начала – с понимания задачи, которую они пытаются решить.

Управление состоянием

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

Представьте, что у вас десятки компонентов, которым нужно как-то взаимодействовать. Часто данные меняются в одном месте, а используются в другом, весьма отдаленном. Если вы будете устанавливать прямые каналы передачи информации между компонентами, то скоро запутаетесь в спагетти-коде.

Выход есть, говорят нам создатели Redux и Vuex. Вам нужно просто собрать все-все-все данные приложения в одном месте, назвать это хранилищем или состоянием и взаимодействовать только с ним.

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

Каждый компонент приложения взаимодействует напрямую с хранилищем состояния

Чем ветвистее дерево компонентов, тем очевиднее преимущества такой схемы. Если что-то идет не так, вы всегда знаете, где искать проблему. Такие проекты легко тестировать и расширять, так как разные компоненты мало зависят друг от друга.

Одна цель

Redux и Vuex созданы для решения одной и той же задачи – управления состоянием большого приложения. Возможно, они делают это принципиально разными способами, давайте посмотрим.

Хранилище состояния

И в Redux, и во Vuex данные приложения хранятся в виде обычного объекта. На самом деле, в JavaScript довольно сложно придумать другой способ, так что особого выбора у них не было.

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

Redux

Создадим хранилище Redux:

            const store = new Store(reducers, initialState); 
        

В reducers передается набор обработчиков, изменяющих данные. Как именно происходит этот процесс, мы увидим чуть позже. initialState – это исходные данные, которые нужно положить в хранилище в самом начале работы.

У хранилища в Redux есть несколько полезных методов:

  • getState() – получение актуального состояния;
  • dispatch() – вызов действия для изменения состояния;
  • subscribe() – подписка на изменения.
Хранилище в Redux и его взаимодействие с компонентами

Vuex

А вот так создается хранилище во Vuex:

            new Vuex.Store({
  state: {},
  getters: {},
  mutations: {},
  actions: {},
}); 
        

Сами данные хранятся в объекте state. getters, как вы, вероятно, догадались, будет содержать геттеры для получения информации. С mutations и actions разберемся чуть позже, сейчас достаточно понимать, что их основное предназначение – изменять данные в хранилище.

Одна идея

Между хранилищами в Redux и Vuex нет принципиальной разницы, но есть организационная. Объект с данными прячется внутри некоторого Store и его изменение осуществляется только опосредованно – через мутации (Vuex) или действия (React). Это позволяет защитить данные от неконтролируемых прямых изменений.

Перейдем к реализации взаимодействия компонентов с хранилищем.

Получение данных

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

Redux

Redux обзавелся специальным методом getState(), который возвращает все состояние приложения разом.

            <HelloWorld text={store.getState().text} /> 
        

Для отслеживания изменений данных и реакции на них обычно используется какая-нибудь дополнительная система UI-биндинга, например, библиотека react-redux (при использовании Redux в React-приложениях). Но при желании вы можете напрямую подписаться на обновления состояния с помощью метода subscribe.

            store.subscribe(() => console.log(store.getState())) 
        

Vuex

Во Vuex вы можете обратиться к свойствам напрямую, достав их из store.state:

            computed: {
  count() {
    return this.$store.state.count;
  }
} 
        

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

Пример из документации:

            // определяем геттер при создании хранилища

const store = new Vuex.Store({
  state: {
    todos: [ ]
  },
  getters: {
    doneTodos: state => {
      return state.todos.filter(todo => todo.done);
    }
  }
});

// затем обращаемся к нему в компоненте

computed: {
  doneTodosCount () {
    return this.$store.getters.doneTodosCount
  }
} 
        

Во Vuex есть еще несколько плюшек, облегчающих труд разработчика (вроде mapState), но они не вносят радикальных перемен в концепцию.

Что касается отслеживания изменений данных, вы можете полностью положиться на Vuex. Хранилище реактивно, поэтому компоненты, полагающиеся на его состояние, будут автоматически обновляться при необходимости.

Разные методы

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

Изменение данных

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

Redux

В основе философии Redux лежит понятие иммутабельности (неизменяемости). Нельзя изменить объект с данными, можно только заменить его целиком. Никаких сайд-эффектов, никаких неприятных сюрпризов – только чистый функциональный подход.

В Redux есть лишь один законный способ изменить состояние приложения – совершить действие (actions).

Экшн – это самый обычный объект, который описывает происходящее изменение. Например, он может выглядеть вот так:

            let action = {
  type: 'ADD_USER',
  user: {name: 'Dan'}
}; 
        

Поле type – обязательное, оно содержит тип действия. Все остальное зависит только от вас.

Теперь это действие нужно передать в хранилище с помощью метода dispatch:

            store.dispatch(action);
 
        

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

            const someReducer = function(state, action) {
  ...
  return newState;
} 
        

Все очень просто:

  1. Какой-нибудь компонент хочет изменить состояние и диспатчит соответствующий экшн.
  2. Оно прогоняется через редьюсеры, которые анализируют тип действия и вносят необходимые изменения.
  3. Возвращенный цепочкой редьюсеров объект становится новым состоянием приложения.

Философия Redux требует, чтобы редьюсеры соответствовали концепции чистых функций (pure functions), то есть не имели никаких побочных эффектов и не зависели от внешних условий.

Vuex

Во Vuex тоже всего один законный способ изменять состояние – мутации (mutations).

Каждая мутация имеет собственное название и соответствующую ей функцию-обработчик. Все мутации описываются при инициализации хранилища.

            const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    increment (state, count) {
      state.count += count
    }
  }
}); 
        

Первым параметром передается текущее состояние, вторым – дополнительные данные мутации (нагрузка). Внутри мутации мы просто обращаемся к свойству state.count напрямую.

Вызов мутации во Vuex очень похож на вызов экшна в Redux:

            store.commit('increment', 10); 
        

Каждая мутация состояния отслеживается и реактивно передается компонентам.

Разные понятия

На первый взгляд, подходы Vuex и Redux к изменению состояния принципиально отличаются. Редьюсеры и иммутабельность данных против мутаций. Разные концепции, разные понятия, но суть для разработчика одна: не лезь в данные напрямую, используй только разрешенные методы изменения.

Redux несколько серьезнее относится к отсутствию сайд-эффектов, Vuex предоставляет несколько более интуитивный интерфейс.

Асинхронное изменение данных

Асинхронность – одна из основных концепций JavaScript в целом. Асинхронных действий в вебе не меньше (а может и больше), чем синхронных. Представим простую асинхронную задачу.

Есть компонент-кнопка, при нажатии на которую отправляется запрос на сервер. Пока не получен ответ, нужно вывести прелоадер, чтобы пользователь знал, что что-то происходит. Затем нужно отобразить полученные данные (например, вывести табличку) или показать ошибку, если запрос завершился неудачно.

Redux

В Redux нет асинхронной функциональности из коробки.

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

Vuex

Мутации в Vuex тоже являются синхронными, внутри них нельзя отправлять запросы к серверу. Но у библиотеки тут есть козырь в рукаве – действия (не путать с действиями в Redux).

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

            const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment(state) {
      state.count++;
    }
  },
  actions: {
    increment(context) {
      context.commit('increment');
    }
  }
}); 
        

Вызвать такое действие не сложнее, чем запустить мутацию:

            store.dispatch('increment'); 
        

Внутрь действия может быть помещена любая асинхронная логика.

Документация советует избегать запуска мутаций напрямую, а вызывать их только внутри действий.

Обратите внимание: действия являются лишь оберткой над мутациями – единственным законным способом изменения состояния.

Разные возможности

Что ж, тут Vuex из коробки обошел Redux, создав собственную (и весьма простую!) асинхронную функциональность.

Правая и левая палочки Twix

Redux и Vuex – это две палочки Twix. Они производятся на разных фабриках и по уникальным технологиям, но в итоге мы имеем очень похожие продукты.

На палочку Redux карамель льется волнами, а на палочку Vuex – струится:

  • Redux многословнее и строго придерживается принципов функционального программирования.
  • Vuex понятнее для изучения и проще при разработке.

На палочку Redux шоколад наливается вертикально, а на палочку Vuex – сверху вниз:

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

А чему отдаете предпочтение вы?

31
Авг
2019

10 полезных книг для Java программиста на русском языке

10 полезных книг для Java программиста на русском языкеНебольшая подборка интересных и полезных книг для Java программиста по самому языку, фреймворкам и программированию в целом. Книги по программированию – штука субъективная. Даже качественное руководство может вам не понравиться просто потому, что в данный момент оно вам не нужно. Поэтому мы собрали довольно разноплановый список – что-нибудь вас точно заинтересует. Все книги в нем […]

Запись 10 полезных книг для Java программиста на русском языке впервые появилась Библиотека программиста.

26
Авг
2019

Мастер отзывчивого дизайна за 5 минут: 4 секретные техники

Современный веб-сайт должен хорошо выглядеть в любых условиях. Разбираемся в основах отзывчивого дизайна и учим страницу подстраиваться. Что такое отзывчивый дизайн?  Это способность вашего сайта подстраиваться под изменение внешних условий, например, ширины браузера, без потери визуальной привлекательности. Смотрите небольшой ликбез по отзывчивости и адаптивности в вебе. Зачем нужен отзывчивый дизайн?  Чтобы пользователи могли с одинаковым […]

Запись Мастер отзывчивого дизайна за 5 минут: 4 секретные техники впервые появилась Библиотека программиста.