Isomorphic คืออะไร ? + สอนวิธีทำด้วย React

Suranart Niamcome
SiamHTML
Published in
13 min readMay 24, 2015

หากใครตามข่าวในวงการเว็บหน่อยก็คงจะเคยได้ยินคำว่า Isomorphic JavaScript หรือที่สมัยนี้เปลี่ยนมาเรียกว่า Universal JavaScript กันมาบ้างใช่มั้ยล่ะครับ หรือถ้าเพิ่งเคยได้ยิน ก็ขอบอกเลยว่ามันกำลังมาแรงมากๆ ในตอนนี้ฮะ วันนี้ SiamHTML เลยจะมาเล่าสู่กันฟังซะหน่อยว่าเจ้า Isomorphic นี้มันคืออะไร รับรองเลยว่ามันเจ๋งสุดๆ ครับ

ทุกวันนี้เราทำเว็บกันอย่างไร ?

ก่อนอื่นเรามาดูกันว่าตั้งแต่อดีตจนถึงปัจจุบัน เราทำเว็บกันอย่างไร ?

  • Server เป็นคน Render ในยุคแรกๆ เราจะเอา business logic, route และ template ของหน้าต่างๆ ฝากไว้ที่ฝั่ง server ทั้งหมดครับ คือ server จะทำหน้าที่ render หน้าเว็บทั้งหน้าขึ้นมา แล้วถึงจะส่งหน้าเว็บที่สมบูรณ์แล้ว กลับไปยัง client ทั้งนี้ก็เพราะว่าเครื่อง client สมัยก่อนยังไม่ค่อยแรงเท่าไร ภาระส่วนใหญ่จึงตกอยู่ที่เครื่อง server ครับ
  • Client เป็นคน Render ในปัจจุบัน เครื่อง client เร็วขึ้นมากครับ เลยเกิดเทคนิคการทำเว็บรูปแบบใหม่ที่เรียกว่า Single-page application หรือ SPA ขึ้นมา โดยการทำเว็บแบบ SPA นี้ business logic, route และ template ของหน้าต่างๆ จะย้ายมาอยู่ฝั่ง client แทนครับ การ render หน้าเว็บก็จะทำที่ฝั่ง client เลย ไม่ต้องเสียเวลามาโหลดหน้าเว็บใหม่ทั้งหน้าเหมือนแต่ก่อน ส่วน server ก็จะเหลือหน้าที่เพียงอย่างเดียวนั่นก็คือการดึงข้อมูลจาก database มาให้ หรือพูดง่ายๆ ก็คือเอา server มาทำ API นั่นเองครับ
  • Server/Client ช่วยกัน Render สุดท้ายมาดูเทคนิคใหม่ล่าสุดที่รวมเอาข้อดีของ 2 วิธีแรก มารวมกันครับ คือวิธีนี้ตอนโหลดมาทีแรกจะให้ server เป็นคน render หน้าเว็บทั้งหน้ามาให้ก่อน แล้วหลังจากนั้น client จะเป็นคนรับหน้าที่ render ทั้งหมดแทนครับ ซึ่งการจะทำแบบนี้ได้ เราจะต้องอาศัยสิ่งที่เรียกว่า Isomorphic JavaScript เข้ามาช่วย

Isomorphic JavaScript คืออะไร ?

เวลาเราจะเขียนโค้ดสำหรับดึงค่า cookie มาใช้งาน สมมติว่าที่ฝั่ง server เราใช้ PHP แล้วกัน เราก็จะเขียนโค้ดแบบนึง ส่วนฝั่ง client เราใช้ JavaScript เราก็จะต้องเขียนโค้ดอีกแบบนึงถูกมั้ยครับ เพราะมันคนละภาษากัน syntax ก็ย่อมไม่เหมือนกันอยู่แล้ว ทีนี้ปัญหาคืออะไรครับ ? เราจะต้องมาทำงาน 2 ครั้ง ถูกมั้ยครับ หรืออย่างน้อยเราก็จะต้องเรียนรู้ 2 ภาษา ที่มีวิธีการเขียนที่ไม่เหมือนกัน ปัญหาเหล่านี้จะหมดไปหากเราใช้ Isomorphic JavaScript ครับ

คำว่า Isomorphic JavaScript นั้นมาจากคำว่า Isomorphic ที่แปลว่า “รูปแบบเดียวกัน” ครับ ซึ่งถ้าจะแปลในบริบทของการทำเว็บแล้ว Isomorphic JavaScript ก็จะหมายถึงรูปแบบการเขียนโค้ด JavaScript ที่เหมือนกันไม่ว่าจะเขียนที่ฝั่ง server หรือ ฝั่ง client ก็ตามครับ

เอ๊ะ! JavaScript มันสามารถเขียนที่ฝั่ง server ได้ด้วยหรอ ? ได้สิครับ ด้วยการใช้ Node.js นั่นเอง หากพูดให้เข้าใจง่ายๆ Isomorphic JavaScript ก็คือการใช้ Node.js ร่วมกับ template engine ที่สามารถ render ได้ทั้งที่ฝั่ง server และฝั่ง client ครับ วิธีนี้จะช่วยให้เราเขียน business logic, route รวมไปถึง template เพียงแค่ครั้งเดียวเท่านั้น ตอนโหลดครั้งแรก เราจะให้ server ทำหน้าที่ render หน้าเว็บให้ก่อน แล้วหลังจากนั้น พอ user กดดูข้อมูลอะไร เราก็จะส่งไม้ต่อให้ client ทำหน้าที่ render ข้อมูลส่วนนั้นครับ

ข้อดีของเว็บแบบ Isomorphic

ผมขอออกตัวนิดนึงนะครับว่าผมนี่เป็นแฟนของ Isomorhic เลย เพราะข้อดีของมันนี่เรียกว่าตอบโจทย์เว็บสมัยนี้ได้ดีจริงๆ

  • ใช้โค้ดร่วมกันทั้ง Server และ Client อย่างที่บอกครับว่าสิ่งที่เราทำก็แค่เขียน JavaScript ที่เราคุ้นเคยเป็นอย่างดี ไม่ต้องไปแตะภาษาอื่นๆ ที่เราไม่ค่อยคุ้นเคยเลย (ขอบอกว่า Front-end Developer ทุกคนสามารถทำได้แน่นอนครับ)
  • โหลดครั้งแรกไม่กระพริบ ใครทำ SPA บ่อยๆ จะรู้ดีครับว่า ตอนโหลดหน้าเว็บครั้งแรกมันจะชอบกระพริบๆ เพราะมันไม่ได้ render มาตั้งแต่ฝั่ง server ซึ่งอาการนี้จะไม่พบในเว็บแบบ Isomorphic แน่นอนครับ
  • SEO ประเด็นนี้สำคัญสุดๆ ครับ เพราะว่าเว็บแบบ SPA นั้น ไม่ค่อยดีต่อ SEO เท่าไร (จริงๆ ก็ทำให้ดีได้ แต่ค่อนข้างจะลำบาก) แต่ Isomorphic นั้นได้ SEO ไปเต็มๆ ครับ เพราะ server จะ render หน้าที่มีข้อมูลสมบูรณ์มาให้ตั้งแต่ตอนที่โหลดมาครั้งแรกแล้ว

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

พื้นฐานที่ควรรู้

