Web Frameworks ใดแก้ไขได้และจะทำอย่างไรถ้าไม่มี (ตอนที่ 1)

เผยแพร่แล้ว: 2022-03-10
สรุปโดยย่อ ↬ ในบทความนี้ Noam Rosenthal เจาะลึกถึงคุณลักษณะทางเทคนิคบางอย่างที่เหมือนกันในกรอบงานต่างๆ และอธิบายว่ากรอบงานต่างๆ บางส่วนนำไปใช้อย่างไรและราคาเท่าไหร่

ฉันเพิ่งสนใจที่จะเปรียบเทียบเฟรมเวิร์กกับ JavaScript วานิลลามาก มันเริ่มต้นหลังจากความยุ่งยากบางอย่างที่ฉันใช้ React ในโครงการอิสระบางโครงการของฉัน และด้วยความคุ้นเคยที่ใกล้ชิดยิ่งขึ้นกับมาตรฐานเว็บเมื่อเร็วๆ นี้ในฐานะเครื่องมือแก้ไขข้อกำหนด

ฉันสนใจที่จะดูว่าสิ่งที่ เหมือนกันและความแตกต่างระหว่างเฟรมเวิ ร์ก คืออะไร แพลตฟอร์มเว็บเสนออะไรให้เป็นทางเลือกที่น้อยกว่า และเพียงพอหรือไม่ วัตถุประสงค์ของฉันไม่ใช่เพื่อทุบตีเฟรมเวิร์ก แต่เพื่อทำความเข้าใจต้นทุนและผลประโยชน์ เพื่อพิจารณาว่ามีทางเลือกอื่นหรือไม่ และเพื่อดูว่าเราสามารถเรียนรู้จากมันได้หรือไม่ แม้ว่าเราจะตัดสินใจใช้เฟรมเวิร์กก็ตาม

ในส่วนแรกนี้ ฉันจะเจาะลึกถึงคุณสมบัติทางเทคนิคสองสามอย่างที่พบได้ทั่วไปในเฟรมเวิร์กต่างๆ และวิธีที่เฟรมเวิร์กต่างๆ นำไปใช้ ฉันจะดูต้นทุนของการใช้เฟรมเวิร์กเหล่านั้นด้วย

กรอบการทำงาน

ฉันเลือกกรอบงานสี่แบบที่จะพิจารณา: React ซึ่งเป็นกรอบที่โดดเด่นในวันนี้ และอีกสามกรอบงานใหม่ที่อ้างว่าทำสิ่งต่าง ๆ แตกต่างจาก React

  • ปฏิกิริยา
    “React ทำให้การสร้าง UI แบบโต้ตอบนั้นไม่ลำบาก มุมมองที่เปิดเผยทำให้โค้ดของคุณคาดเดาได้ง่ายขึ้นและดีบั๊กได้ง่ายขึ้น”
  • SolidJS
    “Solid ปฏิบัติตามปรัชญาเดียวกันกับ React… อย่างไรก็ตาม มีการนำไปใช้งานที่แตกต่างไปจากเดิมอย่างสิ้นเชิงที่ละเลยการใช้ DOM เสมือน”
  • Svelte
    “Svelte เป็นแนวทางใหม่ในการสร้างอินเทอร์เฟซผู้ใช้… ขั้นตอนการคอมไพล์ที่จะเกิดขึ้นเมื่อคุณสร้างแอปของคุณ แทนที่จะใช้เทคนิคต่างๆ เช่น DOM เสมือน DOM Svelte จะเขียนโค้ดที่อัปเดต DOM แบบผ่าตัดเมื่อสถานะของแอปเปลี่ยนแปลง”
  • Lit
    “การสร้างบนมาตรฐาน Web Components Lit เพิ่มเพียง … ปฏิกิริยา เทมเพลตที่เปิดเผย และคุณสมบัติที่รอบคอบจำนวนหนึ่ง”

