Redux 101

หมายเหตุ ข้อมูลส่วนใหญ่ภายใน tutorial ฉบับนี้อ้างอิงมาจากจาก https://medium.com/@rajaraodv/step-by-step-guide-to-building-react-redux-apps-using-mocks-48ca0f47f9a#.38tqsub93

Redux คือเฟรมเวิร์คระดับมาเฟียที่ React Apps แทบทุกตัวต้องเลือกใช้ แถมยังมีคนทำ ตัวอย่าง เอาไว้ให้เราได้ศึกษาอีกมากมาย ปัญหาของเฟรมเวิร์คตัวนี้ก็คือ มันมีองค์ประกอบมากมายเหลือเกิน ไม่ว่าจะเป็น “reducer”, “action’ ‘action creator”, “middleware” ฯลฯ ซึ่งน่าจะทำให้มือใหม่กระอักเลือดได้ไม่ยาก

ดังนั้น ใน tutorial บทนี้ จะแสดงขั้นตอนการสร้าง Redux แบบ step-by-step ในแบบที่เข้าใจได้ง่ายที่สุด โดยใช้ Todo list App ซึ่งเป็น app ที่มีเพียงหน้าเดียว มาเป็นตัวอย่างสำหรับใช้อธิบายหลักการของ Redux ซึ่งเมื่อคุณอ่านจนจบแล้ว ก็สามารถนำกระบวนการใน tutorial บทนี้ไปใช้กับการออกแบบระบบอื่นๆ ที่มีหลายหน้าได้เลย

ทำไมต้องเป็น Redux?

React–เป็น JS library ที่ช่วยให้เราสามารถแบ่งส่วนต่างๆ ภายใน app ของเราให้อยู่ในรูปแบบ component หลายๆ ตัว แต่เรื่องของการตรวจติดตามข้อมูลภายในที่เรียกว่า State ทำได้ไม่ดี รวมถึงวิธีการจัดการกับพฤติกรรม (action) ต่างๆ ก็ไม่ชัดเจน

Redux–เป็น library ที่ออกคู่มากับ React (แต่สามารถนำไปใช้กับ JS framework ใดๆ ก็ได้) ที่ช่วยให้เราจัดการกับข้อมูล (state) และพฤติกรรม (action) ได้อย่างมีระเบียบเรียบร้อยขึ้น

Redux คือ lirary ที่ช่วยตัดงานในส่วนของการจัดการ State และ Action ออกจาก React

การสร้างระบบโดยไม่ใช้เฟรมเวิร์คใดๆ เข้ามาช่วยนั้น จะง่ายในตอนสร้างให้ app และจะยุ่งในตอน production ส่วนการใช้เฟรมเวิร์คอย่าง Redux อาจจะดูยุ่งยากในตอนสร้าง app แต่จะง่ายในตอนทำ production

เรียน Redux แบบสังเขป

มีใครเคยไปฝากเงินในต่างประเทศบ้างมั๊ยครับ? ระบบการฝาก/ถอนของฝรั่งเขาจะต่างกับเราพอสมควร ตรงที่ ผู้นำฝากสามารถขับรถเข้าไปที่ “เคาน์เตอร์” ซึ่งตรงนั้นไม่มีพนักงานแบงค์ยืนรอเราอยู่เหมือนในเมืองไทย แต่มันเป็นท่อสุญญากาศขนาดเล็กที่มีไว้เพื่อดูดเงินที่เราต้องการนำฝาก โดยเมื่อรถของเราจอดที่หน้าท่อแท่งนั้น เราจะได้รับสลิปนำฝากที่มีหน้าตาประมาณนี้

mb_deposit_print

คุณก็กรอกข้อมูลนำฝากไป ซึ่งคุณจะต้องกรอกข้อมูลให้ชัดเจนว่าคุณต้องการทำธุรกรรมอะไร เพราะสลิปตัวนี้จะถูกส่งไปถึงพนักงานแบงค์ภายในที่ในวันหนึ่งๆ ต้องรับผิดชอบกับคำร้องขอทำธุรกรรมจากลูกค้านับร้อยคน ดังนั้น เป้าหมายการทำธุรกิจในสลิปต้องชัดเจน จากนั้นก็ทำการแนบเงินสดพร้อมกับสลิปฝากเงินใส่เข้าไปในท่อนำฝาก (ที่มีแต่เฉพาะระบบการฝากเงินในแบงค์ฝรั่ง) ดังรูป

a4s_bofa060615_15339026_8col

หลังจากนี้ก็เป็นหน้าที่ของพนักงานแบงค์ในการดึงสลิปเงินฝาก พร้อมเงินสดออกมาแล้วฝากเข้าไปในบัญชีที่ได้รับการระบุไว้ภายในสลิป

