React หรือ jQuery? กางให้ดูกันชัดๆ ใครเด็ดกว่า!
บทความนี้เป็นส่วนหนึ่งของชุดบทความ “React.JS เครื่องมือที่ Frontend Web Developer ต้องลอง!”
อย่างที่ขายของไปใน โพสต์ที่แล้ว แต่อ่านเองแล้วมันก็ยังแลดู Abstract เล็กๆ ไม่ชัดเจน ไม่เห็นภาพ โพสต์นี้เลยจะกาง Code ให้ดูกันชัดๆ เปรียบเทียบให้เห็นว่า งานชิ้นเดียวกัน โค้ดของ React หรือ โค้ดของ jQuery ใครจะเด็ดกว่ากัน!
App ที่เราจะสร้างกันในวันนี้ เราจะมาสร้าง App ฐานข้อมูล Pokemon อย่างง่ายๆ หรือ “Pokedex Simple Search”
พร้อมแล้ว ก็มาลุยกันเลย!
Demo
ผมทำ Demo ไว้ให้ลองเล่นกันแล้วครับ ลองเปิดเข้าไปดูกันได้ที่ลิงค์ด้านล่าง
ส่วนใครจะเปิด Code ตามไป ก็สามารถ Checkout ที่ Repo นี้ได้ครับ https://github.com/chaintng/pokedex-simple-search
Requirement
เราได้รับความอนุเคราะห์ฐานข้อมูล Pokemon มาจาก https://gist.github.com/shri/9754992 เป็นไฟล์ JSON ดังภาพประกอบ
App ที่เรากำลังจะทำนี้ จะสามารถแสดงข้อมูล ชื่อโปเกมอน, ค่าพลังโจมตี และค่าพลังป้องกัน ออกมาบนหน้าเว็บไซต์ โดยที่สามารถกรองข้อมูล ตามประเภทของโปเกมอน และค่าพลังโจมตีขั้นต่ำได้ ดูตัวอย่างโปรแกรมด้านล่าง
jQuery Style
หลังจากเข้าใจ Requirement แล้ว เรามาเริ่มเขียนด้วยวิธีที่คุ้นเคยกันก่อน นั่นก็คือ การใช้ jQuery นั่นเอง
ขั้นตอนที่ 1: Set Listener ให้ Filter ทั้งสองประเภท
เนื่องจากเราต้องการเห็นข้อมูลเปลี่ยนแปลงทันทีที่เกิด User Action ในแต่ละ input box ต่างๆ เราจึงจำเป็นต้องตั้ง Listener กับ Event ของทั้งสอง Elements ดัง Code ด้านล่างนี้ครับ
ปล. ถ้าเปิดด้วย Medium App มันจะไม่เห็น Code นะครับ… ลองใช้ Web View เปิดแทนดูครับ
ขั้นตอนที่ 2: สร้างฟังก์ชั่น renderPokemonList()
หลังจากที่เราได้ Pokemon ผู้รอดชีวิตจากการกรองในฟังก์ชั่น filterPokemonByTypeAndMinAtk() แล้ว เราก็จะโยนลูกต่อไปให้ฟังก์ชั่น renderPokemonList() เพื่อทำการสร้าง และ Update ก้อน HTML เพื่อแสดงบนหน้าเว็บของเรา
เสร็จ! เข้าใจง่าย! ตรงไปตรงมา!…. Happy ending. Commit. Push. กลับบ้านได้!
ยัง… มาเสียเวลากันอีกนิด เพื่อดู React กันสักหน่อยดีกว่า ว่ามันเขียนยังไง ทำไมไอ้คนเขียนคนนี้มันเชิดชูสะเหลือเกิ๊นนนน…
React Style
งานแรกสุดของ React ก็คือ การ Design Component ครับ… เนื่องจากผมต้องการให้ Code ไม่ซับซ้อนมาก ผมจึงขออนุญาตแบ่งออกเป็นแค่ 3 Components (Pokedex, Filter และ Result) ตามรูปประกอบ
ขั้นตอนที่ 1: Design Components ของ Application
เมื่อเรา Design เสร็จแล้ว ก็ลุยเขียน Component เลยครับ
จากตัวอย่าง จะเห็นโครงสร้างคร่าวๆ ของ Components ต่างๆ จะตรงกับที่เรา Design ไว้ตอนแรก คือ Pokedex จะเป็นตัวหลัก และมี Compoents Filter และ Result อยู่ภายใน
จาก Code ในบรรทัดสุดท้าย จะเป็นการสั่งให้ React Render Component Pokedex ลงใน div#reactOutput
ขั้นตอนที่ 2: กำหนด State และ Property
ในแต่ละ Component ของ React, เราจะเก็บข้อมูลต่างๆ ของ Component นั้น ไว้ใน State (ตัวอย่างข้อมูลเช่น Type และค่าพลังโจมตีของ Pokemon ที่ผู้ใช้เลือก)
เมื่อใดที่ State นั้นมีการเปลี่ยนแปลง, React ก็จะ Render Component ที่เกี่ยวข้องใหม่ทั้งหมดให้เอง
{/* เมื่อมี action จาก User ก็จัดการเปลี่ยนแปลง State ซะ, React จะ Render Component ต่างๆ ที่เกี่ยวข้องใหม่ให้เอง */}onFilterPokemonAtkChange: function(e) {
this.setState({ filterPokemonAtk: e.target.value })
},
ในขั้นตอนนี้ เราจะกำหนด State ให้กับค่า Pokemon Type และ Attack แต่ก่อนจะไปถึงขั้นนั้น มีอีกสิ่งหนึ่งที่เราจำเป็นต้องรู้คือ props
เนื่องจากเราแบ่ง Component ออกเป็น 3 ตัว… Action ที่เกิดขึ้นใน Component Filter จะต้องไปเปลี่ยนแปลงการแสดงผลใน Component Result
ดังนั้น เราไม่สามารถเก็บค่า PokemonType และ PokemonAttack ไว้ใน State ของ Component Filter ได้…
ทางแก้ง่ายมากครับ ให้เรากำหนด State ไว้ที่ Component ตัวแม่ ก็คือ Pokedex แทน แล้วส่งข้อมูลจาก State ใน Pokedex โยนไปให้ Component Result ผ่านสิ่งที่เรียกว่า props นี้แล…
{/* ตัวอย่างการส่งผ่าน props*/}
<Result filterPokemon={filterPokemon} />{/* กำหนดชนิดของ property ให้กับ component */}
var Result = React.createClass({
propTypes: {
filterPokemon: React.PropTypes.array,
},
ดูตัวอย่างกันเลยดีกว่าครับ…
ขั้นตอน 2–1, เป็นการ Setup state และ function ใน Component ของ Pokedex โดยมีขั้นตอนดังนี้
- setup initial state ในฟังก์ชั่น getInitialState() เพื่อเป็นการกำหนดค่าเริ่มต้น
- สร้างฟังก์ชั่นเพื่อรองรับ User Action ตอนเปลี่ยน Filter Pokemon Type และ Pokemon Attack
- ในฟังก์ชั่นนี้ เราจะรับ event ของ user มา และนำ value มากำหนด state ของ Component Pokedex อย่างที่ได้กล่าวไป - เมื่อเรา Set state ใหม่แล้ว, Life-cycle ของ React ก็จะทำการ Render Component นั้นใหม่ (รวมถึงลูกๆ ของมัน <Filter, Result>) เราก็ต้องอัพเดทค่า property ที่จะส่งให้ component Result โดยการโยน List Pokemon เข้า function filterPokemonByTypeAndMinAtk() และเก็บเข้าตัวแปร filterPokemon
- set property ให้ Filter เพื่อให้ตัว Component สามารถเรียกใช้ Function onChange ต่างๆ ได้
- set property ให้ Result เพื่อให้ตัว Component มีข้อมูล Pokemon List ไปแสดง
ขั้นตอนที่ 2–2, เป็นการ Setup property ที่ Component Filter รองรับ และทำการกำหนด function ให้ event listener ของแต่ละ Filter
- ทำการ assign property ที่ component Filter รองรับ พร้อมระบุประเภทของค่า property นั้น (React จะ throw error ถ้าได้ parameter ไม่ตรงประเภท)
- Set ฟังก์ชั่นจาก props ลงใน attribute onChange ของแต่ละ filter
- หาก User มี action onChange, ตัว Application ก้จะไปเรียก function ที่เรา declar ไว้ใน component Pokedex (setState จาก value ของ event)
ขั้นตอน 2–3, ถึงเวลา re-render, ตอนนี้ state ถูกเปลี่ยนแปลงแล้ว แต่เรายังไม่ได้อธิบายให้ React รู้เลยว่า จะแสดงข้อมูลเหล่านั้นอย่างไร มาดู Code ของ Component Result กันดีกว่าครับ
component นี้ค่อนข้างเรียบง่ายครับ เรากำหนด Property type เหมือนเดิม และนำข้อมูลที่เป็น Array นั้น มาวน loop เพื่อแสดง ข้อมูล Pokemon แต่ละตัวใน tag <li>
เสร็จแล้วครับ…. ตัดจบ!
เราเห็นอะไรบ้าง ?
Loosely Coupling
คำนี้เป็น Concept การ Design โปรแกรมชนิดหนึ่ง ซึ่งมีเป้าหมายคือ แต่ละส่วนการทำงานนั้น จะต้องทำให้น้อย รู้ให้น้อย… เป็น Concept การออกแบบที่ดีกว่า เพราะการเปลี่ยนแปลงใดๆ จะไม่กระทบการทำงานส่วนอื่นๆ ของระบบ เนื่องจากแต่ละส่วนทำงาน สามารถทำงานได้ด้วยลำแข้งของตัวเอง (Independent) คำตรงข้ามของมันก็คือ Tight Coupling หรือเรียกให้เห็นภาพก็คือ Spaghetti Code นั่นแหละครับ (ใครอยากฟังเรื่องนี้ ขออนุญาตแนะนำหนังสือ Design Pattern จะเข้าใจความจำเป็นของมันมากขึ้นครับ หรือลองอ่านเบื้องต้นที่ บทความนี้ :-) )
จาก Code ตัวอย่างของ React, จริงอยู่ที่มันมี Listener เหมือนกับของ jQuery แต่มันต่างกันตรงที่ React ทำหน้าที่แค่ Set state ส่วน Component อื่นๆ ก็จะกุลีกุจอ เปลี่ยนแปลงการแสดงผลของตัวเองตามค่าความจริงที่เปลี่ยนไป… เราเรียกแนวความคิดแบบนี้ว่า Declarative Programming หรือ การเขียนโปรแกรมเพื่อแสดงว่าเรา “ต้องการ อะไร” (e.g. ต้องการโปเกมอน เหลือแค่ 20 ตัว)
ในขณะที่ jQuery ต้องระบุไปอย่างชัดเจนว่า ให้ทำอะไรต่อ ปัญหาจะเกิดตอน maintain ยกตัวอย่างง่ายๆ เช่น ถ้า id ของ div มีการเปลี่ยนแปลง ก็จำเป็นต้องเปลี่ยนแปลงถึงสองที่ หรือมากว่า… เราเรียกแนวความคิดแบบนี้ว่า Imperative Programming หรือ เป็นการเขียนเชิงคำสั่ง “ต้องทำ อะไร” (e.g. แสดงผลโปเกมอน 20 ตัว ที่ระบุค่าพลังโจมตี ด้วย div class โน่นนี้นั้น blah blah…)
Speed
ในบทความที่แล้ว มีการพูดถึง Virtual DOM ซึ่งเป็น Layer บางๆ ที่ React ใช้คำนวนก่อนว่า Component ใดๆ บ้าง ที่จำเป็นต้องเปลี่ยนแปลงจริงๆ คำนวนเสร็จ ก็ค่อยจัดการเปลี่ยนแปลงอย่างน้อยที่สุด เพื่อแก้ไขหน้าเว็บไซต์นั้น ในขณะที่ jQuery ใช้วิธี Render HTML ทั้งก้อน ไม่สนหน้าอินทร์หน้าพรหมใดๆ ทั้งสิ้น สิ่งที่ React ได้เปรียบในนัดนี้ก็คือ ความเร็วในการแสดงผลนั่นเอง!
JSX
ผมว่าอันนี้ไม่น่าต้องพูดอะไรมาก… เทียบกันชัดๆ แบบ Before After เลย ตามภาพปลากรอบ
ผมว่าหลายคนที่ทำ jQuery คงมีประสบการณ์ เวลาเจอ Component ที่มีความซับซ้อนมากๆ ไอ้ HTML ก้อนนี้มันก็จะยุ่งเหยิงจนบางทีไม่อยากให้อภัยตัวเองก็เคยมี
Scalability
ต่อเนื่องจากข้อที่แล้ว… การตัดสินใจครั้งนี้ มันส่งผลไปถึงอนาคตอันไกลโพ้น… อาจจะเป็นคุณเอง หรือใครก็ตามที่มาดูแล Code นี้ต่อ… ข้อมูลของเหล่าโปเกมอน จะต้องมีรายละเอียดมากขึ้นในเฟสถัดไป เช่น รูปภาพโปเกมอน, สถานที่จับ, Level ในการเปลี่ยนร่าง ฯลฯ สิ่งที่ React อำนวยก็คือ… เราสามารถสร้าง Element นี้ได้เอง แล้วยกทั้งก้อน แยกไฟล์ไปเป็นอีก Compoenent นึง… (Encapsulation) #มีความเป็นระเบียบ #มีความมีไสตล์ ใน Component นั้น ก็สามารถมี CSS ไว้ตกแต่ง หรือฟังก์ชั่นการคำนวนต่างๆ เป็นของตัวเองได้ด้วย ซึ่งจะทำให้โครงสร้างโปรเจคเราแลดูเป็นระเบียบ… แบ่งเป็นสัดส่วน อยากหาอะไรก็หาเจอ อยากแก้อะไรก็ไม่ไปกระทบทั่งคนโน้นคนนี้… ทำอะไรได้อย่างสบายใจ #มีความมั่นใจ
สรุป
จริงอยู่ที่ Code ของ React มันยาวกว่า แต่สิ่งที่ควรคำนึงไม่ใช่เรื่องความยาวของ Code แต่เป็นเรื่อง Readability และ Reusability มากกว่า
ยิ่งในตัวอย่างนี้ เป็นการจงใจทำทุกอย่างให้อยู่ในไฟล์เดียวกัน เพื่อจะได้สามารถเปรียบเทียบได้อย่างชัดเจน แต่ในความเป็นจริงแล้ว แต่ละส่วนของ React จะถูกจับแยกเป็นชิ้นๆ ออกไปเป็นไฟล์ต่างๆ แยกกันอยู่อย่างมีระบบระเบียบ
พอเกิด Readability, สิ่งที่ตามมาก็คือ Scale ง่าย… Maintain ง่าย… คนเขียนแฮปปี้ คนดูแลก็แฮปปี้… โอกาส Rework ต่ำ ต้นทุนก็ต่ำ หัวหน้าแฮปปี้ บริษัทก็แฮปปี้… ถ้างั้นก็….
บ่นส่งท้าย
เขียนมายาวนาน (แต่ใช้เวลาเลือกภาพประกอบนานกว่า 555) ไม่นึกว่าจะยืดเยื้อขนาดนี้ แถมออกจะน่าเบื่อนิดนึง เพราะเทคนิคอลจ๋ามาก… ไม่รู้จิแทรกมุกกันตรงไหนเลยทีเดียว :-(
อย่างไรก็ตาม, ขอบคุณที่ติดตามอ่านกันมานะครับ รอพบกันในตอนต่อไป หากมีเรื่องไหนที่สงสัย หรืออยากให้อธิบายในส่วนของ Web Development กับ DevOps ก็สามารถ Request แนะนำเข้ามาได้เลยนะครับ ยินดีเสมอครับ :-)
ปล. ขอบคุณทุก Like, Share, Comment, Recommend จากใจจริงครับ เป็นกำลังใจในการเขียนมากๆ ครับ :-) _/\_
แก้ไขครั้งที่ 1: ตัดส่วน Redux ออกไป และใช้ React state ที่เป็นพื้นฐานของ React จริงๆ ส่วน Redux จะนำมาใช้ในกรณีเมื่อเว็บเรามีความซับซ้อนมากขึ้น จำนวนหน้าเพิ่มมากขึ้น และต้องการรักษา Single-Source-of-Truth ให้อยู่ในที่ที่เดียวครับ
Resources
- GitHub ของโปรเจคนี้
https://github.com/chaintng/pokedex-simple-search
ถ้าถูกใจบทความ ขอฝากกดไลค์ Facebook Page เตาะแตะต๊อกแต๊ก ทีนะคร๊าบบ https://www.facebook.com/rootusercc/ เป็นกำลังใจให้ผู้เขียนสุดพลัง :-)