Navigation

router in React Native

ใน React Dom นั้น ใช้ react router ในการ navigate ไปยังหน้าต่างๆ ภายในระบบ แต่สำหรับ React Native นั้น หลักการแบบ router นั้นใช้ไม่ได้ ดังนั้น เราจึงต้องใช้ module ตัวอื่นมาช่วยเพื่อการนี้ชื่อว่า react-native-router-flux ดังนั้น ให้เราทำการติดตั้ง module ตัวนี้เสียก่อน โดยเปิด terminal ขึ้นมาแล้วทำการติดตั้งซะ

Application flow

ในระบบของ manager นั้นจะประกอบด้วย flow การไหลของ UI จะประกอบด้วย 2 ตัว กล่าวคือ flow ของการ login และ flow หลักของระบบ ดังรูป

screen-shot-2559-12-09-at-11-27-33-am

ตัว react-native-router-flux จะเข้ามาช่วยจัดการเรื่องของ flow ui ภายในระบบ โดยมี <Scene> component สำหรับจัดการเรื่องนี้ โดยเอา <Scene> มาเป็นตัว route ระหว่างหน้าต่างๆ screen-shot-2559-12-09-at-11-36-07-am

ระบบของเรามีกี่หน้า ก็ให้ทำ Scene ในจำนวนเท่าๆ กัน ซึ่งวิธีเขียน Scene จะเป็นดังนี้

screen-shot-2559-12-09-at-12-23-07-pm

ภายใน Scene จะมี attribut สี่ตัว

  1. key คือ id ของหน้าจอนั้น ใช้เป็นตัวแทนหรือชื่อเรียกของแต่ละ scene ซึ่งจะต้องไม่ซ้ำกัน
  2. component ระบุว่า scene นี้จะแสดง component ตัวไหน
  3. title เป็น title text ของแต่ละ scene ที่ใช้เพื่อแสดงผลบนหน้าจอ
  4. initial บอก React ว่ามันคือหน้าแรกของระบบ (home)

ลง code

คราวนี้ก็ได้เวลาลง code จริงๆ แล้ว

ขั้นต้นให้สร้างไฟล์ Router.js ภายใต้ folder manager/src/ ทำให้ตอนนี้โครงสร้างไดเรกทอรี่ของระบบเป็นดังนี้

จากนั้นเติม code ลงไปดังนี้

คราวนี้ลองเปิด emulator ดูจะเห็นหน้าจอดังรูป

จากนั้นให้นำ RouterComponent มาใช้ภายใน App.js โดยให้เปิดไฟล์ manager/src/App.js ขึ้นมาแล้วปรับ code ดังนี้

 

screen-shot-2559-12-09-at-8-18-06-pm

จะเห็นได้ว่า Scene จะสร้าง header ให้โดยอัตโนมัติ ปัญหาก็คือ header ตัวนี้มันวางทับบน email field

วิธีแก้ก็คือ ให้ปรับ code ภายใน router.js ดังนี้

คราวนี้ลอง render ใหม่จะได้หน้าจอดังรูป

screen-shot-2559-12-09-at-8-20-45-pm

จัดการหลายหน้าจอ

คราวนี้เราลองใช้ Scene เพื่อจัดการกับหน้าจอหลายๆ หน้าจอดูบ้าง

ให้เราลองสร้าง component ใหม่ขึ้นมาชื่อว่า EmployeeList.js ภายใน manager/src/components/ โดยให้กรอก code ดังนี้

เสร็จปุ๊บก็เอามาวางไว้ใน Router.js ดังนี้

ภายใน <Scene> ที่อ้างอิงถึง EmployeeList นั้นมีการระบุ attribute ตัวหนึ่งคือ initial ซึ่งเป็น attribute ที่แจ้งว่า จะใช้ Scene นี้เป็นหน้าเริ่มต้นของระบบ ดังนั้น เมื่อทำการ refresh หน้าจอ เราจะเห็นภาพ ดังนี้screen-shot-2559-12-09-at-8-26-51-pm

แต่เนื่องจาก เราต้องการให้ Scene loginForm แสดงเป็นหน้าแรกของระบบ ดังนั้น ให้ลบ initial ออกจาก Scene ของ employeeList เสีย

