ปัญหาของ State และ จัดการ Global State ใน React ด้วย Easy-Peasy

Anucha Phudtapranee
I GEAR GEEK
Published in
6 min readSep 14, 2019

ในปัจจุบันการพัฒนาเว็บไซต์นั้นเน้นไปในทาง Component Based หรือง่ายๆก็คือมองหน้าตาของเว็บไซต์เป็นส่วนๆและสามารถนำส่วนนั้นกลับมาใช้ได้อีก (Reusable) และยังแน่ใจได้อีกว่าจะได้ style ที่เหมือนกันทั้งหน้าของเว็บไซต์

http://coenraets.org/blog/wp-content/uploads/2014/12/uimockscript.png

ยกตัวอย่างจากรูปภาพข้างบนซึ่งมีรายละเอียดดั้งนี้

  • App, Homepage, Header, SearchBar, EmployeeList, EmployeeListItem, EmployeePage, Header

เรียงแบบนี้อาจจะ งง งั้นเอาแบบนี้เรามีข้อตกลงว่าถ้า Component ไหนที่คลุม Component อื่นๆอยู่จะเรียกว่า Parent และตัวถูกที่คลุมจะเรียกว่า Child จำแค่ 2 คำนี้ก็ละกัน

จากคำพูดที่พูดมาข้างต้นจะเห็นได้ว่า App เป็น Parent และมี HomePage และ EmployeePage เป็น Child นั่นแปลว่า App เป็นตัวคลุม Component ทั้งหมด

ซึ่ง HomePage และ EmployeePage ไม่ได้เป็นแค่ Child นะจะเห็นได้อีกว่าทั้งสองนั้นเป็น Parent ได้อีกด้วย (เป็นทุกอย่างให้เธอแล้ววว)

จากคำพูดข้างบนที่บอกว่าถ้ามี Component ไหนที่คลุมจะเรียกว่า Parent และตัวที่ถูกคลุมจะเรียกว่า Child ถ้าลองมองดีๆ HomePage และ EmployeePage มันถูกคลุม (เรียกว่า Child) แต่ทั้งสองในตัวมันเองมันก็คลุม Component อื่นๆเหมือนกัน (Header, SearchBar, EmployeeList, EmployeeListItem) ดังนั้นจึงเรียกว่า Parent ได้ (สามารถเป็นได้ทั้ง Parent และ Child)

“คำถาม : แล้ว Component ไหนบ้างที่เป็นแค่ Child อย่างเดียวไม่สามารถเป็น Parent ได้”

https://pixabay.com/illustrations/questions-answers-question-mark-1014060/

ถ้าใครตอบ Header, SearchBar, EmployeeList, EmployeeListItem แสดงว่าคุณเข้าใจพื้นฐาน Concept ของ Component Based แล้ว….

เมื่อเราเข้าใจ Concept ของ Component Based แล้วเรามาดูปัญหาที่มักเจอบ่อยๆของมันกันหน่อย

อย่างที่บอกไปการพัฒนาเว็บในสมัยนี้เน้น Component Based เป็นหลักดังนั้นให้นึกว่าเราต้องมีไฟล์เยอะแน่ๆ (ลองนึกถึง HTML มันไม่ได้มีแค่ Tag เดียวแน่ๆ button ul li img ..) เพราะเราต้องเขียนแยกให้เป็นส่วนๆ (Component) เวลาเรียกใช้เราก็สามารถเรียกไฟล์และเอาไปใช้งานได้เลย ซึ่งปัญหาหลักเลยๆคือการเรียกใช้ค่าที่เหมือนกันเช่น เรากดปุ่มเพิ่มจำนวนตัวเลข “แต่” อยากให้ Component ที่ชื่อ h1 มันอัพเดทข้อความเมื่อกดปุ่ม หลายคนบอกอ๋อออออ ง่ายอะก็แค่ส่งแนบ data ลงใน Component นั้นไงแล้วก็ให้ h1 เอาค่านั้นไปแสดง ใช่ครับในกรณีที่ Component นั้นอยู่ไฟล์เดียวกัน แต่ลองนึกถึง Component ที่อยู่คนละไฟล์ หรือในกรณีที่มี Child และมี Parent ที่ครอบ Child อยู่ซึ่งใน Child นั้นมี Parent อีกเราต้องส่งกลับไปกลับมาแบบนี้คงไม่ดีแน่ นี่แค่ค่าเดียวนะ

