Dioxus 0.5: แอปเว็บ เดสก์ท็อป และมือถือที่พัฒนาด้วย Rust
(dioxuslabs.com)- เฟรมเวิร์ก GUI สำหรับ Rust Dioxus 0.5 ปรับให้ขั้นตอนการพัฒนาเว็บ เดสก์ท็อป มือถือ และ Fullstack เรียบง่ายขึ้นอย่างมาก โดยเน้นการเขียน
dioxus-coreใหม่และการลบ unsafe - ระหว่าง 0.4.3 ถึง 0.5.0 มีการเพิ่มโค้ดมากกว่า 100,000 บรรทัด และคอมมิตมากกว่า 1,400 ครั้ง โดยคอร์ใหม่เปลี่ยนทิศทางไปสู่การตัดการพึ่งพา lifetime และ
Scope - การจัดการสถานะแบบเดิมที่อิง
use_stateและuse_refถูกแทนที่ด้วย API แบบSignalที่เป็นCopyได้ ช่วยลดภาระการCloneซ้ำๆ ใน event handler และ future - ใช้
dioxus::launchเพียงตัวเดียวและโฟลว์dx serve --platformสำหรับรันเว็บ เดสก์ท็อป และแอป Fullstack โดย CLI จะส่ง build feature ให้เหมาะกับแพลตฟอร์มเป้าหมายโดยอัตโนมัติ - มีการเปลี่ยนแปลงเพิ่มเติมทั้ง hot reload ของ asset, การเขียนระบบ event ใหม่, การปรับปรุงการเรนเดอร์บนเดสก์ท็อป และ server function แบบสตรีมมิง ทำให้โค้ดเบส Rust ชุดเดียวใช้งานได้กว้างขึ้น
ทิศทางของการออก Dioxus 0.5
- Dioxus เป็นไลบรารีสำหรับสร้าง GUI ด้วย Rust และใช้สำหรับเผยแพร่เว็บแอป แอปเดสก์ท็อป และแอปมือถือ
- รีลีส 0.5 ถูกออกแบบโดยมีเป้าหมายเพื่อเพิ่ม ความเรียบง่าย ความแข็งแรง และความสมบูรณ์ตามที่ชุมชนต้องการ
- การเปลี่ยนแปลงหลักมีดังนี้
- เขียน
dioxus-coreใหม่ทั้งหมดและลบ unsafe code - นำ API แบบ
Signalมาใช้แทนuse_state,use_ref - ลบ lifetime ทั้งหมดและสถานะ
cx: Scope - ใช้ฟังก์ชัน
launchตัวเดียวในการเริ่มแอปทุกแพลตฟอร์ม - hot reload สำหรับ asset ที่รองรับ Tailwind และ Vanilla CSS
- เขียนระบบ event ใหม่เพื่อให้เข้าถึง native
WebSysevent type ได้ - รวม Error Boundary, Server Future และ Suspense เข้าด้วยกัน
- ปรับปรุง desktop reconciliation ขึ้น 5 เท่า
- รองรับ server function แบบสตรีมมิงและ hot reload สำหรับ Fullstack
- เขียน
- มี migration guide สำหรับผู้ใช้ที่อัปเดตมาจาก Dioxus 0.4
การลบ lifetime และ Scope
- ตั้งแต่ Dioxus 0.1 ถึง 0.4 ค่าในคอมโพเนนต์ภายในจะมี lifetime แบบ
'bumpซึ่งทำให้สามารถใช้ hook, props และ scope ใน event listener ได้โดยไม่ต้องคัดลอก - ใน event handler นั้นทำงานได้ค่อนข้างดี แต่ future ของ Dioxus ต้องเป็น
'staticจึงจำเป็นต้องcloneค่าก่อนย้ายเข้า future - เมื่อเกิด lifetime error ข้อความที่แสดงอาจเป็นลักษณะว่า
cxต้องมีอายุยาวกว่า'staticแทนที่จะชี้ที่ hook โดยตรง ทำให้เกิดความสับสน - Dioxus 0.5 ลบ
Scopeและ lifetime แบบ'bumpออก และทำให้Elementไม่มี lifetime - ตอนนี้คอมโพเนนต์สามารถรับ props ได้โดยตรงโดยไม่ต้องมีพารามิเตอร์ scope
- ตัวอย่าง:
fn MyComponent(name: String) -> Element
- ตัวอย่าง:
- ฟังก์ชัน runtime ใช้งานได้โดยตรงไม่เฉพาะในคอมโพเนนต์ แต่รวมถึงภายใน future และ event handler ด้วย
- เมื่อ
Elementกลายเป็น'staticก็สามารถใช้ภายใน hook หรือส่งผ่าน context API ได้ด้วย - การเปลี่ยนแปลงนี้เป็นพื้นฐานที่ช่วยให้สร้าง API อย่าง virtual list หรือ offscreen rendering ได้ง่ายขึ้น
การลบ unsafe ออกจาก dioxus-core
- การลบ lifetime แบบ
'bumpและ scope เปิดโอกาสให้ลด unsafe code ภายใน Dioxus ได้ dioxus-core 0.5ไม่มี unsafe code- ยังมี unsafe อยู่เล็กน้อยในบาง dependency และทีม Dioxus วางแผนจะลบออกในช่วงรีลีส 0.5
- unsafe ที่เหลืออยู่ถูกจัดเป็นส่วนที่ลบออกได้โดยตรง หรือเป็นส่วนที่จำเป็นเพราะ FFI
การจัดการสถานะด้วย Signal
- Dioxus 0.5 นำ Signal มาเป็น primitive หลักสำหรับสถานะของคอมโพเนนต์
- Signal มีข้อดีเหนือ
use_stateและuse_refเดิมอยู่ 2 อย่าง- เป็น
Copyได้เสมอ - ไม่ต้องมีการ subscribe แบบ manual
- เป็น
-
สถานะแบบ Copy
Signal<T>เป็นCopyแม้ว่าค่าTภายในจะไม่ใช่Copy- พฤติกรรมนี้เกิดขึ้นได้ด้วย crate generational-box ที่ทำงานโดยไม่ใช้ unsafe
- หากต้องการ สามารถทำให้ Signal เป็น
Send+Syncเพื่อย้ายข้ามเธรดได้ - การรวมกันของสถานะแบบ Copy,
Send+SyncSignal และคอมโพเนนต์แบบ static ทำให้ย้ายสถานะไปยัง future, event handler, thread และตำแหน่งที่ต้องการได้ง่าย - รูปแบบการใช้หน่วยความจำแทบไม่ต่างจาก 0.4 แต่ไม่ต้อง
Cloneแบบชัดเจน
-
การ subscribe แบบอัจฉริยะ
- Signal จะตัดสินใจได้ละเอียดขึ้นว่าควรให้คอมโพเนนต์ใดรันใหม่เมื่อค่ามีการเปลี่ยนแปลง
- จะรันคอมโพเนนต์ใหม่เฉพาะเมื่อคอมโพเนนต์นั้นอ่านค่า Signal
- หากอ่านใน async task หรือ event handler จะไม่ถือเป็นการ subscribe เพื่อรันคอมโพเนนต์ใหม่
- ถ้า parent เปลี่ยน Signal จากการคลิกปุ่มแต่ไม่ได้อ่านค่าเอง และมีเพียง child ที่อ่านค่า ก็จะ render ใหม่เฉพาะ child
- ด้วยโครงสร้างนี้จึงไม่จำเป็นต้องใช้ Fermi ซึ่งเป็น crate จัดการสถานะแยกอีกต่อไป
- เดิม Fermi มี API คล้าย
use_stateที่ใช้ static เป็นคีย์ - ใน Dioxus 0.5 สามารถวาง
GlobalSignalไว้ใน static และใช้งานเหมือน Signal ทั่วไปได้ - Signal ยังทำงานร่วมกับ context API ได้ จึงแชร์สถานะระหว่างคอมโพเนนต์ได้โดยไม่ต้องมี hook
use_shared_stateแยก - หากอ่าน Signal ภายใน hook อย่าง
use_future,use_memoระบบจะเพิ่ม Signal นั้นเข้า dependency ของ hook โดยอัตโนมัติ
CSS และ hot reload ของ asset
- Dioxus 0.5 เพิ่ม hot reload สำหรับไฟล์ CSS ในไดเรกทอรี asset เป็นส่วนหนึ่งของการปรับโครงสร้างระบบ asset
- หากไฟล์ CSS ปรากฏใน RSX, CLI
dxจะเฝ้าดูไฟล์นั้นและสตรีมอัปเดตไปยังแอปที่กำลังรันทันที - รองรับ Web, Desktop และ Fullstack ส่วนมือถือจะตามมาในอัปเดตที่เน้น mobile ในอนาคต
- หากใช้ร่วมกับ Tailwind watcher ก็จะรองรับ Tailwind CSS hot reload ด้วย
- ใน VSCode ยังสามารถรับคำใบ้คลาส Tailwind ได้ผ่าน custom regex extension
- สามารถสตรีมการเปลี่ยนแปลงไปยังหลายอุปกรณ์พร้อมกัน ทำให้ hot reload บนอุปกรณ์ปลายทางทั้งหมดได้พร้อมกัน
การเขียนระบบ event ใหม่
- ตั้งแต่เปิดตัว Dioxus ใช้ synthetic event system เพื่อสร้าง API event แบบข้ามแพลตฟอร์ม
- Synthetic event มีประโยชน์ต่อพฤติกรรม event ข้ามแพลตฟอร์มและการ serialize ผ่านเครือข่าย แต่ก็มีข้อจำกัด
- Dioxus 0.5 เปิดเผย event type พื้นฐาน ของแต่ละแพลตฟอร์ม และมี trait สำหรับ API แบบข้ามแพลตฟอร์มให้ด้วย
- ข้อดีของการเปลี่ยนแปลงนี้มี 2 อย่าง
- สามารถดึงข้อมูลที่ต้องการจาก platform event type โดยตรง หรือส่งต่อให้ไลบรารีอื่นได้
- สามารถทำ bundle splitting สำหรับโค้ด event ที่แอปไม่ได้ใช้งาน
- ในตัวอย่าง hello world ขนาดหลัง gzip ลดลงประมาณ 25%
- เคล็ดลับสำหรับการทำ bundle ให้เล็กลงมีอยู่ใน Dioxus optimization guide
API สำหรับรันข้ามแพลตฟอร์ม
- Dioxus 0.5 เพิ่ม API แบบข้ามแพลตฟอร์ม ใหม่สำหรับการรันแอป
- แทนที่จะ import renderer package แยก เพียงเปิด feature ใน crate
dioxusแล้วเรียกฟังก์ชันlaunchจาก prelude - แอปเดียวกันสามารถรันเป้าหมายต่อไปนี้ได้
- Desktop:
dx serve --platform desktop - SPA Web:
dx serve --platform web - Fullstack:
dx serve --platform fullstack
- Desktop:
- CLI จะส่ง build feature ที่เหมาะสมตามแพลตฟอร์มเป้าหมายให้อัตโนมัติ
ระบบ asset แบบเบต้าและ Manganis
- ใน Dioxus และเว็บแอป เส้นทางของ asset มักล้าสมัยได้ง่าย ลิงก์อาจต่างกันระหว่างเดสก์ท็อปกับเว็บ และต้องเพิ่ม asset เข้า bundle ด้วยตนเอง
- asset ยังอาจเป็นคอขวดด้านประสิทธิภาพได้
- ในตัวอย่างคู่มือ Dioxus Mobile เวอร์ชัน 0.4 ใช้เวลาโหลด 7 วินาทีและส่ง resource 9MB
- คู่มือ mobile ของ 0.5 ใช้ภาพชุดเดียวกันแต่โหลดได้ในเวลาน้อยกว่า 1 วินาที และลด resource ที่ต้องใช้ลงเหลือ 1/3
- Dioxus 0.5 นำระบบ asset ใหม่ manganis มาใช้
- ทำงานร่วมกับ CLI เพื่อยืนยัน รวม bundle และ optimize asset ของแอป
- API ยังไม่เสถียรจึงปล่อยเป็น crate แยก
- หากครอบ asset ด้วยแมโคร
mg!CLI จะตรวจพบโดยอัตโนมัติ - รายละเอียดเพิ่มเติมดูได้ที่ manganis docs
- มีแผนเพิ่ม hot reload ให้กับ asset ของ manganis ในช่วงรีลีส 0.5 ด้วย
การเรนเดอร์บนเดสก์ท็อปดีขึ้น 5 เท่า
- Dioxus ใช้การ optimize หลายแบบเพื่อสร้าง rendering diff ให้เร็ว
- Templates ช่วยข้ามการ diff ส่วน static ของแมโคร
rsx! - บน Dioxus Web มี sledgehammer เพื่อ apply การเปลี่ยนแปลง DOM จาก Rust ได้อย่างรวดเร็ว
- Dioxus 0.5 นำแนวคิดเดียวกันมาใช้กับการ apply การเปลี่ยนแปลงผ่านเครือข่ายด้วย
- renderer ของ Desktop และ LiveView สื่อสารการเปลี่ยนแปลงด้วย binary protocol แทน JSON
- ในงานที่มีภาระเรนเดอร์สูง renderer ใหม่ลดเวลา apply การเปลี่ยนแปลงในเบราว์เซอร์เหลือ 1/5 และลด latency ลงเหลือ 1/2
- benchmark ที่ renderer ของ Dioxus 0.4 เคยค้างต่อเนื่อง สามารถรันได้ลื่นบน Dioxus 0.5
ความสะดวกในการเขียนคอมโพเนนต์
- Dioxus 0.5 รองรับการขยาย element เฉพาะและการกระจาย props ลงไปยัง element
- ตัวอย่างเช่น คอมโพเนนต์
ImgPlusที่ขยายคุณสมบัติของ elementimgสามารถรับ props ทั่วไปอย่างwidth,height,srcได้ตรงๆ
- ตัวอย่างเช่น คอมโพเนนต์
- เมื่อต้องส่งค่าให้ props หรือคอมโพเนนต์ สามารถใช้ไวยากรณ์ย่อแบบ struct initialization ได้
- เขียน
classแทนclass: classได้
- เขียน
- props แบบย่อใช้ได้กับทุกอย่างที่ implement
IntoAttributeและ Signal ก็ได้รับประโยชน์เช่นกัน - ตอนนี้ props ที่เป็น Signal ยังไม่ข้ามขั้นตอน diffing แต่มีแผนเพิ่มเป็นการ optimize ด้านประสิทธิภาพในช่วงรีลีส 0.5
- props ที่แยกหลายบรรทัดสามารถรวมเข้าด้วยกันได้
- หากเพิ่มค่าแบบมีเงื่อนไขให้กับ props
classเดียวกัน ระบบจะรวมโดยใช้ช่องว่างเป็นตัวคั่น - สิ่งนี้สำคัญกับไลบรารีอย่าง Tailwind ที่ต้อง parse ตอนคอมไพล์ แต่ก็ต้องรองรับความ dynamic ตอน runtime
- ไวยากรณ์นี้รวมเข้ากับ Tailwind compiler เพื่อลด runtime overhead ของไลบรารีอย่าง
tailwind-merge
- หากเพิ่มค่าแบบมีเงื่อนไขให้กับ props
Server function แบบสตรีมมิงและ Fullstack
- Dioxus 0.5 รองรับ server functions crate รุ่นล่าสุดที่รองรับข้อมูลแบบสตรีมมิง
- server function สามารถสตรีมข้อมูลไปยังไคลเอนต์ หรือสตรีมข้อมูลจากไคลเอนต์ไปยังเซิร์ฟเวอร์ได้
- server function แบบสตรีมมิงสร้างได้ด้วยการกำหนด output type และให้ server function คืนค่า
TextStream - เหมาะกับการอัปเดตไคลเอนต์ระหว่างงานที่ใช้เวลานาน
- มีตัวอย่างที่ใช้ Kalosm และ local LLM เพื่อให้ความสามารถคล้าย OpenAI ChatGPT endpoint บนฮาร์ดแวร์ทั่วไป
- ที่เก็บตัวอย่าง: https://github.com/ealmloff/dioxus-streaming-llm
- ตอนนี้ CLI รองรับแพลตฟอร์ม
fullstackและมี hot reload รวมถึงการ build แบบขนานสำหรับฝั่งไคลเอนต์และเซิร์ฟเวอร์dx servedx serve --platform fullstack
LiveView, asset handler และการจัดการไฟล์
- ใน Dioxus 0.5 router ทำงานเป็นค่าเริ่มต้นในแอป LiveView
- PR ที่เกี่ยวข้อง: https://github.com/DioxusLabs/dioxus/pull/1505
- Dioxus Desktop รองรับ custom asset handler
- PR ที่เกี่ยวข้อง: https://github.com/DioxusLabs/dioxus/pull/1719
- Custom asset handler ช่วยให้สตรีมข้อมูลจากโค้ด Rust ไปยังเบราว์เซอร์ได้อย่างมีประสิทธิภาพโดยไม่ต้องผ่าน JavaScript
- เหมาะกับการสื่อสารที่ใช้แบนด์วิดท์สูง เช่น การสตรีมวิดีโอ
- PR ที่เกี่ยวข้อง: https://github.com/DioxusLabs/dioxus/pull/1727
- สามารถส่งข้อมูล gstreamer หรือ webrtc เข้า webview ได้โดยตรง จึงลดความจำเป็นในการเข้ารหัสและถอดรหัสเฟรมด้วยตนเอง
- การลากไฟล์มาวางบนเดสก์ท็อปก็ถูกรวมเข้าในระบบ event แบบ native เช่นกัน
การจัดการข้อผิดพลาด
- Dioxus ทำให้จัดการข้อผิดพลาดจากคอมโพเนนต์ระดับบนได้ง่ายผ่าน Error Boundary และ trait
throw - แนวทาง
throwรวมข้อดีของ error state และ early return เข้าด้วยกัน - สามารถเรียก
throwบนResultที่ implementDebugเพื่อเปลี่ยนเป็น error state และใช้?สำหรับ early return ได้ - คอมโพเนนต์
ErrorBoundaryจะ render คอมโพเนนต์อื่นเมื่อมีข้อผิดพลาดที่โยนจากลูก ErrorBoundaryสามารถซ้อนกันได้ จึงดักจับข้อผิดพลาดได้หลายระดับของแอป- รูปแบบนี้มีประโยชน์สำหรับจัดการ global error state เมื่อเกิดข้อผิดพลาดที่กู้คืนไม่ได้ โดยไม่ต้อง panic หรือจัดการสถานะแยกสำหรับแต่ละ error ด้วยตนเอง
ประสบการณ์นักพัฒนาและเทมเพลต
- Dioxus เพิ่ม hot reload ใน 0.3, รองรับบน Desktop ใน 0.4 และเปิดใช้เป็นค่าเริ่มต้นใน 0.5
- เมื่อรันแอปด้วย
dx serveจะเปิด hot reload ในโหมดพัฒนาโดยอัตโนมัติ - แม้ในแอป Desktop ที่ไม่สามารถ hot reload ได้และต้องคอมไพล์ใหม่ทั้งหมด ก็ยังคงรักษาและกู้คืนสถานะของหน้าต่างที่เปิดไว้
- ขนาดและตำแหน่งหน้าต่างแอปจะถูกเก็บไว้
- ช่วยลดสถานการณ์ที่แอปบังเต็มจอทุกครั้งที่แก้ไข
- เทมเพลตใหม่ถูกจัดระเบียบให้สร้างแอป Web, Desktop, Mobile, TUI และ Fullstack ได้ด้วยคำสั่งเดียว
- แอปเริ่มต้นของ
dx newถูกปรับให้ใกล้เคียง create-react-app มากขึ้น- มี assets, CSS และการตั้งค่าดีพลอยพื้นฐานให้
- มีลิงก์ไปยังทรัพยากรที่มีประโยชน์ เช่น dioxus-std, VSCode Extension, เอกสาร และบทเรียน
Dioxus Community และระบบนิเวศ
- Dioxus Community อัปเดต crate สำคัญในระบบนิเวศเพื่อรองรับรีลีส 0.5
- crate อย่าง icons, charts และ standard library สำหรับ Dioxus ถูกเตรียมให้ใช้งานได้ทันทีเมื่อ 0.5 เปิดตัว
- โครงการ
Dioxus Communityเป็นองค์กร GitHub ใหม่ที่ตั้งขึ้นเพื่อคง crate สำคัญให้อัปเดตอยู่เสมอ แม้ผู้ดูแลเดิมจะถอยออกไป - หากสร้างไลบรารีสำหรับ Dioxus ทาง Dioxus สามารถช่วยดูแลรักษาได้ และตั้งเป้าให้คงสถานะการสนับสนุนในระดับ “Tier 2” โดยพฤตินัย
ฟีเจอร์ที่วางแผนไว้ต่อไป
- แผนหลัง 0.5 มีรายการต่อไปนี้
- ทำให้ระบบ asset เสถียรและผสานรวมได้ลึกขึ้น
- ทำ bundle splitting ของไฟล์
.wasmโดยตรงร่วมกับ lazy component - Islands, resumable interactivity และการ serialize Signal
- รวม Server component และ LiveView เข้ากับ Fullstack
- Devtools และเฟรมเวิร์กทดสอบที่ดียิ่งขึ้น
- ปรับโฉม Mobile ทั้งหมด
- ปรับปรุง Fullstack รวมถึง WebSocket, SSE และ progressive form
ตัวอย่างล่วงหน้าของ Dioxus-Blitz ที่ใช้ Servo
- ใน “Blitz 2.0” ของ Dioxus-Blitz มีเป้าหมายจะรวม Servo เพื่อทำ native rendering ผ่าน WGPU ด้วย CSS engine แบบเดียวกับที่ใช้ขับ Firefox
- Nico Burns ผู้สร้างไลบรารี layout อย่าง Taffy เข้าร่วมงานเต็มเวลาเพื่อผลักดันโครงการนี้
- ในเดโมสามารถเรนเดอร์
google.comบน GPU ได้ที่ 900 FPS - การพัฒนาในปัจจุบันยังไม่สมบูรณ์และการเรนเดอร์
google.comก็ยังดูแปลกอยู่บ้าง แต่กำลังเข้าใกล้ระดับใช้งานได้อย่างรวดเร็ว - ดูที่เก็บโค้ดได้ที่ https://github.com/jkelleyrtp/stylo-dioxus
วิธีมีส่วนร่วม
- โครงการ Dioxus ต้องการการมีส่วนร่วมดังต่อไปนี้
- แปลเอกสาร
- ลองทำ “Good First Issues”
- ปรับปรุงเอกสาร
- ร่วมพัฒนา CLI
- ช่วยตอบคำถามในชุมชน Discord
- ทีม Dioxus ขอบคุณสำหรับการสนับสนุนจากชุมชนตลอดช่วงเวลาที่เหลือของปี 2024 และขอความช่วยเหลือในการเปลี่ยนแปลงการพัฒนาแอป
1 ความคิดเห็น
ความเห็นจาก Hacker News
ตอนเริ่มงานมันยังเป็นเวอร์ชัน 0.2 และดูเหมือนว่าการเปลี่ยนแปลงใน 0.5 ครั้งนี้จะทำให้ความซับซ้อนที่เจอตอนนั้นหายไปเกือบหมดแล้ว ยังไม่ได้ลองใช้เอง แต่การเอา lifetime ออกและลดภาระที่ต้อง clone ตลอด น่าจะทำให้ใช้งานสบายขึ้นมาก
มี Rust framework สำหรับทำ native reactive UI ที่ deploy ไปเดสก์ท็อป, เว็บ/wasm, มือถือ ฯลฯ ได้อยู่พอสมควร https://github.com/flosse/rust-web-framework-comparison เลยกังวลว่าถ้าเลือกผิดอาจต้องคอยดูแล framework ที่ถูกทิ้งร้าง หรือไม่ก็ต้องย้ายระบบแบบทรมาน
ฝั่ง Rust HTTP server framework ก็คล้ายกัน คือมีเยอะมาก แล้วตอนนี้ดูเหมือน Axum, Actix, Rocket จะนำอยู่ และกระแสชุมชนเหมือนกำลังย้ายไปทาง Axum เลยแอบสงสัยว่าการเลือก Actix เป็นการตัดสินใจที่ผิดหรือเปล่า อยากรู้ว่า Dioxus มีสัญญาณว่าจะชนะไหม นอกจากมีชุมชนใหญ่, การสนับสนุนจาก YC และ momentum แล้ว ยังมีตัวชี้วัดอะไรอีกที่ทำให้รู้สึกว่าเลือกตอนนี้ได้
Leptos กับ Yew ก็เป็นคู่แข่งหลักด้วยไหม และมีเหตุผลอะไรที่ทำให้มันดีกว่าหรือด้อยกว่า Dioxus บ้าง บริษัทเราลงทุนกับ Rust, Actix, Bevy ไปเยอะ และต่อไปอยากผูก framework อย่าง Bevy กับ Dioxus เข้าด้วยกันเพื่อทำไคลเอนต์ native บนเดสก์ท็อปและมือถือ
แล้วก็อยากรู้ว่าชื่อ Dioxus มาจาก Deoxys ของ Pokémon หรือเปล่า โลโก้ให้ความรู้สึกแบบนั้น แต่ใน codebase ไม่มีการอ้างอิง Pokémon เลย
เมื่อราว 9 เดือนก่อนผมทำ GUI frontend สำหรับ sshfs ด้วย Dioxus และรู้สึกว่ามันเป็น GUI framework ที่ยอดเยี่ยมมาก จนกระทั่งไปชนกำแพงที่นักพัฒนายังทำบางฟีเจอร์ไม่เสร็จ
การแชร์ state ระหว่าง context ที่ต่างกันก็ยังเจ็บปวดเป็นบางครั้ง แต่ GUI framework ทุกตัวที่ผมเคยใช้ก็เป็นแบบนั้น ไม่ว่าจะใช้ภาษาอะไรหรือเทคโนโลยีพื้นฐานอะไร Dioxus 0.5 ดูเหมือนจะก้าวหน้าอย่างมากในเรื่องนี้ บล็อกของผม render HTML อยู่พอสมควรด้วย Dioxus และมันทำงานได้ดีมากตราบใดที่ไม่ได้ใช้งานแบบดันจนสุดขีด
อย่างไรก็ดี วิธีแก้ปัญหาเรื่อง การเอา lifetime ออก ดูชวนสงสัยนิดหน่อย generational-box มันเหมือน garbage collector สำหรับคนงบน้อยหรือเปล่า เลยสงสัยว่ามีผลต่อประสิทธิภาพแค่ไหน
เพิ่มเติมคือ ลิงก์
[generational-box]([https://crates.io/crates/generational-box](<https://crates.io/crates/generational-box>))เสียอยู่use_hookเป็นเจ้าของค่าอยู่ตลอดอายุของคอมโพเนนต์ ดังนั้นเมื่อคอมโพเนนต์หายไป ค่านั้นก็จะถูก drop ไปด้วย signal ทุกตัวยังคงใช้use_hookอยู่ ดังนั้นอายุก็เหมือนเดิม ปกติไม่แนะนำให้เรียกGenerationalBox::new()นอกuse_hookอยู่แล้ว จึงไม่มีผลด้านประสิทธิภาพถ้าคุณเรียก
GenerationalBox::new()รัว ๆ ในลูป ขยะพวกนั้นก็จะค้างอยู่จนกว่าคอมโพเนนต์จะถูก drop แต่โดยมากคนจะ push/pop ใส่ Map หรือ Vec กันมากกว่า ซึ่งกรณีนั้นก็ใช้ memory semantics ปกติเข้าใจว่าเป็น arena allocation ชนิดหนึ่ง แต่ไม่เข้าใจว่ามันรองรับ “copy โดยไม่ต้อง copy” ได้อย่างไร และคำอธิบายที่ว่ามันสร้าง arena ของ
RefCellแบบมี generation ภายใน แล้วGenerationalBoxเป็นCopyนั้นปลอดภัยได้อย่างไรผมพอเข้าใจว่ามันสามารถสร้าง pointer ไปยังข้อมูลแบบ static ได้ แต่ถ้าเป็นค่าที่ไม่มี static lifetime จะเกิดอะไรขึ้น
การอ้างอิงข้อมูลนั้นอยู่ได้ตลอดอายุของโปรแกรม generational box ทำให้ใส่ข้อมูลที่มีอายุสั้นกว่าชีวิตโปรแกรมได้ แต่ข้อมูลนั้นห้ามมี reference อยู่ข้างใน เมื่อลบข้อมูลที่ใส่ไปแล้ว กล่องนั้นก็จะถูกนำกลับไปใช้กับ allocation อื่น
วิธีนี้คล้ายกับ generational arena มาก ต่างกันแค่ใช้กล่องแทน arena แบบรวมศูนย์ ซึ่งเลือกแบบนี้เพื่อหลีกเลี่ยงปัญหาเรื่อง lock ถ้าพยายามเข้าถึงข้อมูลผ่าน reference แบบ
Copyหลังจากถูก drop ไปแล้ว มันจะล้มเหลวพร้อมข้อความ error ที่ดีCopyมีความหมายเฉพาะถ้า type ใด implement trait
Copyได้ แปลว่ามันสามารถถูกคัดลอกด้วยmemcpyได้ และไม่ใช่ deep copy แต่ใกล้เคียงกับ shallow copy มากกว่าดังนั้นมันไม่ใช่ “copy โดยไม่ต้อง copy” แต่ควรเข้าใจว่าเป็นการทำให้ type ที่ปกติไม่ใช่
Copyถูกใช้งานคล้าย type ที่เป็นCopyมากกว่า ใน README ก็บอกว่าต้องใช้เนื้อหาแบบ static ดังนั้นถ้าเป็นค่าที่ไม่มี static lifetime คำตอบก็คือ “ทำแบบนั้นไม่ได้”สิ่งที่ชอบใน Dioxus คือมันหยิบหลายอย่างที่ทำให้ React ประสบความสำเร็จมาใช้ แต่ก็ยังนวัตกรรมและส่งมอบต่อจากจุดนั้นได้อย่างรวดเร็ว ผมตื่นเต้นที่จะได้ลอง signals ในรีลีสนี้
ผมยอมรับว่า React ทำสิ่งดี ๆ ให้กับ JS และเว็บไว้มาก และชื่อเสียงของมันก็สมควรแล้ว แต่ถ้าในปี 2024 เราจะออกแบบภาษา หรือ DSL ขึ้นมาใหม่ตั้งแต่ต้น ผมไม่คิดว่าโค้ดสำหรับ “reactive UI” จำเป็นต้องหน้าตาเหมือน React เสมอไป
ไม่ได้หมายความว่า SwiftUI สมบูรณ์แบบ แต่โค้ดที่เขียนด้วย SwiftUI ให้ความรู้สึกสะอาด เป็นระเบียบ และแยกส่วนได้ดีกว่ามาก เมื่อเทียบกับการเขียนโค้ดคล้ายกันด้วย React
ข้อดีที่แท้จริงของการใช้ JSX กับ GUI แบบข้ามแพลตฟอร์มน่าจะมีประมาณการนำไลบรารีเดิมสำหรับเว็บกลับมาใช้ซ้ำได้ แต่ RSX ดูเหมือนจะมี “คุณค่าที่พกพาต่อได้” ไม่มากไปกว่าการที่นักพัฒนาสามารถย้ายความรู้แนวคิด JSX มาใช้กับ RSX ได้ ทางที่ดีอาจเป็นการประนีประนอมที่ดีกว่าถ้าจะเรียนรู้กระบวนทัศน์ใหม่ที่มองว่าเหนือกว่า React/JSX อย่างเป็นกลาง
สรุปคือ ถ้ามีโปรเจกต์ที่เป็น “SwiftUI แต่ข้ามแพลตฟอร์ม” ก็คงดี แต่ตอนนี้ยังไม่มี รู้จัก @Tokamak/TokamakUI อยู่ แต่ยังไม่สมบูรณ์มากและดูเหมือนกิจกรรมก็ลดลงแล้ว
ตอนนี้รองรับเฉพาะแอปเดสก์ท็อปเนทีฟบน Linux/mac/windows แต่มีแผนรองรับ WASM, เว็บ, และมือถือ
มันน่าจะเป็น เว็บไซต์แบบกระจายศูนย์ แรกที่คนซึ่งตั้งค่า Freenet จะได้เห็นครั้งแรก ค่อนข้างคล้ายกับ Kweb เฟรมเวิร์กเว็บ Kotlin ที่ผมทำ ๆ หยุด ๆ มาหลายปีเหมือนกัน โดยเฉพาะการจัดการสถานะและรูปแบบ DSL ที่แมปจากโค้ดไปเป็น HTML จนถึงตอนนี้ชอบมาก
https://freenet.org/
https://kweb.io/
ที่จริงผมเป็นแฟน Kotlin และชอบทั้ง Kotlin DSL กับโมเดล concurrency ของมัน
Tauri จะใส่ฟรอนต์เอนด์ไว้ในเว็บวิว และต้องสื่อสารกับฟังก์ชัน Rust ฝั่งเนทีฟผ่าน ขอบเขต IPC แบบเดียวกับ Electron
ใน Dioxus โค้ด Rust อยู่ฝั่งเนทีฟอยู่แล้ว ดังนั้นงานอย่างการอ่านไฟล์ระบบหรือเว็บซ็อกเก็ตจึงไม่ต้องใช้ IPC ส่วน Tauri บังคับให้คอมไพล์ฟรอนต์เอนด์เป็น WASM และก็มี Rust crate ที่น่าสนใจไม่น้อยที่คอมไพล์เป็น wasm ไม่ได้
มันอธิบายเป็นคำพูดได้ยากว่าการพัฒนาเรียบง่ายขึ้นแค่ไหนเมื่อไม่มีขอบเขต IPC เครื่องมือของ Dioxus เจาะจงกับ Rust อย่างเดียว จึงไปจาก 0 จนได้
.appที่บันเดิลแล้วภายในไม่ถึง 1 นาทีได้ บิลด์ใหม่ใช้ราว 12 วินาที บันเดิลใหม่ราว 20 วินาทีถึงอย่างนั้นผมก็ชอบความยืดหยุ่นที่ Tauri ให้มาก นั่นคือถ้าเป็น UI ที่เข้ากับเว็บได้ก็เอามาใช้เป็นฟรอนต์เอนด์ได้ทั้งหมด และคุณก็ใช้ Dioxus ภายในแอป Tauri ได้เหมือนกัน
ดีที่ Dioxus พูดถึงเรื่องนี้ตรง ๆ ใน README แต่ก็อยากรู้ประสบการณ์จริงจากคนที่ใช้มาทั้งคู่ด้วย
ผมเคยทำแอปเดสก์ท็อปเล่น ๆ ด้วย Tauri และได้ยืนยันว่า IPC เร็วพอที่เว็บฟรอนต์เอนด์จะรีเฟรชและทำงานทุกครั้งที่กดคีย์โดยไม่หน่วง ซึ่งคำตอบคือ “ใช่” แต่ถ้าถามว่าส่งไฟล์ใหญ่จากฟรอนต์เอนด์ไปยังชั้น Rust ทุกครั้งที่กดคีย์ แล้วส่งกลับมาฟรอนต์เอนด์ได้ไหม อย่างน้อยในการทำแบบไร้เดียงสาของผม คำตอบคือ “ไม่”
ทั้ง Tauri และ Dioxus ต่างก็มีกรณีใช้งานที่เหมาะสมหลายแบบ และในบางกรณี Dioxus ก็น่าจะดีกว่า อยากฟังประสบการณ์เปรียบเทียบแบบ “ในโปรเจกต์ของเราเลือก Dioxus หรือ Tauri เพราะ X และ Y” Dioxus ดูเจ๋งมากจนตื่นเต้นที่จะได้ลองใช้
stylo คือส่วนที่แชร์กันระหว่าง Servo กับ Firefox ในระยะยาวอยากให้คนย้ายไปใช้เรนเดอเรอร์ WGPU แต่ตอนนี้ยังค่อนข้างระยะแรก และหลายบริษัทที่ใช้ Dioxus ก็รู้ตามความเป็นจริงว่าเว็บวิวเป็นทางออกที่ดีพอสำหรับแอปราว 90% ของทั้งหมด
ผมเข้าใจว่าบางครั้งคนก็ใช้
unsafeง่ายเกินไป แต่ใน standard library เองก็เต็มไปด้วยunsafeและการไปขีดเส้นแบ่งไว้ตรงนั้นบางทีก็ดูเหมือนขีดเส้นบนทรายตอนนี้ใช้ในสามจุด คือการแก้ FFI บางส่วนของ iOS, implementation เพื่อให้ signal เรียกใช้ด้วยรูปแบบฟังก์ชันได้, และส่วนที่ implement Send/Sync ให้ ID ที่ใช้พอยน์เตอร์เป็นแฮช
พอมาดูตอนนี้ อันสุดท้ายน่าจะลบออกได้ถ้าเปลี่ยนไปใช้
usizeunsafeมากเกินไปนิดหน่อยถึงอย่างนั้น การที่ผู้เขียน crate ตั้งเป้าจะเอา
unsafeออกจาก crate ของตัวเองก็ไม่จำเป็นต้องเป็นการตัดสินใจที่แย่บ่อยครั้งผู้เขียน crate ที่พยายามลบ
unsafeทั้งหมด ต้องการลดภาระด้านความไว้วางใจที่ผู้ใช้ต้องแบกรับ และผมคิดว่านี่แหละคือพลังของคีย์เวิร์ดunsafeที่จริงมันอาจควรถูกแยกเป็นบล็อกtrust_meกับฟังก์ชันcheck_yourselfด้วยซ้ำunsafeทำให้การพูดคุยเรื่อง memory safety ถูกจำกัดไว้ในจุดที่แคบมากและตรวจสอบได้ในโค้ด ขณะเดียวกันก็สร้างบทสนทนาใหม่เรื่องความไว้วางใจที่จัดการได้มากขึ้นuse*สำหรับ hook API แต่ก็สงสัยว่าใน Dioxus ทำไมฟังก์ชันสร้าง signal ใหม่ถึงชื่อuse_signallet mut count = use_signal(|| 0);นี่คือการเรียกเพื่อสร้าง signal ใหม่ไม่ใช่หรือ? signal ไม่ได้ถูกสร้างใหม่ทุกครั้งที่เรนเดอร์ และนั่นก็เป็นแก่นของ signal เลยใน SolidJS จะสร้าง signal ด้วย createSignal แบบ
const [count, setCount] = createSignal(0)ซึ่งฝั่งนี้เข้าใจง่ายกว่ามากชื่อ API สำคัญ เพราะ state hook ทำงานต่างออกไป และอาจต้องมีสิ่งเสริมอย่าง
useMemoด้วย เลยสงสัยว่านอกจากอยากให้ Dioxus ดูใกล้กับ React แล้ว มีเหตุผลอะไรอีกไหมที่เลือกชื่อuse_signalมันใกล้กับ Preact มากกว่า Solid
การทำ optimization รวมถึงการแยกชิ้นส่วนไดนามิกของ UI ไปไว้ใน “diff bin” ต่างหาก, memoization โดยปริยาย, และการที่ signal ทำเครื่องหมายพร็อพเพอร์ตีเป็น dirty/managed โดยปริยาย