คราวนี้สังเกตุมั๊ยครับว่า ตัว loginForm ไม่ได้ระบุ initial ภายใน scene แต่ทำไมมันถึงกลายเป็นหน้าแรกของระบบไปได้ นั่นเป็นเพราะ react-native-router-flux นั้นนอกจากจะใช้ initial ในการระบุว่า Scene ใดเป็นหน้าแรกแล้ว เรายังสามารถใช้ลำดับในการวาง Scene เป็นตัวบอกได้อีกด้วย จากตัวอย่าง loginForm วางอยู่อันดับแรก ดังนั้น Scene loginForm จึงกลายเป็นหน้าหลักของระบบไป

Route ไปหน้าอื่นด้วย Actions

task ต่อไปของเราคือ เมื่อเรา login success แล้ว ระบบจะเปลี่ยนจากหน้า login ไปยังหน้า employeeList ต่อ วิธีการก็ง่ายมาก โดยให้เปิดไฟล์ manager/src/actions/index.js ขึ้นมาแล้วเพิ่ม code ดังนี้

เพียงแค่ code บรรทัดนี้

ก็ทำให้ระบบวิ่งไปที่หน้า employeeList ได้แล้ว ดังนั้น การที่เราเอา code ดังกล่าวไปวางไว้ใน loginUserSuccess ก็แปลว่า หลังจาก login success แล้ว ก็ให้ไปรัน Actions.employeeList() ต่อ

ให้ refresh emulator แล้ว login เข้าไปด้วย email และ password ที่ถูกต้อง หากไม่มีอะไรผิดพลาด ระบบจะพาเราไปที่หน้านี้

screen-shot-2559-12-09-at-8-43-36-pm

คำถามคือ แล้วเจ้า .employeeList() มันมาจากไหน คำตอบก็คือ employeeList คือ key ของ Scene นั่นเอง

ดังนั้น key ของ Scene จงมีความสำคัญอย่างมาก เพราะเป็นตัวสำหรับใช้ในการ route ไปยังหน้าต่างๆ ได้

สังเกตมั๊ยครับว่า พอ login เสร็จแล้ว เข้ามาที่หน้า employeeList ตรง header จะมี icon back สำหรับย้อนกลับไปหน้าก่อนหน้าให้โดยอัตโนมัติ ซึ่งถือเป็น feature ที่แถมมาให้สำหรับ module ตัวนี้ แต่บังเอิญว่า หลังจาก login แล้ว เราไม่อยากจะให้มันย้อนกลับมาได้อีก

วิธีแก้ก็คือ ให้แบ่ง Scene ออกเป็นกลุ่ม (เรียกเป็นทางการว่า bucket) โดยแยกตาม flow ซึ่งหากยังจำกันได้ ตอนนี้เราจะมีสอง flow นั่นคือ flow ของ login และ flow ตัว main ดังนั้น หากเราจะแยก flow ให้เรานำ Scene มาซ้อนภายใน Scene อีกที พูดแล้วงง ไปดู code จริงกันเลยดีกว่า

เปิดไฟล์ manager/src/Router.js ขึ้นมาแล้วปรับ code ดังนี้

นั่นคือ เราให้ scene login อยู่ภายใน flow ที่ชื่อ auth และ scene employeeList อยู่ใน flow ที่ชื่อ main

แต่งานยังไม่จบ เพราะเราต้องไปแก้ code ที่ตัว Actions ด้วย ดังนั้นให้เปิดไฟล์ manager/src/actions/index.js แล้วภายในฟังก์ชั่น loginUserSuccess ให้แก้ code ดังนี้

แทนที่เราจะเรียกใช้ key ของ Scene ตรงๆ เราจะต้องเรียกใช้ key ของ flow ซึ่งในที่นี้คือ main จึงจะได้

ให้เราลอง refresh emulator แล้วจะเห็นว่า หลังจาก login ไปแล้ว พอไปถึงหน้า employeeList ปุ่ม back บน header ก็จะหายไปแล้ว

เพิ่ม navbar right button บน header

กลับมาดูที่ mockup กันใหม่อีกรอบ

screen-shot-2559-12-09-at-8-59-31-pm

จะเห็นว่า หน้าจอที่สองคือ employeeList จะมีปุ่ม + อยู่ทางด้านขวาของ header ด้วย ซึ่งเราสามารถเพิ่มปุ่มดังกล่าวได้ไม่ยาก เพราะ <Scene> มีเตรียม attribute ไว้ให้อยู่แล้ว ซึ่งสามารถไปดูรายละเอียดตัวเต็มได้ที่นี่ https://github.com/aksonov/react-native-router-flux/blob/master/docs/API_CONFIGURATION.md#navigation-bar-right-button

คราวนี้เราจะลองทดสอบการใช้ attribute เหล่านี้ในหน้า employeeList ด้วยการเปิดไฟล์ manager/src/Router.js ขึ้นมาแล้วปรับ code ดังนี้

