React Router

เนื้อหาของคอร์สนี้ เรียบเรียงขึ้นจาก React.js Fundamental
React Router คือ module ที่ทำหน้าที่ในการ Navigating หรือการเปลี่ยน page ไปมา ภายในระบบของ React แต่ก่อนที่จะเข้าไปศึกษาเรื่อง React Router กันเต็มๆ นั้น เรามาเรียนรู้เรื่องของ props.children

ว่าด้วยเรื่อง this.props.children

เปิดไฟล์ app/index.js ขึ้นมาแล้วแก้ code ตามนี้

จาก code ข้างต้น เป็นการสร้าง component ใหม่ขึ้นมาชื่อว่า Link ซึ่งเป็น component ที่ถูกนำมาใช้ภายใน ProfileLink อีกที

ประเด็นสำคัญของ code ข้างต้นอยู่ตรงบรรทัดนี้

ซึ่งเป็นการบอกว่า ให้นำสิ่งที่ถูกนำไปใส่ไว้ภายใต้ <Link></Link> ทั้งหมดมาแสดง หากลองไปดู code ของ ProfileLink

มีการอ้างอิงถึง <Link> ภายในตัว component และภายใต้ <Link></Link> มี {this.props.username} ซึ่งในที่นี้มันจะกลายมาเป็น children ของ Link ณ จุดนี้ไป

screen-shot-2559-11-03-at-5-13-00-am

จากนั้นให้เราลอง browse ไปยัง localhost:8080 ดู ก็จะเจอกับหน้าจอแบบเดิม

screen-shot-2559-11-03-at-5-16-02-am

ซึ่งอาจจะมีคนถามต่อว่า “เฮ้ย แม่งก็เหมือนเดิมนี่หว่า หน้าก็ไม่ได้ต่างจากเดิมเลย” บางคนอาจจะเริ่มงงว่า แล้วไอเจ้า {this.props.children} มีประโยชน์อะไร

ให้เราลองแก้ code ใน app/index.js ดังนี้ครับ

screen-shot-2559-11-03-at-5-20-01-am

จะเห็นได้ว่า หากเราไม่ใช้ {this.props.children} ตัว Component ที่ชื่อ Link จะไม่มีวัน อ้างอิงถึงข้อมูลภายใน ProfileLink หรือตัวแม่ (ต่อไปนี้จะขอเรียกตัวแม่ว่าเป็น container) ของมันได้เลย

ปรับ style และใส่ callback function ให้ Link Component

ให้เราทำการแก้ไข code ภายใน Link Component ดังนี้

เราได้ทำการใส่ style เข้าไปในตัว Link Component และ handle event ที่ชื่อ onClick ให้ทำการ route ไปยัง url ที่ถูกระบุอยู่ในตัวมัน ผ่าน this.props.href

ผมมีคำถามฝากไว้ครับว่า ไอเจ้า this.props.href เนี่ย มันมาจากไหน ใครรู้บ้าง ช่วยตอบที (หากตอบไม่ได้ ให้ลองทบทวนเรื่อง props ในมุมที่มีลักษณะเหมือน argument ใน function ครับ จากนั้นก็ลองเข้าไปดูที่ตัว ProfileLink component จะได้คำตอบ) หากตอบไม่ได้จริงๆ ตอนท้ายของบทความตอนนี้จะเฉลยให้ครับ

React Router

มาถึงเรื่องหลักของเราซักที React Router

ก่อนอื่นให้เราทำการติดตั้ง react-router เสียก่อน โดยเปิด terminal แล้วเข้าไปที่ root project จากนั้นให้พิมพ์ command ดังนี้

หมายเหตุ: หากเราหยุดรัน server เพื่อ install third party modules ครั้งใด อย่าลิม start server อีกครั้งด้วยคำสั่ง

หลังจากที่เราทำการติดตั้ง react-router ไปแล้ว ให้กลับไปที่โปรเจ็กท์ของเรา จากนั้นให้สร้าง folder config ภายใต้ app folder และภายใต้ config ให้สร้างไฟล์ที่ชื่อว่า routes.js (app/config/routes.js) โดยภายใน routes.js ให้กรอก code ดังนี้