มาดูกันครับว่าก่อนที่จะเริ่มลงมือทำเว็บแบบ Isomorphic เราควรจะมีพื้นฐานอะไรมาก่อนบ้าง

  • Node.js อันนี้ขาดไม่ได้เลยครับ เพราะเราจะต้องอาศัยมันในการใช้ JavaScript เป็นภาษาของฝั่ง server
  • Express + Jade / Handlebars ต่อมาเป็น framework ที่จะใช้กับ Node.js ครับ ซึ่งจริงๆ แล้วจะใช้หรือไม่ใช้ก็ได้ สำหรับบทความนี้ ผมจะขอใช้ Express แล้วกัน ส่วน template engine ที่จะใช้กับ Express นั้น จะใช้หรือไม่ใช้ก็แล้วแต่เลยครับ
  • Browserify / Webpack สิ่งสำคัญที่ขาดไม่ได้เลยก็คือ tool ที่จะใช้แปลง JavaScript Module แบบ CommonJS ให้สามารถรันบน web browser ได้ครับ สำหรับบทความนี้ผมจะขอใช้เป็น Webpack แล้วกันนะฮะ
  • React มาถึงพระเอกของงานนี้ครับ นั่นก็คือ template engine ที่สามารถ render ได้ทั้งฝั่ง server และฝั่ง client นั่นเอง ซึ่งจริงๆ แล้วในปัจจุบันมีให้เลือกใช้หลายท่าเลย แต่ในบทความนี้ ผมจะขอเลือกใช้เป็น React แล้วกันนะฮะ (จริงๆ แล้ว React นั้นไม่ใช่ template engine นะครับ เพียงแต่เราจะอาศัยความสามารถส่วนหนึ่งของมันมาเป็น template engine เท่านั้นเอง)
  • ECMAScript 2015 อันนี้ถึงแม้ว่าจะไม่จำเป็น แต่ผมแนะนำให้เรียนรู้ไว้ครับ เพราะโค้ดของ project ต่างๆ ที่เกี่ยวกับ Isomorphic ส่วนใหญ่จะเขียนด้วย ECMAScript 2015 หมดเลยฮะ

ถ้าใครเป็นทั้งหมดนี่มาก่อนแล้ว ผมว่าสบายแล้วล่ะครับ แต่ถ้าใครยังเป็นไม่ครบก็ลองไล่อ่านบทความที่ผมทำลิ้งค์ไว้ แล้วค่อยๆ ทำความเข้าใจไปก็ได้ฮะ อย่าใจร้อน เพราะขืนข้ามขั้นไปเล่น Isomorphic เลยนี่ ผมว่าคงจะงงจนอาจจะถึงขั้นท้อก่อนที่จะทำเป็นเลยก็ได้ครับ

ตกลงกันก่อน !

มาถึงตรงนี้ หากใครยังไม่เคลียร์ในประเด็นไหน ผมขอแนะนำให้ย้อนกลับไปอ่านอีกทีก่อนนะครับ เพราะหลังจากนี้ เราจะเริ่มลงมือเขียนโค้ดกันจริงๆ แล้ว ซึ่งตอนนั้นผมจะถือว่าพื้นฐานของเราแน่นแล้ว รายละเอียดเล็กๆ น้อยๆ ผมอาจจะไม่ได้อธิบาย เพราะไม่อยากให้บทความนี้ยาวเกินไปครับ (นี่ขนาดไม่อยากแล้วนะ -_-”)

เอาล่ะครับ เราจะมาเริ่มลงมือเขียนเว็บแบบ Isomorphic กัน ก่อนอื่นผมจะขอพูดถึงพระเอกของงานนี้อย่าง React ซักนิดนึง เพราะมันถือเป็นหัวใจสำคัญของ Isomorphic เลยทีเดียว

React คืออะไร ?

ในเว็บของ React เขียนเอาไว้ว่า React เป็น JavaScript Library ที่เอาไว้สร้าง User Interface ครับ ฟังดูแล้วเหมือน scope มันจะเล็กๆ เนอะ ว่ามั้ย คือเอาไว้ทำแค่พวก component บนหน้าเว็บอะไรทำนองนี้อะหรอ ? คำตอบคือใช่เลยครับ บางคนอาจจะนึกถึง Web Components หรือ Custom Directive ของ AngularJS แต่ขอบอกเลยว่า React มันเทพกว่านั้นเยอะ

จุดเด่นของ React

มาดูกันครับว่าสิ่งที่ทำให้ React ดูโดดเด่นกว่าชาวบ้านนั้นมีอะไรบ้าง

  • สร้างโดย Facebook ก่อนอื่นต้องขอบอกก่อนนะครับว่าคนทำ React นั้นไม่ใช่ใครที่ไหน Facebook นั่นเองครับ แล้วทุกวันนี้ React ก็ยังคงถูกใช้อยู่ทั้งใน Facebook และ Instagram ก็ลองคิดกันเอาเองนะครับว่ามันจะสุดยอดขนาดไหน
  • Render ได้ทั้ง 2 ฝั่ง ข้อนี้เอง ที่ทำให้ React ดูดีกว่า AngularJS ในแง่ของ SEO ครับ เนื่องจาก AngularJS นั้นจะทำกับ DOM ได้เพียงอย่างเดียว crawler จึงไม่สามารถ index เนื้อหาที่โหลดเข้ามาทีหลังได้ครับ
  • Virtual DOM ในส่วนของการ render ที่ฝั่ง client นั้น React ก็ยังดูดีกว่า AngularJS ตรงที่ React จะไม่ทำอะไรกับ DOM โดยตรง แต่ React จะสร้างสิ่งที่เรียกว่า Virtual DOM ขึ้นมา แล้วดูว่า DOM ที่มีอยู่เดิมกับ DOM ที่กำลังจะเปลี่ยนไปนั้นแตกต่างกันอย่างไร เมื่อหาความแตกต่างได้แล้ว React จึงค่อยไปแก้ DOM จริงๆ ซึ่งขั้นตอนที่ว่านี้อยู่ใน memory ทั้งหมดครับ เรียกได้ว่าการใช้ React ทำ DOM Manipulation นั้น เร็วกว่าการใช้ JavaScript แบบเพียวๆ ซะอีก
  • Unidirectional ในปัจจุบัน เรามักจะพบว่า JavaScript framework ส่วนใหญ่จะเป็นแบบ Two-way Data Binding ซึ่งมันก็คือการที่ data ใน model และ view จะเปลี่ยนแปลงตามกันและกัน แต่ React นั้นตรงกันข้ามครับ คือจะเป็นแบบ Unidirectional หรือแบบทางเดียวนั่นเอง เพราะ React มองว่า Two-way นั้นอาจทำให้เกิดการอัพเดทข้อมูลที่ไม่จำเป็นขึ้นมาได้ครับ

มาถึงตรงนี้ ผมว่าจะต้องมีคนมองว่าผมอวย React แน่ๆ เลย หรือมองว่าผมไม่ชอบ AngularJS หรือเปล่า ? ตอบเลยว่าไม่ใช่ครับ ทั้ง React และ AngularJS ต่างก็เป็น tool ที่ดีด้วยกันทั้งคู่ และไม่ได้เกิดมาเพื่อทดแทนกันครับ หากสงสัยว่า React นั้นมีข้อเสียอะไรบ้าง ? ขอให้อดใจรอนิดนึง เพราะเดี๋ยวเราจะได้เจอกับตัวตอนที่เขียนโค้ดในบทความนี้แหละครับ

Syntax ของ React

เล่ามาตั้งนาน มาดูวิธีการเขียนโค้ดของ React กันดีกว่าครับ ก่อนอื่นเราจะต้องเข้าใจก่อนว่า แนวคิดของ React นั้นก็คือ การมองทุกสิ่งทุกอย่างบนหน้าเว็บเป็น component หากมองดีๆ จะเห็นว่าหน้าเว็บนั้นก็จะมี root element อันนึง ภายในก็จะประกอบไปด้วยกล่องต่างๆ แล้วภายในกล่องต่างๆ นั้นก็อาจจะมีกล่องลูกๆ ซ้อนเข้าไปข้างในอีกที เจ้าพวกกล่องเหล่านี้แหละ ที่เราจะเรียกมันว่า component

Top-Level API

ก่อนจะเริ่มสร้าง component กัน เรามาดู api ที่เราจะต้องใช้กันแน่ๆ ครับ ว่ามันมีอะไรบ้าง

  • React.createClass เอาไว้สร้าง component ครับ เพราะจริงๆ แล้ว component มันก็คือ class ที่ประกอบไปด้วย method ต่างๆ นั่นเอง
  • React.createElement เอาไว้สร้าง ReactElement ขึ้นมาจาก component ที่เราสร้างครับ
  • ReactDOM.render เอาไว้ render ReactElement เข้าไปใน DOM ครับ เราจะใช้ method นี้ ในการ render ทางฝั่ง client
  • ReactDOMServer.renderToString เอาไว้ render ReactElement ออกมาเป็น string ครับ เราจะใช้ method นี้ ในการ render ทางฝั่ง server