จากนั้นให้ลองเปิด emulator ขึ้นมา แล้วเปิดโหมด debug จากนั้น login ให้เรียบร้อย พอไปถึงหน้า employeeList จะมีปุ่ม Add อยู่ด้านขวามือของ header หากเราลองคลิกไป ตัว console จะแสดงผลดังรูป

screen-shot-2559-12-09-at-9-04-02-pm

route ไปยังหน้าฟอร์มสร้าง employee

จาก mockup ก่อนหน้านี้เมื่อเราคลิกปุ่ม Add แล้ว มันจะไปยังหน้า create employee แต่เนื่องจากเรายังไม่มีหน้าดังกล่าว จึงต้องสร้างขึ้นมา

ให้เราสร้างไฟล์ชื่อว่า EmployeeCreate.js ขึ้นมาภายในโฟลเดอร์ manager/src/components/ แล้วให้เพิ่ม code ไปดังนี้

จากนั้นให้นำ component ตัวนี้ไปใช้กับ Router.js ดังนี้

ก็เหมือนเดิมคือ ใช้ Actions แล้วเรียก key เข้าไปตรงๆ (สังเกตว่าเวลาเรียก key ต้องเรียก key ของ Scene ที่อยู่ใน ระดับเดียวกัน และ employeeList ก็อยู่ระดับเดียวกับ employeeCreate ดังนั้น เราจึงสามารถใช้ Actions.employeeCreate() ตรงๆ จากภายใน employeeList ได้)

คราวนี้ลองเปิด emulator ขึ้นมาดู ทำการ login แล้วคลิกปุ่ม Add ทางขวาของ header หากไม่มีอะไรผิดพลาด มันพาเรามาที่หน้านี้

screen-shot-2559-12-09-at-9-25-30-pm

ออกแบบ EmployeeCreate

EmployeeCreate จะต้องเก็บข้อมูล 3 ฟิลด์ได้แก่ Name, Phone และ Shift (กะ) โดยจะมีหน้าโครงข้อมูล และ ui ประมาณรูปนี้screen-shot-2559-12-09-at-9-27-37-pm

ให้เราเปิดไฟล์ manager/src/components/EmployeeCreate.js ขึ้นมา แล้วปรับ code ดังต่อไปนี้

 

เชื่อมต่อกับ Redux

ได้เวลา setup redux กันอีกรอบแล้วสำหรับงานในส่วนของ employee

1. สร้าง type constant

อันดับแรกจาก mockup ข้างต้น ฟังก์ชั่นที่เราต้องการสำหรับหน้าจอ employeeCreate ก็คือ การ update employee ดังนั้น ขั้นแรกให้ไปกำหนด type constant เสียก่อน โดยเปิดไฟล์ manager/src/actions/types.js แล้วเพิ่ม code ดังนี้

2. สร้าง action creator

เมื่อได้ type constant มาแล้ว ให้สร้าง action creator ขึ้นมาใหม่ แต่เนื่องจาก action คราวนี้เป็นงานที่เกี่ยวข้องกับเรื่อง employee ดังนั้น เราจึงควรแยกไฟล์ action ต่างหาก โดยให้สร้างไฟล์ชื่อ EmployeeActions.js ภายใต้ folder manager/src/actions/ จากนั้น ให้เพิ่ม code ไปดังนี้

3. สร้าง reducer

ขั้นต่อจาก action creator ก็ต้องเป็น reducer ต่อ และเนื่องจากเรายังไม่มี reducer สำหรับงาน employee ดังนั้น เราจึงต้องสร้างไฟล์ใหม่ขึ้นมา

ให้สร้างไฟล์ EmployeeFormReducer.js ขึ้นมาภายใต้ folder manager/src/reducers/ แล้วเพิ่ม code ไปดังนี้

สังเกตมั๊ยครับว่า เรามี syntax ใหม่อีกแล้ว

มันคือการกำหนด key: value นั่นแหละ เพียงแต่คราวนี้ แทนที่จะระบุ key แบบตรงๆ เราใช้วิธีการระบุแบบ dynamic แทน

ตัวอย่างเช่น

หาก action.payload.prop คือ name และ action.payload.value คือ ‘Jane’ code ข้างต้นจะกลายร่างเป็น

หรือหาก action.payload.prop คือ phone และ action.payload.value คือ ‘088-000-9898’ code ข้างต้นจะกลายร่างเป็น