มาไล่กันทีละบรรทัด

code ข้างต้นคือการ import module มาจากทั้ง react และ react-router โดย assign ไว้ที่ variable ที่ชื่อว่า React กับ ReactRouter ตามลำดับ จากนั้น ก็สร้าง variable ตัวใหม่ขึ้นมาชื่อว่า Router ซึ่งถูก assign จาก ReactRouter.Router หรือ function Router จาก react-router module นั่นเอง (ตัว Route ก็เช่นกัน)

ส่วน hashHistory นั้นผมจะพูดในตอนหลัง ตอนนี้ติดไว้ก่อน

เป็นการ import component ที่ชื่อ Main กับ Home ซึ่งยังไม่ได้ถูกสร้างขึ้นมา แต่อีกเดี๋ยวเราจะไปสร้างครับ รอแป๊บ

เป็นการสร้าง component แล้ว assign ให้กับตัวแปรที่ชื่อว่า routes

โดยภายใน component นี้คือการนำ Router มาใช้สร้าง navigator ให้กับโปรเจ็กท์ของเรา โดยเซตให้ root url (‘/’) ชี้ไปที่ component ที่ชื่อว่า Main และมี path ย่อยต่อจาก root คือ /home ก็ให้ชี้ไปที่ component ที่ชื่อว่า Home ตามลำดับ

จากนั้นให้เราสร้าง folder components ภายใต้ app แล้วสร้างไฟล์ใหม่ขึ้นมาสองไฟล์ คือ Main.js และ Home.js โดยแต่ละไฟล์จะมี content ดังนี้

app/components/Main.js

app/components/Home.js

 

จากไฟล์ routes.js นั้น จะเห็นว่า ตัว root path จะใช้ Main component เป็นตัวครอบ Home Component

ตัว react-router นั้นจะมีความสามารถพิเศษในการช่วย route ได้ว่า จะให้ใครมาเป็น children component ให้กับ Main ได้ โดยในที่นี้ หากเมื่อใดที่ user เข้า path /home ตัว Main component จะมี ค่า props.children เป็น Home ทันที

สมมติว่า เราเขียน Code แบบนี้ (ไม่ต้องเขียนตามนะครับ แค่ยกตัวอย่าง)

หาก user เข้าไปที่ /home ตัว props.children ของ Main จะเป็น Home component และหากเมื่อใดที่มีการเข้าไปที่ /second ตัว props.children ของ Main จะเปลี่ยนไปเป็น Second component ทันที

นี่คือความสามารถพิเศษของ react-router ตัวหนึ่ง

ขั้นตอนสุดท้าย ให้เราเปิดไฟล์ app/index.js แล้วแก้ code ตามนี้

จาก code ข้างต้น เราทำไปสองอย่าง

  1. ลบ component ที่สร้างไว้ภายใน index.js ทิ้งทั้งหมด
  2. ทำการ render routes ที่เราสร้างไว้ใน app/config/routes.js ให้ไปแสดงบนหน้า template (index.html) แทนที่จะเป็น component แบบตรงๆ อย่างที่เคยทำมา นั่นเท่ากับว่า จากนี้ไป เราจะให้ตัว routes เป็นตัวควบคุมหน้า ui ทั้งหมดของระบบ

ดังนั้น หากเราลอง browse ไปที่ localhost:8080 จะเจอกับภาพนี้

screen-shot-2559-11-03-at-7-03-07-am

จากนั้นหากลองเข้าไปที่ http://localhost:8080/#/home (ตอนนี้เวลา route ไปแต่ละ path ต้องคั่นกลางด้วย # ก่อน เดี๋ยวตอนท้ายเราค่อยมาแก้กัน) เราจะเจอภาพนี้

screen-shot-2559-11-03-at-7-04-39-am

จะเห็นว่า พอเราเข้าไปที่ /home แล้ว ระบบจะโชว์ทั้ง Main component และ Home component ที่เป็นเช่นนี้เพราะ code ดังต่อไปนี้ใน Main.js