เพื่อสรุปสิ่งที่กรอบงานพูดเกี่ยวกับตัวสร้างความแตกต่าง:

  • React ทำให้การสร้าง UI ง่ายขึ้นด้วยมุมมองที่เปิดเผย
  • SolidJS ปฏิบัติตามปรัชญาของ React แต่ใช้เทคนิคที่ต่างออกไป
  • Svelte ใช้วิธีการคอมไพล์เวลากับ UI
  • Lit ใช้มาตรฐานที่มีอยู่พร้อมคุณสมบัติน้ำหนักเบาที่เพิ่มเข้ามา

กรอบงานอะไรแก้

เฟรมเวิร์กกล่าวถึงคำว่า Declarative, Reactive และ DOM เสมือน มาดูกันว่านั่นหมายถึงอะไร

การเขียนโปรแกรมประกาศ

การเขียนโปรแกรมเชิงประกาศเป็นกระบวนทัศน์ที่มีการกำหนดตรรกะโดยไม่ระบุขั้นตอนการควบคุม เราอธิบายถึงผลลัพธ์ที่ต้องการ มากกว่าขั้นตอนที่จะพาเราไปที่นั่น

ในช่วงแรก ๆ ของเฟรมเวิร์กการประกาศ ประมาณปี 2010 DOM API นั้นเปล่าเปลี่ยวและละเอียดกว่ามาก และการเขียนเว็บแอปพลิเคชันด้วย JavaScript ที่จำเป็นนั้นจำเป็นต้องมีโค้ดสำเร็จรูปจำนวนมาก นั่นคือเมื่อแนวคิดของ “model-view-viewmodel” (MVVM) เป็นที่แพร่หลาย ด้วยเฟรมเวิร์ก Knockout และ AngularJS ที่ก้าวล้ำในขณะนั้น ให้เลเยอร์ประกาศ JavaScript ที่จัดการความซับซ้อนนั้นภายในไลบรารี

MVVM ไม่ใช่คำที่ใช้กันอย่างแพร่หลายในปัจจุบัน และเป็นการแปรผันของคำว่า "data-binding" ที่เก่ากว่า

การผูกข้อมูล

การผูกข้อมูลเป็นวิธีที่เปิดเผยในการแสดงวิธีการซิงโครไนซ์ข้อมูลระหว่างแบบจำลองและอินเทอร์เฟซผู้ใช้

เฟรมเวิร์ก UI ยอดนิยมทั้งหมดมีรูปแบบการผูกข้อมูล และบทช่วยสอนจะเริ่มต้นด้วยตัวอย่างการผูกข้อมูล

นี่คือการเชื่อมโยงข้อมูลใน JSX (SolidJS และ React):

 function HelloWorld() { const name = "Solid or React"; return ( <div>Hello {name}!</div> ) }

การผูกข้อมูลใน Lit:

 class HelloWorld extends LitElement { @property() name = 'lit'; render() { return html`<p>Hello ${this.name}!</p>`; } }

การผูกข้อมูลใน Svelte:

 <script> let name = 'world'; </script> <h1>Hello {name}!</h1>

ปฏิกิริยา

การเกิดปฏิกิริยาเป็นวิธีที่เปิดเผยเพื่อแสดงการแพร่กระจายของการเปลี่ยนแปลง

เมื่อเรามีวิธีแสดงการเชื่อมโยงข้อมูลอย่างชัดเจน เราต้องการวิธีที่มีประสิทธิภาพสำหรับกรอบงานในการเผยแพร่การเปลี่ยนแปลง

เอ็นจิ้น React จะเปรียบเทียบผลลัพธ์ของการเรนเดอร์กับผลลัพธ์ก่อนหน้า และใช้ความแตกต่างกับ DOM เอง วิธีจัดการกับการแพร่กระจายการเปลี่ยนแปลงนี้เรียกว่า DOM เสมือน

ใน SolidJS สิ่งนี้ทำได้อย่างชัดเจนยิ่งขึ้นด้วยการจัดเก็บและองค์ประกอบในตัว ตัวอย่างเช่น องค์ประกอบ Show จะติดตามสิ่งที่เปลี่ยนแปลงภายใน แทนที่จะเป็น DOM เสมือน

ใน Svelte รหัส "reactive" จะถูกสร้างขึ้น Svelte รู้ว่าเหตุการณ์ใดสามารถทำให้เกิดการเปลี่ยนแปลงได้ และสร้างโค้ดที่ตรงไปตรงมาซึ่งลากเส้นแบ่งระหว่างเหตุการณ์และการเปลี่ยนแปลง DOM