จะเห็นว่าเป็นการเขียน code ที่สั้นมาก แต่รองรับรูปแบบการ assign ค่าได้หลากหลายมาก

เมื่อมีการสร้าง file reducer ขึ้นมาใหม่ เราก็ต้องนำมันไปใส่ไว้ใน combineReducers ไว้เพื่อให้ Redux รู้จักกับ reducer ตัวนี้ด้วย (เหมือนพาพนักงานใหม่ไปแนะนำตัวให้ซีอีโอรู้จัก)

ให้เปิดไฟล์ manager/src/reducers/index.js แล้วเพิ่ม reducer ตัวใหม่เข้าไปดังนี้

Refactoring Auth Action Creator

แต่คราวนี้สังเกตมั๊ยครับว่า ในไฟล์ manager/src/actions/index.js ซึ่งควรจะเป็นไฟล์กลางนั้น กลับกลายเป็นที่วาง action creator สำหรับ auth ซึ่งไม่ควรจะเป็นเช่นนั้นเลย  และเพื่อความเป็นระเบียบ ผมจึงขอ refactor ให้ action creator ของ auth ถูกแยกออกมาจากไฟล์ index.js

ให้สร้างไฟล์ AuthActions.js ขึ้นมาภายใต้ folder manager/src/actions/ แล้วให้ copy code ทั้งหมดจาก manager/src/actions/index.js ไปวางไว้ในตัวมัน ดังนี้

จากนั้น ในไฟล์ manager/src/actions/index.js ให้ทำหน้าที่เป็น “ประตู” เพื่อ export actions ทั้งหมดภายในโฟลเดอร์เดียวให้ component สามารถนำไปใช้ได้ โดยให้แก้ code เป้นดังนี้

เพิ่ม dynamic content ให้กับ EmployeeCreate component

ได้เวลานำสิ่งต่างๆ ที่เรา setup ไปใน Redux มาใช้ภายใน EmployeeCreate กันแล้ว

ให้เปิดไฟล์ manager/src/components/EmployeeCreate.js ขึ้นมา แล้วเพิ่ม code ดังนี้

ขอย้ำอีกครั้งกันลืมว่า state.employeeForm นั้นเรากำหนดจากไฟล์ reducer กลางนะครับ เพราะ reducer จะมีหน้าที่ในการส่งและรับ state ด้วย (พูดลอยๆ ไว้เผื่อคุณผู้อ่านลืม เลยย้ำอีกรอบ) หากใครลืมก็ไปดูได้ที่ไฟล์ manager/src/reducers/index.js รับรองว่าความจำจะกลับมาโดยพลัน

React Native Picker

ตอนนี้ในหน้า employeeCreate ยังเหลืออีกหนึ่ง ฟิลด์ที่เรายังไม่ได้สร้างขึ้นมารองรับ นั่นคือ shift หรือกะทำงาน ซึ่งจะมีค่าตั้งแต่วันจันทร์ ถึงวันอาทิตย์ ดังนั้น เราต้องใช้ component ที่มีคุณลักษณะเหมือน dropdownlist ซึ่งทาง react native ได้เตรียม build in component เอาไว้ให้แล้ว มีชื่อว่า picker

วิธีใช้ก็ไม่ยาก ให้ไปดูที่ code ตรงๆ ก็น่าจะเข้าใจกันได้อย่างสบายๆ ดังนั้น ให้เปิดไฟล์ manager/src/components/EmployeeCreate.js ขึ้นมาแล้วเพิ่ม code ดังนี้

การสร้าง picker ขึ้นมาก็ไม่ได้ต่างอะไรกับพวก dropdownlist แบบ web ทั่วๆ ไป แต่มีจุดสังเกตนิดนึงคือ เวลาใช้งาน picker จะต้องเซต style ให้มีค่า flex: 1 ด้วย มิฉะนั้น มันจะไม่แสดงตัวบนหน้าจอ (ไม่เข้าใจว่าทำไม facebook ถึงไม่ทำให้มันเป็น default ไปเลย ให้เรามาเซตทำไมให้วุ่นวาย เห็นมีหลายตัวใน React Native แล้วที่เป็นแบบนี้)

สำหรับผู้ที่ต้องการดู code ต้นแบบ สามารถไปดูได้ที่

https://github.com/himaeng/manager20.git

สรุป

เนื้อหาในบทนี้จะเน้นไปที่การใช้ router ของ React Native ที่ชื่อว่า react-native-router-flux ซึ่งช่วยอำนวยความสะดวกให้เรามากมายในเรื่องของการ routing ระหว่าง screen ต่างๆ ภายในระบบ