นี่คือระบบในโลกของความเป็นจริง ซึ่งบังเอิญเหลือเกินว่า มันดันไปเหมือนกับระบบของ Redux (จริงๆ ระบบ หรือโครงสร้างของระบบ IT มันก็เลียนแบบระบบอื่นๆ ในโลกแห่งความเป็นจริงทั้งนั้นแหละ)

รายละเอียดเป็นดังนี้

สมมติเรากำลังสร้าง application ให้กับระบบฝาก/ถอน และออกแบบให้ state ของระบบเป็นที่เก็บข้อมูลบัญชีของลูกค้าทั้งหมด ซึ่งมีทั้งชื่อบัญชี และเงินคงเหลือ ซึ่งหน้าตาของ state จะเป็นดังนี้

key แต่ละตัวภายใน state จะใช้เลขที่บัญชีแทนค่า โดยมี value เป็นข้อมูลต่างๆ ของแต่ละบัญชี (ชื่อบัญชี และยอดเงินคงเหลือ)

ย้อนกลับมาที่เหตุการณ์เดิมอีกครั้ง คุณขับรถเข้าไปที่ท่อสุญญากาศ ดึงสลิปออกมา แล้วกรอกข้อมูลลงไปเพื่อบรรยายลงไปว่าคุณอยากจะทำอะไรกับเงินคงเหลือในบัญชีของคุณ ซึ่งคุณอาจจะเพิ่มเงินคงเหลือ (ฝาก) หรือหักออก (ถอน)

ในโลกของ Redux นั้นสลิปเงินฝากจะถูกเรียกว่าเป็น action หรือ object ที่แจ้งให้รู้ว่า จะมีการเปลี่ยนแปลงค่าใน state ตัวไหนบ้าง

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

action แต่ละตัวนั้น จะมี property แบ่งได้เป็นสองกลุ่มคือ กลุ่ม “type” หรือชนิดของ action และกลุ่ม “ข้อมูล” ซึ่งเป็นข้อมูลที่จะนำไปใช้กับ action ใน type นั้น

จากตัวอย่างข้างต้น นี่คือ action ที่มีชนิดเป็น “การฝากเงิน” โดยมีการแนบเลขบัญชี และเงินจำนวน 10 เหรียญมาด้วย

ตัวต่อมาก็คือ ท่อนำฝากที่ใช้ใส่สลิป และเงินสด ซึ่งประโยชน์ของมันก็คือ ช่วยห่อหุ้มให้ทั้งสลิป และเงินสด (action) สามารถเคลื่อนย้ายไปยังที่ไหนก็ได้โดยไม่หล่นกระจัดกระจาย สำหรับท่อนำฝากนั้น ใน Redux เรียกว่าเป็น action creator

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

มาถึงพนักงานแบงค์ หน้าที่รับผิดชอบหลักของเขาก็คือการคว้าท่อนำฝากแล้วควักเอาทุกสิ่งอย่างออกมาจากตัวมัน ซึ่งประกอบไปด้วยข้อมูลบัญชีของคุณ เงินสด และชนิดของคำร้องว่าจะให้ทำอะไรกับบัญชีดังกล่าว (ฝาก หรือถอน) Redux เรียกพนักงานแบงค์ว่าเป็น reducer

ตัว reducer จะรับข้อมูล state ปัจจุบันทั้งหมด (หรือบางส่วน) ของ application และ action ที่ระบุไว้ว่าจะแก้ไขข้อมูลใน state ตัวไหน จากนั้น reducer จะสร้าง state ตัวใหม่ขึ้นมา แก้ไขข้อมูลตามที่ action ระบุไว้ แล้วสุดท้ายเราก็จะได้ state ตัวใหม่ที่มีข้อมูลที่ได้รับการเปลี่ยนแปลงแล้ว

Object.assign() function

จาก code ข้างต้น จะมีการเรียกใช้ฟังก์ชั่นตัวหนึ่ง

ฟังก์ชั่นนี้จะทำการ merge ข้อมูลภายในของ argument ทุกตัว จากนั้นก็จะ return object ที่ได้รับการ merge ไปเรียบร้อยแล้ว

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

จากตัวอย่างข้างต้น เมื่อเรานำ o1, o2 และ o3 มาใส่ในฟังก์ชั่น Object.assign มันจะทำการ merge ข้อมูลทั้งหมดเข้ามาเป็น object ตัวเดียว โดยในที่นี้จะได้ object ตัวใหม่ รวมทั้งตัว o1 ซึ่งเป็น argument ตัวแรก (target object) ก็ถูกเปลี่ยนค่าไปด้วย