ใน Lit การเกิดปฏิกิริยาทำได้โดยใช้คุณสมบัติขององค์ประกอบ โดยอาศัยปฏิกิริยาในตัวขององค์ประกอบที่กำหนดเอง HTML

เพิ่มเติมหลังกระโดด! อ่านต่อด้านล่าง↓

ตรรกะ

เมื่อเฟรมเวิร์กจัดเตรียมอินเทอร์เฟซแบบเปิดเผยสำหรับการผูกข้อมูลด้วยการนำปฏิกิริยาไปใช้ ก็จำเป็นต้องจัดเตรียมวิธีในการแสดงตรรกะบางอย่างที่เขียนโดยปกติความจำเป็น โครงสร้างพื้นฐานของตรรกะคือ "ถ้า" และ "สำหรับ" และกรอบการทำงานหลักทั้งหมดมีการแสดงออกขององค์ประกอบพื้นฐานเหล่านี้

เงื่อนไข

นอกเหนือจากการผูกข้อมูลพื้นฐาน เช่น ตัวเลขและสตริงแล้ว ทุกเฟรมเวิร์กยังมี "เงื่อนไข" ดั้งเดิมอีกด้วย ใน React ดูเหมือนว่านี้:

 const [hasError, setHasError] = useState(false); return hasError ? <label>Message</label> : null; … setHasError(true);

SolidJS จัดเตรียมองค์ประกอบตามเงื่อนไขในตัว Show :

 <Show when={state.error}> <label>Message</label> </Show>