จากสิ่งที่พูดมาค่าที่ส่งไปส่งมานั้นเราเรียกกันว่า “State”

State คือ ลองนึกถึง object ที่ข้างในที่มีค่าต่างๆที่เราใช้อยู่ภายในแต่ละ Component ซึ่งมันจะถูกใช้ได้แค่ Component เดียวเท่านั้น

Global State คือ เหมือนกับ State เลยเป็น object ที่มีค่าต่างๆที่เราใช้แต่มันต่างจาก State ก็เพียงแต่มันจะถูกเรียกใช้ที่ไหนก็ได้ Component ไหนก็ได้ (ก็มาจากคำว่า Global นะแหล่ะ)

Props คือ ปัญหาของ State คือมันใช้ได้แต่ Component ของตัวมันเองเท่านั้น Props จึงเกิดขึ้นมาโดยมันมีหน้าที่ รับและส่งค่าของ Component ไปให้อีก Component นึงใช้งาน (แต่มันต่างจาก Global ตรงที่ต้องมีตัวรับส่งอยู่เสมอ)

จากภาพข้างบนจะเห็นว่า Application คือ Parent และมี Login, MainPage, Playing Movie เป็น Child ยกตัวอย่างเช่น PlayingMovie Component ต้องการส่งค่า ชื่อหนังที่ผู้ใช้กำลังดูอยู่ ส่งไปให้ Movie Component เพื่อไปบอกให้ Movie Component นั้นอัพเดทชื่อหนังที่กำลังมีคนดูอยู่สิ่งที่ทำต้องทำยังไงนะ อย่างแรก Application ต้องส่งค่าให้ PlayingMovie ตอบกลับ (เพราะ Application เป็น Child ดังนั้น MainPage และ PlayingMovie ไม่ได้มีการรู้จักกัน) หลังจากที่ตอบกลับ Application ต้องส่งให้ MainPage เพื่อให้ MainPage ส่งต่อให้ Genre List และก็ส่งไปเรื่อยๆครับ Genre List > Genre* > Movie* ก็ง่ายนิจอกมากกกกก งั้นเอางี้ถ้า Login ต้องการส่งค่าบางอย่างให้ Expanded Movie หลังจากเข้าสู่ระบบสำเร็จก็ต้องทำแบบเดิม

  • เริ่มจาก Application ต้อง assign value มาซักหนึ่งตัวเพื่อรอรับค่าจาก Login
  • จากนั้นเมื่อ Application ได้ค่ามาแล้วจะทำการส่งให้ Main Page
  • จากนั้น Main Page ส่งต่อให้ Genre List
  • จากนั้น Genre List ก็ส่งต่อให้ Genre
  • และ Genre ก็ส่งต่อให้ Expanded Movie และ Expanded Movie ถึงจะใช้ค่านั้นได้

หลายคนคงมีคำถามในใจ (ถ้ามันมี Child อีกหล่ะ) ใช่ครับก็ส่งต่อไปเรื่อยๆ (แล้วบาง Component ไม่ได้ใช้ก็ต้องถือ Data ไว้แบบนั้นหรอ) ใช่ครับเพื่อรอให้ส่งต่อหรือส่งกลับ

“ปัญหาของ Component Based หลักๆมีแค่นี้ครับคือ ส่งค่าไปอีก Component และส่งกลับไปอีก Component”

Talk is cheap. Show me the code.

Linus Torvalds

