หลังจากหายไปนานกับงานเขียนเนื่องด้วยภาระหลายๆอย่าง วันนี้มีเวลาว่างนิดหน่อยจึงมีโอกาสได้มาเขียนสรุปการสร้าง Project mobile cross platform ด้วย ReactNative + MobX + Router flux โดยจะเขียนยาววว เลยไม่แบ่งย่อยนะครับ(เป็นบทความที่ยาวที่สุดที่เคยเขียนมาครับ) Show
Conceptสำหรับในบทความจะเป็นการสร้างและตั้งค่าProject โดยจะมีการจำลองการทำงานในส่วนต่างๆบ้าง เพื่อเป็นพื้นฐานในการต่อยอดทำแอพอื่นๆต่อไปในอนาคต ซึ่งในบทความนี้จะใช้
สำหรับใครที่ยังไม่เคยมีพื้นฐานด้านการทำ ReactNative เลยแนะนำให้ไปอ่านบทความเก่าๆก่อนนะครับ เพื่อปูพื้นให้เข้าใจมากยิ่งขึ้นครับ สิ่งที่ควรศึกษาเบื้องต้น
Create Projectเริ่มจาก เปิด Terminal หรือ Cmd.exe แล้วไปที่ Path ที่ต้องการสร้าง Project หลังจากนั้นทำการสร้างProject ด้วยคำสั่ง react-native init ตามด้วยชื่อProject เช่น react-native init CellSpeedTest เมื่อทำการสร้าง Project เสร็จแล้วจะแสดงดังรูป จากนั้นทำการเข้าไปที่กล่องProject ที่เราสร้าง โดยใช้คำสั่ง cd ตามด้วยชื่อProject ตรงนี้เป็นอันเสร็จในการสร้างProject ReactNative แล้วใช้เวลาไปไม่ถึง 30วินาที Install Packageตรงนี้เราจะทำการลงตัว Package ต่างๆที่จะให้เป็นพื้นฐานของแอพที่จะสร้างนะครับโดยจะทำการลงทั้งหมด 8Package ซึ่งเมื่อก่อนนั้นส่วนใหญ่เวลาเราจะลง Package เราจะใช้ 1.Router-Fluxrouter-flux เป็น Package ที่ช่วยในการจัดการเรื่องกระบวนการในการเปลี่ยนหน้าจอ คิดง่ายๆว่า เวลาจะต้องการเปลี่ยนจาก หน้าจอLogin ไปเป็นหน้าจอหลัก ในการใช้งานปกติแล้ว จะมีกระบวนการทำานวุ่นวายมากๆ ซึ่งเจ้า router-flux จะมาช่วยให้กระบวนการดังกล่าว ง่ายและเป็นระบบมากขึ้น จัดการลง Package router-flux โดยใช้คำสั่ง yarn add yarn add react-native-router-flux ใช้เวลาประมาณ 8 วินาทีนิดๆก็เป็นอันเสร็จ 2.MobXสำหรับ MobX เป็น Package ที่ช่วยในการจัดการเรื่องข้อมูลภายในแอพเรานะครับสำหรับรายละเอียดเพิ่มเติมสามารถอ่านได้ ตรงนี้ครับ จัดการลง Package MobX โดยใช้คำสั่ง yarn add yarn add mobx 3.Mobx-Reactสำหรับ MobX-React เป็น Package ที่ช่วยให้ตัว ReactNative เองสามารถใช้งาน MobX ได้อย่างสมบูรณ์ จัดการลง Package MobX-React โดยใช้คำสั่ง yarn add yarn add mobx-react รอจนลงเสร็จใช้เวลาประมาณ 5 วินาที 4.react-native-elementsสำหรับ react-native-elements เป็น Package ที่รวบรวม UI Toolkit ต่างๆไว้ เพื่อให้เราสามารถนำมาใช้งานได้สะดวกรวดเร็ว(ไม่ต้องมาทำเอง 555) เช่นการออกแบบ button ต่างๆ โดยสามารถดู elements ทั้งหมดได้ที่นี่ครับ react-native-elements จัดการลงอย่างไว yarn add react-native-elements 5.react-native-vector-iconsสำหรับใครที่รู้สึกว่าเวลาทำแอพครั้งนึงการหา Icon เป็นเรื่องวุ่นวานมาก เราขอแนะนำ react-native-vector-icons เป็น Package ที่รวบรวม Icons ต่างๆไว้ เพื่อให้เราสามารถนำมาใช้งานได้สะดวกรวดเร็ว(ไม่ต้องมาทำเองอีกแล้วววว) โดยสามารถดู Icons ทั้งหมดได้ที่นี่ครับ react-native-vector-icons ทำการลง package yarn add react-native-vector-icons **ข้อสำคัญ หลังจากทำการลง react-native-vector-icons เสร็จแล้ว ต้องทำการเชื่อมตัว Binary ของ react-native-vector-icons เข้ากับตัวproject เราด้วยคำสั่ง react-native link react-native-vector-icons 6.prop-typesสำหรับ prop-types เป็น Package ที่ช่วยทำการ validators ประเภทของข้อมูลให้ครับ yarn add prop-types 7.autobind-decoratorautobind-decorator เป็น Package ที่ช่วยในการจัดการเรื่องกระบวนการในการ bind(this) ให้อัตโนมัติ ซึ่งหลายคนอาจจะสงสัยว่า bind(this) คืออะไร ต้องย้อนกลับไปถึงตัว javascript(js) เองจะมี this ให้เรียกใช้งานโดย this นั้นแทนค่าบางอย่างซึ่งขึ้นอยู่กับว่าใครเป็นคนเรียกใช้งานมัน เช่น <button id="1" onClick="alert(this.id)">B1</button> <button id="2" onClick="alert(this.id)">B2</button> จะเห็นว่า Code ทั้ง 2 บรรทัดมีการเรียกใช้งาน this.id เหมือนกัน แต่เมื่อ B1ถูกคลิก this ตัวนี้จะหมายถึง Tag button B1 ทั้งหมด เนื่องจาก มันถูกเรียกใช้งานโดยการกดผ่าน Event onClick บน button B1 ดังนั้นเมื่อ แสดงค่า this.id มาจะเป็นค่า id ของตัว Tag button B1 เองคือ 1 แล้วทีนี้มันก็มีปัญหาตามมาคือ แล้วถ้าเป็นรูปแบบนี้ล่ะ let obj={ a:5, abc(){ return this.a } }function fgh(fn) { alert(fn()) }alert(obj.abc()) fgh(obj.abc) จากด้านบนเมื่อเรียก alert(obj.abc()) หมายความว่าเราเป็นคนที่เรียก functionผ่านทาง obj เองดังนั้น this จะหมายถึง obj เมื่อทำการแสดงค่า จึงแสดงค่า 5 ออกมากลับกันเมื่อเรียก function fgh โดยส่งค่า obj.abc เพื่อให้ fgh เป็นผู้เรียกใช้งาน this จะไม่เท่ากับกับ obj จึงทำให้ค่าที่แสดงออกมากเป็น undefined จึงทำให้ต้องมีการทำการ bind() ขึ้นมาเพื่อให้รู้ว่า this หมายถึงใคร yarn add autobind-decorator 8.babel-plugin-transform-decorators-legacybabel-plugin-transform-decorators-legacy เป็น Package ที่ช่วยทำให้ Package ที่เขียนในรูปแบบเก่าสามารถใช้งานได้ไม่มีปัญหา yarn add babel-plugin-transform-decorators-legacy หลังจากนั้นทำการตั้งค่า โดยทำการแก้ไขไฟล์ .babelrc ในproject จาก { เป็น { Project structureการวางแผนโครงสร้างของproject แต่ละคนจะมีการวางโครงสร้างในรูปแบบขอตัวเอง อาจจะเหมือนหรือไม่เหมือนกันก็ได้ สำหรับเบื้องต้นจะวางโครงสร้างโดยการแยก components,model,image ไว้คนละส่วนครับ เริ่มต้นสร้างกล่อง app ก่อน ภายในกล่อง app สร้าง กล่อง components,model,images สร้าง Model ด้วย MobXModel ตรงนี้มีไว้จัดเก็บโครงสร้างและกระบวนการต่างๆที่กระทำกับข้อมูลภายในแอพ ตัวอย่างเช่น แอพนี้เป็นเก็บที่ไว้ทดสอบความเร็ว Internet อย่างน้อยที่สุด ควรจะมีการออกแบบโครงสร้างข้อมูลประกอบไปด้วย
ทีนี้เวลาจะมาสร้างเป็น Model รูปแบบ MobX จะทำได้ดังนี้ 1.สร้างไฟล์ personal.js 2.ทำการเรียกใช้งาน lib ของ package MobX และ autobind-decorator import {observabl} from ‘mobx’; import autobind from ‘autobind-decorator’ 3.เรียกใช้ autobind @autobind 4.สร้างClass class PersonalStore { } 5.สร้างโครงสร้างข้อมูล @observable carrier = ‘’ 6.สร้างfunction สำหรับจัดการข้อมูล โดยมีทั้งหมด 3 function คือ
this.carrier=c } setOS(o){ this.os=o } setSpeed(s){ this.speed_now=s }7. กระบวนการสุดท้ายทำการ export class ไปใช้งาน export default new PersonalStore() สร้างหน้าหลักต่อมาเราจะมาสร้างหน้าแรกของแอพเรากัน โดยปกติแล้วหน้าแรกจะถูกสร้างมาอยู่แล้วตอนสร้างproject โดยสร้างไว้ที่ไฟล์ App.js ซึ่งตรงนี้เราจะย้ายข้อมูลในไฟล์นี้มาสร้างใหม่ภายในกล่อง components เริ่มด้วยการสร้างไฟล์ชื่อ PageA.js หลังจากนั้นทำการ คัดลอก ข้อความทั้งหมดในไฟล์ App.jsมาใส่ ทำการเปลี่ยนชื่อ Class เป็น PageA หลังจากนั้น ไปที่ไฟล์ index.js แก้ไขส่วน import จากเดิมชี้ไปที่ไฟล์ App.js ให้ชี้มาที่ไฟล์ PageA.js ที่เราสร้าง ของเดิมของใหม่ จากนั้นเปลี่ยนข้อความส่วนของ Render ใน PageA ให้เหลือแค่ return ( หลังจากนั้นทดสอบ run บน simulator ด้วยคำสั่ง สำหรับ MacOS react-native run-ios สำหรับ Windows react-native run-android สำหรับ Windows ต้องทำการเปิด AVD ไว้ก่อนนะครับแล้วค่อยรันคำสั่ง โดยต้องเป็น Version ดังภาพนะครับ https://facebook.github.io/react-native/docs/getting-started.html โดยเมื่อrun สำเร็จแล้วจะได้ดังภาพ ตั้งค่า Navigationมาถึงส่วนนี้จะเป็นการสร้าง Class หลักมีหน้าที่คอยจัดการ Navigationโดยการนำ Package router-flux มาช่วยในการทำ Navigation 1.สร้างไฟล์ MainApp.jsทำการสร้างไฟล์ MainApp.js ในกล่อง components 2.เรียกใช้ Lib ที่จำเป็นทำเรียกใช้ lib ต่างๆที่จำเป็นจาก Package ต่างๆ import React, { Component } from ‘react’; 3.สร้างClass และ extends Componentclass CellSpeedTest extends Component { } 4.ปรับให้ Class เป็น observerที่ต้องปรับให้ class เป็น observer เพราะว่า เมื่อมีค่าใดๆใน Model PersonalStore มีการเปลี่ยนแปลง ค่าที่เราดึงมาใช้ภายในหน้านี้ก็จะเปลี่ยนไปเป็นค่าเดียวกันโดยอัตโนมัติ ถ้าไม่ปรับเมื่อครั้งแรกหน้านี้แสดงผลค่าใดออกมาจากModel จะแสดงค่านั้นไปตลอดไม่เปลี่ยนแปลง observer(class CellSpeedTest extends Component { }) 5.Export Class ไปใช้งานเนื่องจากClassนี้ เป็นClassหลักจึงทำการ Export เป็นค่า default ไว้นะครับ export default observer(class CellSpeedTest extends Component { }) 6.Function พื้นฐานมาถึงตรงนี้เราจะสร้าง function พื้นฐานสำหรับการใช้งานนะครับ โดยจะประกับไปด้วย
} componentWillUnmount() { } render() { return (null) }6.ตั้งค่า Navigation ในส่วน Renderตรงนี้เราจะมาตั้งค่าเบื้องต้นของการทำ Navigation ทำการแก้ส่วน render โดยทำการเพิ่ม ส่วนจัดการ navigation เข้าไปดังนี้ render() { ส่วนนี้ประกอบไปด้วย 1.Router เป็นส่วน การเรียกใช้ lib ของ router-flux โดยมีการตั้งค่า 2 ส่วน
2.Stack เป็นส่วนที่ใช้จัดกลุ่มของหน้าจอ 3.Scene เป็นส่วนหน้าจอแต่ละหน้าที่มีในแอพ ต่อมาจะทำการเพิ่ม Scene โดยจะใช้ PageA เป็น Scene หลัก เริ่มต้นด้วยการ import หน้า PageA ต่อมาทำการสร้าง Scene โดยใน Scene ประกอบไปด้วย
ต่อมาทำการกลับไปแก้ไขไฟล์ PageA.js เนื่องจากตอนนี้มี MainApp มาเป็น default Class แล้ว โดยทำการแก้จาก export default export default เป็น module.exports = สุดท้ายทำการแก้ไฟล์ index.js ให้ชี้ไปที่ MainApp.js แทน PageA.js จากนั้นทดลอง Run จะเป็นดังภาพ ใช้งาน Navigationในส่วนนี้เราจะทดลองใช้งาน navigation เพื่อเปลี่ยนจากหน้า PageA ไป หน้า PageB สร้างหน้าจอใหม่ทำการคัดลอกไฟล์ PageA.js แล้วเปลี่ยนชื่อเป็น PageB.js จากนั้นทำการแก้ไขชื่อ Class แล้วข้อความ จาก PageA เป็น PageB ทำการบันทึกไฟล์เป็นอันจบ ตั้งค่า Navigationเมื่อมีการเพิ่มหน้าจอใหม่ แล้วต้องการนำหน้าจอนั้นมาใช้งาน ต้องมาทำกระบวนการดังต่อไปนี้เสมอ 1.ทำการเปิดไฟล์ MainApp.js มาแก้ไข 2.ทำการ Import ไฟล์หน้าจอใหม่เข้ามา import pageb from ‘./PageB’; 3.ทำการเพิ่ม scene ในส่วนของ Router <Scene key=’pageb’ component={pageb} title=’Demo PageB’ /> ทำการบันทึก ทดสอบเรียกใช้งาน หน้า PageBการทดสอบนั้นเราจะทำการสร้าง button อันนึงบนหน้า PageA เมื่อทำการกด button นั้นแล้วจะทำการเปลี่ยนหน้าจอจาก PageA ไป PageB 1.ทำการเปิดไฟล์ PageA.js มาแก้ไข 2.ทำการ import ตัว Actions,ActionConst จาก lib router-flux เพื่อไว้ใช้ในการเปลี่ยนหน้าจอ import { Actions,ActionConst } from ‘react-native-router-flux’ 3.สร้าง function เพื่อทำการ เรียกใช้งาน Actions เพื่อเปลี่ยนจากหน้าจอ PageA ไป PageB goToB() { จากด้านบนจะเห็นว่า เราได้สร้าง function ชื่อ goToB โดยfunctionนี้ ทำการเรียกใช้คำสั่ง Actions คำสั่ง Actions เป็นคำสั่งที่ใช้ในการเปบี่ยนหน้าจอ ซึ่งมีวิธีการเรียกใช้คือ พิมพ์ Actions.ตามด้วย key ของ scene ที่ต้องการจะไป ซึ่งตัวอย่างด้านบนคือเราต้องการจะไปที่ scene ที่มี key คือ pageb ดังรูปด้านล่าง 4.import Button จาก lib elements มาใช้ import { Button } from ‘react-native-elements’ 5.ทำการสร้าง button เพื่อเรียกใช้ function goToB <Button ทำการบันทึกแล้วทดสอบกด button ดูครับ เมื่อกด button จะได้ดังภาพ นำ Model personal มาใช้งานเมื่อตอนช่วงต้นเรามีการสร้าง model personal ไว้ ตอนนี้เราจะนำmodel นี้มาใช้งานโดยจะเอาค่า speed_now มาแสดงผล และทำ button ไว้สำหรับเพิ่มค่า speed_now เพื่อทดสอบการทำงาน observer ของ MobX 1.import modelเนื่องจากเรามีการใช้ navigation ในการจัดการหน้าจอต่างๆ เราจึงต้องทำการส่งค่าของ model ผ่านไปทาง Router ด้วยครับ เริ่มต้นด้วยการเปิดไฟล์ MainApp.js มาแก้ไข ทำการ import model ตั้งชื่อว่า pstore import pstore from ‘../model/personal’; 2.ส่งค่าของ model ผ่าน Routerpersonalstore={pstore} จากด้านบนจะเห็นว่าเราส่งค่า pstore ไปในชื่อ personalstore เวลาหน้าจอต่างๆเรียกใช้งาน model นี้จะเรียกใช้ผ่าน personalstore 3.เปลี่ยน Class ให้เป็น observerที่จำเป็นต้องเปลี่ยน class เป็น observer เพราะเมื่อค่าของข้อมูลใน Model มีการเปบี่ยนแปลง หน้าจอนั้นจะทำการ renderใหม่อัตโนมัติ 1.เปลี่ยน PageA ทำการ import observer จาก mobx-react/native import { observer } from ‘mobx-react/native’; ทำการเปิดไฟล์ PageA.js มาแก้ไข โดยทำการครอบ class ด้วย observer() 2.เปลี่ยน PageB ทำเช่นเดียวกับ PageA 4.นำ speed_now แสดงผลการเรียกใช้งานmodel จะเรียกใช้งานผ่าน this.props ตามด้วยชื่อที่ใช้ ตามด้วยตัวแปรที่ต้องการดึงค่า เช่น this.props.personalstore.speed_now จะเห็นว่ามีการเรียก this.props ตัวด้วย .personalstore.speed_now เพื่อดึงค่าขอ speed_now ทีนี้ลองมาเพิ่มบนหน้าจอ PageA โดยจะทำการแสดงค่าแทนข้อความเดิม Welcome to PageA! จะได้ดังนี้ <Text style={styles.welcome}> จากด้านบนจะสังเกตุเห็นว่า การนำค่าต่างๆไปแสดงใน Text จะต้องครอบด้วยปีกกา { } เสมอนะครับ ทดสอบrun ดูจะได้ผลดังนี้ 5.ทำ button และ function เพื่อจัดการค่า speed_nowมาถึงตรงนี้เราจะจำลองกระบวนการใช้งาน model ว่า modelนี้เวลามีค่าเปลี่ยนแปลงจะส่งผลถึงหน้าจอต่างๆอย่าไร เพิ่ม function AddSpeed AddSpeed(s) { จากกระบวนการด้านบน จะเห็นว่า function นี้มีการรับตัวแปร 1 ตัวคือ s หลังจากนั้นจะทำการสร้าง ตัวแปร nows ที่มีค่าเท่ากับ ค่า speed_now ณ ปัจจุบัน บวกกับค่า s จากนั้นทำการเรียกใช้งาน functon setSpeed ของmodel personal แล้วโดยทำการส่งค่า nows ไป ต่อมาทำการเพิ่ม button เพื่อเรียกใช้ function AddSpeed <Button จะเห็นว่าเมื่อกดที่ปุ่มจะมีการเรียกใช้ function AddSpeedและส่งค่า 1 ให้function บันทึกและทดสอบRun ดู จะพบว่าทุกครั้งที่กดปุ่มค่าจะแสดงเพิ่มขึ้นทีละ 1 เสมอ 6.ทดลองนำค่า speed_now ไปแสดงที่ PageBตรงนี้เราจะลองนำค่า speed_now ไปแสดงที่ PageB เพื่อให้เห็นว่าค่าของ Model จะเหมือนกันในทุกๆหน้าที่เรียกใช้ เปิดไฟล์ PageB.js แก้ไขส่วนข้อความจากเดิมเป็น Welcome to PageB! แก้เป็น PageB! {this.props.personalstore.speed_now} บันทึกและทดสอบRunดูผล จะเห็นว่าค่าที่แสดงทั้งสองหน้าคือค่าเดียวกัน 7.ทดลองนำobserver ที่ PageA ออกส่วนนี้จะช่วยให้เข้าใจกระบวนการ observer มากยิ่งขึ้นโดยให้ลองนำ observer ที่ครอบ class PageA ออกแล้วทดสอบ Run ดู ผลที่ได้จะเห็นว่าไม่ว่ากดปุ่ม speed+1 กี่ครั้งค่า speed_now ก็ไม่แสดง กลับกันถ้าลองเปลี่ยนไปที่หน้า PageB ค่ากลับแสดงออกมา |