Svelte ให้คำสั่ง #if :

 {#if state.error} <label>Message</label> {/if}

ใน Lit คุณจะต้องใช้การดำเนินการแบบไตรภาคที่ชัดเจนในฟังก์ชันการ render :

 render() { return this.error ? html`<label>Message</label>`: null; }

รายการ

กรอบงานพื้นฐานทั่วไปอื่น ๆ คือการจัดการรายการ รายการเป็นส่วนสำคัญของ UI เช่น รายชื่อผู้ติดต่อ การแจ้งเตือน ฯลฯ และเพื่อให้ทำงานได้อย่างมีประสิทธิภาพ รายการเหล่านี้จะต้องมีปฏิกิริยาตอบสนอง ไม่อัปเดตรายการทั้งหมดเมื่อรายการข้อมูลหนึ่งรายการมีการเปลี่ยนแปลง

ใน React การจัดการรายการจะมีลักษณะดังนี้:

 contacts.map((contact, index) => <li key={index}> {contact.name} </li>)

React ใช้แอตทริบิวต์ key พิเศษเพื่อแยกความแตกต่างระหว่างรายการ และทำให้แน่ใจว่ารายการทั้งหมดจะไม่ถูกแทนที่ด้วยการแสดงผลทุกครั้ง

ใน SolidJS ใช้องค์ประกอบในตัว for และ index :

 <For each={state.contacts}> {contact => <DIV>{contact.name}</DIV> } </For>

ภายใน SolidJS ใช้ร้านค้าของตัวเองร่วมกับ for และ index เพื่อตัดสินใจว่าจะอัปเดตองค์ประกอบใดเมื่อรายการเปลี่ยนแปลง มีความชัดเจนมากกว่า React ทำให้เราสามารถหลีกเลี่ยงความซับซ้อนของ DOM เสมือนได้

Svelte ใช้ each คำสั่ง ซึ่งได้รับการ transpiled ตามตัวอัปเดต:

 {#each contacts as contact} <div>{contact.name}</div> {/each}

Lit มีฟังก์ชัน repeat ซึ่งทำงานคล้ายกับการแมปรายการตาม key ของ React:

 repeat(contacts, contact => contact.id, (contact, index) => html`<div>${contact.name}</div>`

รุ่นส่วนประกอบ

สิ่งหนึ่งที่อยู่นอกขอบเขตของบทความนี้คือโมเดลคอมโพเนนต์ในเฟรมเวิร์กต่างๆ และวิธีจัดการกับองค์ประกอบ HTML ที่กำหนดเอง

หมายเหตุ : นี่เป็นหัวข้อใหญ่ และฉันหวังว่าจะครอบคลุมในบทความต่อๆ ไป เพราะหัวข้อนี้จะยาวเกินไป :)

ค่าใช้จ่าย

กรอบงานจัดเตรียมการเชื่อมโยงข้อมูลแบบเปิดเผย การควบคุมเบื้องต้นของโฟลว์ (เงื่อนไขและรายการ) และกลไกเชิงปฏิกิริยาเพื่อเผยแพร่การเปลี่ยนแปลง

พวกเขายังให้ข้อมูลสำคัญอื่นๆ เช่น วิธีการนำส่วนประกอบกลับมาใช้ใหม่ แต่นั่นเป็นหัวข้อสำหรับบทความแยกต่างหาก

กรอบงานมีประโยชน์หรือไม่? ใช่. พวกเขาให้คุณสมบัติที่สะดวกสบายทั้งหมดเหล่านี้แก่เรา แต่นั่นเป็นคำถามที่ถูกต้องหรือไม่? การใช้กรอบงานมีค่าใช้จ่าย เรามาดูกันว่าค่าใช้จ่ายเหล่านั้นมีอะไรบ้าง

ขนาดมัด

เมื่อดูขนาดบันเดิล ฉันชอบดูขนาดที่ไม่ใช่ Gzip ที่ย่อเล็กสุด นั่นคือขนาดที่เกี่ยวข้องกับต้นทุน CPU ของการดำเนินการ JavaScript มากที่สุด

  • ReactDOM มีขนาดประมาณ 120 KB
  • SolidJS มีขนาดประมาณ 18 KB
  • Lit มีขนาดประมาณ 16 KB
  • Svelte มีขนาดประมาณ 2 KB แต่ขนาดของโค้ดที่สร้างขึ้นจะแตกต่างกันไป

ดูเหมือนว่าเฟรมเวิร์กของวันนี้ทำงานได้ดีกว่า React ที่ทำให้บันเดิลมีขนาดเล็ก DOM เสมือนต้องการ JavaScript จำนวนมาก

บิลด์

อย่างไรก็ตาม เราเคยชินกับการ "สร้าง" เว็บแอปของเรา เป็นไปไม่ได้ที่จะเริ่มต้นโปรเจ็กต์ส่วนหน้าโดยไม่ต้องตั้งค่า Node.js และบันเดิล เช่น Webpack จัดการกับการเปลี่ยนแปลงการกำหนดค่าล่าสุดใน Babel-TypeScript starter pack และเพลงแจ๊สทั้งหมด

ยิ่งขนาดบันเดิลของเฟรมเวิร์กมีความชัดเจนและเล็กลงเท่าใด ภาระของเครื่องมือสร้างและเวลาในการถ่ายโอนก็จะยิ่งมากขึ้นเท่านั้น

Svelte อ้างว่า DOM เสมือนเป็นค่าใช้จ่ายที่บริสุทธิ์ ฉันเห็นด้วย แต่บางที "การสร้าง" (เช่นเดียวกับ Svelte และ SolidJS) และเครื่องมือเทมเพลตฝั่งไคลเอ็นต์ที่กำหนดเอง (เช่นเดียวกับ Lit) ก็เป็นค่าใช้จ่ายที่บริสุทธิ์เหมือนกันหรือไม่

แก้จุดบกพร่อง

ด้วยการสร้างและการขนถ่ายมีค่าใช้จ่ายที่แตกต่างกัน

โค้ดที่เราเห็นเมื่อเราใช้หรือดีบักเว็บแอปนั้นแตกต่างไปจากที่เราเขียนโดยสิ้นเชิง ตอนนี้เราใช้เครื่องมือการดีบักพิเศษที่มีคุณภาพแตกต่างกันเพื่อทำวิศวกรรมย้อนกลับสิ่งที่เกิดขึ้นบนเว็บไซต์และเชื่อมต่อกับจุดบกพร่องในโค้ดของเราเอง

ใน React call stack ไม่เคยเป็น "ของคุณ" — React จัดการการตั้งเวลาให้คุณ ใช้งานได้ดีเมื่อไม่มีข้อบกพร่อง แต่พยายามระบุสาเหตุของการเรนเดอร์ซ้ำแบบอนันต์ แล้วคุณจะอยู่ในโลกแห่งความเจ็บปวด

ใน Svelte ขนาดบันเดิลของไลบรารีมีขนาดเล็ก แต่คุณจะต้องจัดส่งและดีบักโค้ดที่สร้างที่คลุมเครือทั้งพวงซึ่งเป็นการนำปฏิกิริยาของ Svelte ไปใช้ ซึ่งปรับแต่งตามความต้องการของแอปของคุณ

เมื่อใช้ Lit มันไม่เกี่ยวกับการสร้าง แต่การจะดีบั๊กอย่างมีประสิทธิภาพ คุณต้องเข้าใจเทมเพลทเอ็นจิ้นของมัน นี่อาจเป็นเหตุผลที่ใหญ่ที่สุดว่าทำไมความรู้สึกของฉันที่มีต่อเฟรมเวิร์กจึงไม่น่าไว้วางใจ

เมื่อคุณมองหาโซลูชันการประกาศแบบกำหนดเอง คุณจะจบลงด้วยการดีบักที่จำเป็นที่เจ็บปวดกว่า ตัวอย่างในเอกสารนี้ใช้ Typescript สำหรับข้อกำหนด API แต่โค้ดเองก็ไม่ต้องการ transpilation

อัพเกรด

ในเอกสารนี้ ฉันได้ดูสี่เฟรมเวิร์กแล้ว แต่มีเฟรมเวิร์กมากกว่าที่ฉันสามารถนับได้ (AngularJS, Ember.js และ Vue.js เป็นต้น) คุณวางใจได้ว่าเฟรมเวิร์ก นักพัฒนา แชร์ความคิด และระบบนิเวศจะทำงานให้กับคุณในขณะที่มันพัฒนาขึ้นหรือไม่

สิ่งหนึ่งที่น่าผิดหวังมากกว่าการแก้ไขจุดบกพร่องของคุณเองคือการต้องหาวิธีแก้ไขปัญหาเฉพาะหน้าสำหรับจุดบกพร่องของเฟรมเวิร์ก และสิ่งหนึ่งที่น่าผิดหวังมากกว่าข้อบกพร่องของเฟรมเวิร์กคือจุดบกพร่องที่เกิดขึ้นเมื่อคุณอัพเกรดเฟรมเวิร์กเป็นเวอร์ชันใหม่โดยไม่ต้องแก้ไขโค้ดของคุณ

จริงอยู่ ปัญหานี้ยังมีอยู่ในเบราว์เซอร์ แต่เมื่อเกิดปัญหาขึ้น ปัญหานี้จะเกิดขึ้นกับทุกคน และในกรณีส่วนใหญ่ การแก้ไขหรือวิธีแก้ไขปัญหาชั่วคราวที่เผยแพร่ก็ใกล้เข้ามาแล้ว นอกจากนี้ รูปแบบส่วนใหญ่ในเอกสารนี้อิงตาม API แพลตฟอร์มเว็บสำหรับผู้ใหญ่ ไม่จำเป็นต้องไปกับขอบเลือดออกเสมอไป

สรุป

เราเจาะลึกลงไปอีกเล็กน้อยในการทำความเข้าใจเฟรมเวิร์กของปัญหาหลักที่พยายามแก้ไขและวิธีแก้ปัญหา โดยเน้นที่การเชื่อมโยงข้อมูล ปฏิกิริยา เงื่อนไข และรายการ เรายังดูค่าใช้จ่าย

ในส่วนที่ 2 เราจะมาดูกันว่าปัญหาเหล่านี้สามารถแก้ไขได้โดยไม่ต้องใช้เฟรมเวิร์กอย่างไร และเราจะเรียนรู้อะไรจากมันได้บ้าง คอยติดตาม!

ขอขอบคุณเป็นพิเศษสำหรับบทวิจารณ์ทางเทคนิคสำหรับบุคคลต่อไปนี้: Yehonatan Daniv, Tom Bigelajzen, Benjamin Greenbaum, Nick Ribal และ Louis Lazaris