Thế giới phát triển web rất rộng lớn và thật dễ dàng để cảm thấy lạc lõng trong dòng công nghệ mới liên tục xuất hiện mỗi ngày. Hầu hết các công nghệ mới này được xây dựng bằng JavaScript hoặc TypeScript. Tuy nhiên, trong bài viết này, tôi sẽ giới thiệu với bạn cách phát triển web bằng Swift gốc, trực tiếp bên trong trình duyệt của bạn và tôi tin rằng nó sẽ gây ấn tượng với bạn. Làm thế nào là nó có thể? Để Swift hoạt động tự nhiên trên trang web, trước tiên, nó cần được biên dịch thành mã byte WebAssembly và sau đó JavaScript có thể tải mã đó lên trang. Toàn bộ quá trình biên dịch hơi phức tạp một chút vì chúng ta cần sử dụng chuỗi công cụ đặc biệt và xây dựng các tệp trợ giúp, đó là lý do tại sao có sẵn các công cụ CLI trợ giúp: Carton và Webber. Sử dụng chuỗi công cụ của bên thứ ba có ổn không? Cộng đồng đã thực hiện rất nhiều công việc để có thể biên dịch Swift thành WebAssugging bằng cách vá chuỗi công cụ Swift gốc. Họ cập nhật chuỗi công cụ bằng cách tự động lấy các thay đổi từ chuỗi công cụ ban đầu mỗi ngày và sửa bản fork của họ nếu thử nghiệm không thành công. Mục tiêu của họ là trở thành một phần của chuỗi công cụ chính thức và họ hy vọng điều đó sẽ xảy ra trong tương lai gần. SwiftWasm Thùng giấy hay Webber? được tạo bởi cộng đồng SwiftWasm và có thể được sử dụng cho các dự án Tokamak, đây là một khung cung cấp cho bạn khả năng viết trang web bằng SwiftUI. Thùng carton được tạo cho các dự án SwifWeb. SwifWeb khác ở chỗ nó bao hàm toàn bộ các tiêu chuẩn HTML và CSS, cũng như tất cả các API web. Webber Mặc dù bạn có thể thích viết các ứng dụng web bằng SwiftUI để đảm bảo tính nhất quán của mã, nhưng tôi tin rằng đây là cách tiếp cận sai lầm vì quá trình phát triển web vốn dĩ đã khác và không thể tiếp cận theo cách giống như SwiftUI. Đó là lý do tại sao tôi đã tạo SwifWeb, cung cấp cho bạn khả năng sử dụng tất cả sức mạnh của HTML, CSS và API web trực tiếp từ Swift, sử dụng cú pháp đẹp mắt với tính năng tự động hoàn thành và tài liệu. Và tôi đã tạo công cụ Webber vì Carton không thể biên dịch, gỡ lỗi và triển khai các ứng dụng SwifWeb đúng cách vì công cụ này không được tạo cho nó. Tên tôi là Mikhail Isaev và tôi là tác giả của SwifWeb. Trong bài viết này, tôi sẽ chỉ cho bạn cách bắt đầu xây dựng một trang web bằng SwifWeb. Công cụ bắt buộc Nhanh Bạn cần cài đặt Swift, cách dễ nhất để có nó: trên macOS là cài Xcode trên Linux hoặc Windows (WSL2) là sử dụng tập lệnh từ swiftlang.xyz Trong các trường hợp khác, hãy xem hướng dẫn cài đặt trên trang web chính thức CLI webber Tôi đã tạo Webber để giúp bạn xây dựng, gỡ lỗi và triển khai các ứng dụng của mình. Trên macOS, thật dễ dàng để cài đặt với HomeBrew (cài đặt nó từ của họ) trang web brew install swifweb/tap/webber để cập nhật lên phiên bản mới nhất sau đó chỉ cần chạy brew upgrade webber Trên Ubuntu hoặc Windows (Ubuntu trong WSL2) sao chép và biên dịch thủ công Webber sudo apt-get install binaryen curl https://get.wasmer.io -sSfL | sh apt-get install npm cd /opt sudo git clone https://github.com/swifweb/webber cd webber sudo swift build -c release sudo ln -s /opt/webber/.build/release/Webber /usr/local/bin/webber để cập nhật lên phiên bản cuối cùng sau đó chạy cd /opt/webber sudo git pull sudo swift build -c release nhánh luôn chứa mã ổn định, vì vậy hãy lấy các bản cập nhật từ nó chính Tạo dự án mới Mở terminal và thực thi webber new Trong menu tương tác, chọn hoặc và nhập tên dự án. pwa spa Thay đổi thư mục thành dự án mới tạo và thực thi . webber serve Lệnh này sẽ biên dịch dự án của bạn thành WebAssugging, đóng gói tất cả các tệp cần thiết bên trong một thư mục đặc biệt và bắt đầu phục vụ dự án của bạn trên tất cả các giao diện sử dụng cổng theo mặc định. .webber 8888 Đối số bổ sung cho webber serve loại ứng dụng cho Ứng dụng web lũy tiến -t pwa cho Ứng dụng web đơn -t spa Tên của mục tiêu nhân viên dịch vụ (thường được đặt tên là trong dự án PWA) Service -s Service Tên của mục tiêu ứng dụng ( theo mặc định) App -a App In thêm thông tin trong bảng điều khiển -v Cổng cho máy chủ Webber (mặc định là ) 8888 -p 8080 Sử dụng để kiểm tra giống như SSL thực (với cài đặt SSL tự ký được phép) -p 443 Tên trình duyệt đích để tự động khởi chạy trong hoặc --browser safari --browser chrome Phiên bản bổ sung của trình duyệt với cài đặt SSL tự ký được phép để gỡ lỗi nhân viên dịch vụ --browser-self-signed Phiên bản bổ sung của trình duyệt ở chế độ ẩn danh --browser-incognito Ứng dụng Ứng dụng bắt đầu trong Sources/App/App.swift import Web @main class App: WebApp { @AppBuilder override var app: Configuration { Lifecycle.didFinishLaunching { app in app.registerServiceWorker("service") } Routes { Page { IndexPage() } Page("login") { LoginPage() } Page("article/:id") { ArticlePage() } Page("**") { NotFoundPage() } } MainStyle() } } Vòng đời Nó hoạt động theo cách giống như iOS: khi ứng dụng mới bắt đầu didFinishLaunching khi ứng dụng sắp chết willTerminate khi cửa sổ sắp không hoạt động willResignActive khi cửa sổ đang hoạt động didBecomeActive khi cửa sổ đang ở chế độ nền didEnterBackground khi cửa sổ đi vào nền trước willEnterForeground Phương pháp hữu ích nhất ở đây là vì đây là nơi tuyệt vời để định cấu hình ứng dụng. Bạn thấy nó thực sự giống như một ứng dụng iOS! 😀 didFinishLaunching này chứa các phương pháp tiện lợi hữu ích: app gọi để đăng ký nhân viên dịch vụ PWA registerServiceWorker(“serviceName“) lệnh gọi để thêm tập lệnh tương đối hoặc bên ngoài addScript(“path/to/script.js“) gọi để thêm kiểu tương đối hoặc kiểu bên ngoài addStylesheet(“path/to/style.css“) gọi để thêm phông chữ tương đối hoặc phông chữ bên ngoài, tùy chọn đặt loại addFont(“path/to/font.woff”, type:) gọi để thêm biểu tượng, tùy chọn đặt loại và màu addIcon(“path/to/icon“, type:color:) Ngoài ra, nó còn là nơi để cấu hình các thư viện bổ sung như , , , v.v. Autolayout Bootstrap Materialize tuyến đường Định tuyến là cần thiết để hiển thị trang thích hợp dựa trên URL hiện tại. Để hiểu cách sử dụng định tuyến, bạn phải hiểu URL là gì https://website.com/hello/world - đây là /hello/world đường dẫn Như bạn đã thấy, ban đầu, trong lớp Ứng dụng, chúng ta nên khai báo tất cả các tuyến cấp cao nhất. Cấp cao nhất có nghĩa là các trang được khai báo trong các tuyến này sẽ chiếm toàn bộ không gian trong cửa sổ. Ok, ví dụ, tuyến gốc có thể được đặt theo ba cách Page("/") { IndexPage() } Page("") { IndexPage() } Page { IndexPage() } Mình thấy tấm cuối đẹp nhất 🙂 Các tuyến đăng nhập hoặc đăng ký có thể được đặt như thế này Page("login") { LoginPage() } Page("registration") { RegistrationPage() } Các tuyến liên quan đến tham số Page("article/:id") { ArticlePage() } trong ví dụ trên là một phần động của route. Chúng ta có thể truy xuất mã định danh này trong lớp để hiển thị bài viết được liên kết với nó. :id ArticlePage class ArticlePage: PageController { override func didLoad(with req: PageRequest) { if let articleId = req.parameters.get("id") { // Retrieve article here } } } Bạn có thể có nhiều tham số trong đường dẫn. Truy xuất tất cả chúng theo cùng một cách. Truy vấn Điều thú vị tiếp theo trong đường dẫn là , cũng rất dễ sử dụng. Ví dụ: hãy xem xét tuyến đường , dự kiến sẽ có các tham số truy vấn và tìm kiếm. truy vấn /search age text https://website.com/search**?text=Alex&age=19** - phần cuối cùng là truy vấn Chỉ cần khai báo lộ trình tìm kiếm Page("search") { SearchPage() } Và lấy dữ liệu truy vấn trong lớp như thế này SearchPage class SearchPage: PageController { struct Query: Decodable { let text: String? let age: Int? } override func didLoad(with req: PageRequest) { do { let query = try req.query.decode(Query.self) // use optional query.text and query.age // to query search results } catch { print("Can't decode query: \(error)") } } } Bất cứ điều gì Bạn cũng có thể sử dụng để khai báo tuyến đường chấp nhận mọi thứ trong phần đường dẫn cụ thể như thế này * Page("foo", "*", "bar") { SearchPage() } Tuyến đường trên sẽ chấp nhận mọi thứ giữa foo và bar, ví dụ: /foo/aaa/bar, /foo/bbb/bar, v.v. Bắt hết Với dấu bạn có thể đặt một tuyến đường bắt tất cả đặc biệt sẽ xử lý mọi thứ chưa khớp với các tuyến đường khác tại một đường dẫn cụ thể. ** Sử dụng nó để tạo tuyến đường 404 toàn cầu Page("**") { NotFoundPage() } hoặc cho đường dẫn cụ thể, ví dụ: khi người dùng không tìm thấy Page("user", "**") { UserNotFoundPage() } Hãy làm rõ các tình huống với các route được khai báo ở trên /user/1 - nếu có một lộ trình cho /user/:id thì nó sẽ trả về . Nếu không sẽ rơi vào… UserPage Người dùngNotFoundPage /user/1/hello - nếu có route cho /user/:id/hello thì nó sẽ rơi vào UserNotFoundPage /something - nếu không có route cho /something thì nó sẽ rơi vào NotFoundPage Định tuyến lồng nhau Chúng tôi có thể không muốn thay thế toàn bộ nội dung trên trang cho lộ trình tiếp theo mà chỉ một số khối nhất định. Đây là nơi mà có ích! FragmentRouter Hãy xem xét rằng chúng ta có các tab trên trang . Mỗi tab là một tuyến con và chúng tôi muốn phản ứng với những thay đổi trong tuyến con bằng cách sử dụng . /user FragmentRouter Khai báo top-level route trong lớp App Page("user") { UserPage() } Và khai báo trong lớp FragmentRouter UserPage class UserPage: PageController { @DOM override var body: DOM.Content { // NavBar is from Materialize library :) Navbar() .item("Profile") { self.changePath(to: "/user/profile") } .item("Friends") { self.changePath(to: "/user/friends") } FragmentRouter(self) .routes { Page("profile") { UserProfilePage() } Page("friends") { UserFriendsPage() } } } } Trong ví dụ trên, xử lý các định tuyến con và và hiển thị nó bên dưới , vì vậy trang không bao giờ tải lại toàn bộ nội dung mà chỉ tải lại các đoạn cụ thể. FragmentRouter /user/profile /user/friends Thanh điều hướng Cũng có thể được khai báo nhiều hơn một đoạn với các chương trình con giống hoặc khác nhau và tất cả chúng sẽ hoạt động cùng nhau như ma thuật! Btw là một và bạn có thể định cấu hình nó bằng cách gọi FragmentRouter Div FragmentRouter(self) .configure { div in // do anything you want with the div } biểu định kiểu Bạn có thể sử dụng các tệp CSS truyền thống, nhưng bạn cũng có khả năng kỳ diệu mới để sử dụng biểu định kiểu được viết bằng Swift! Khái niệm cơ bản Để khai báo quy tắc CSS bằng Swift, chúng ta có đối tượng . Quy tắc Nó có thể được xây dựng một cách khai báo bằng cách gọi các phương thức của nó Rule(...selector...) .alignContent(.baseline) .color(.red) // or rgba/hex color .margin(v: 0, h: .auto) hoặc cách giống như SwiftUI bằng cách sử dụng @resultBuilder Rule(...selector...) { AlignContent(.baseline) Color(.red) Margin(v: 0, h: .auto) } Cả hai cách đều như nhau, tuy nhiên, tôi thích cách thứ nhất hơn vì tính năng tự động hoàn thành ngay sau khi tôi nhập 😀 . Tất cả các phương thức CSS được mô tả trên MDN đều khả dụng. Hơn thế nữa, nó tự động xử lý các tiền tố của trình duyệt! Tuy nhiên, bạn có thể đặt thuộc tính tùy chỉnh theo cách này trong một số trường hợp cụ thể Rule(...selector...) .custom("customKey", "customValue") Bộ chọn Để thiết lập những yếu tố Quy tắc sẽ ảnh hưởng, chúng ta phải thiết lập một bộ chọn. Tôi thấy bộ chọn là truy vấn trong cơ sở dữ liệu, nhưng các phần của truy vấn bộ chọn đó tôi gọi là con trỏ. Cách dễ nhất để xây dựng một con trỏ là khởi tạo nó bằng chuỗi thô Pointer("a") Nhưng cách nhanh chóng đúng đắn là xây dựng nó bằng cách gọi tại thẻ HTML cần thiết như thế này .pointer H1.pointer // h1 A.pointer // a Pointer.any // * Class("myClass").pointer // .myClass Id("myId").pointer // #myId Đó là về các con trỏ cơ bản, nhưng chúng cũng có các công cụ sửa đổi như , v.v. :hover :first :first-child H1.pointer.first // h1:first H1.pointer.firstChild // h1:first-child H1.pointer.hover // h1:hover Bạn có thể khai báo bất kỳ công cụ sửa đổi hiện có nào, tất cả chúng đều có sẵn. Nếu thiếu một cái gì đó, đừng ngần ngại tạo một tiện ích mở rộng để thêm nó! Và đừng quên gửi pull request trên github để add cho mọi người nhé. Bạn cũng có thể nối các con trỏ H1.class(.myClass) // h1.myClass H1.id(.myId) // h1#myId H1.id(.myId).disabled // h1#myId:disabled Div.pointer.inside(P.pointer) // div p Div.pointer.parent(P.pointer) // div > p Div.pointer.immediatedlyAfter(P.pointer) // Div + p P.pointer.precededBy(Ul.pointer) // p ~ ul Cách sử dụng bộ chọn trong Quy tắc Rule(Pointer("a")) // or Rule(A.pointer) Cách sử dụng nhiều bộ chọn trong Quy tắc Rule(A.pointer, H1.id(.myId), Div.pointer.parent(P.pointer)) Nó tạo ra mã CSS sau a, h1#myId, div > p { } khả năng phản ứng Hãy khai báo các kiểu tối và sáng cho ứng dụng của chúng ta và sau này, chúng ta sẽ có thể dễ dàng chuyển đổi giữa chúng. import Web @main class App: WebApp { enum Theme { case light, dark } @State var theme: Theme = .light @AppBuilder override var app: Configuration { // ... Lifecycle, Routes ... LightStyle().disabled($theme.map { $0 != .happy }) DarkStyle().disabled($theme.map { $0 != .sad }) } } và có thể được khai báo trong các tệp riêng biệt hoặc ví dụ: trong App.swift LightStyle DarkStyle class LightStyle: Stylesheet { @Rules override var rules: Rules.Content { Rule(Body.pointer).backgroundColor(.white) Rule(H1.pointer).color(.black) } } class DarkStyle: Stylesheet { @Rules override var rules: Rules.Content { Rule(Body.pointer).backgroundColor(.black) Rule(H1.pointer).color(.white) } } Và sau đó ở đâu đó trong giao diện người dùng của một số trang, chỉ cần gọi App.current.theme = .light // to switch to light theme // or App.current.theme = .dark // to switch to dark theme Và nó sẽ kích hoạt hoặc hủy kích hoạt các bảng định kiểu liên quan! Điều đó không tuyệt sao? 😎 Nhưng bạn có thể nói rằng mô tả phong cách trong Swift thay vì CSS khó hơn, vậy vấn đề là gì? Điểm chính là phản ứng! Chúng ta có thể sử dụng @State với các thuộc tính CSS và thay đổi giá trị một cách nhanh chóng! Chỉ cần xem qua, chúng ta có thể tạo một lớp với một số thuộc tính phản ứng và thay đổi nó bất cứ lúc nào trong thời gian chạy, do đó, bất kỳ phần tử nào trên màn hình sử dụng lớp đó sẽ được cập nhật! Nó hiệu quả hơn nhiều so với việc chuyển lớp cho nhiều phần tử! import Web @main class App: WebApp { @State var reactiveColor = Color.cyan @AppBuilder override var app: Configuration { // ... Lifecycle, Routes ... MainStyle() } } extension Class { static var somethingCool: Class { "somethingCool" } } class MainStyle: Stylesheet { @Rules override var rules: Rules.Content { // for all elements with `somethingCool` class Rule(Class.hello.pointer) .color(App.current.$reactiveColor) // for H1 and H2 elements with `somethingCool` class Rule(H1.class(.hello), H2.class(.hello)) .color(App.current.$reactiveColor) } } Sau này từ bất kỳ nơi nào trong mã chỉ cần gọi App.current.reactiveColor = .yellow // or any color you want và nó sẽ cập nhật màu trong Biểu định kiểu và trong tất cả các phần tử sử dụng nó 😜 Ngoài ra, có thể thêm CSS thô vào Biểu định kiểu class MainStyle: Stylesheet { @Rules override var rules: Rules.Content { // for all elements with `somethingCool` class Rule(Class.hello.pointer) .color(App.current.$reactiveColor) // for H1 and H2 elements with `somethingCool` class Rule(H1.class(.hello), H2.class(.hello)) .color(App.current.$reactiveColor) """ /* Raw CSS goes here */ body { margin: 0; padding: 0; } """ } } bạn có thể trộn các chuỗi CSS thô nhiều lần nếu cần trang Bộ định tuyến đang hiển thị các trang trên mỗi tuyến. Trang là bất kỳ lớp nào được kế thừa từ . PageController có các phương thức vòng đời như , các phương thức giao diện người dùng và và biến bao bọc thuộc tính cho các phần tử HTML. PageController willLoad didLoad willUnload didUnload buildUI body Về mặt kỹ thuật, chỉ là một Div và bạn có thể đặt bất kỳ thuộc tính nào của nó trong phương thức . PageController buildUI class IndexPage: PageController { // MARK: - Lifecycle override func willLoad(with req: PageRequest) { super.willLoad(with: req) } override func didLoad(with req: PageRequest) { super.didLoad(with: req) // set page title and metaDescription self.title = "My Index Page" self.metaDescription = "..." // also parse query and hash here } override func willUnload() { super.willUnload() } override func didUnload() { super.didUnload() } // MARK: - UI override func buildUI() { super.buildUI() // access any properties of the page's div here // eg self.backgroundcolor(.lightGrey) // optionally call body method here to add child HTML elements body { P("Hello world") } // or alternatively self.appendChild(P("Hello world")) } // the best place to declare any child HTML elements @DOM override var body: DOM.Content { H1("Hello world") P("Text under title") Button("Click me") { self.alert("Click!") print("button clicked") } } } Nếu trang của bạn rất nhỏ, bạn có thể khai báo nó theo cách ngắn gọn này PageController { page in H1("Hello world") P("Text under title") Button("Click me") { page.alert("Click!") print("button clicked") } } .backgroundcolor(.lightGrey) .onWillLoad { page in } .onDidLoad { page in } .onWillUnload { page in } .onDidUnload { page in } Nó không đẹp và ngắn gọn sao? 🥲 Phương pháp tiện lợi tiền thưởng - phương thức JS trực tiếp alert(message: String) alert - chuyển đổi đường dẫn URL changePath(to: String) Phần tử HTML Cuối cùng, tôi sẽ cho bạn biết cách (!) để xây dựng và sử dụng các phần tử HTML! Tất cả các phần tử HTML với các thuộc tính của chúng đều có sẵn trong Swift, danh sách đầy đủ ví dụ như trên . MDN Chỉ là một ví dụ về danh sách ngắn các phần tử HTML: mã swifWeb Mã HTML Div() <div></div> H1(“text“) <h1>text</h1> A(“Click me“).href(““).target(.blank) <a href=”” target=”_blank”>Click me</a> Button(“Click“).onClick { print(“click“) } <button onclick=”…”>Click</button> InputText($text).placeholder("Title") <input type=”text” placeholder=”title”> InputCheckbox($checked) <input type=”checkbox”> Như bạn có thể thấy, rất dễ dàng truy cập vào bất kỳ thẻ HTML nào trong Swift vì tất cả chúng đều được thể hiện dưới cùng một tên, ngoại trừ các đầu vào. Điều này là do các loại đầu vào khác nhau có các phương pháp khác nhau và tôi không muốn trộn lẫn chúng. đơn giản Div Div() chúng ta có thể truy cập tất cả các thuộc tính và thuộc tính kiểu của nó như thế này Div().class(.myDivs) // <div class="myDivs"> .id(.myDiv) // <div id="myDiv"> .backgroundColor(.green) // <div style="background-color: green;"> .onClick { // adds `onClick` listener directly to the DOM element print("Clicked on div") } .attribute("key", "value") // <div key="value"> .attribute("boolKey", true, .trueFalse) // <div boolKey="true"> .attribute("boolKey", true, .yesNo) // <div boolKey="yes"> .attribute("checked", true, .keyAsValue) // <div checked="checked"> .attribute("muted", true, .keyWithoutValue) // <div muted> .custom("border", "2px solid red") // <div style="border: 2px solid red;"> phân lớp Phân lớp phần tử HTML để xác định trước kiểu dáng cho nó hoặc để tạo một phần tử tổng hợp có nhiều phần tử con được xác định trước và một số phương thức thuận tiện có sẵn bên ngoài hoặc để đạt được các sự kiện vòng đời như và . didAddToDOM didRemoveFromDOM Hãy tạo một phần tử chỉ là một nhưng với lớp được xác định trước Divider Div .divider public class Divider: Div { // it is very important to override the name // because otherwise it will be <divider> in HTML open class override var name: String { "\(Div.self)".lowercased() } required public init() { super.init() } // this method executes immediately after any init method public override func postInit() { super.postInit() // here we are adding `divider` class self.class(.divider) } } Điều rất quan trọng là gọi các siêu phương thức khi phân lớp. Nếu không có nó, bạn có thể gặp hành vi không mong muốn. Đang thêm vào DOM Phần tử có thể được thêm vào DOM của hoặc ngay hoặc sau này. PageController phần tử HTML Ngay lập tức Div { H1("Title") P("Subtitle") Div { Ul { Li("One") Li("Two") } } } Hoặc sau này sử dụng lazy var lazy var myDiv1 = Div() lazy var myDiv2 = Div() Div { myDiv1 myDiv2 } Vì vậy, bạn có thể khai báo trước một và thêm nó vào DOM bất kỳ lúc nào sau đó! phần tử HTML Xóa khỏi DOM lazy var myDiv = Div() Div { myDiv } // somewhere later myDiv.remove() Truy cập phần tử gốc Bất kỳ phần tử HTML nào cũng có thuộc tính giám sát tùy chọn cấp quyền truy cập cho cha mẹ của nó nếu nó được thêm vào DOM Div().superview?.backgroundColor(.red) điều kiện nếu/khác Chúng ta thường chỉ cần hiển thị các phần tử trong một số điều kiện nhất định, vì vậy hãy sử dụng cho điều đó if/else lazy var myDiv1 = Div() lazy var myDiv2 = Div() lazy var myDiv3 = Div() var myDiv4: Div? var showDiv2 = true Div { myDiv1 if showDiv2 { myDiv2 } else { myDiv3 } if let myDiv4 = myDiv4 { myDiv4 } else { P("Div 4 was nil") } } Nhưng nó không phản ứng. Nếu bạn cố đặt thành thì không có gì xảy ra. showDiv2 false ví dụ phản ứng lazy var myDiv1 = Div() lazy var myDiv2 = Div() lazy var myDiv3 = Div() @State var showDiv2 = true Div { myDiv1 myDiv2.hidden($showDiv2.map { !$0 }) // shows myDiv2 if showDiv2 == true myDiv3.hidden($showDiv2.map { $0 }) // shows myDiv3 if showDiv2 == false } Tại sao chúng ta nên sử dụng $showDiv2.map {…} ? Sắp xếp: bởi vì nó không phải là SwiftUI. Ở tất cả. Đọc thêm về @State bên dưới. HTML thô Bạn cũng có thể cần thêm HTML thô vào trang hoặc phần tử HTML và có thể dễ dàng Div { """ <a href="https://google.com">Go to Google</a> """ } Cho mỗi ví dụ tĩnh let names = ["Bob", "John", "Annie"] Div { ForEach(names) { name in Div(name) } // or ForEach(names) { index, name in Div("\(index). \(name)") } // or with range ForEach(1...20) { index in Div() } // and even like this 20.times { Div().class(.shootingStar) } } ví dụ động @State var names = ["Bob", "John", "Annie"] Div { ForEach($names) { name in Div(name) } // or with index ForEach($names) { index, name in Div("\(index). \(name)") } } Button("Change 1").onClick { // this will append new Div with name automatically self.names.append("George") } Button("Change 2").onClick { // this will replace and update Divs with names automatically self.names = ["Bob", "Peppa", "George"] } CSS Tương tự như ví dụ trên, nhưng cũng có BuilderFunction Stylesheet { ForEach(1...20) { index in CSSRule(Div.pointer.nthChild("\(index)")) // set rule properties depending on index } 20.times { index in CSSRule(Div.pointer.nthChild("\(index)")) // set rule properties depending on index } } Bạn có thể sử dụng trong vòng lặp để tính toán một số giá trị chỉ một lần như giá trị trong ví dụ sau BuilderFunction ForEach delay ForEach(1...20) { index in BuilderFunction(9999.asRandomMax()) { delay in CSSRule(Pointer(".shooting_star").nthChild("\(index)")) .custom("top", "calc(50% - (\(400.asRandomMax() - 200)px))") .custom("left", "calc(50% - (\(300.asRandomMax() + 300)px))") .animationDelay(delay.ms) CSSRule(Pointer(".shooting_star").nthChild("\(index)").before) .animationDelay(delay.ms) CSSRule(Pointer(".shooting_star").nthChild("\(index)").after) .animationDelay(delay.ms) } } Nó cũng có thể lấy chức năng như một đối số BuilderFunction(calculate) { calculatedValue in // CSS rule or DOM element } func calculate() -> Int { return 1 + 1 } cũng có sẵn cho các phần tử HTML :) BuilderFunction Phản ứng với @State là điều mong muốn nhất hiện nay đối với khai báo. @State lập trình Như tôi đã nói với bạn ở trên: nó không phải là SwiftUI, vì vậy không có máy trạng thái toàn cầu nào theo dõi và vẽ lại mọi thứ. Và các phần tử HTML không phải là các cấu trúc tạm thời mà là các lớp, vì vậy chúng là các đối tượng thực và bạn có quyền truy cập trực tiếp vào chúng. Nó tốt hơn và linh hoạt hơn nhiều, bạn có toàn quyền kiểm soát. Nó là gì dưới mui xe? Nó là một trình bao bọc thuộc tính thông báo cho tất cả những người đăng ký về những thay đổi của nó. Làm thế nào để đăng ký thay đổi? enum Countries { case usa, australia, mexico } @State var selectedCounty: Countries = .usa $selectedCounty.listen { print("country changed") } $selectedCounty.listen { newValue in print("country changed to \(newValue)") } $selectedCounty.listen { oldValue, newValue in print("country changed from \(oldValue) to \(newValue)") } Các phần tử HTML có thể phản ứng với những thay đổi như thế nào? Ví dụ văn bản đơn giản @State var text = "Hello world!" H1($text) // whenever text changes it updates inner-text in H1 InputText($text) // while user is typing text it updates $text which updates H1 Ví dụ số đơn giản @State var height = 20.px Div().height($height) // whenever height var changes it updates height of the Div Ví dụ boolean đơn giản @State var hidden = false Div().hidden($hidden) // whenever hidden changes it updates visibility of the Div ví dụ ánh xạ @State var isItCold = true H1($isItCold.map { $0 ? "It is cold 🥶" : "It is not cold 😌" }) Ánh xạ hai trạng thái @State var one = true @State var two = true Div().display($one.and($two).map { one, two in // returns .block if both one and two are true one && two ? .block : .none }) Ánh xạ nhiều hơn hai trạng thái @State var one = true @State var two = true @State var three = 15 Div().display($one.and($two).map { one, two in // returns true if both one and two are true one && two }.and($three).map { oneTwo, three in // here oneTwo is a result of the previous mapping // returns .block if oneTwo is true and three is 15 oneTwo && three == 15 ? .block : .none }) Tất cả các thuộc tính HTML và CSS có thể xử lý các giá trị @State Tiện ích mở rộng Mở rộng các phần tử HTML Bạn có thể thêm một số phương thức tiện lợi vào các phần tử cụ thể như Div extension Div { func makeItBeautiful() {} } Hoặc các nhóm phần tử nếu bạn biết cha của chúng. class Có ít lớp cha. - dành cho các phần tử có thể được khởi tạo bằng chuỗi, như , , v.v. BaseActiveStringElement a h1 - dành cho tất cả các phần tử có thể chứa nội dung bên trong nó, như , , v.v. BaseContentElement div ul - dành cho tất cả các phần tử BaseElement Vì vậy, phần mở rộng cho tất cả các yếu tố có thể được viết theo cách này extension BaseElement { func doSomething() {} } Khai báo màu sắc Lớp chịu trách nhiệm về màu sắc. Nó có màu HTML được xác định trước, nhưng bạn có thể có màu của riêng mình màu extension Color { var myColor1: Color { .hex(0xf1f1f1) } // which is 0xF1F1F1 var myColor2: Color { .hsl(60, 60, 60) } // which is hsl(60, 60, 60) var myColor3: Color { .hsla(60, 60, 60, 0.8) } // which is hsla(60, 60, 60, 0.8) var myColor4: Color { .rgb(60, 60, 60) } // which is rgb(60, 60, 60) var myColor5: Color { .rgba(60, 60, 60, 0.8) } // which is rgba(60, 60, 60, 0.8) } Sau đó, sử dụng nó như H1(“Text“).color(.myColor1) khai báo các lớp extension Class { var my: Class { "my" } } Sau đó, sử dụng nó như Div().class(.my) khai báo id extension Id { var myId: Id { "my" } } Sau đó, sử dụng nó như Div().id(.my) API web Cửa sổ đối tượng được bao bọc hoàn toàn và có thể truy cập thông qua biến . window App.current.window Tham khảo đầy đủ có sẵn trên . MDN Hãy làm tổng quan ngắn dưới đây Cờ tiền cảnh Bạn có thể nghe nó trong trong hoặc trực tiếp theo cách này Lifecycle App.swift App.current.window.$isInForeground.listen { isInForeground in // foreground flag changed } hoặc chỉ cần đọc nó mọi lúc mọi nơi if App.current.window.isInForeground { // do somethign } hoặc phản ứng với nó bằng phần tử HTML Div().backgroundColor(App.current.window.$isInForeground.map { $0 ? .grey : .white }) Cờ hoạt động Nó giống như cờ Foreground, nhưng có thể truy cập qua App.current.window.isActive Nó phát hiện nếu người dùng vẫn đang tương tác bên trong cửa sổ. Trạng thái trực tuyến Tương tự như cờ Foreground, nhưng có thể truy cập qua App.current.window.isOnline Nó phát hiện nếu người dùng vẫn có quyền truy cập vào internet. trạng thái chế độ tối Tương tự như cờ Foreground, nhưng có thể truy cập qua App.current.window.isDark Nó phát hiện xem trình duyệt hoặc hệ điều hành của người dùng có ở chế độ tối hay không. Kích thước bên trong Kích thước của khu vực nội dung của cửa sổ (khung nhìn) bao gồm thanh cuộn là đối tượng trong các giá trị và bên trong. App.current.window.innerSize Kích thước width height Cũng có sẵn dưới dạng biến . @State Kích thước bên ngoài Kích thước của cửa sổ trình duyệt, bao gồm thanh công cụ/thanh cuộn. là đối tượng trong các giá trị và bên trong. App.current.window.outerSize Kích thước width height Cũng có sẵn dưới dạng biến . @State Màn hình Đối tượng đặc biệt để kiểm tra các thuộc tính của màn hình mà cửa sổ hiện tại đang được hiển thị. Có sẵn qua . App.current.window.screen Thuộc tính thú vị nhất thường là . pixelRatio Lịch sử Chứa các URL được người dùng truy cập (trong cửa sổ trình duyệt). Có sẵn qua hoặc chỉ . App.current.window.history History.shared Nó có thể truy cập dưới dạng biến , vì vậy bạn có thể lắng nghe những thay đổi của nó nếu cần. @State App.current.window.$history.listen { history in // read history properties } Nó cũng có thể truy cập dưới dạng biến đơn giản History.shared.length // size of the history stack History.shared.back() // to go back in history stack History.shared.forward() // to go forward in history stack History.shared.go(offset:) // going to specific index in history stack Thông tin chi tiết có sẵn trên . MDN Vị trí Chứa thông tin về URL hiện tại. Có sẵn qua hoặc chỉ . App.current.window.location Location.shared Nó có thể truy cập dưới dạng biến , vì vậy bạn có thể lắng nghe những thay đổi của nó nếu cần. @State Đây là cách bộ định tuyến hoạt động chẳng hạn. App.current.window.$location.listen { location in // read location properties } Nó cũng có thể truy cập như một biến đơn giản Location.shared.href // also $href Location.shared.host // also $host Location.shared.port // also $port Location.shared.pathname // also $pathname Location.shared.search // also $search Location.shared.hash // also $hash Thông tin chi tiết có sẵn trên . MDN Hoa tiêu Chứa thông tin về trình duyệt. Có sẵn qua hoặc chỉ App.current.window.navigator Navigator.shared Các thuộc tính thú vị nhất thường là . cookieEnabled language platform userAgent Lưu trữ cục bộ Cho phép lưu các cặp khóa/giá trị trong trình duyệt web. Lưu trữ dữ liệu không có ngày hết hạn. Có sẵn dưới dạng hoặc chỉ . App.current.window.localStorage LocalStorage.shared // You can save any value that can be represented in JavaScript LocalStorage.shared.set("key", "value") // saves String LocalStorage.shared.set("key", 123) // saves Int LocalStorage.shared.set("key", 0.8) // saves Double LocalStorage.shared.set("key", ["key":"value"]) // saves Dictionary LocalStorage.shared.set("key", ["v1", "v2"]) // saves Array // Getting values back LocalStorage.shared.string(forKey: "key") // returns String? LocalStorage.shared.integer(forKey: "key") // returns Int? LocalStorage.shared.string(forKey: "key") // returns String? LocalStorage.shared.value(forKey: "key") // returns JSValue? // Removing item LocalStorage.shared.removeItem(forKey: "key") // Removing all items LocalStorage.shared.clear() Theo dõi thay đổi LocalStorage.onChange { key, oldValue, newValue in print("LocalStorage: key \(key) has been updated") } Theo dõi tất cả các mục loại bỏ LocalStorage.onClear { print("LocalStorage: all items has been removed") } PhiênStorage Cho phép lưu các cặp khóa/giá trị trong trình duyệt web. Lưu trữ dữ liệu chỉ trong một phiên. Có sẵn dưới dạng hoặc chỉ . App.current.window.sessionStorage SessionStorage.shared API hoàn toàn giống như trong được mô tả ở trên. LocalStorage Tài liệu Đại diện cho bất kỳ trang web nào được tải trong trình duyệt và phục vụ như một điểm vào nội dung của trang web. Có sẵn qua . App.current.window.document App.current.window.document.title // also $title App.current.window.document.metaDescription // also $metaDescription App.current.window.document.head // <head> element App.current.window.document.body // <body> element App.current.window.documentquerySelector("#my") // returns BaseElement? App.current.window.document.querySelectorAll(".my") // returns [BaseElement] bản địa hóa nội địa hóa tĩnh Bản địa hóa cổ điển là tự động và phụ thuộc vào ngôn ngữ hệ thống của người dùng Cách sử dụng H1(String( .en("Hello"), .fr("Bonjour"), .ru("Привет"), .es("Hola"), .zh_Hans("你好"), .ja("こんにちは"))) Bản địa hóa động Nếu bạn muốn thay đổi các chuỗi được bản địa hóa trên màn hình khi đang di chuyển (không cần tải lại trang) Bạn có thể thay đổi ngôn ngữ hiện tại bằng cách gọi Localization.current = .es Nếu bạn đã lưu ngôn ngữ của người dùng ở đâu đó trong cookie hoặc bộ lưu trữ cục bộ thì bạn phải đặt ngôn ngữ đó khi khởi chạy ứng dụng Lifecycle.didFinishLaunching { Localization.current = .es } Cách sử dụng H1(LString( .en("Hello"), .fr("Bonjour"), .ru("Привет"), .es("Hola"), .zh_Hans("你好"), .ja("こんにちは"))) Ví dụ nâng cao H1(Localization.currentState.map { "Curent language: \($0.rawValue)" }) H2(LString(.en("English string"), .es("Hilo Español"))) Button("change lang").onClick { Localization.current = Localization.current.rawValue.contains("en") ? .es : .en } Tìm về import FetchAPI Fetch("https://jsonplaceholder.typicode.com/todos/1") { switch $0 { case .failure: break case .success(let response): print("response.code: \(response.status)") print("response.statusText: \(response.statusText)") print("response.ok: \(response.ok)") print("response.redirected: \(response.redirected)") print("response.headers: \(response.headers.dictionary)") struct Todo: Decodable { let id, userId: Int let title: String let completed: Bool } response.json(as: Todo.self) { switch $0 { case .failure(let error): break case .success(let todo): print("decoded todo: \(todo)") } } } } XMLHttpRequest import XMLHttpRequest XMLHttpRequest() .open(method: "GET", url: "https://jsonplaceholder.typicode.com/todos/1") .onAbort { print("XHR onAbort") }.onLoad { print("XHR onLoad") }.onError { print("XHR onError") }.onTimeout { print("XHR onTimeout") }.onProgress{ progress in print("XHR onProgress") }.onLoadEnd { print("XHR onLoadEnd") }.onLoadStart { print("XHR onLoadStart") }.onReadyStateChange { readyState in print("XHR onReadyStateChange") } .send() ổ cắm web import WebSocket let webSocket = WebSocket("wss://echo.websocket.org").onOpen { print("ws connected") }.onClose { (closeEvent: CloseEvent) in print("ws disconnected code: \(closeEvent.code) reason: \(closeEvent.reason)") }.onError { print("ws error") }.onMessage { message in print("ws message: \(message)") switch message.data { case .arrayBuffer(let arrayBuffer): break case .blob(let blob): break case .text(let text): break case .unknown(let jsValue): break } } Dispatch.asyncAfter(2) { // send as simple string webSocket.send("Hello from SwifWeb") // send as Blob webSocket.send(Blob("Hello from SwifWeb")) } Bảng điều khiển tương đương với trong JavaScript print(“Hello world“) console.log('Hello world') Các phương pháp cũng được bao bọc bằng tình yêu ❤️ điều khiển Console.dir(...) Console.error(...) Console.warning(...) Console.clear() Xem trước trực tiếp Để thực hiện công việc xem trước trực tiếp, hãy khai báo lớp WebPreview trong mỗi tệp bạn muốn. class IndexPage: PageController {} class Welcome_Preview: WebPreview { @Preview override class var content: Preview.Content { Language.en Title("Initial page") Size(640, 480) // add here as many elements as needed IndexPage() } } Xcode Vui lòng đọc hướng dẫn trên . Đó là giải pháp phức tạp nhưng hoàn toàn hiệu quả 😎 trang kho lưu trữ VSCode Chuyển đến bên trong và tìm kiếm . Tiện ích mở rộng VSCode Webber Khi nó được cài đặt, nhấn (hoặc trên Linux/Windows) Cmd+Shift+P Ctrl+Shift+P Tìm và khởi chạy . Webber Live Preview Ở bên phải, bạn sẽ thấy cửa sổ xem trước trực tiếp và cửa sổ này sẽ làm mới bất cứ khi nào bạn lưu tệp chứa lớp . WebPreview Truy cập vào JavaScript Nó có sẵn thông qua , nền tảng của . JavaScriptKit SwifWeb Read how to you nằm trong . kho lưu trữ chính thức Tài nguyên Bạn có thể thêm , , , và bất kỳ tài nguyên tĩnh nào khác bên trong dự án. css js png jpg Nhưng để chúng có sẵn hoặc trong các tệp cuối cùng, bạn phải khai báo tất cả chúng trong như thế này trong quá trình gỡ lỗi phát hành Package.swift .executableTarget(name: "App", dependencies: [ .product(name: "Web", package: "web") ], resources: [ .copy("css/*.css"), .copy("css"), .copy("images/*.jpg"), .copy("images/*.png"), .copy("images/*.svg"), .copy("images"), .copy("fonts/*.woff2"), .copy("fonts") ]), Sau này bạn sẽ có thể truy cập chúng, ví dụ như thế này Img().src(“/images/logo.png“) gỡ lỗi Khởi chạy theo cách sau Webber chỉ để nhanh chóng khởi chạy nó webber serve để khởi chạy nó ở chế độ PWA webber serve -t pwa -s Service thông số bổ sung hoặc để hiển thị thêm thông tin trong bảng điều khiển cho mục đích gỡ lỗi -v --verbose hoặc để khởi động máy chủ webber trên cổng 443 thay vì 8888 mặc định -p 443 --port 443 để tự động mở trình duyệt mong muốn, theo mặc định, nó không mở bất kỳ trình duyệt nào --browser chrome/safari cần thiết để gỡ lỗi nhân viên dịch vụ cục bộ, nếu không thì chúng không hoạt động --browser-self-signed để mở phiên bản bổ sung của trình duyệt ở chế độ ẩn danh, chỉ hoạt động với chrome --browser-incognito Vì vậy, để xây dựng ứng dụng của bạn ở chế độ gỡ lỗi, hãy tự động mở ứng dụng đó trong Chrome và tự động làm mới trình duyệt bất cứ khi nào bạn thay đổi bất kỳ tệp nào, hãy khởi chạy ứng dụng đó theo cách này cho SPA webber serve --browser chrome để thử nghiệm PWA thực webber serve -t pwa -s Service -p 443 --browser chrome --browser-self-signed --browser-incognito Tải ứng dụng ban đầu Bạn có thể muốn cải thiện quá trình tải ban đầu. Đối với điều đó, chỉ cần mở thư mục bên trong dự án và chỉnh sửa tệp . .webber/entrypoint/dev index.html Nó chứa mã HTML ban đầu với các trình lắng nghe rất hữu ích: . WASMLoadingStarted WASMLoadingStartedWithoutProgress WASMLoadingProgress WASMLoadingError Bạn có thể tự do chỉnh sửa mã đó thành bất kỳ thứ gì bạn muốn để triển khai phong cách tùy chỉnh của mình 🔥 Khi bạn hoàn thành việc triển khai mới, đừng quên lưu vào thư mục .webber/entrypoint/release phát hành tòa nhà Chỉ cần thực hiện hoặc cho PWA. webber release webber release -t pwa -s Service Sau đó lấy các tệp đã biên dịch từ thư mục và tải chúng lên máy chủ của bạn. .webber/release cách triển khai Bạn có thể tải tệp của mình lên bất kỳ dịch vụ lưu trữ tĩnh nào. Dịch vụ lưu trữ phải cung cấp Loại nội dung chính xác cho các tệp ! wasm Có, điều rất quan trọng là phải có đúng tiêu đề cho các tệp , nếu không, rất tiếc, trình duyệt sẽ không thể tải ứng dụng WebAssugging của bạn. Content-Type: application/wasm wasm Ví dụ: không cung cấp Loại nội dung chính xác cho các tệp nên rất tiếc là không thể lưu trữ các trang WebAssembly trên đó. GithubPages wasm Nginx Nếu bạn sử dụng máy chủ của riêng mình với nginx, hãy mở và kiểm tra xem nó có chứa ghi. Nếu có thì bạn tốt để đi! /etc/nginx/mime.types application/wasm wasm; Phần kết luận Tôi hy vọng hôm nay tôi đã làm bạn ngạc nhiên và ít nhất bạn sẽ dùng thử SwifWeb và tối đa sẽ bắt đầu sử dụng nó cho dự án web lớn tiếp theo của bạn! Vui lòng đóng góp cho bất kỳ nào và đồng thời gắn dấu sao ⭐️tất cả chúng! thư viện SwifWeb Tôi có một nơi bạn có thể tìm thấy sự hỗ trợ to lớn, đọc các hướng dẫn nhỏ và được thông báo trước về mọi bản cập nhật sắp tới! Sẽ rất tuyệt khi gặp bạn với chúng tôi! cộng đồng SwiftStream tuyệt vời trong Discord, Đây mới chỉ là bước khởi đầu, vì vậy hãy theo dõi các bài viết khác về SwifWeb! Hãy nói với bạn bè của bạn!