โจทย์ในวันนี้มีอยู่ว่าให้สร้าง Layout 3 ตัว (Header, Content, Footer) ตามฉบับเว็บทั่วไปเลย แต่เราต้องการเมื่อเรากดคลิกเพิ่มข้อมูลตัวละคร Dota2, Avengers, OnePieceใน Component Content เราต้องการให้ Header แสดงจำนวนของตัวละครทั้งหมด และ Footer จะแสดงตัวละครที่พึ่งเพิ่มเข้ามาล่าสุด

Result

งั้นมาลง Code กันดีกว่าขั้นตอนแรก New Project React กันก่อนเลยครับ

npx create-react-app gb-state-mng && cd gb-state-mng

หลังเจอคำว่า Happy hacking! แล้วพิมพ์ npm start เลยครับ (หรือใครใช้ yarn ก็ yarn start)

  1. หลังจาก npm start แล้วจะมี Tab ใหม่ที่มี Title ว่า React App และมี Logo ของ React หมุนอยู่ตรงกลาง
  2. Coding….

เริ่มต้นด้วยการสร้าง Structure File ตามนี้เลยครับ

File structure

หลังจากสร้างไฟล์แล้วให้พิมพ์ Tag Html ตามรูปภาพเลยครับ (เดี๋ยวจะอธิบายต่อด้านล่างรูปภาพว่ามันทำอะไรบ้าง)

App.js

จากรูปภาพเราได้สร้างตัวแปร initialValue ให้มีค่าเป็น Array ที่มีค่าว่าง จากนั้นเราใช้ useState เพื่อ initial ค่าเริ่มต้นให้มัน โดยเราจะมี 3 ตัวแปรเพื่อเก็บค่า รายชื่อของตัวละคร Avengers, Onepiece, Dota2 ในบรรทัดที่ 17–19 และ 30–32 เรา pass value ตัวละครทั้งหมดเพื่อไปให้ header และ footer แสดง และที่น่าสนใจคือ Content จะแตกต่างจาก Component อื่นเพราะว่ามันรับค่าไปเยอะที่สุด รับทั้ง value และ function

Content.jsx

เมื่อเรา Pass value และ function มาให้ Content แล้วเราต้องการรับค่าที่ Props มาแต่จะเห็นได้ว่ายังมีอีก 3 Component ที่ต้องส่งค่าแต่ละอย่างไป OnePieceListsComponent จะส่งรายชื่อตัวละครและฟังค์ชั่นเพื่อเพิ่มรายชื่อไปที่เหลือก็ตามนั้นเลย

ทั้งสามส่วนนี้ก็ไม่มีอะไรมากเราจำเป็นต้องสร้าง value และ setValue เพื่อ assign ค่าให้กับ input ของเราและดัก Event Onchange ไว้เพื่อให้มี Value สำหรับส่งต่อให้รายชื่อที่ props เข้ามาส่วน Header และ Footer ก็ตามภาพข้างล่างเลยครับ

จากภาพข้างบนจะเห็นได้ว่า Header และ Footer รับตัวแปร Lists ของแต่ละเรื่องเข้ามาเพื่อแสดงจำนวนตัวละคร และ ตัวละครที่เพิ่มเข้ามาล่าสุด

Result

จากภาพด้านบนโค้ดของเราทำงานได้แล้วววโค้ดที่เห็นก็ไม่มีอะไรมากแค่ Pass Value จาก Parent และให้ Child รอ props ค่าที่ได้มางั้นเราลองมาสร้างปัญหาง่ายๆกัน เริ่มจากเราต้องการให้มีปุ่มเพิ่มอีกอันนึง แต่เราต้องการให้เห็นไปสร้าง Unorder List (UL) ที่ AvengersListsComponent

Content.jsx