api พวกนี้ เรายังไม่ต้องไปท่องจำนะครับว่าอันไหนเอาไว้ใช้ทำอะไรบ้าง ผมอยากให้เราเห็นภาพรวมคร่าวๆ ว่าเราจะต้องสร้าง component ของ react ขึ้นมาก่อน แล้วค่อยบอกให้ react มัน render component เหล่านั้น เข้าไปใน DOM ก็เท่านั้นเองฮะ

วิธีสร้าง Component แบบง่ายๆ

สมมติว่าเรามี container เปล่าๆ อันนึง ชื่อ div#content หากเราต้องการจะใส่ component ง่ายๆ อย่าง p.greet เข้าไปแบบนี้ เราจะต้องเขียนโค้ดอย่างไร ?

<body>
<div id="content">
<p class="greet">Hello, SiamHTML!</p>
</div>
</body>

จากโจทย์ด้านบน เราสามารถเขียนโค้ดด้วย React ได้แบบนี้ครับ

// โหลด React มาสร้าง component
var React = require('react');
// โหลด ReactDOM มา render component ใส่ DOM
var ReactDOM = require('react-dom');
// สร้าง component ชื่อ Greet
var Greet = React.createClass({
// component นี้ แสดงผลอย่างไร ?
render: function() {
return (
// เป็น p.greet ที่มี text เป็น "Hello, SiamHTML!"
React.createElement(
'p',
{className: "greet"},
"Hello, SiamHTML!"
)
);
}
});
// แสดงผล component ชื่อ Greet ข้างใน #content
ReactDOM.render(
React.createElement(Greet, null),
document.getElementById('content')
);
จะเห็นว่าเราใช้ React.createClass ในการสร้าง component ขึ้นมาครับ ซึ่งภายในก็จะเป็น object ที่จะเอาไว้ใช้ในการอธิบายว่า component นี้มีลักษณะอย่างไร สังเกตนะครับว่าผมจำเป็นต้องใส่ method render เอาไว้ด้วย ไม่งั้น React มันจะไม่รู้ว่าจะแสดงผล component นี้ออกมาอย่างไร
แม่เจ้า! โจทย์ง่ายๆ แค่นี้ React ล่อไปกี่บรรทัดเนี่ย ใช้ jQuery เขียนบรรทัดเดียวจบเลยนะ! อย่าเพิ่งบ่นไปครับ เพราะคนทำ React เค้าเตรียมวิธีแก้ปัญหานี้มาแล้ว ด้วยสิ่งที่เรียกว่า JSXรู้จักกับ JSX Syntaxจากโจทย์เดิมครับ เราจะเปลี่ยนมาสร้าง component โดยใช้ syntax แบบใหม่ที่เรียกว่า JSX แทน ลองดูโค้ดด้านล่างนี้ฮะvar Greet = React.createClass({
render: function() {
return (
// เขียนแบบ JSX
<p className="greet">Hello, SiamHTML!</p>
);
}
});
// เขียนแบบ JSX
ReactDOM.render(<Greet />, document.getElementById('content'));
เห็นมั้ยครับว่าโค้ดของเราจะสั้นลง และอ่านง่ายขึ้นเยอะเลย จะเห็นว่าการเขียนด้วย JavaScript ด้วย JSX นั้น คล้ายกับการเขียน XML มากเลยครับ จะต่างเล็กน้อยก็ตรงที่การระบุ class นั้น เราจะต้องเปลี่ยนมาใช้ className แทนเท่านั้นเอง
ทีนี้ลองมาดูตัวอย่าง component ที่ซับซ้อนขึ้นอีกนิดนึงครับ สมมติเราจะสร้าง component ใหม่ที่ชื่อ <Header /> แล้วข้างในมี component ลูก อยู่ 2 ตัว คือ <Menu /> และ <Search /> เราก็จะเขียนโค้ดแบบนี้ครับvar Menu = React.createClass({
render: function() {
return (
<ul>
<li>Menu 1</li>
<li>Menu 2</li>
<li>Menu 3</li>
</ul>
);
}
});
var Search = React.createClass({
render: function() {
return (
<form>
<input type="text" value="Search" />
<input type="submit" value="Submit" />
</form>
);
}
});
var Header = React.createClass({
render: function() {
return (
<header>
<Menu />
<Search />
</header>
);
}
});
ReactDOM.render(<Header />, document.getElementById('content'));
JSX ช่วยให้อะไรๆ ง่ายขึ้นเยอะเลยใช่มั้ยล่ะครับ มาถึงตรงนี้ เราคงพอจะเห็นภาพแล้วว่า การใช้ React สร้าง UI ของหน้าเว็บ ก็คือการแบ่งของในหน้าเว็บออกเป็น component ย่อยๆ ก่อน แล้วค่อยจัดกลุ่ม component ย่อยๆ เหล่านั้นให้กลายเป็น component ที่ใหญ่ขึ้น เมื่อ component ใหญ่ขึ้นเรื่อยๆ สุดท้ายแล้ว มันก็จะกลายเป็นหน้าเว็บที่สมบูรณ์ครับ
ส่งต่อข้อมูลไปยัง Component อื่นด้วย Propsในการทำเว็บจริงๆ คงไม่มีใครใส่ข้อมูลลงไปใน template ตั้งแต่แรกถูกมั้ยครับ เรามักจะใช้วิธี assign ข้อมูลเหล่านั้น เข้าไปใน template แทน การใช้ React ก็เหมือนกันครับ สมมติเราต้องการจะส่งค่าอะไรให้กับ component เราสามารถเขียนแบบนี้ได้เลยฮะReactDOM.render(<Greet name="SiamHTML" />, document.getElementById('content'));แบบนี้จะเป็นการส่ง props ชื่อ name ที่มีค่าเป็น "SiamHTML" ไปให้กับ component ที่ชื่อ Greet ครับ ทีนี้ หากเราอยากจะดึงข้อมูลที่ได้รับ มาแสดง ให้เราใช้ this.props แล้วตามด้วยชื่อ props แบบนี้ได้เลยครับvar Greet = React.createClass({
render: function() {
return (
// ดึงค่าของ property ชื่อ name มาใช้
<p className="greet">Hello, {this.props.name}!</p>
);
}
});
ผลลัพธ์ที่ได้ก็จะออกมาเป็น "Hello, SiamHTML!" ครับ

แชร์ข้อมูลภายใน Component ด้วย State

แต่บางที แค่ property อย่างเดียวอาจจะทำอะไรได้ไม่มากนะครับ เพราะพอเรา assign ค่าอะไรให้ component แล้ว พอมัน render เสร็จ มันก็จะอยู่นิ่งๆ แบบนั้นไปตลอด หากเราอยากจะทำให้ component มี interaction ใดๆ เราจะต้องอาศัยสิ่งที่เรียกว่า state เข้ามาช่วยครับสำหรับผมแล้ว state นี่ถือเป็นจุดขายของ React เลยนะครับ หน้าที่ของมันก็คือการเก็บ "สภาพ" ของ component ในขณะนั้นๆ เอาไว้ เมื่อไรก็ตามที่ state มีค่าเปลี่ยนไป เมื่อนั้น method render จะถูกสั่งให้ทำงานทันทีครับ พูดง่ายๆ ก็คือ หากเราต้องการจะอัพเดทหน้าตาของ component ไหน ก็ให้เราไปเปลี่ยน state ของมันนั่นแหละครับ เพื่อให้เห็นภาพมากขึ้น ลองมาดูโค้ดสร้าง component ที่เป็นปุ่ม Like กันครับvar LikeButton = React.createClass({
// method นี้ เอาไว้กำหนด state เริ่มต้น
getInitialState: function() {
return {liked: false};
},
// เพิ่ม custom method ชื่อ handleClick
handleClick: function(event) {
// เปลี่ยน state "liked" ให้มีค่าตรงข้ามกับค่า liked เดิม
this.setState({liked: !this.state.liked});
},
render: function() {
var text = this.state.liked ? 'Unike' : 'Like';
return (
// เมื่อถูก click ให้รัน method handleClick
<button onClick={this.handleClick}>
{text}
</button>
);
}
});
จะเห็นว่าที่ handleClick นั้น เราไม่ต้องไปสั่งให้มัน render ใหม่นะครับ เพราะเมื่อไรที่ state เปลี่ยน component ก็จะถูก render ใหม่โดยอัตโนมัติอยู่แล้วครับ