และตอนนี้ props.children ของ Main component คือ Home (จากการช่วยเหลือของ react-router) ทำให้หน้าจอจึงแสดงออกมาอย่างที่เห็น

IndexRoute

คราวนี้มีคำถามขึ้นมาว่า หากเราอยากจะให้ Home component โชว์เป็นหน้าหลักโดยที่ user เพียงแค่เข้ามาที่ root url (ในที่นี้คือ localhost:8080 แต่ในกรณีของเว็บไซต์ ก็อาจจะเป็น twitter.com, facebook.com เป็นต้น) ไม่ต้องพิมพ์ /home ให้ยืดยาว เราจะทำได้อย่างไร?

react-router มีคำตอบให้ครับ เพราะมันได้เตรียม component มาตัวหนึ่งชื่อว่า IndexRoute

ให้เราเปิดไฟล์ app/config/routes.js แล้วแก้ code ดังนี้

จาก code ข้างต้น เราทำการเปลี่ยน path ของ /home ให้ใช้ IndexRoute แทนที่จะเป็น Route แบบธรรมดา อีกทั้งตัด path ออกไป (เพราะการเป็น index มันก็หมายความว่าไม่จำเป็นต้องมี path เฉพาะกิจอีกต่อไป แต่จะเป็น path เดียวกับ path ของแม่มันเลย) คราวนี้ลองให้เราลอง browse ไปที่ localhost:8080 อีกครั้ง

screen-shot-2559-11-03-at-7-12-33-am

คราวนี้จะเห็นว่า เราไม่จำเป็นต้องเข้าไปที่ localhost:8080/#/home อีกแล้ว เพราะหน้า index ในตอนนี้ได้กลายเป็นหน้าของ Home component ไปเรียบร้อยแล้ว

ว่าด้วยเรื่อง history

หากยังจำกันได้ มี code อยู่ชุดนึงใน app/config/routes.js ที่ผมยังไม่ได้อธิบาย นั่นคื

hashHistory เป็นตัวช่วยให้ browser สามารถจดจำได้ว่า step ของ app. เราไปถึงไหนแล้ว ทำให้เราสามารถใช้ปุ่ม forward และ backward ได้

แล้วมันพิเศษยังไง ในเมื่อเว็บทั่วๆ ไปมันก็ต้องใช้ปุ่ม   forward กับ backward ของทุก browser ได้อยู่แล้ว

ต้องอย่าลืมว่า React นั้นมีธรรมชาติเป็นแบบ Single Page Application นั่นคือ เวลาที่เราคลิก app. ของเราบนหน้าเว็บ ตัว url ข้างบนจะไม่เปลี่ยนไปตาม physical ไฟล์ที่อยู่บน server แต่จะ route ไปตาม component ต่างๆ ซึ่งไม่ใช่มาตรฐานของ browser ทั่วไป ดังนั้น ทาง react-router จึงต้องจัด component ที่ชื่อว่า hashHistory มาช่วยให้เราใช้งานปุ่มทั้งสองตัวของ browser ได้ด้วย

จัดระเบียบ url ให้งดงาม

ก่อนหน้านี้ เวลาที่เราจะเข้าไปที่ url ใดก็ตาม ตัว  hashHistory ของ react-router จะสร้าง # ขึ้นมาให้ ซึ่งในหลายกรณีมันดูไม่เรียบร้อย และไม่เป็นผลดีต่อการนำไปแชร์ต่อ เพราะ url มันจะดูวุ่นวาย และจำยาก

ทาง react-router จึงได้เตรียม component มาอีกตัวหนึ่งที่ชื่อว่า browserHistory มาให้เพื่อแก้ปัญหานี้

ให้เราเปิดไฟล์ app/config/routes.js ขึ้นมาแล้วแก้ไข code ดังนี้

พูดให้ง่ายก็คือ เปลี่ยนจากตัว hashHistory ให้กลายมาเป็น browserHistory ให้หมด แล้วคราวนี้ลองเข้าไปที่ localhost:8080 ใหม่

screen-shot-2559-11-03-at-7-29-18-am