นอกจากนี้หากใน object แต่ละตัวมี property ที่ซ้ำกัน มันจะทำการ merge ให้ด้วยความฉลาดดังนี้

จะเห็นว่า ตัว o2 มี property ที่ซ้ำกับ o1 สองตัว Object.assign() ก็จะนำค่าใน o2 มาวางทับใน property ที่ซ้ำกัน และ o3 ก็มีค่า c ที่ซ้ำกับ o2 ดังนั้น ผลลัพธ์สุดท้ายของ ค่า c จึงเท่ากับ 3 แปลว่า ตัวหลังสุด มีค่า priority มากที่สุด

กลับมาที่ Redux

โดยสรุป เมื่อนำข้อมูลใน redux กับ ระบบการฝาก/ถอนเงินมาเปรียบเทียบกัน เราจะได้ข้อมูลดังนี้

 ธนาคาร  Redux
 สลิป  action
 ท่อนำฝาก  action creator
 พนักงานแบงค์  reducer

 

Redux นั้นยังมีองค์ประกอบอื่นๆ อีกนอกเหนือไปจาก action, action creator และ reducer เพียงแต่ทั้งสามตัวนี้คือองค์ประกอบหลักของเฟรมเวิร์คตัวนี้ หากสามารถเข้าใจหลักการทำงานของพวกมัน ก็เท่ากับเข้าใจ Redux ไปแล้วเกิน 70%

ขั้นต่อไป เรามาเริ่มดูขั้นตอนการพัฒนา app จริงๆ กันเลยดีกว่าว่า Redux ทำงานอย่างไร โดยเราจะว่ากันทีละขั้น ดังนี้

ขั้นที่ 1–สร้าง Screen Mockup

Mockup ที่ดีนั้นควรจะแสดงข้อมุลที่จำเป็นให้ครบถ้วน รวมไปถึงพวก visual effects ต่างๆ (อย่างการขีดฆ่าตัว TodoItem บางตัวหรือ การแสดงตัวกรอง All ให้แสดงผลออกมาในรูปแบบของ text ธรรมดา แทนที่จะเป็น link button แบบตัวอื่น)

step1

ขั้นที่ 2–แบ่ง app ออกเป็น component หลายๆ ตัว

พยายามแบ่ง app ออกเป็นก้อน component โดยให้แบ่งตาม “จุดประสงค์” การใช้งาน

เราสามารถแบ่ง app นี้ออกได้เป็น 3 component กล่าวคือ “AddToDo”, “TodoList” และ “Filter”

step2

ขั้นที่ 3–แจกแจงรายละเอียดของ State และ Action ของ component แต่ละตัว

นำ component ที่เราแตกได้แล้วในขั้นตอนที่ 2 มาแจกแจงรายละเอียดของ state และ action ทีละตัว

ตอนนี้เราแยก component ได้ 3 ตัว ดังนั้น เราจะเริ่มแสดง action และ state ของแต่ละตัวกันเลย

3.1 AddTodo component–State และ Action

component ตัวนี้จะไม่มีการเปลี่ยนหน้าตาใดๆ ดังนั้น มันจึงไม่จำเป็นต้องมีข้อมูล state แต่งานของมันก็คือ เมื่อผู้ใช้งานกรอก todo แล้วกดปุ่ม Add มันจะต้องแจ้งให้ component ตัวอื่นๆ รับรู้ ผ่าน action ที่ชื่อว่า “ADD_TODO”

step31

3.2 TodoList Component–State และ Action

TodoList component ต้องใช้ array ของรายการ todo ทั้งหมด เพื่อนำมาใช้ในการ render หน้าจอ ดังนั้น state ที่ต้องมีก็คือ Todos ซึ่งมีชนิดข้อมูลเป็น array นอกจากนี้ยังต้องมี State ที่ใช้สำหรับกรองข้อมูล todo แต่ละตัวว่าจะแสดงหรือซ่อนจากหน้าจอ ผมขอตั้งชื่อ state ตัวนี้ว่าเป็น VisibilityFilter ซึ่งมีชนิดข้อมูลเป็น boolean

ในส่วนของ action นั้น component ตัวนี้จะสามารถให้ผู้ใช้คลิกไปที่ todo แต่ละตัวได้ การคลิกแต่ละครั้งจะเป็นการเปลี่ยนสถานะของ todo จาก not completed ไปเป็น completed (หรือตรงกันข้ามก็ได้) ซึ่งการเปลี่ยนแปลงสถานะของ todo นี้จำเป็นต้องให้ component ตัวอื่นรับทราบด้วย ดังนั้น เราขอตั้งชื่อ action ตัวนี้ว่า “TOGGLE_TODO”