Lifecycle Methods ของ Component

สุดท้ายเรามาดู Lifecycle ของ component กันหน่อยครับว่า ตั้งแต่ component ถูกสร้างขึ้นมา จนถึงตอนที่มันถูกถอดออกจากหน้าเว็บนั้น มันเกิดอะไรขึ้นบ้าง
  • componentWillMount เกิดขึ้นก่อนที่ component จะถูก render ขึ้นมา
  • componentDidMount เกิดขึ้นหลังจากที่ component ได้ถูก render แล้ว
  • componentWillReceiveProps เกิดขึ้นหลังจากที่ component ได้รับ props ค่าใหม่
  • componentWillUpdate เกิดขึ้นก่อนที่ component จะถูก render ใหม่ หลังจากที่ได้รับ props ใหม่
  • componentDidUpdate เกิดขึ้นหลังที่จาก component ถูก render ใหม่
  • componentWillUnmount เกิดขึ้นก่อนที่ component จะถูกเอาออกจาก DOM
สาเหตุที่เราต้องรู้ Lifecycle ของ component ก็เพราะว่าเราสามารถนำ Lifecycle เหล่านี้มาใส่เป็น method ให้กับ component ได้ครับ ลองดูตัวอย่างโค้ดด้านล่างนี้var Timer = React.createClass({
// กำหนด state ชื่อ secondsElapsed ให้มีค่าเป็น 0
getInitialState: function() {
return {secondsElapsed: 0};
},
// custom method สำหรับอัพเดท state ชื่อ secondsElapsed
tick: function() {
this.setState({secondsElapsed: this.state.secondsElapsed + 1});
},
// หลังจาก render แล้ว ให้ทำอะไร ?
componentDidMount: function() {
this.interval = setInterval(this.tick, 1000);
},
// ก่อนจะลบออกจาก DOM ให้ทำอะไร ?
componentWillUnmount: function() {
clearInterval(this.interval);
},
// แสดงผลออกมาอย่างไร ?
render: function() {
return (
<div>Seconds Elapsed: {this.state.secondsElapsed}</div>
);
}
});
ก็น่าจะพอเห็นประโยชน์ของ Lifecycle methods กันบ้างแล้วใช่มั้ยครับ ทีนี้เราลองมาดูวิธีการนำ React ไปใช้งานจริงกันดีกว่าครับ

วิธีนำ React มาใช้งาน

สำหรับการใช้งาน React นั้นจะแบ่งออกเป็น 2 วิธีด้วยกัน ดังนี้ครับ

1. ใช้ที่ฝั่ง Client

แบบแรกจะเป็นการเอา React มาใช้สร้าง UI ที่ฝั่ง client ครับ วิธีใช้ก็ง่ายๆ ให้เราไปดาวน์โหลด React มาก่อน หรือจะใช้จากเว็บของ facebook ไปก่อนก็ได้ฮะ จากนั้นก็ติด script ของ React เข้าไปแบบนี้ได้เลย<!DOCTYPE html>
<html>
<head>
<script src="https://fb.me/react-0.14.3.min.js"></script>
<script src="https://fb.me/react-dom-0.14.3.min.js"></script>
</head>
<body>
<div id="content"></div>
</body>
</html>
สังเกตนะครับว่าเราจะต้องติด script ของ React ถึง 2 ตัว ด้วยกัน คือตัวแรกจะเป็น core ของ React เลย ส่วนตัวที่สองจะเป็น script ที่ดูแลในส่วนของการ render ใส่ DOM ของหน้าเว็บครับ แล้วถ้าเราเขียน React โดยใช้ JSX ด้วย เราก็จะต้องเพิ่ม script ที่ชื่อ Babel ที่มีความสามารถในการแปลง JSX เข้ามาอีกตัวครับ ไม่งั้นโค้ดที่เราเขียนจะพังเพราะ web browser มันยังไม่รู้จัก JSX นั่นเอง<head>
<script src="https://fb.me/react-0.14.3.min.js"></script>
<script src="https://fb.me/react-dom-0.14.3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>
</head>
ส่วนโค้ดหลักของเรา ก็แยกไปเขียนอีกไฟล์ก็ได้ครับ แล้วค่อยใส่เข้ามาต่อท้าย container หลักของเราแบบนี้<body>
<div id="content"></div>
<script type="text/babel" src="siamhtml.js"></script>
</body>
สังเกตนะครับว่า เราจะต้องใส่ type ของ script ให้เป็น text/babel ด้วย เพราะ Babel จะแปลงให้เฉพาะ script ที่เป็น type นี้เท่านั้นครับ เพียงเท่านี้ เราก็พร้อมที่จะรันโค้ดที่เขียนด้วย React แล้วล่ะครับ

2. ใช้ที่ฝั่ง Server

