- Оригинальный адрес:Недостающий ☑️: SwiftWebUI
- Оригинальный автор:The Always Right Institute
- Перевод с:Программа перевода самородков
- Постоянная ссылка на эту статью:GitHub.com/rare earth/gold-no…
- Переводчик:EmilyQiRabbit
- Корректор:iWeslie,Pingren
SwiftUI в Интернете: SwiftWebUI
Ранее в этом месяце AppleWWDC 2019объявилSwiftUI. Это автономная «кросс-платформенная», «декларативная» структура для создания пользовательских интерфейсов (UI) для tvOS, macOS, watchOS и iOS. а такжеSwiftWebUIМиграция этого фреймворка в веб-разработку ✔️.
Отказ от ответственности: SwiftWebUI - это просто проект игрушечного уровня! Не используйте в производственной среде. Рекомендуется изучить SwiftUI и его внутреннюю работу.
SwiftWebUI
такSwiftWebUIДля чего это можно использовать? Ответ заключается в использовании SwiftWebUI, который предоставляет SwiftUI, который вы пишете в веб-браузере.View.
import SwiftWebUI
struct MainPage: View {
@State var counter = 0
func countUp() { counter += 1 }
var body: some View {
VStack {
Text("🥑🍞 #\(counter)")
.padding(.all)
.background(.green, cornerRadius: 12)
.foregroundColor(.white)
.tapAction(self.countUp)
}
}
}
Результат запуска кода:
В отличие от некоторых других усилий по кодовой базе, он не просто отображает представления SwiftUI в HTML. Он также устанавливает соединение между браузером и кодом сервера Swift для поддержки взаимодействия с пользователем, включая кнопки, средства выбора, степперы, списки, навигацию и т. д., все это поддерживается.
другими словами:SwiftWebUIЯвляется реализацией API SwiftUI в браузере (реализована большая часть API, но не все).
повторятьОтказ от ответственности: SwiftWebUI - это просто проект игрушечного уровня! Не используйте в производственной среде. Рекомендуется изучить SwiftUI и его внутреннюю работу.
Изучите один раз, используйте везде
Основная цель SwiftUI не в том, чтобы «Код один раз, запускайте где угодно", но "Изучите один раз, используйте везде". Не ожидайте, что вы возьмете красивое приложение SwiftUI для iOS, скопируете код в проект SwiftWebUI и увидите точно такой же рендеринг в браузере. Потому что смысл SwiftWebUI не в этом.
Суть в том, чтобы уметьknoff-hoffЭто также позволяет разработчикам имитировать SwiftUI для проведения экспериментов с кодом и просмотра результатов, а также для совместного использования на разных платформах. В этом смысле Интернет имеет преимущество.
Теперь давайте перейдем к деталям и напишем простое приложение SwiftWebUI. Придерживаясь философии «Узнай один раз, используй где угодно», давайте взглянем на эти две минуты WWDC:Введение в SwiftUIа такжеЯдро SwiftUI. Хотя мы не будем углубляться в этот пост в блоге, я рекомендую вам ознакомиться с ним.Поток данных в SwiftUI(Большинство этих концепций применимы и к SwiftWebUI).
необходимые приготовления
В настоящее время из-за несовместимости Swift ABI SwiftWebUI требуетmacOS Catalinaбежать. К счастью,Установите Catalina на отдельный том APFSПростой. Также необходимо установитьXcode 11, поэтому вы можете использовать последние функции Swift 5.1, которые SwiftUI будет активно использовать. Вы понимаете? отлично!
Что делать, если вы используете систему Linux? Этот проект имеетвот-вот будет готовРаботает на Linux, но работа еще не сделана. Недостающая часть текущего проекта — это параCombine PassthroughSubject, и у меня есть небольшие трудности с этим. На данный момент подготовлен код:NoCombine. Все желающие могут отправлять пулл-реквесты для проекта!
Что делать, если вы используете Мохаве? Есть способ запустить проект на Mojave и Xcode 11. Вам нужно создать проект симулятора iOS 13, а затем запустить весь проект в симуляторе.
Начните создавать свое первое приложение
Создайте проект SwiftWebUI
Откройте Xcode 11, выберите «Файл > Создать > Проект...» или напрямую используйте сочетание клавиш Cmd-Shift-N:
Выберите «Инструмент MacOS / командной строки» Шаблоны проекта:
Чтобы спроектировать подходящее имя, мы используем его «AvocadoToast»:
потомSwiftWebUIДобавьте в диспетчер пакетов Swift и импортируйте проект. Эта опция находится в меню «Файл/Пакеты Swift»:
войтиhttps://github.com/SwiftWebUI/SwiftWebUI.git
В качестве URL-адреса пакета:
"Филиал" настроен наmaster
вариант, чтобы вы всегда получали самый последний и самый лучший код (вы также можете использовать ревизии или использоватьdevelop
ветвь):
наконецSwiftWebUI
Библиотека добавляется к целевому инструменту:
Это оно. Теперь у вас есть прямойimport SwiftWebUI
инструментальный проект. (Xcode может занять некоторое время, чтобы получить и построить зависимости.)
Отображение Hello World с помощью SwiftWebUI
Давайте начнем учиться использовать SwiftWebUI прямо сейчас. Открытьmain.swift
файл, а затем замените содержимое на:
import SwiftWebUI
SwiftWebUI.serve(Text("Holy Cow!"))
Скомпилируйте код и запустите приложение в Xcode, откройте браузер Safari и посетитеhttp://localhost:1337/
:
Что происходит за кулисами: сначала упоминается модуль SwiftWebUI (будьте осторожны, чтобы случайно не сослаться на macOS SwiftUI 😀)
Далее мы звонимSwiftWebUI.serve
, он может использовать замыкание, возвращающее View, или просто View, которое, как показано выше, возвращаетText
Представление (также известное как «UILabel», которое может отображать обычный или форматированный текст).
За кулисами
serve
функция создает очень простойSwiftNIOHTTP-сервер, этот сервер будет прослушивать порт 1337. Когда браузер обращается к этому серверу, он создаетsessionи передал наше (текстовое) представление в этот сеанс.
Наконец, SwiftWebUI создает «Shadow DOM» на сервере, отображает представление в HTML и отправляет результат в браузер. Этот «Shadow DOM» (и объект состояния, который будет к нему привязан) хранится в сеансе.
Существует разница между приложением SwiftWebUI и приложением SwiftUI на watchOS или iOS. Приложение SwiftWebUI может обслуживать несколько пользователей, а не только одного пользователя, как приложение SwiftUI.
Добавьте взаимодействие с пользователем
После выполнения первого шага мы оптимизируем структуру кода. Создайте новый файл Swift в проекте и назовите его «MainPage.swift. и добавьте к нему простое определение представления SwiftUI:
import SwiftWebUI
struct MainPage: View {
var body: some View {
Text("Holy Cow!")
}
}
Настройте main.swift для обслуживания нашего пользовательского представления:
SwiftWebUI.serve(MainPage())
Теперь мы можем оставить это в покоеmain.swift
, можно настроить в нашемView
завершить другую работу. Теперь давайте добавим к нему взаимодействие с пользователем:
struct MainPage: View {
@State var counter = 3
func countUp() { counter += 1 }
var body: some View {
Text("Count is: \(counter)")
.tapAction(self.countUp)
}
}
нашView
Eстьcounter
изState
Переменные (не уверен, что это такое? Предлагаю взглянутьВведение в SwiftUI). и простая функция, которая увеличивает счетчик.
Затем мы используем модификаторы SwiftUI.tapAction
Привяжите функцию обработчика времени к нашемуText
начальство. Наконец, мы отображаем текущее значение в метке:
🧙♀️как по волшебству🧙
За кулисами
Как все это работает? Когда мы нажимаем на браузер, SwiftWebUI создает сеанс с «Shadow DOM». Затем он отправит HTML-описание представления в браузер.tapAction
Добавлено через HTMLonclick
Обработка событий может быть вызвана для выполнения. SwiftWebUI также может передавать код JavaScript в браузер (только небольшой объем кода, а не код большого фрейма!), эта часть кода будет обрабатывать событие щелчка и пересылать событие на наш сервер Swift.
Затем пришла магия SwiftUI. SwiftWebUI связывает событие click с нашим обработчиком событий в «Shadow DOM» и вызываетcountUp
функция. путем изменения переменнойcounter
State
, функция устанавливает рендеринг представления как недопустимый. В этот момент SwiftWebUI начинает сравнивать различия и изменения, которые появляются в «Shadow DOM». Затем эти изменения будут отправлены обратно в браузер.
Эти изменения будут отправлены в виде массивов JSON, которые можно проанализировать с помощью фрагментов JavaScript на странице. Если изменяется все содержимое поддерева структуры HTML (например, предполагается, что пользователь вводит новое представление), то изменение может представлять собой более крупный фрагмент HTML, который будет применен к innerHTML.
或
outerHTML方法。 但是通常情况下,修改都比较微小,例如
add class,
установить атрибут HTML` следующим образом (т.е. модификация DOM браузера).
🥑🍞 Приложение для тостов с авокадо
Отлично, теперь у нас есть вся основа. Добавим больше интерактивности. Следующее содержимое основано на «Приложении Avocado Toast App», которое доступно по адресуЯдро SwiftUIДоклад был использован в качестве примера SwiftUI. Если вы еще не видели, советую посмотреть, ведь речь идет о вкусных тостах (тост также означает ломтик хлеба).
Стили HTML и CSS не идеальны и некрасивы. И вы знаете, что мы не веб-дизайнеры, поэтому нам нужна помощь каждого в этом отношении. Пулл-реквесты для проектов приветствуются!
Если вы хотите пропустить подробное объяснение и сразу перейти к анимации приложения, вы можете скачать ее на GitHub:🥑🍞.
🥑🍞 Форма заказа для заявки
Давайте начнем со следующего кода (около 6 минут видео), сначала мы запишем его в новыйOrderForm.swift
документ:
struct Order {
var includeSalt = false
var includeRedPepperFlakes = false
var quantity = 0
}
struct OrderForm: View {
@State private var order = Order()
func submitOrder() {}
var body: some View {
VStack {
Text("Avocado Toast").font(.title)
Toggle(isOn: $order.includeSalt) {
Text("Include Salt")
}
Toggle(isOn: $order.includeRedPepperFlakes) {
Text("Include Red Pepper Flakes")
}
Stepper(value: $order.quantity, in: 1...10) {
Text("Quantity: \(order.quantity)")
}
Button(action: submitOrder) {
Text("Order")
}
}
}
}
Это можно проверить непосредственноmain.swift
серединаSwiftWebUI.serve()
И новый вид OrderForm`.
Ниже показан эффект, отображаемый в браузере:
SemanticUIМожет использоваться для определения стилей для некоторого контента в SwiftWebUI. Это не обязательно для логики действий, но может помочь вам с некоторыми красивыми виджетами. Примечание: он использует только CSS/шрифты, а не компоненты JavaScript.
Расслабьтесь: познакомьтесь с некоторыми макетами SwiftUI
существуетЯдро SwiftUIПримерно на 16-й минуте выступления они начинают объяснять макет SwiftUI и порядок модификаторов представления:
var body: some View {
HStack {
Text("🥑🍞")
.background(.green, cornerRadius: 12)
.padding(.all)
Text(" => ")
Text("🥑🍞")
.padding(.all)
.background(.green, cornerRadius: 12)
}
}
Результат здесь, обратите внимание, как порядок модификаторов связан друг с другом:
SwiftWebUI пытается воспроизвести некоторые распространенные макеты SwiftUI, но это не совсем удалось. В конце концов, эта работа связана с системой компоновки браузера. Нам нужна помощь, особенно специалисты по верстке flexbox!
🥑🍞 Порядок истории приложений
Давайте вернемся к введению приложения.Примерно через 19 минут и 50 секунд речь представляет метод, который можно использовать для отображения исторических заказов приложения для тостов с авокадо.ListВид. Вот как это выглядит в сети:
List
Представление перебирает массив, содержащий все заказы, и создает дочернее представление для каждого элемента (OrderCell
) и передать информацию о каждом заказе в списке в этотOrderCell
.
Вот код, который мы используем:
struct OrderHistory: View {
let previousOrders : [ CompletedOrder ]
var body: some View {
List(previousOrders) { order in
OrderCell(order: order)
}
}
}
struct OrderCell: View {
let order : CompletedOrder
var body: some View {
HStack {
VStack(alignment: .leading) {
Text(order.summary)
Text(order.purchaseDate)
.font(.subheadline)
.foregroundColor(.secondary)
}
Spacer()
if order.includeSalt {
SaltIcon()
}
else {}
if order.includeRedPepperFlakes {
RedPepperFlakesIcon()
}
else {}
}
}
}
struct SaltIcon: View {
let body = Text("🧂")
}
struct RedPepperFlakesIcon: View {
let body = Text("🌶")
}
// Model
struct CompletedOrder: Identifiable {
var id : Int
var summary : String
var purchaseDate : String
var includeSalt = false
var includeRedPepperFlakes = false
}
Представление списка SwiftWebUI крайне неэффективно, оно всегда отображает всю коллекцию дочерних элементов. Ячейка (ячейка списка) вообще не используется повторно 😎. В веб-приложении есть много разных способов решить эту проблему, например, с помощью разбиения на страницы или использования дополнительной логики на стороне клиента.
Мы подготовили для вас пример кода данных, использованного в речи, вам не нужно вводить его снова:
let previousOrders : [ CompletedOrder ] = [
.init(id: 1, summary: "Rye with Almond Butter", purchaseDate: "2019-05-30"),
.init(id: 2, summary: "Multi-Grain with Hummus", purchaseDate: "2019-06-02",
includeRedPepperFlakes: true),
.init(id: 3, summary: "Sourdough with Chutney", purchaseDate: "2019-06-08",
includeSalt: true, includeRedPepperFlakes: true),
.init(id: 4, summary: "Rye with Peanut Butter", purchaseDate: "2019-06-09"),
.init(id: 5, summary: "Wheat with Tapenade", purchaseDate: "2019-06-12"),
.init(id: 6, summary: "Sourdough with Vegemite", purchaseDate: "2019-06-14",
includeSalt: true),
.init(id: 7, summary: "Wheat with Féroce", purchaseDate: "2019-06-31"),
.init(id: 8, summary: "Rhy with Honey", purchaseDate: "2019-07-03"),
.init(id: 9, summary: "Multigrain Toast", purchaseDate: "2019-07-04",
includeSalt: true),
.init(id: 10, summary: "Sourdough with Chutney", purchaseDate: "2019-07-06")
]
🥑🍞 Средство выбора спреда для приложения (расширяемое средство выбора)
Элементы управления Picker и способы их использования с типами enum рассматриваются примерно за 43 минуты. Во-первых, давайте рассмотрим типы перечисления различных опций всплывающих уведомлений;
enum AvocadoStyle {
case sliced, mashed
}
enum BreadType: CaseIterable, Hashable, Identifiable {
case wheat, white, rhy
var name: String { return "\(self)".capitalized }
}
enum Spread: CaseIterable, Hashable, Identifiable {
case none, almondButter, peanutButter, honey
case almou, tapenade, hummus, mayonnaise
case kyopolou, adjvar, pindjur
case vegemite, chutney, cannedCheese, feroce
case kartoffelkase, tartarSauce
var name: String {
return "\(self)".map { $0.isUppercase ? " \($0)" : "\($0)" }
.joined().capitalized
}
}
Мы можем добавить их к нашемуOrder
Структура:
struct Order {
var includeSalt = false
var includeRedPepperFlakes = false
var quantity = 0
var avocadoStyle = AvocadoStyle.sliced
var spread = Spread.none
var breadType = BreadType.wheat
}
Затем используйте различные типы средств выбора для их отображения. Вы можете просто перебрать все значения типа перечисления напрямую:
Form {
Section(header: Text("Avocado Toast").font(.title)) {
Picker(selection: $order.breadType, label: Text("Bread")) {
ForEach(BreadType.allCases) { breadType in
Text(breadType.name).tag(breadType)
}
}
.pickerStyle(.radioGroup)
Picker(selection: $order.avocadoStyle, label: Text("Avocado")) {
Text("Sliced").tag(AvocadoStyle.sliced)
Text("Mashed").tag(AvocadoStyle.mashed)
}
.pickerStyle(.radioGroup)
Picker(selection: $order.spread, label: Text("Spread")) {
ForEach(Spread.allCases) { spread in
Text(spread.name).tag(spread) // there is no .name?!
}
}
}
}
Результат запуска кода:
Опять же, нам нужно немного CSS-умений, чтобы интерфейс выглядел лучше…
🥑🍞 Конечный результат применения
На самом деле мы немного отличаемся от нативного интерфейса SwiftUI, и мы еще не совсем его закончили. Хоть он и не выглядит идеальным, но все же его можно использовать для демонстрации 😎
Готовый код приложения можно посмотреть на GitHub:AvocadoToast.
HTML и семантический интерфейс
UIViewRepresentable
Эквивалент в SwiftWebUI предназначен для создания собственного HTML-кода.
Он предоставляет две переменные,HTML
выведет строку как есть или перенесет содержимое через HTML:
struct MyHTMLView: View {
var body: some View {
VStack {
HTML("<blink>Blinken Lights</blink>")
HTML("42 > 1337", escape: true)
}
}
}
Используя эту структуру, вы можете построить любой HTML-код, который захотите.
Чуть более продвинутый, но также используемый в SwiftWebUI.HTMLContainer
. Например, этоStepper
Как реализовать управление:
var body: some View {
HStack {
HTMLContainer(classes: [ "ui", "icon", "buttons", "small" ]) {
Button(self.decrement) {
HTMLContainer("i", classes: [ "minus", "icon" ], body: {EmptyView()})
}
Button(self.increment) {
HTMLContainer("i", classes: [ "plus", "icon" ], body: {EmptyView()})
}
}
label
}
}
HTMLContainer
Будьте более гибкими, например, если класс, стиль или атрибут элемента изменяются, он произведет обычное изменение DOM (а не повторный рендеринг всего).
SemanticUI
SwiftWebUI также включает некоторыеSemanticUIКонтролируемая предварительная конфигурация:
VStack {
SUILabel(Image(systemName: "mail")) { Text("42") }
HStack {
SUILabel(Image(...)) { Text("Joe") } ...
}
HStack {
SUILabel(Image(...)) { Text("Joe") } ...
}
HStack {
SUILabel(Image(...), Color("blue"),
detail: Text("Friend"))
{
Text("Veronika")
} ...
}
}
... визуализированный результат:
Обратите внимание, что SwiftWebUI также поддерживает некоторые имена изображений из встроенной библиотеки значков (SFSymbols) (используя Image(systemName:)`). Семантический интерфейсПоддержка шрифта Awesomeэто техническая поддержка за кулисами.
В то же время SwiftWebUI также включаетSUISegment
,SUIFlag
а такжеSUICard
:
SUICards {
SUICard(Image.unsplash(size: UXSize(width: 200, height: 200),
"Zebra", "Animal"),
Text("Some Zebra"),
meta: Text("Roaming the world since 1976"))
{
Text("A striped animal.")
}
SUICard(Image.unsplash(size: UXSize(width: 200, height: 200),
"Cow", "Animal"),
Text("Some Cow"),
meta: Text("Milk it"))
{
Text("Holy cow!.")
}
}
... который отображается как:
Добавлять такой вид очень легко и приятно. Используя представления SwiftUI от WOComponent, каждый может быстро создавать сложные и красивые макеты.
Image.unsplash
будет работать в соответствии сhttp://source.unsplash.com
API для построения запросов изображений. Просто передайте ему некоторые параметры запроса, такие как размер изображения, который вы хотите, и другие настраиваемые параметры. Примечание. Этот сервис Unsplash иногда работает медленно и ненадежно.
Суммировать
Все вышеперечисленное является содержанием этой демонстрации. Надеюсь, вам понравится! но сноваОтказ от ответственности: SwiftWebUI - это просто проект игрушечного уровня! Не используйте в производственной среде. Рекомендуется изучить SwiftUI и его внутреннюю работу.
Но мы думаем, что это хорошая демонстрация начального уровня и ценный инструмент для изучения внутренней работы SwiftUI.
Необязательно читать технические примечания
Вот список советов по различным аспектам технологии. Можете пропустить, это уже не так интересно 😎
Issues
У нашего проекта много тикетов, некоторые из которых есть на Github:Issues. Вы также можете попробовать отправить нам больше вопросов.
Это включает в себя много контента, связанного с разметкой HTML (например,ScrollView
Иногда он не прокручивается), но также есть много открытых вопросов, например, о форме (что может быть проще реализовать, если вы используете SVG или CSS).
Также есть вопрос о том, что If-ViewBuilder недействителен. Пока неизвестно почему:
var body: some View {
VStack {
if a > b {
SomeView()
}
// 目前还需要一个空的 else 语句:`else {}` 来使其可以编译。
}
}
Нам нужна помощь, и добро пожаловать на получение запросов для нас!
Сравнение с родным SwiftUI
Наша текущая реализация очень проста и не эффективна. Официальная версия должна была обрабатывать высокочастотные изменения состояния, менять все анимационные эффекты на частоту кадров 60 Гц и т. д.
В настоящее время мы сосредоточены на выполнении основ, таких как то, как работают состояние и привязки, когда и как обновляются представления и так далее. Много раз метод реализации мог быть неправильным, но Apple забыла отправить нам исходный код как часть Xcode 11.
WebSockets
Теперь мы используем AJAX для подключения браузера к серверу. На самом деле использование WebSockets может принести еще большие преимущества:
- Порядок запуска событий может быть гарантирован (запросы AJAX являются асинхронными, а порядок возврата не фиксирован)
- Не требует инициализации пользователя, но обновляет DOM на стороне сервера (например, таймеры, push)
- Может обнаруживать истечение сеанса
Это упростит презентацию чат-клиента.
Добавить WebSockets в проект на самом деле довольно просто, так как события уже отправляются в формате JSON. Все, что нам нужно, это прокладки клиента и сервера. Эта часть контента уже находится вswift-nio-irc-webclientДля реализации достаточно мигрировать в проект.
SPA
В настоящее время SwiftWebUI представляет собой проект SPA (одностраничное приложение), связанный с серверной службой, которая поддерживает состояние.
Существуют и другие способы реализации SPA, такие как сохранение неизменного дерева состояний, когда пользователь переключается между разными страницами в приложении по обычным ссылкам. он же веб-объекты ;-)
Как правило, это хороший выбор, если вы хотите получить более полный контроль над генерацией DOM ID, генерацией ссылок, маршрутизацией и т. д. Но, в конце концов, пользователям, возможно, придется отказаться от «выучить один раз, использовать где угодно», потому что обработчики поведения SwiftUI часто строятся на том факте, что они предназначены для захвата произвольного состояния.
Далее мы увидим, что делает серверная среда на основе Swift 👽
WASM
Весь код становится более функциональным, когда мы используем правильный Swift WASM. Приходите и учитесь вместе WASM!
WebIDs
Некоторые представления SwiftUI, такие какForEach
,все нужноIdentifiable
Объекты, так что используйте егоid
может быть произвольнымHashable
стоимость. Но при использовании в DOM его производительность не очень хорошая, потому что нам нужен идентификатор строкового типа для идентификации узлов.
Вместо этого сопоставьте идентификаторы со строками через глобальную структуру сопоставления, и это прекрасно работает. Технически это не так сложно (просто конкретный вопрос о ссылках на классы).
Резюме: для веб-кода разумнее использовать строки или числа для идентификации элементов.
Form
Форма получила много народных симпатий:Issue.
SemanticUI имеет много хороших макетов форм. Мы можем переписать эту часть поддерева. Еще предстоит довести до совершенства.
WebObjects 6 для Swift
Подождите секунду и нажмите еще раз:
Краткое изложение SwiftUI для пользователей старше 40 лет.pic.twitter.com/6cflN0OFon
— Хельге Хесс (@helje5)7 июня 2019 г.
использоватьSwiftUI, Apple действительно дала нам режим SwiftWebObjects6!
Далее: (давайте с нетерпением ждем новой эры) Direct To Web и Swiftified EOF (т.е. CoreData или ZeeQL).
Ссылка на ссылку
- SwiftWebUI on GitHub
-
SwiftUI
- Introducing SwiftUI (204)
- SwiftUI Essentials (216)
- Data Flow Through SwiftUI (226)
- SwiftUI Framework API
- SwiftObjects
- SemanticUI
- SwiftNIO
связаться с нами
Привет, мы надеемся, что вам понравилась эта статья, и мы очень рады вашим отзывам! Отзыв можно отправить в Twitter или:@helje5,@ar_instituteВсе будет хорошо. Адрес электронной почты:wrong@alwaysrightinstitute.com. Slack: ищите нас на SwiftDE, swift-server, noze, ios-developers.
Написано 30 июня 2019 г.
Если вы обнаружите ошибки в переводе или в других областях, требующих доработки, добро пожаловать наПрограмма перевода самородковВы также можете получить соответствующие бонусные баллы за доработку перевода и PR. начало статьиПостоянная ссылка на эту статьюЭто ссылка MarkDown этой статьи на GitHub.
Программа перевода самородковэто сообщество, которое переводит высококачественные технические статьи из Интернета сНаггетсДелитесь статьями на английском языке на . Охват контентаAndroid,iOS,внешний интерфейс,задняя часть,блокчейн,товар,дизайн,искусственный интеллектЕсли вы хотите видеть более качественные переводы, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.