step32

 3.3 Filter component–State and Action

Filter component มีหน้าที่ในการเปลี่ยนตัวเองให้เป็น Link หรือ Text ธรรมดา ขึ้นอยู่กับว่า filter ตัวไหน ระหว่าง All, Active และ Completed จะ active (ตัว active จะแสดงเป็น text ธรรมดา ส่วนตัวที่ inactive จะแสดงเป็น Link) ซึ่งเราจะต้องใช้ state ที่มีชื่อว่า “CurrentFilter”

เมื่อใดก็ตามที่ผู้ใช้มาคลิก Filter component มันจะต้องแจ้งให้ component อื่นได้รับรู้ผ่าน action ที่ชื่อ “SET_VIBILITY_FILTER”

step33

ขั้นที่ 4–สร้าง action creator ประกบกับ action ทุกตัว

ตอนนี้เรามี action ทั้งหมด 3 ตัวแล้ว นั่นคือ ADD_TODO, TOGGLE_TODO และ SET_VISIBILITY_FILTER ในขั้นตอนนี้เราจะมาสร้าง action creator (หากงง ให้นึกถึงท่อนำฝากสลิปในระบบฝาก/ถอน ครับ ส่วน action คือ สลิปนำฝาก พร้อมเงินสด) ให้กับ action แต่ละตัวกัน

หมายเหตุ: สังเกตุวิธีตั้งชื่อ action creator (ท่อน้ำส่งสลิป) มั๊ยครับ มันเอา action มาทำเป็น camelCase เช่น TOGGLE_TODO กลายมาเป็น toggleTodo เป็นต้น

ได้เวลาของ “reducer” (พนักงานแบงค์)

reducer (พนักงานแบงค์) คือฟังก์ชั่นที่รับ argument สองตัว กล่าวคือ state (ที่มาจาก redux) และ action object จากนั้นก็ return ข้อมูล state ของ application ชุดใหม่ออกมาคืนให้กับ redux ไป

step การทำงานโดยคร่างๆ ของ reducer จะเป็นดังนี้

  1. เมื่อผู้ใช้มี action อะไรกับระบบ มันจะไปเรียกใช้ฟังก์ชั่น reducer
  2. แล้วหลังจากที่ reducer ทำการเปลี่ยนแปลงค่า state ไปแล้ว Redux จะส่งค่าของ state ใหม่นี้ไปยัง component แต่ละตัว เพื่อให้พวกมันทำการ re-render ตัวมันเอง ตามข้อมูลใน state

ยกตัวอย่างเช่น ในฟังก์ชั่น reducer ต่อไปนี้ จะรับค่า state จาก Redux (ซึ่งคือ array ของรายการ todos) แล้วจากนั้นก็จะทำการ return array ของ todos ชุด ใหม่ กลับไป โดยจะมีการเติม todo ตัวใหม่เข้าไปด้วย หาก action ที่วิ่งเข้ามาคือ “ADD_TODO”

ES6 Spread

จาก code ข้างบนจะเห็น syntax แปลกๆ …state ซึ่งเป็นอีกหนึ่งฟีเจอร์ของ ES6 เรียกว่า spread ซึ่ง spread นี้จะช่วยอำนวยความสะดวกให้กับการเขียน Redux ได้อย่างมาก 

กล่าวโดยสรุปก็คือ spread นั้นก็คือ Object.assign() นั่นแหละ ดังนั้น จาก code

สามารถเขียนแบบใช้ Object.assign() ได้เป็น

จะเห็นว่าการใช้ spread (…) จะช่วยลดจำนวน code ที่เราต้องเขียนลง และทำให้อ่านง่ายขึ้น (หรือเปล่า)

ขั้นที่ 5: เขียน reducer เพื่อรองรับ action ทั้งหมด

reducer โดยส่วนมากนั้นจะเขียนแบบ switch…case เพื่อใช้ในการคัดกรอง action.type ว่าตรงกับตัวไหน แล้วจึงค่อยประมวลผลสร้าง state ใหม่ขึ้นมาเพื่อ return กลับไป

Component VS Container (Presentational VS Container)

การเอา logic ทั้งของ React และ Redux มายำรวมกันอยู่ใน component เดียวกันคงทำให้ code ยุ่งพิลึก ดังนั้น Redux จึงแนะนำให้นักพัฒนาสร้าง component ที่ใช้เพื่อจุดประสงค์การแสดงผลอย่างเดียวขึ้นมา (Presentational component) แล้วพวก code ที่เกี่ยวกับ Redux อย่าง action หรือ logic ต่างๆ ให้ใส่เข้ามาที่ Container เอา