ส่วนแบบที่สองจะเป็นการเอา React มาใช้สร้าง UI ด้วยภาษาฝั่ง server อย่าง Node.js ครับ วิธีนี้จะค่อนข้างซับซ้อนกว่าวิธีแรกนิดนึง เพราะเราจะต้องเปลี่ยนมาใช้ React แบบที่เป็นโมดูล CommonJS แทน// ไฟล์ SiamHTML.jsx// โหลด React ที่เป็นโมดูลแบบ CommonJS มาใช้งาน
var React = require('react');
// ใช้ React สร้าง component
var SiamHTML = React.createClass({
render: function() {
return (
<p>SiamHTML</p>
);
}
});
// export component นี้ ออกเป็น module
module.exports = SiamHTML;
ส่วนฝั่ง server ก็ให้ใช้ ReactDOMServer ในการ render component ออกมาเป็น string ครับ
// ไฟล์ app.js// โค้ดสำหรับสร้าง HTTP server
...
// โหลด React มาใช้งาน
var React = require('react');
// โหลด ReactDOMServer มา render component ให้เป็น String
var ReactDOMServer = require('react-dom/server');
// โหลด component ที่ชื่อ SiamHTML มาใช้
var SiamHTML = require('./components/SiamHTML.jsx');
// โค้ดสำหรับ render component ที่ใช้กับฝั่ง server
app.get('*', function(req, res) {
res.send(ReactDOMServer.renderToString(React.createElement(SiamHTML)));
});
มาถึงตรงนี้ บางคนอาจจะสงสัยว่า ถ้าจะทำ Isomorphic มันก็ต้อง render ได้ทั้ง 2 ฝั่งเลยสิ แล้วเราจะใช้วิธีไหนล่ะ ?
Workshop - Setup Isomorphicเอาล่ะครับ เมื่อเรารู้พื้นฐานต่างๆ ครบแล้ว เรามาเริ่มเขียน Isomorphic กันเลยดีกว่า*** เนื่องจากวิธีการทำเว็บแบบ Isomorphic นั้นยังไม่นิ่ง หลายๆ dependencies ที่เราจำเป็นต้องใช้ ก็ยังมีการพัฒนาอยู่เรื่อยๆ บางทีถึงขั้นเปลี่ยน syntax ไปเลยก็มีเยอะฮะ ผมเลยต้องขออภัยล่วงหน้าก่อนเลย หากตัวอย่างโค้ดในบทความนี้ เพื่อนๆ นำไปรันแล้วมันไม่ผ่าน เพราะบางทีอัพเดทโค้ดตามไม่ทันจริงๆ ครับ แต่โดยรวมๆ แล้ว หลักการในบทความนี้ยังคงใช้งานได้เหมือนเดิมนะฮะ เพียงแต่เราจะต้องไปปรับ syntax ให้มันอัพเดทหน่อยเท่านั้นเอง ระหว่างนี้ เจอ error อะไร ก็ขอให้เพื่อนๆ ไปไล่อ่าน doc ของตัวที่มันเกิด error ที่เว็บหลักของมันไปก่อนแล้วกันนะครับ ยังไงผมจะมาไล่อัพเดทเนื้อหาในบทความนี้อยู่เรื่อยๆ ฮะ T__Tเตรียมไฟล์และโฟลเดอร์เริ่มด้วยการจัดโครงสร้างของเว็บแอปเราครับ ให้เราสร้างไฟล์และโฟลเดอร์ขึ้นมาตามนี้ได้เลยmyApp/
|
|-- public/
| |
| |-- images/ # เก็บรูปต่างๆ ที่จะใช้ในเว็บ
| |
| `-- js/
| |
| `-- bundle.js # ไฟล์ bundle ที่ได้จากการรัน webpack
|
|-- src/
| |
| |-- js/
| | |
| | |-- components/ # เก็บ component ทั้งหมด (.jsx)
| | |
| | |-- client.js # โค้ดสำหรับ render ฝั่ง client (เป็น entry point ของ webpack)
| | |
| | |-- router.jsx # middleware สำหรับทำ routing
| | |
| | |-- routes.jsx # เก็บ routes ทั้งหมดของแอป
| | |
| | `-- server.js # โค้ดสำหรับ render ฝั่ง server
| |
| `-- templates/ # เก็บ template ต่างๆ ของแอป
|
|-- .babelrc # ไฟล์กำหนด option ของ Babel
|
|-- app.js # ไฟล์ entry point ของฝั่ง server (รันด้วย Node.js)
|
|-- package.json # ระบุ dependency ที่จะใช้
|
`-- webpack.config.js # ไฟล์ config ของ webpack
ก่อนไปต่อผมอยากให้ทำความเข้าใจกับโครงสร้างนี้ให้ดีๆ นะครับ เพราะถ้าเราเข้าใจมันแล้ว ขั้นตอนต่อๆ ไปก็จะง่ายขึ้นเยอะเลยล่ะครับ

ติดตั้ง Babel

เนื่องจากสมัยนี้ เราไม่ได้เขียน JavaScript กันแบบเพียวๆ แล้ว เราเลยจะต้องติดตั้ง Babel มาช่วยแปลงโค้ดของเราให้กลับมาเป็นโค้ด JavaScript แบบธรรมดาฮะnpm install babel babel-register --save-devก่อนหน้านี้พอติดตั้ง Babel เสร็จแล้ว มันจะพร้อมใช้งานได้ทันทีฮะ แต่ตอนนี้ไม่ได้แล้วครับ เพราะเราจะต้องบอก Babel ก่อนว่าอยากจะให้ Babel ช่วยแปลงอะไรบ้าง สำหรับโปรเจคนี้ เราจะมีการใช้ JSX และ ECMAScript 2015 ครับ ให้เราติดตั้ง preset ของ Babel ตามนี้ได้เลยnpm install babel-preset-es2015 babel-preset-react --save-devจากนั้นให้เราไปสร้างไฟล์ .babelrc ที่จะเอาไว้กำหนด option ต่างๆ ให้กับ Babel แล้วใส่ preset ที่เราโหลดมาเมื่อกี้ลงไป{
"presets": ["es2015", "react"]
}
เพียงเท่านี้ Babel ก็พร้อมใช้งานแล้วล่ะครับ ^0^

สร้าง Entry point ของฝั่ง Server

เริ่มแรกให้เราเข้าไปดูที่ไฟล์ app.js ซึ่งจะเป็นไฟล์ที่เราเอาไว้ start app Node.js ของเราฮะ ให้เราใส่โค้ดด้านล่างนี้ลงไป// ให้ Babel ช่วยแปลง ECMAScript 2015 และ JSX
require('babel-register');
// โหลดไฟล์ที่เอาไว้ใช้ start server
require('./src/js/server.js');
จะเห็นว่าไฟล์นี้ เราแค่จะเอาไว้เรียก Babel มาใช้งานเท่านั้นเองฮะ ทีนี้เราก็จะสามารถเขียนโค้ดด้วย JSX และ ECMAScript 2015 ได้แล้วล่ะครับ
ใส่โค้ดสำหรับ Render ฝั่ง Serverต่อมาไปดูที่ไฟล์ server.js ครับ ไฟล์นี้เราจะเอาไว้สร้าง http server ขึ้นมาเพื่อรับ request จาก client ให้เราใส่โค้ดสำหรับสร้าง http server ขึ้นมาตามปกติ โดยบทความนี้ ผมจะขอใช้ Express เป็น framework สำหรับ Node.js และใช้ Handlebars เป็น template engine ครับ ให้เราติดตั้ง dependency ที่จำเป็นตามด้านล่างนี้npm install express express-handlebars serve-favicon --save-devจากนั้นก็ใส่โค้ดสำหรับ create server ด้วย Express ลงไปimport path from 'path';
import express from 'express';
import exphbs from 'express-handlebars';
import favicon from 'serve-favicon';
var app = express();
app.use(favicon(path.join(__dirname, '../../public/images/favicon.ico')));
app.use(express.static('public'));
app.engine('handlebars', exphbs());
app.set('view engine', 'handlebars');
app.set('views', path.join(__dirname, '../templates'));

var server = app.listen(3000, function() {
var host = server.address().address;
var port = server.address().port;
console.log('listening at http://%s:%s', host, port);
});
app.get('*', function(req, res){
res.render("index.handlebars", {
markup: 'Hello, SiamHTML!'
});
});
จะเห็นว่าเป็นโค้ดธรรมดาๆ ที่เราน่าจะคุ้นเคยกันดีครับ ทีนี้มาต่อกันที่ไฟล์ index.handlebars ที่เราจะใช้เป็น template หลักกันบ้างครับ ให้เราใส่โค้ดด้านล่างนี้ลงไป
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Isomorphic with React Tutorial by SiamHTML</title>
</head>
<body>
<div id="content">{{{markup}}}</div>
</body>
</html>
เสร็จแล้วก็ลองรันดูเลยครับ เราก็จะได้หน้าเว็บที่มีคำว่า "Hello, SiamHTML!" แสดงออกมาทีนี้เราจะเปลี่ยนการ assign ค่า markup จากการยัด string ลงไปตรงๆ มาเป็นการ render ด้วย component ของ React กันบ้างครับ ก่อนอื่นให้เราสร้าง component ที่ง่ายสุดๆ ขึ้นมาสักอัน แล้วตั้งชื่อไฟล์ว่า App.jsx ครับvar React = require('react');var App = React.createClass({
render: function () {
return (
<div>
Hello, SiamHTML!
</div>
);
}
});
module.exports = App;
แต่อย่างที่บอกครับ สมัยนี้เค้านิยมเขียน ECMSScript 2015 กัน งั้นเราก็ตามเทรนด์ไปหน่อยละกันฮะ เราก็จะได้โค้ดสำหรับสร้าง <App /> ที่มีหน้าตาแบบนี้
import React from 'react';

class App extends React.Component {
render() {
return (
<div>
Hello, SiamHTML!
</div>
);
}
}
export default App;
จะเห็นว่ามันแก้แค่นิดเดียวเองฮะ เอาเป็นว่าต่อจากนี้ไป ผมจะเขียน ECMAScript 2015 ตลอดเลยละกันนะครับ ใครยังไม่คุ้นกับ syntax ก็ลองไปอ่านทบทวนกันดูได้ฮะ
พอเราจะเปลี่ยนมาใช้ React เราก็จะต้องแก้โค้ดในส่วนของการ render ที่ไฟล์ server.js นิดนึงครับ แต่อย่าลืมนะครับว่าการ render ที่ฝั่ง server นั้น เราจะต้องอาศัย ReactDOMServer ด้วย ให้เราติดตั้ง dependency เพิ่ม ตามด้านล่างนี้ฮะnpm install react react-dom --save-devจากนั้นก็เข้าไปแก้ไฟล์ server.js ตรงส่วนของ routing นิดนึงครับ// โค้ดสำหรับสร้าง HTTP server...import React from 'react';
import ReactDOMServer from 'react-dom/server';
import App from './components/App.jsx';
app.get('*', function(req, res){
res.render("index.handlebars", {
markup: ReactDOMServer.renderToString(React.createElement(App))
});
});
เมื่อลองรันดูอีกที ก็จะพบว่าผลลัพธ์ยังคงเหมือนเดิมเลยครับ เพียงแต่ตอนนี้เราเปลี่ยนมา render ด้วย React แล้ว
ทำ Routingเมื่อ render ด้วย server ได้แล้ว ต่อมาก็จะเป็นการทำ routing ครับ ซึ่งในการทำ Isomorphic นั้น เราจะไม่ใช้ routing ของ Express นะครับ เพราะว่ามันไม่สามารถแชร์โค้ดกับฝั่ง client ได้ วิธีแก้ก็คือการใช้โมดูลที่มีชื่อว่า react-router ครับnpm install react-router --save-devจากนั้นให้เราสร้างไฟล์ src/js/routes.jsx แล้วใส่โค้ดด้านล่างนี้ลงไปimport React from 'react';// โหลดความสามารถของ react-router มาใช้งาน
import { Route, IndexRoute } from 'react-router';
// โหลด component ต่างๆ
import App from './components/App.jsx';
import Home from './components/Home.jsx';
import About from './components/About.jsx';
// ระบุว่า path นี้จะถูก handle ด้วย component ไหน
export default (
<Route path="/" component={App}>
<IndexRoute component={Home}/>
<Route path="/about" component={About}/>
</Route>
);
หากไล่โค้ดดู ก็คงจะเข้าใจได้ไม่ยากครับ ข้อสังเกตก็คือ โค้ด JSX ในส่วนของการกำหนด route นั้นสามารถซ้อนต่อกันไปได้เรื่อยๆ เลยนะครับ หาก path ของ <Route /> ไหน match สำเร็จ component ของ <Route /> เหล่านั้น ก็จะถูกนำมา render ตรงตามระดับที่เราซ้อนกันไว้ครับ จากโค้ดด้านบน หาก path เป็น /about สิ่งที่ render ออกมาก็จะเป็น <App /> ที่มี <About /> อยู่ข้างในครับ
ต่อมาให้เราเตรียม component ของแต่ละ route มาให้พร้อมเลยครับ ไฮไลท์นั้นอยู่ที่ไฟล์ App.jsx ครับ เพราะมันเป็น component ระดับสูงสุดของเรา ให้เราลบโค้ดเดิมใน App.jsx ออกให้หมด แล้วเปลี่ยนมาใช้โค้ดด้านล่างนี้แทนครับimport React from 'react';// โหลดความสามารถของ react-router มาใช้งาน
import { Link } from 'react-router';

class App extends React.Component {
// ใส่ link ไปยังหน้า Home และ About
render() {
return (
<div>
<header>
<ul>
<li>
<a href="/">Home</a>
</li>
<li>
<a href="/about">About</a>
</li>
</ul>
</header>
{this.props.children}
</div>
);
}
}
export default App;
สังเกตนะครับว่าที่ <App /> จะมีการใส่ props ที่ชื่อ children เข้ามาด้วย ซึ่งเจ้า props นี้เอง ที่เราจะเอาไว้ render component ลูก ที่ match กับ route ที่เราได้กำหนดไว้ฮะ อย่างที่ได้บอกไปว่าถ้า path เป็น /about react มันก็จะ render <About /> ลงไปยังตำแหน่งที่ this.props.children วางอยู่นั่นเองฮะ
มาดูที่ component ของหน้า Home กันบ้างครับ ให้เราสร้างเป็น component ง่ายๆ ไปก่อนแบบนี้ฮะimport React from 'react';class Home extends React.Component {
render() {
return (
<div>
Hello, SiamHTML!
</div>
);
}
}
export default Home;
ส่วน component ขอหน้า About ก็เหมือนกันครับ ให้เราสร้างขึ้นมาแบบง่ายๆ เอาแค่กำหนดข้อความที่จะแสดงให้ตรงกับชื่อ component ก็ได้ครับ
เมื่อสร้าง route และ component ต่างๆ เสร็จทั้งหมดแล้ว ให้เราสร้างไฟล์ src/js/router.jsx ขึ้นมาเพื่อเป็น middleware สำหรับทำ routing ฝั่ง server ครับ ให้เราใส่โค้ดด้านล่างนี้ลงไปimport React from 'react';
import ReactDOM from 'react-dom/server';
import { RoutingContext, match } from 'react-router'
import routes from './routes.jsx';export default function(req, res) {
match({ routes, location:req.url }, (error, redirectLocation, renderProps) => {
res.render("index.handlebars", {
markup: ReactDOM.renderToString(<RoutingContext {...renderProps} />)
});
});
}
จากนั้นให้เรากลับไปที่ไฟล์ server.js เพื่อถอดโค้ด routing เดิม ที่ทำด้วย Express ออก แล้วเพิ่มโค้ดสำหรับเรียกใช้ middleware ที่เราเอาไว้คุยกับ react-router ลงไปแทน
// โค้ดสำหรับสร้าง HTTP server

...
import router from './router.jsx';app.use(router);
เสร็จแล้วก็ลองรันดูเลยครับ ให้เราลองกดลิ้งค์ต่างๆ ดู ก็จะพบว่าหน้าเว็บสามารถ render ได้ตรงกับที่เราได้กำหนดไว้ใน routes.jsx แล้วล่ะครับ แถมพอกด view source ดูแล้วก็ยังพบว่าเนื้อหาของแต่ละ component นั้น ถูก render ออกมาด้วย ซึ่งเหมาะกับการทำ SEO สุดๆ ขั้นตอนต่อไป เราจะมาแปลงเว็บแอปของเราให้ได้ feel แบบ SPA ครับ
ใส่โค้ดสำหรับ Render ฝั่ง Clientทีนี้เราจะมาทำให้เว็บแอปของเราให้สามารถ render ที่ฝั่ง client ได้ด้วยครับ พูดง่ายๆ ก็คือ เวลาเรากดลิ้งค์ไปหน้าอื่นแล้ว มันจะไม่โหลดใหม่ทั้งหน้าแล้ว แต่จะโหลดมาเฉพาะเนื้อหาส่วนที่เปลี่ยนไปเท่านั้น วิธีทำก็ไม่ยากเลยครับ ให้เราสร้างไฟล์ src/js/client.js แล้วใส่โค้ดด้านล่างนี้ลงไป// โหลด React มาใช้งาน
import React from 'react'
import ReactDOM from 'react-dom';
// โหลดความสามารถของ react-router มาใช้งาน
import { Router } from 'react-router';
import createBrowserHistory from 'history/lib/createBrowserHistory';
// โหลด route ต่างๆ ที่เราได้กำหนดไว้
import routes from './routes.jsx';
// render ลงไปใน DOM ที่ #content
ReactDOM.render(
<Router routes={routes} history={createBrowserHistory()} />,
document.getElementById('content')
);
หากลองไล่โค้ดดูจะเห็นว่าหลักการจะคล้ายๆ กับการใช้ react-router ที่ทางฝั่ง server เลยครับ แต่สังเกตนะครับว่าเราจะใช้ ReactDOM.render() แทน ไม่ได้ใช้ ReactDOMServer.renderToString เหมือนกับทางฝั่ง server แล้ว เนื่องจากครั้งนี้ เราจะ render เข้าไปใน DOM เลยครับ ส่วนสาเหตุที่ต้องมีการโหลด createBrowserHistory มาใช้งานด้วยก็เพราะเราต้องการจะลบ # ออกจาก url เวลามีการเปลี่ยนหน้านั่นเองฮะ
จากนั้น ให้เราเข้าไปแก้ไฟล์ src/js/components/App.jsx นิดนึงครับ ในส่วนของ <header> ให้เราแก้เป็นแบบนี้<ul>
<li><Link to='/'>Home</Link></li>
<li><Link to='/about'>About</Link></li>
</ul>
จะเห็นว่าเราต้องแก้ลิ้งค์จากเดิมที่ใช้ <a /> มาเป็น <Link /> แทนครับ เพื่อเป็นการบอกว่าลิ้งค์นี้ เป็นลิ้งค์ที่เราต้องการจะให้ render ด้วยฝั่ง client นะ ส่วนจะลิ้งค์ไปไหนนั้น เราจะกำหนดด้วย props ที่ชื่อว่า to ซึ่งค่าที่กำหนดเข้าไปนั้นก็คือ path ของ Route ที่เราได้กำหนดเอาไว้ในไฟล์ src/js/routes.jsx นั่นเองฮะ

รวมไฟล์ JavaScript ด้วย Webpack

มาถึงตรงนี้ จะเห็นว่าการ render ของทั้ง 2 ฝั่ง นั้น มีการแชร์โค้ดกันถูกมั้ยครับ ทั้งส่วนของ route และส่วนของ component เลย ซึ่งการจะแชร์กันได้นั้น เราจะต้องเขียน JavaScript เป็นโมดูลแบบ CommonJS ครับ สำหรับฝั่ง server นั้นสบาย เพราะ Node.js รองรับโมดูลแบบนี้อยู่แล้ว แต่ฝั่ง client มันไม่รู้จักหรอกครับ เราเลยจะต้องแปลงโค้ดทั้งหมดที่เราเขียนให้สามารถรันบน browser ได้ซะก่อน ซึ่ง tool ที่ผมชอบใช้นั้นจะเป็น webpack ครับ ให้เราติดตั้งโมดูลเพิ่มตามด้านล่างนี้npm install webpack -g
npm install webpack babel-loader --save-dev
จากนั้นให้เราสร้างไฟล์ webpack.config.js ขึ้นมา แล้วใส่โค้ดด้านล่างนี้ลงไปvar path = require('path');
var webpack = require('webpack');
module.exports = {// ให้ webpack เริ่มรวมโค้ดที่ไฟล์ client.js
entry: path.resolve(__dirname, 'src/js/client.js'),
// แล้วตั้งชื่อไฟล์ output ว่า bundle.js
output: {
path: path.resolve(__dirname, 'public/js'),
filename: 'bundle.js'
},
// อ่านไฟล์นามสกุล .js, .jsx ด้วย Babel
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel-loader'
}
]
}
};
สิ่งสำคัญก็คือการกำหนด entry point ครับ ให้เราเลือกไปที่ไฟล์ src/js/client.js เพราะไฟล์นี้ถือเป็นจุดเริ่มต้นของโค้ดสำหรับ render ฝั่ง client (เราจะกำหนด entry point ไปที่ไฟล์เดียว เนื่องจากไฟล์นั้นมันจะไป import โมดูลอื่นๆ ต่อกันไปเป็นทอดๆ อยู่แล้ว)
จากนั้นก็เข้าไปเพิ่มโค้ดสำหรับโหลด bundle.js มาใช้ ที่ src/templates/index.handlebars ครับ<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Isomorphic with React Tutorial by SiamHTML</title>
</head>
<body>
<div id="content">{{{markup}}}</div>
<script src="/js/bundle.js"></script>
</body>
</html>
จากนั้นก็รัน webpack ได้เลยฮะwebpackเสร็จแล้วก็ลองรันหน้าเว็บดูอีกทีครับ เราจะพบว่าเวลากดลิ้งค์มันจะไม่โหลดใหม่ทั้งหน้าแล้ว ในที่สุดเราก็ทำสำเร็จครับ!

แนวทางการเขียน React

จาก workshop เมื่อกี้นี้ สิ่งที่เราทำไปมันก็แค่โครงสร้างของเว็บแอปแบบ Isomorphic เท่านั้นเองนะครับ T__T ซึ่งขั้นตอนหลังจากนี้ ถึงจะเป็นการเริ่มลงมือสร้าง component ที่เราต้องการจะใช้จริงๆ ขึ้นมาด้วย React ครับ แต่ก่อนจะเริ่มเขียน React เรามาดูวิธีคิดแบบใหม่ ที่เราจะต้องทำความเข้าใจให้ถ่องแท้กันดีกว่า

1. แบ่งหน้าเว็บออกเป็น Component ย่อยๆ ก่อน

ขั้นตอนแรก ให้เราพยายามมองหน้าเว็บออกเป็น component ย่อยๆ ให้ได้ก่อนครับ ส่วนจะใช้เกณฑ์อะไรในการดูว่าส่วนไหนเป็น component บ้าง ผมแนะนำให้แบ่งย่อยเข้าไปถึงระดับที่สิ่งนั้นสามารถอยู่ได้โดยอิสระ โดยที่ไม่ได้ขึ้นอยู่กับ component ใดๆ แล้วครับหากเขียน React มาถึงจุดๆ หนึ่ง เราจะมองออกครับว่าจริงๆ แล้ว เจ้า component มันก็คือ Class ดีๆ นี่เอง ตามหลักของ Single Responsibility Principle แล้ว Class ควรจะมีหน้าที่เพียงหนึ่งเดียวเท่านั้นครับ ดังนั้น ให้เราซอย component ให้เล็กลงเรื่อยๆ จนแต่ละ component เหลือเพียงหน้าที่เดียวเท่านั้น2. ประกอบ Component เหล่านั้น ให้แสดงผลออกมาได้พอได้ component มาทั้งหมดแล้ว ให้เราเขียนโค้ด React เพื่อประกอบ component เหล่านั้นเข้าด้วยกันครับ ในขั้นตอนนี้ เราจะใช้แต่ props เท่านั้นในการส่งข้อมูลให้กับ component ที่เป็น parent แล้วก็ให้มันส่งต่อไปยัง component ลูกไปเรื่อยๆ ส่วนข้อมูลที่จะส่งนั้น ขอให้ mock ขึ้นมาเองได้เลยครับ พอทำเสร็จแล้ว เราจะได้เว็บหน้าตาเหมือนจริงเลย เพียงแต่มันจะยังเป็นแค่ static version คือจะยังไม่มีการโต้ตอบใดๆ ครับ

3. ดูว่าควรเก็บอะไรไว้ใน State บ้าง

มาถึงขั้นตอนที่น่าจะยากที่สุดครับ นั่นก็คือการมองให้ออกว่าข้อมูลอะไรควรเก็บไว้ใน state บ้าง มีหลักง่ายๆ ก็คือ ถ้าข้อมูลมีลักษณะแบบนี้ เราจะไม่เก็บไว้ใน state ครับ
  • ถ้า parent สามารถส่งมาให้ได้
  • ถ้าข้อมูลนั้นไม่ได้เปลี่ยนแปลงไปตามกาลเวลา
  • ถ้าข้อมูลนั้นสามารถคำนวณได้จาก state หรือ props อื่นๆ
บางคนอาจสงสัยว่าทำไมเราต้องมาคิดด้วยว่าอะไรไม่ควรอยู่ใน state เก็บๆ ไปเลยไม่ได้หรอ ? คำตอบคือมันมีผลต่อ performance อะครับ เราควรจะเก็บเฉพาะข้อมูลที่มีขนาดเล็กมากๆ เท่านั้น เพราะหน้าที่จริงๆ ของ state ก็คือการเอาไว้ switch UI ตาม event ต่างๆ ที่เกิดขึ้นเท่านั้นเองครับ

4. ดูว่าควรใส่ State ให้กับ Component ไหน

ตอนนี้เรารู้แล้วว่าข้อมูลอะไร ที่ต้องเก็บไว้ใน state บ้าง ต่อมาเราจะต้องมองให้ออกครับว่าเราควรจะกำหนด state ให้กับ component ไหน ขั้นตอนนี้ก็ยากมากเหมือนกันครับหากเราเริ่มเขียน React ใหม่ๆ แต่เราก็มีหลักง่ายๆ ดังนี้ครับ
  • ดูว่ามี component ไหนบ้าง ที่การ render ของมันขึ้นอยู่กับ state
  • หา component ที่เป็น parent ของ component เหล่านั้น แล้วใส่ state ให้มัน
  • หากไม่มี component ที่เป็น parent ของ component เหล่านั้นเลย ให้สร้าง component ใหม่มาครอบ component เหล่านั้น แล้วค่อยใส่ state ให้มัน
พูดง่ายๆ ก็คือ ในการเขียน React นั้น เราจะใช้วิธีใส่ state เอาไว้ที่ตัวแม่ แล้วให้มันส่งข้อมูลที่จำเป็นไปยังลูกๆ ผ่าน props ครับ ลองนึกดูว่า หากเราไปใส่ state ที่ลูก แล้วลูกตัวอื่นๆ ที่ต้องการจะใช้ข้อมูลจาก state จะรู้ได้อย่างไรว่า state ได้เปลี่ยนไปแล้ว ? อย่าลืมว่า state นั้นจะอยู่แค่ภายใน component นั้นๆ เท่านั้นนะครับ

แนวทางศึกษาเพิ่มเติม

มาถึงตรงนี้ ผมคิดว่าพื้นฐานของเราโอเคในระดับหนึ่งแล้วล่ะครับ ที่เหลือก็แค่ฝึกซ้อมจนเขียน React ได้คล่องเท่านั้นเอง เวลาเห็น design มางี้แล้วต้องรู้เลยว่าจะต้องซอยอะไรออกมาเป็น component บ้าง ต้องใส่ state ไว้ตรงไหน ตรงนี้ต้องมีชั่วโมงบินสูงๆ ถึงจะทำได้ครับ หรือถ้าใครอยากเป็นเร็ว ผมขอแนะนำให้ลองติดตั้ง React Developer Tools ดูครับ แล้วเราจะรู้เลยว่าแต่ละเว็บที่ใช้ React ทำนั้น เค้ามีการออกแบบ component อย่างไร ใส่ state อะไร เอาไว้ที่ ไหนบ้างผมขอสารภาพให้เจ็บใจเล่นๆ นิดนึงนะครับว่า หากจะเอา Isomorphic ไปใช้กับ production จริงๆ แล้ว เราจะไม่เขียนโค้ดในรูปแบบที่เราเขียนกันในบทความนี้หรอกครับ เพราะมันเป็นแค่โค้ดพื้นฐานสุดๆ ที่ทำให้ Isomorphic ทำงานได้เท่านั้นเอง (อารมณ์เหมือนเขียน PHP เพียวๆ ในช่วงหัดเขียนแรกๆ แต่สุดท้ายก็เปลี่ยนไปใช้ framework อยู่ดี) ซึ่งถ้าผมเอารูปแบบการเขียนโค้ดที่เค้าใช้กันจริงๆ มาอธิบายด้วย บทความนี้ก็คงจะยาวกว่านี้อีกเยอะเลยล่ะครับ ก็ถือซะว่าเนื้อหาในบทความนี้เป็นก้าวแรกในการศึกษาเรื่อง Isomorphic แล้วกันนะครับ ^0^หากใครจะมาเอาดีทางด้าน Isomorphic หรือจะลองนำไปใช้กับ production ดู ผมขอแนะนำให้ไปศึกษาเรื่องพวกนี้ต่อเลยครับ
  • ECMAScript 2015 project ที่เกี่ยวกับ Isomorphic ต่างๆ บน GitHub มักจะเขียนด้วย ECMAScript 2015 ครับ ถ้าเราไม่เป็นเลย รับรองว่าไล่โค้ดคนอื่นลำบากแน่ๆ
  • Flux แล้วส่วนใหญ่เค้าก็จะใช้ Flux เป็น pattern ในการเขียน React กันครับ ลองไปหัดเล่นดูนะครับ แล้วจะพบว่ามันทำให้การเขียน React นั้นเข้าใจง่ายขึ้นเยอะเลย
  • Redux เมื่อเข้าใจหลักการของ Flux แล้ว ผมแนะนำให้ลองเล่น Redux ดูครับ เพราะมันเป็น library ที่นำ Flux ไปต่อยอดให้ดียิ่งขึ้น ซึ่งจริงๆ library ที่นำ Flux ไปต่อยอดนั้น มีเยอะมากๆ เลยนะครับ แต่ที่ผมแนะนำ Redux ก็เพราะว่า docs ของมันค่อนข้างจะโอเคแล้ว แล้วคนที่ใช้ก็ถือว่าเยอะพอสมควรเลยครับ ในชั่วโมงนี้ถือว่าเป็นตัวที่น่าเล่นที่สุดแล้ว

ความรู้สึกหลังลองทำเว็บแบบ Isomorphic

ผมมองว่า Isomorphic คืออนาคตของเว็บนะครับ ข้อดีผมพูดไปหลายครั้งแล้ว ขอพูดถึงข้อเสียมั่งละกัน หลักๆ เลยก็คือ learning curve ที่แอบสูงนิดๆ ครับ อาจจะเป็นเพราะมันยังใหม่อยู่ด้วยมั้ง เราเลยยังไม่ค่อยเจอบทความไหนที่อ่านแล้วเข้าใจง่ายๆ เท่าไรส่วนข้อเสียอีกเรื่องซึ่งผมเพิ่งเจอมาหมาดๆ เลยก็คือ แต่ละส่วนในการทำ Isomorphic มันพัฒนาแยกกันฮะ คือวันดีคืนดี หากเราอัพเดท dependency ที เราอาจจะพบว่าโค้ดเดิมของเราใช้งานไม่ได้ซะละ หรือบางทีเราพบว่าบางโมดูลมี bug แล้วการจะแก้ bug นี้ได้ ต้องอัพเดทเวอร์ชั่นใหม่ ที่ syntax มันเปลี่ยนยกเครื่องเลย นี่เป็นสิ่งที่เราพบเจอได้บ่อยๆ ในการทำ Isomorphic ณ วันนี้ฮะ ก็ชั่งใจเอาเองนะครับว่างานของเรามันเหมาะกับ tool ตัวไหน จำเป็นต้องแคร์เรื่อง SEO มั้ย ? ส่วนตัวผมก็ใช้เกณฑ์นี้แหละ เป็นตัวตัดสินใจว่าจะใช้ AngularJS หรือ React ดีแต่อย่าเพิ่งคิดว่าโลกของเว็บหลังจากนี้ React จะผูกขาดนะครับ เพราะกระแสของ Angular 2 นั้นเริ่มจะมาแล้ว แถมในเวอร์ชั่นใหม่นี้ยังสามารถทำ server-rendering ได้แล้วด้วย ประเด็นเรื่อง SEO จึงตัดออกไปได้ฮะ อีกตัวที่ทำได้(นานแล้ว) ก็จะเป็น Meteor ครับ ก็ลองไปศึกษากันต่อดูนะครับ ว่า tool ตัวไหนมันเหมาะกับงานของเราก็ต้องทำใจกันหน่อยนะครับ สำหรับชาว Front-end ทั้งหลาย ที่ต้องคอยเรียนรู้อะไรใหม่ๆ อยู่ตลอดเวลา บ่อยครั้งที่เรารู้สึกว่าพอใช้ตัวนี้จนเก่งแล้ว ตัวใหม่ที่ดีกว่าดันออกมาอีกละ เอาเป็นว่าผมจะคอยช่วยกรองมาให้ชั้นนึงก่อนแล้วกันนะครับว่า tool ตัวไหนดูดี มีอนาคต SiamHTML จะรีบนำมารีวิวให้ได้อ่านกันก่อนใครแน่นอน แล้วพบกันใหม่บทความหน้าครับผม

--

--