
Большинство сообщений в блогах о React Native, которые я читал, были написаны с точки зрения разработчика JavaScript для мобильных устройств. Вместо этого это будет с точки зрения разработчика iOS на React Native и JavaScript.
Я надеюсь, что любые другие разработчики, перешедшие с iOS и Swift на React Native, сочтут это полезным. Точно так же любой JS-разработчик, интересующийся Swift, также может найти эту ссылку полезной. Цель состоит не в том, чтобы сосредоточиться на том, какой вариант лучше или хуже, а в том, чтобы выделить сходства и различия, чтобы другие разработчики могли облегчить свое обучение.
Это также не охватывает все сходства и различия. Первоначально я намеревался приблизиться к этому (да, верно!), Но я пошел по пути написания энциклопедии и никогда не публиковал ее, а не сообщения в блоге. При этом сходства не будут на 100% одинаковыми или на 100% отсутствовать, но в основном.
От JS к iOS и обратно в будущее
Мне казалось, что в предыдущей жизни я разрабатывал в основном MAMP и AJAX около 2 лет после окончания обучения в области компьютерных наук. Затем, примерно с конца 2010 года, я работал почти исключительно с родным iOS (Objective-C и Swift) и немного под родным Android. До тех пор, пока я не решил расширить свои знания по всем аспектам мобильной разработки и присоединился к Highline BETA в середине 2018 года.
Поначалу возвращение в JavaScript было похоже на возвращение Марти МакФлая в 1985 год в параллельный мир империи Биффа. Это было немного похоже на… Я смутно узнаю этот мир, я просто не знаю, где я нахожусь. Не то чтобы это было плохо (в отличие от империи Биффа), просто ничего не имело смысла, несмотря на то, что это был тот же мир, в котором я был раньше.
Итак, в первый день мои коллеги-инженеры Highline BETA посоветовали, прежде чем углубляться в React Native и FRP, мне следует изучить ES6 в JavaScript.
Для мобильных разработчиков, не знакомых с ES6, вот краткая разбивка без поиска в Google: в основном стандарт ECMAScript (на котором основан JS) редко обновлялся. Это включало длительный перерыв в пустоте и / или колебаниях в 1999–2009 годах, а затем еще один длительный разрыв в 2011–2015 годах. Это похоже на то, как если бы они молча надеялись, что JS просто исчезнет ... когда стало очевидно, что это не так, они наконец-то выяснили, как развивать стандарт, не внося критических изменений в WWW. Они переименовали стандарт в ES6 и смогли создать прецедент для ES7, ES8 и ES9 в последующие годы.
Возможно, основная концепция, к которой нужно привыкнуть в JavaScript, - это не конкретная основная концепция программирования, а философская концепция, которая все работает. Одна из ценностей React Native заключается в том, что он пытается стандартизировать это - он пытается стандартизировать практику. Я попробую затронуть концепции ES6, аналогичные Swift (используя список Акселя Раушмайера в качестве руководства, без определенного порядка), а затем расскажу о React Native в следующем сообщении в блоге.
Подобные концепции
1. Стрелочные функции
В основном похож на сокращенный синтаксис, но для функций.
Объявление такой функции, как:
function foo(param) {}
Эквивалентно объявлению функции как константы в ES6:
const arrowFoo = (param) => {}
Насколько я понимаю, стрелочные функции возникли из-за ограничений традиционных функций JavaScript, которые не позволяли вам получить доступ к области объектов через this, известный как контекст, в обратном вызове. Еще больше усложняет это контекст выполнения, который применим к области действия, а не к контексту, что предотвращает привязку исходной функции, которая была выполнена, к другому контексту выполнения.
Это сбивает с толку, и я не хочу вдаваться в подробные объяснения, потому что это приведет к перехвату этого сообщения в блоге, когда оно уже слишком длинное, но эта ссылка хорошо помогает объяснить его.
Поскольку Swift - это язык на платформе, поддерживающей многопоточность, у него не было такой проблемы с контекстами выполнения. Но он по-прежнему поддерживает потокобезопасность благодаря осторожному использованию эквивалента this. Что касается версии this , self Swift, нам все равно придется использовать [weak self] в закрытии, чтобы гарантировать безопасный доступ. Но это больше связано с безопасностью, а не с какой-либо невозможностью получить технический доступ к методам и свойствам экземпляра.
Кроме того, поскольку стрелочные функции обычно возвращают функцию (или, чтобы лучше объяснить это, стрелочные функции являются константами с функцией в качестве значения), они аналогичны объявлению константы как закрытие в Swift:
let arrowFoo = (param: ParamType) -> ReturnType
2. Изменяемые и постоянные переменные
Раньше в JS было var, но теперь он по существу устарел в пользу let (переназначаемый) и const (однократно назначаемый).
const setOnce = 101
let setWhenever = 202
Между JS var и let есть различия в области видимости, и, хотя это не стоит вдаваться в контекст сравнений Swift, его стоит немного коснуться. let переменные по умолчанию ограничены рамками того, в чем они объявлены, будь то функция, for цикл и т. Д.
Эквивалент Swift является инверсией JS. let используется для констант, а ключевое слово var используется для переназначаемых переменных:
let setOnce = 101
var setWhenever = 202
Это незначительное различие, но от привычки трудно избавиться, как и вождение автомобиля с левым рулем на правый.
Что касается ограниченной области JS let, в Swift функции var и let ограничены областью действия, или при закрытии это будет эквивалентно отсутствию добавления __block к вашей переменной или объявлению объекта как закрытого.
3. Объектные литералы
В JavaScript вы никогда по-настоящему не могли создавать объекты, по крайней мере, в том смысле, в каком вы можете это делать на строго типизированном языке ООП, где объект создается с помощью класса, конструктора и т. Д. Объекты в JS не наследуются от объекта. по умолчанию, хотя они могут использовать прототипное наследование, чтобы включить наследование через фабричный шаблон, такой как create().
Все объектные литералы, по сути, являются структурами "ключ-значение". Объект определяется внутри двоеточием, отделяющим ключ от значения, например, определение кофейной кружки:
const CoffeeMug = {
millileters: 300,
artworkPath: "./images/coffee-mug-logo.png",
coffeeType: "espresso",
onRefill: refill()
}
Как и следовало ожидать от объекта, вы можете определить любой тип для своих свойств, включая другие объекты, массивы или функции.
В Swift это эквивалентно нескольким вещам.
Во-первых, определение литералов объекта синтаксически и функционально эквивалентно объявлению словаря. Кроме того, перечисление или структура в Swift могут использоваться для обеспечения большей гибкости, чем словарь, но также с тем преимуществом, что не нужны явные конструкторы, геттеры или сеттеры.
Во-вторых, неявная установка объекта без определения его типа посредством вывода типа. Это было особенностью JS с самого начала, но в Swift любой предполагаемый тип будет поддерживать этот тип в течение всего жизненного цикла. Это совершенно верно в Swift, если бы мы собирались объявить его как словарь:
let CoffeeMug = [
"millileters": "300",
"artworkPath": "./images/coffee-mug-logo.png",
"coffeeType": "espresso",
"onRefill": "refill"
]
Если вам нужно добавить аннотацию типа, это если вы хотите сохранить эти значения как относящиеся к любому типу. Вот измененная версия со значением millileters , установленным как int, coffeeType установленным как перечисляемый тип и refill установленным как закрытие:
let refill: () -> () = { /** refill code here… */ }
enum type { case espresso, drip, pourover }
let CoffeeMug: [String: Any] = [
"millileters": 300,
"artworkPath": "./images/coffee-mug-logo.png",
"coffeeType": type.espresso,
"onRefill": refill
]
4. Шаблонные литералы
Строковая интерполяция недавно стала возможной в JS, устранив избыточную необходимость добавлять строку с + к переменным.
Шаблонные литералы - это синтаксис, необходимый для интерполяции строк в JavaScript, достигаемой с помощью `${...}`. Например, следующая строка, относящаяся к ИМТ человека:
const bmi = weight / heightconst bmiReading = "Your BMI is: `${bmi}`"
Эквивалент в Swift:
let bmi = weight / heightlet bmiReading = "Your BMI is: \(bmi)"
5. Модули
Это способ, которым JS поощряет модульное повторное использование кода. Я не уверен, какие модули предшествовали (тег скрипта кажется похожим, но каким-то образом другим), но мне это кажется требованием для любого современного языка программирования. Или, может быть, я слишком долго был в мире Swift, где это воспринимается как должное.
Например, вы можете объявить и определить константу в ее собственном файле с намерением стать внешним модулем.
const ButtonModule = () => { ... }export default ButtonModule
Ключевое слово export также позволяет указать, что является частным или общедоступным для внутреннего модуля или других модулей, использующих его. Хотя это вид инкапсуляции структур или классов, имейте в виду, что JS лучше всего подходит, когда он не встроен в язык, основанный на классах. Вот почему здесь работают модули, это подходит JS!
Однако у Swift нет точного эквивалента. Если мы будем исходить из предположения, сделанного мною ранее, где модули - это способ JavaScript «поощрять» модульное повторное использование кода, в Swift нет ничего, что могло бы направить вас таким же образом.
Самое близкое сравнение в Swift - это фреймворки, но это не совсем отражает то, для чего служат JS-модули. Хотя фреймворки могут быть легкими, обычно они представляют собой полные библиотеки, а не единую абстракцию. Фреймворки также необходимо явно импортировать, но на этом сравнение заканчивается.
Наиболее близким сравнением с единственной абстракцией, возможно, были бы протоколы. Протоколы определяют, что может повлечь за собой поведение или внешний вид, без сохранения состояния, которое могла бы повлечь за собой структура или класс. Любой протокол, который вы создаете, также не нужно явно импортировать (если не имеет отношения к Objective-C, который берет свои реплики из импорта файла заголовка C).
6. Операторы по умолчанию и операторы восстановления
Параметры по умолчанию имеют эквивалент в Swift. Они работают одинаково, в том смысле, что любой параметр со значением по умолчанию может быть опущен в вызывающей программе.
function find(searchTerm, sort=true) {...}//You are able to call this as... find(“toronto”)
То же самое в Swift:
func find(_ searchTerm: String, _ sort: Bool=true) -> Bool {...}//You are able to call this as... find(“toronto”)
Rest допускает неограниченное количество аргументов. Когда перед параметром добавляется …, это означает, что ряд аргументов был передан в виде массива.
function outputArgLength(...groupedArguments) {
return 'output: ' + groupedArguments.length
}
outputArgLength(2, 4, 6)
//output: 3
ОБНОВИТЬ:
В Swift есть эквивалент этому параметру, который называется Variadic parameters (я просто не использую его слишком часто). Синтаксис немного отличается: … добавляется к типу, а не перед именем аргумента.
Имейте в виду, что параметрам нужны типы в Swift, поскольку это язык со строгой типизацией. Хотя это может принимать форму универсального, а не примитива или объекта, но это тема для других статей в блоге.
func outputArgLength(_ groupedArguments: Int...) -> String {
return "output: \(groupedArguments.count)"
}
outputArgLength(2, 4, 6)
//output: 3
7. Обещания
Причина существования обещаний - это в значительной степени основная причина, по которой я сбежал с JS на iOS все эти годы назад - чтобы облегчить то, что позже будет называться (но изначально было разочаровано) адом обратного вызова.
Обещания - это асинхронные обратные вызовы, удобно инкапсулированные для перехвата или продолжения потока. Все, что возвращает обещание, дает вам возможность использовать цепочку с .then (который передает успешный результат) или .catch (который передает ошибку). Определите обещание следующим образом:
function refill() {
return new Promise(
function (resolve, reject) {
resolve(result)
reject(error)
}
)
}
Что, если последовательно выполняются 2 или более асинхронных вызова (обещанный эквивалент вложенности)? Что ж, удобно распространять ошибки до тех пор, пока ошибка не будет обработана:
refill()
.then(refillResult => {
return requestMoreBeans()
})
.then(moreBeansResult => {})
.catch(error => {});
В Swift замыкания устраняют необходимость асинхронного выполнения функции. Закрытие само по себе не является асинхронным, но эту потребность можно удовлетворить с помощью Grand Central Dispatch (GCD). Ниже приводится базовый пример асинхронного выполнения закрытия:
DispatchQueue.global().async {
refill()
DispatchQueue.main.async {
//Perform UI related code on the main thread...
}
}
Хотя и в этой реализации можно стать жертвой ада обратных вызовов, привычка обращаться к основному потоку, а также следовать хорошим шаблонам проектирования и принципам SOLID решает эту проблему.
GCD по своей природе является FIFO, но если вам нужен больший контроль над зависимостями и состояниями вашей очереди, а также возможность приостанавливать и отменять операции, существует OperationQueue. Хотя это может показаться нелогичным, поскольку OperationQueue технически использует GCD, а GCD является API более низкого уровня, использование OperationQueue часто является излишним. Поскольку Promise имеет диапазон из трех состояний (ожидает, выполнено, отклонено), он, вероятно, больше соответствует OperationQueue по своей природе, но синтаксически больше похож на GCD нижнего уровня.
Операции в OperationQueue по умолчанию выполняются в фоновом потоке, в отличие от GCD, где это необходимо указать. И GCD, и OperationQueue предоставляют абстракцию при непосредственном создании потоков (принятие решений об асинхронности и синхронизации предоставляется вам в GCD). Управление потоками с этими абстракциями достаточно сложно, не говоря уже о том, чтобы без них, но это важно для мобильных устройств. Если вы работаете в Swift, вы будете часто использовать одну или обе эти библиотеки.
8. Типы функций как возвращаемые типы
Это требует почетного упоминания, потому что шаблон объявления функции, возвращающей функцию, очень распространен в React & React Native. Похожая цель может быть достигнута в Swift, и хотя я нечасто этим пользовался, вдохновение из React могло бы стать основой для попытки применить аналогичный подход в следующий раз, когда я погрузюсь в Swift.
Единственная разница в JavaScript заключается в том, что вы можете объявить константу как функцию без предварительного определения функции (хотя это может быть достигнуто с помощью замыканий в Swift, как упоминалось ранее):
const refillDone = () => {
//do stuff: refill the coffee, then return how long it took...
return 0
}
function refillToFull(full) {
return refillDone
}
В Swift это не следует путать с закрытием как параметром, которое в некотором смысле позволяет функции выполнять функцию, переданную в качестве параметра. Основное отличие состоит в том, что при возврате функции вызывающий решает, когда и нужно ли ее выполнять. Однако закрытие выполняется по усмотрению функции.
Снова используя наш refill пример. Мы определим пополнение как функцию, возвращающую функцию. Функция, которую он возвращает, будет другой функцией, которую мы определим, с именем refillDone , которая возвращает TimeInterval , обозначающую время, затраченное на добавление кофе:
func refillDone() -> TimeInterval {
//do stuff: refill the coffee, then return how long it took...
return 0
}
func refill(toFull full: Bool) -> () -> TimeInterval {
return refillDone
}
Понятия не так похожи
1. Операторы распространения
Spread выполняет итерацию по всем элементам в массиве в качестве аргумента (нарисуйте цикл for на уровне параметров). Нет эквивалента Swift.
function sumArguments(x, y, z) { //You are appearing within the scope of the function as arguments... return x + y + z }//You are being passed in as an array... sumArguments([2, 4, 6])
2. Разрушение
Возможно, одна из самых странных функций JS. Он может обеспечивать сопоставление с образцом, подразумевая установку переменных. Наиболее уместно в заданной структуре, например в массиве.
В Swift нет настоящего эквивалента. Возможно, способ обхода Диапазонов - это самое близкое сравнение.
Насколько я понимаю, скажем, если вы извлекаете первый, средний и последний элементы из массива, вы можете использовать деструктурирование следующим образом:
var arrayToUse = [1, 2, 3, 4, 5]
var [head,, middle,, tail] = arrayToUse
console.log(head, middle, tail)
// 1, 3, 5
Для деструктуризации объекта можно использовать этот синтаксис (чтобы присвоить новым переменным имя и город), потому что имена переменных могут подразумеваться, если имена свойств совпадают:
var { name, city } = { name: “dwight”, city: “toronto” };
console.log(name);
// "dwight"
console.log(city);
// "toronto"
Специальное замечание для разработчиков Swift - если вы видите какие-либо странности в том, как объявлена переменная, вы, вероятно, можете обратиться к деструктуризации для ответов: https://hacks.mozilla.org/2015/05/es6-in-depth-destructuring/
3. Генераторы
Функция является генератором, если она объявлена с * перед именем функции.
Генератор предоставляет способ по умолчанию ссылаться на элемент в наборе с помощью next (), как и LinkedList. Так что, я думаю, если вы используете реализацию в виде списка, это, возможно, удалит большую часть шаблонного кода.
function* incrementer() { let index = 0 while(true) yield index++ }let gen = incrementer() const first = gen.next().value const second = gen.next().value
Во второй части я воспользуюсь аналогичным подходом к сравнению любых сходств между React Native и Swift.
Особая благодарность Рохану и Жанетте из Highline BETA за проверку точности и содержания этого сообщения, а также всей команде за обучение меня перед этим сообщением в блоге.