เราได้สร้าง state และ funtion สำหรับ assign ค่าให้มันซักตัวนึงโดยมีค่า default คือ array ว่าง สาเหตุที่เราต้องตั้งค่าแบบนี้เพราะว่าการที่จะให้ OnePieceListComponent นำข้อมูลของ Component มันเองมาแสดงใน AvengersListsComponent นั้นทำไม่ได้เพราะมันเป็น Child เหมือนกันเราจึงต้อง assign ค่าใน parent และส่ง function สำหรับการเปลี่ยนค่าไปให้ child ทำกันเองในที่นี้ก็คือ OnepieceListComponent กระทำการเปลี่ยนแปลง แต่ AvengersLists รอรับค่าอยู่แล้ว

จากภาพข้างบน OnePieceListComponent สร้าง input และ button เพิ่มมาสำหรับ assign ค่าใหม่ที่จะส่งให้ AvengersListsComponent และ AvengersListsComponent ก็จะแสดงผลตามที่กด Add เข้ามา

Result

หลังจากกด Add ที่ข้อมูลจะมาแสดงใน AvengersComponent ที่เดียว (เพราะเราส่งมาให้มันที่เดียว) จากตัวอย่างที่ยกมาจะบางคนคงเข้าใจปัญหาของมันบ้างแล้ว หรือ ถ้าใครยังไม่เข้าใจให้ลองคิดว่าถ้ามี Component ที่มันย่อยไปมากกว่านี้และแต่ละ Component มีค่าที่ต้องการต่างกัน บาง Component นั้นไม่ได้ใช้ค่าอะไรเลยแต่ต้องมานั่ง props data ไว้เพื่อให้ Component อื่นๆที่อยู่ในนั้นใช้งาน แถมยังกลับมา maintenance code ยากอีกด้วยใครมาดูก็ลำบากเพราะต้องไล่หาค่านี้ props มาจากไหนส่งไปให้ Component ไหนบ้างอันนี้ใช้หรือป่าว อันนี้ไม่ได้ใช้หรือป่าว

แต่ปัญหานั้นเราสามารถแก้มันได้ด้วยการใช้ Global State โดยส่วนมากแล้วคนที่ใช้ React คงหนีไม่พ้น Redux หรือ Mobx แต่วันนี้ผมจะมาสอนจัดการ Global State โดยการใช้ Easy-Peasy

Vegetarian friendly state for React

Easy-Peasy คืออะไร ??

ง่ายๆเลยครับมันคือคำพูดตอนจบท้ายเกมส์ไงล่ะ “gg easy, gg ez” (ไม่ใช่ละ)

เขาเคลมว่าสามารถที่จะช่วยจัดการกับ State “ง่าย, เร็ว” และ ไว อีกทั้งยังไม่ต้องมานั่ง config อะไรยุ่งยากเลย มีให้พร้อมหมดแล้ว Thunk ไว้สำหรับเป็น middleware เช่นตอนเรียก API ประสิทธิภาพยังดีอีกด้วย และยัง support developer tools ที่เอาไว้ debug อีกด้วย สำหรับใครอยากอ่านต่อเชิญที่นี่เลย https://easy-peasy.now.sh/

เริ่มต้นด้วยการติดตั้ง

npm i easy-peasy

หลังจากที่ติดตั้งเสร็จแล้ว เราจะเปลี่ยนจากการ pass value และรอ props value แบบเดิมๆที่เราทำกันเมื่อกี้มาใช้ easy-peasy แทน

เริ่มแรกเราต้องประกาศ StoreProvider กันซะก่อนเพื่อให้มันใช้งาน Global State ได้

StoreProvider

เป็นเสมือนตัวกลางในการเก็บข้อมูลภายใน Application ของเราทั้งหมดหากเราต้องการจะให้ข้อมูลไหนเป็น Global เราสามารถมาประกาศในนี้ได้เลย

App.js

