
В этом видео мы с вами познакомимся с такой библиотекой, как Ramda. Если вы когда нибудь использовали Underscore или Lodash, то это тоже самое, только в функциональном стиле.
То есть Ramda - это набор функций, которые необходимы нам каждый день и которых так не хватает в javascript.
И перед тем, как мы начнем писать код - пару слов о функциональном программировании. В его основе лежат функции. И его идея в том, чтобы писать маленькие функции из которых мы, как из блоков, можем составлять все новые и новые функции.
Есть две очень важные вещи в функциональном программировании. Первая - все функции должны быть чистыми (по- английски pure). Это значит, что они не должны иметь сайд-эффектов. То есть они не могут присваивать что-то к переменным, которые находятся вне функции, читать из базы или модифицировать параметры, которые мы в них передали.
Вот простейшая чистая функция
const double = x => x * 2
У нас функция принимает число и умножает его на 2. Основная идея чистых функций в том, что вызывая ее с одним и тем же аргументом снова и снова - мы получим такой же результат.
Давайте посмотрим на пример плохой функции
let a = 2
const double = x => {
a += 2
return x * a
}
Здесь, a - переменная не внутри функции, и наша функция модифицирует переменные, которые находятся снаружи, плюс к этому каждый раз мы будем получать разные результаты при вызове с одним и тем же аргументом. Вот так писать не нужно.
Вторая идея функционального программирования состоит в том, что все функции должны быть иммутабельные. Иммутабельные - значит не мутирующие данные, а возвращающие новую копию. Если мы хотим написать функцию, которая добавит данные в массив, то она должна возвращать нам новым массив, а не модифицировать старый.
Все функции библиотеки Ramda, в отличии от других библиотек, являются иммутабельными.
Вот у меня к проекту подключена Ramda. И, если, у нас есть массив и мы запушим в него новый элемент, то это не мутирует наш исходный массив, а вернет копию с данными.
const ar = [1, 2, 3]
const newAr = R.append(4, ar)
console.log(ar)
console.log(newAr)
Также очень важная особенность Ramda в том, что все ее методы каррированы. Это позволяет сразу писать более гибко код.
Давайте попробуем на простом примере. Допустим, у нас есть массив обьектов и мы хотим найти обьект, в котором id = 2.
const users = [
{
id: 1,
name: 'John'
},
{
id: 2,
name: 'Alex'
},
{
id: 3,
name: 'Bill'
}
]
const alex = users.find(user => user.id === 1)
На Ramda есть метод propEq, который возвращает true или false в зависимости от того, равняется ли поле обьекта указанному значению или нет.
R.propEq('id', 2, {id: 2})
Поэтому мы можем использовать R.propEq внутри find.
const alex = R.find(R.propEq('id', 2), users)
Конечно, на таком примере количество кода меньше не стало, но мы можем очень легко выносить подобные вещи в маленькие функции для переиспользования.
const isAlex = R.propEq('id', 2)
const alex = R.find(isAlex, users)
Так как propEq каррирован и возвращает функцию, пока не получит третий аргумент, то мы можем вынести его в переменную и передавать ее в наш find.
Давайте разберем другой пример, где Ramda должна упростить код. Например, у нас есть система голосования и есть несколько хелперов, которые нам помогают проверять пользователей.
const wasBornInCountry = person => person.birthCountry === 'UK'
const wasNaturalized = person => Boolean(person.naturalizationDate)
const isOver18 = person => person.age >= 18
const isCitizen = person => wasBornInCountry(person) || wasNaturalized(person)
const isEligibleToVote = person => isOver18(person) && isCitizen(person)
const testUser = {
age: 20,
birthCountry: 'UK'
}
console.log(isEligibleToVote(testUser))
С помощью Ramda мы можем упростить этот код. У нас есть функции both и either. both - принимает две функции и возвращает новую функцию, которая возвращает true, если обе функции возвращают true.
Давайте рассмотрим пример, чтобы было понятнее.
const test = R.both(value => value < 2, value => value > 0)
console.log(test(1))
То есть R.both возвращает функцию и мы передаем в нее 1. Этот аргумент попадает в каждую функцию, написанную внутри R.both. Если результат обоих функций true, то R.both возвращает true.
В нашем случае, мы можем изменить isEligibleToVote
const isEligibleToVote = R.both(isOver18, isCitizen)
То есть isEligibleToVote - это функция, которая принимает юзера на вход и передает его в isOver18 и isCitizen. Если обе эти функции вернут true, то isEligibleToVote вернет true.
Если мы посмотрим в браузер, то все по-прежнему работает.
Точно также мы можем использовать функцию R.either. Он работает также как R.both, но возвращает true, если хотя бы одна из функций вернет true.
const wasBornInCountry = person => person.birthCountry === 'UK'
const wasNaturalized = person => Boolean(person.naturalizationDate)
const isOver18 = person => person.age >= 18
const isCitizen = R.either(wasBornInCountry, wasNaturalized)
const isEligibleToVote = R.both(isOver18, isCitizen)
const testUser = {
age: 20,
birthCountry: 'UK'
}
console.log(isEligibleToVote(testUser))
Все по-прежнему работает.
Также, мы можем переписать wasBornInCountry, ведь мы с вами уже разобрались в методе propEq.
const wasBornInCountry = R.propEq('birthCountry', 'UK')
И из этого вытекает еще одна особенность Ramda. Она позволяет писать point free code. То есть функции без аргументов. Как вы видите, у нас есть 3 функции без аргументов, так как они каррируются. Point free хорош тем, что он не завязывается на конкретные аргументы и может переиспользоваться во многих местах.
Например
const idEquals = R.propEq('id')
const isAlex = idEquals(2, {id: 2, name: 'Alex'})
const isFirstPost = idEquals(1, {id: 1, title: 'My first Post'})
Если у вас возникли какие-то вопросы или комментарии, пишите их прямо под этим видео.