จะเห็นว่า เจ้าตัว # อันแสนอัปลักษณ์ได้หายไปแล้ว

แต่ก็มีคำถามหนึ่งผุดขึ้นมาอีกว่า ก็ในเมื่อเจ้า browserHistory มันดีขนาดนี้ แล้วจะใช้ hashHistory ไปทำไมตั้งแต่แรก #คืออยากให้กรูงง #หรืออยากโชว์ว่ามึงเจ๋ง

มันมีเหตุผลครับว่า ทำไมบางคนถึงเลือกใช้ hashHistory  แทนที่จะเป็น browserHistory อยากรูว่าทำไมให้ลองเข้าไปที่ localhost:8080/home ดูครับ

screen-shot-2559-11-03-at-7-32-54-am

จะเห็นว่า ตัว server เรายังไม่ได้ทำความรู้จักกับ url แบบนี้เลย ดังนั้น browser จึงมีปัญหาอย่างที่เห็น

หากอยากจะใช้งาน browser ให้เราไป config server ให้รู้จักมันเสียก่อน (เริ่มยุ่งแล้วใช่ปะ) ดังนี้

ให้เปิดไฟล์ package.json แล้วแก้ไข code ดังนี้

คือไม่มีอะไรครับ แค่เติม –history-api-fallback เข้าไปหลัง command webpack-dev-server ซึ่งเป็น application server ที่เราใช้รันในตอนนี้

จากนั้นให้ทำการ restart npm server ใหม่ โดยเข้าไปที่หน้า terminal ที่รัน server อยู่แล้วกด cmd+C จากนั้นก็รัน npm run start ใหม่อีกรอบ

คราวนี้พอเข้าไปที่ localhost:8080/home ตัว error เก่าก็จะหายไป แต่จะมองไม่เห็นอะไร มีเพียงหน้าเปล่าๆ เนื่องจาก IndexRoute ที่เราเซตให้กับ Home component และ /home path นั้น มันจะไม่ทำอะไรหากเราเข้าไปที่ path ของมันตรงๆ

หากเราอยากพิสูจน์ว่าตอนนี้เราได้แก้ปัญหาแล้ว ให้เปิดไฟล์ app/config/routes.js ขึ้นมาแล้วแก้ code ดังนี้

นั่นคือ เปลี่ยนจาก IndexRoute ให้กลับมาเป็น Route ก่อน เพื่อจะลองทดสอบ path /home ว่ามัน work จริงหรือไม่

จากนั้นให้ลองเข้าไปที่ localhost:8080/home

screen-shot-2559-11-03-at-7-41-33-am

เพียงแค่นี้ก็เรียบร้อย

หมายเหตุ: อย่าลืมเปลี่ยน code กลับไปใช้ IndexRoute พร้อมทั้งลบ path ใน IndexRoute ให้เหมือนเดิมด้วยนะครับ เพราะจากนี้ไป เราจะให้ Home เป็นหน้า index ของเรา

สำหรับผู้ที่อยากได้ code ต้นแบบเพื่อใช้ในการตรวจเช็คความถูกต้อง สามารถเข้าไปดูได้ที่นี้ครับ

https://github.com/himaeng/react-fun-course/tree/master/03-react-router

 

เฉลย

จากคำถามที่ฝากไว้ว่า เจ้าตัว this.props.href ภายใน Link Component เนี่ยมันมาจากไหนกัน? ให้กลับไปดูที่ component ที่ชื่อว่า ProfileLink

จะเห็นได้ว่า ในการเรียกใช้ Link Component นั้น เราได้กรอก props ที่ชื่อว่า href เข้าไปใน Link ด้วย ซึ่งหากเรายังจำกันได้ถึงหลักคิดว่า props ก็เหมือนกับ argument ของ function ซึ่งตอนนี้เราเอา props ที่ชื่อ href ใส่เข้าไปใน Link แล้ว ดังนั้น ภายใน Link เองก็สามารถอ้างอิงถึงข้อมูล props ตัวนี้ได้ โดยเรียกใช้ผ่าน {this.props.href} นั่นเอง