เริ่มจากการเรียกใช้ createStore และ StoreProvider กันก่อนเลย โดยใน Store ของเราจะเก็บ object ไว้ซึ่งใน object จะมีชื่อตัวละครแต่ละเรื่องอยู่ในนั้น จากนั้นโยน Store เข้าไปใน StoreProvider เพื่อบอกว่าเราจะใช้ค่าพวกนี้เป็น Global State นะ

ได้เวลาเรียกใช้งานแล้วววววววว

useStoreState

หากเราต้องการเรียกใช้ State เราสามารถใช้ useStoreState เพื่อให้มัน return state ที่เราต้องการใช้โดยถ้าหากมี Component ไหนเราก็แค่เพียง assign ตัวแปรซักตัวและนำค่ามาเก็บในตัวแปรนั้นได้เลย

ส่วนวิธีเรียกใช้งานก็ง่ายๆเลยแค่ import useStoreState เข้ามาจากนั้นถ้าเราต้องการใช้ state ตัวไหนก็เพียงแค่ useStoreState แล้วมันจะ return state ทั้งหมดที่เก็บไว้ใน store ออกมาใช้งานเราแค่เพียงเรียกให้ถูก key object ที่จะใช้

Result

ตอนนี้เราดึงค่ามาแสดงได้แล้ว แล้วถ้าเราต้องการจะเพิ่มค่าเข้าไปใน store ล่ะ ???

Action

action นั้นเป็นตัวที่จะส่งข้อมูลจาก Application ของเราไปยัง Store เพื่อบอกว่าเราต้องการจะเปลี่ยนแปลง State ตัวไหน

App.js

เริ่มแรกเราต้องทำการ Import action เข้ามาใช้งานก่อนซึ่งการ assign action ก็สามารถทำได้ด้วยการประกาศเข้าไปใน object โดยเราสามารถตั้งชื่อ key ที่จะใช้เรียกได้เลย action จะรับ parameter อยู่ 2 ตัว คือ state ก่อนหน้านี้ และ payload คือค่าที่ส่งเข้ามา (state สามารถใช้หลักการ Destructuring ได้ด้วยนะ ถ้า object นั้นมี state หลายตัว)

อย่างแรกเราต้อง import useStoreActions เข้ามาก่อน (เหมือนกับ useStoreState เลย) เพื่อเข้ามาเรียกใช้ Action ที่เหล่า assign ไว้ก่อนหน้านี้ จากนั้นก็เรียกใช้ useStoreActions ได้เลยโดยเราก็งานเหมือนเรียกใช้ state ได้เลยแต่เปลี่ยนเป็นชื่อ key ของ action ที่เรา assign ไว้

Result

งั้นเราเอาปัญหาที่เราลองทำเล่นๆมาใช้กัน เริ่มแรกเราก็สร้าง object ใน store ขึ้นมาใหม่ซักอันเพื่อเอาไว้เก็บข้อมูลนั้นโดยเฉพาะ

App.js

เมื่อเราประกาศได้แล้วเราก็จะสามารถเรียกใช้ state และ action ได้ทันทีเลย

Result

สรุป

ในการพัฒนาเว็บโดยใช้ Component Based นั้นปัจจุบันได้รับการนิยมมากและปัญหาหลักๆของมันคือการจัดการกับ State แต่ทั้งนี้ทั้งนั้นเราสามารถใช้ Easy-Peasy เข้ามาช่วยแทนได้ (หรืออาจจะ Redux , Mobx) แต่ตั้งแต่รู้สึกมาใช้ Easy-Peasy มันสะดวกขึ้นนี่แค่เรื่อง state และ action นะจริงๆมันยังมีเรื่อง middleware thunk , reducer, และ DI อีกด้วยอีกทั้งยังมี Typescript Support ด้วย ยังไงถ้ามีเวลาผมจะมาเขียนให้อ่านกันอีกครั้ง หรือใครอยากให้ผมเขียนเรื่องอะไรสามารถ reply ทิ้งไว้ได้เลย ถ้ามีเวลาจะศึกษามาเขียนให้

--

--