หน้าที่ของ Container (หรือที่ผมชอบเรียกว่า component ตัวแม่) คือ ส่ง data ไปให้กับ presentation component, จัดการ event, และดูแลเรื่องต่างๆ ที่เป็นงานหลังบ้านของ React

screen-shot-2559-11-30-at-11-39-50-am

คำอธิบาย:
เส้นประสีเหลือง = Presentational Component
เส้นประสีดำ = Container Component

ขั้นที่ 6–สร้าง Presentational Component ทั้งหมด

ได้เวลาสร้าง component ทั้งสามตัวแล้ว

6.1–สร้าง AddTodoForm Presentational Component

step61

6.3–สร้าง TodoList Presentational Component

step62

6.3–สร้าง Link Presentational Component

step63

หมายเหตุ: Link component แต่ละตัว จะเป็น component ภายใน container ที่ชื่อ “FilterLink” แล้วจากนั้นจึงนำ “FilterLink” ทั้งสามตัวไปวางไว้ใน “Footer” container

ขั้นที่ 7–สร้าง Container ให้กับ Presentational Component

นี่คือขั้นตอนที่เราจะนำ Redux มาใช้งานร่วมกับ component แต่ละตัวกันแล้ว

7.1 สร้าง Container–AddTodostep7

 

จาก code ใน AddTodo จะมีการเรียกใช้ฟังก์ชั่นที่ชื่อ connect ซึ่งเป็นฟังก์ชั่นของ Redux ทำหน้าที่ในการส่งผ่าน state และ action creator เข้าไปใน component ปลายทาง ดังรูป

code

connect จะทำการนำ action creator (onSubmit) ภายใน mapDispatchToProps ซึ่งเป็น argument ตัวที่สอง มาฉีดเข้าไปใน component ปลายทาง ซึ่งในที่นี้คือ AddTodoForm

โดย code ตัวเต็มของ AddTodo container เป็นดังนี้

7.2 สร้าง Container–TodoList

step72

ซึ่ง code ตัวเต็มของ VisibleTodoList จะเป็นดังนี้

จะเห็นว่าฟังก์ชั่น connect ในครั้งนี้มี argument ตัวแรกเพิ่มเข้ามา ซึ่งเจ้า mapStateToProps นี้จะทำหน้าที่ในการนำ state มา “ฉีด” เป็น props ใส่เข้าไปใน component ปลายทาง ซึ่งในที่นี้ก็คือ TodoList

สรุปก็คือ connect() จะรับ argument สองตัวนั่นคือ ฟังก์ชั่นที่แปลง state เป็น props (mapStateToProps) และ ฟังก์ชั่นที่เปลี่ยน action creator ให้กลายเป็น props (mapDispatchToProps) แล้วจากนั้นก็ ฉีด props ทั้งสองกลุ่มนี้ (กลุ่มที่มาจาก state และกลุ่มที่มาจาก action creator) เข้าไปใน component ปลายทาง

หากอ่านแล้วงง ก็ไม่ต้องกังวลไป เพราะกว่าผมในฐานะผู้เขียนจะเข้าใจหลักการนี้ได้ ก็ใช้เวลานานโขอยู่ เดี๋ยวใน tutorial ตอนต่อไปจะกล่าวถึงวิธีการใช้ connect() อีกเยอะ แล้วเดี๋ยวคุณผู้อ่านเขียน code ไปเรื่อยๆ ก็จะเข้าใจไปเอง (หวังว่านะ)

7.3 สร้าง Container– FilterLink

step73

code ตัวเต็มของ FilterLink จะเป็นดังต่อไปนี้

ถือเป็นจบงานครับ

หากผู้อ่านท่านใดอย่างเห็นตัวอย่าง code จริงสามารถเข้าไปดูได้ที่

https://github.com/rajaraodv/redux/tree/master/examples/todos

สรุป

tutorial คราวนี้ว่าด้วยเรื่องของ Redux ภาคทฤษฎี ที่มีการอ้างอิงถึงขั้นตอนในการออกแบบระบบแถมมาให้ด้วย

โดยแกนหลักของ Redux จะมีอยู่สามเสาหลัก ได้แก่

  • action (สลิปเงินฝาก พร้อมเงินสด)
  • action creator (ท่อนำฝาก)
  • reducer (พนักงานแบงค์)

ท่องจำสามเสาหลักตัวนี้ไว้ (พร้อมตัวเปรียบเปรย) รับรองว่าคุณจะไม่เสียใจ