Tuesday, July 18, 2023

มาทำความรู้จัก React Context API กันเถอะ

 เมื่อโปรเจคของเราใหญ่มากขึ้นเรื่อยๆ เรามักจะเจอกับปัญหาการจัดการState วันนี้ผมจะมาพาทุกคนไปรู้จักกับReact APIตัวหนึ่งที่ช่วยให้เราสามารถจัดการStateได้สะดวกมากยิ่งขึ้น โดยเฉพาะในโปรเจคขนาดใหญ่

What is Context?

ContextคือReact APIที่ช่วยให้เราสามารถแชร์ข้อมูลระหว่างComponentsได้โดยไม่ต้องส่งProps

การแชร์ข้อมูลระหว่าง Components โดยไม่ใช้ Context

เมื่อก่อนสมัยที่เรายังไม่มีContextหากเราต้องการแชร์ข้อมูลกันระหว่างComponentsเราต้องประกาศStateไว้ที่Top-Level Componentและส่งเป็นPropsลงมาท่ีChildren Componentsเพื่อให้แต่ละComponentสามารถใช้ข้อมูลร่วมกันได้

Why?

แผนภาพ Components ในรูปแบบ DOM Tree

สมมติว่าจุดแต่ละจุดในแผนภาพด้านบนเป็นตัวแทนของComponentต่างๆ และให้ComponentsสีดำแทนComponentsที่มีการใช้ข้อมูลร่วมกัน ในทางตรงกันข้ามComponentsสีขาวจะไม่มีการใช้ข้อมูลร่วมกันเลย

เนื่องจากLeft ComponentและGrand-child of Right Componentต้องการใช้ข้อมูลร่วมกัน เราจึงต้องประกาศStateที่จะแชร์ข้อมูลไว้ที่Top-Level Componentและส่งเป็นPropsไปให้Left ComponentและGrand-child of Right Componentแต่เนื่องจากComponentนี้ไม่ได้อยู่ติดกับTop-Level Componentมันจึงจำเป็นต้องส่งPropsไปให้Right ComponentและChild of Right Componentก่อน แล้วจึงส่งต่อให้Grand-child of Right Component

คำถามคือทำไมComponentsที่ไม่ได้ใช้ข้อมูลร่วมกับLeft ComponentและGrand-child of Right componentต้องมาทำหน้าที่ส่งต่อPropsให้ด้วย วิธีนี้ทำให้โค้ดของเราดูไม่สบายตาเท่าไหร่นัก และนอกจากนี้หากPropsที่ส่งมามีการเปลี่ยนแปลง Componentsที่ทำหน้าที่ส่งข้อมูลให้ก็จะต้องRe-renderตามไปด้วย โดยที่ไม่จำเป็นเลย

การRe-renderนั้นควรเกิดขึ้นในComponentที่ใช้ข้อมูลนั้นๆเท่านั้น ไม่เช่นนั้นในที่สุดแล้วอาจทำให้เกิดปัญหาด้าน Performance ได้

และนี่จึงเป็นคำตอบของคำถามที่ว่าเรามีContextไว้ทำไม Contextเกิดขึ้นมาเพื่อลบข้อเสียที่ได้กล่าวมาข้างต้น

Contextช่วยให้เราสามารถใช้ข้อมูลร่วมกันระหว่างComponentsได้ โดยที่ไม่ต้องใช้Propsหากเราต้องการใช้ข้อมูลที่ใด ก็เรียกใช้Contextที่Componentนั้นพอ Componentsอื่นๆ และหากข้อมูลภายในContextเปลี่ยนแปลง ก็จะเกิดการRe-renderเฉพาะComponentsที่ใช้Contextนั้นเท่านั้น

นอกจากนี้ Context ยังช่วยทำให้ComponentsของเราสามารถReuseได้ง่ายขึ้นอีกด้วย เนื่องจากมันไม่ได้ผูกกับPropsแล้ว

When?

Context นั้นควรใช้ในกรณีที่จำเป็น เนื่องจากเป็นการเพิ่มความซับซ้อนให้แก่โค้ดของเรา ทำให้เราเจอความลำบากมากขึ้นเมื่อต้องมาแก้ไขโค้ดในภายหลัง

React DOM Treeแสดงเหตุการณ์ที่เหมาะสมต่อการใช้งานContext

จากDOM Treeด้านบนจะเห็นได้ว่าComponent1,6,7,8ต่างต้องการใช้งานข้อมูลร่วมกัน และแต่ละComponentนั้นอยู่ในระดับที่แตกต่างกันทั้งแนวตั้งและแนวดิ่ง การส่งเป็นPropsลงมาจะทำให้ทุกComponentต้องถือPropsเดียวกัน ทั้งที่บางComponentอาจไม่จำเป็นต้องใช้เลย ดังนั้นจึงควรใช้Contextเพื่อหลีกเลี่ยงปัญหาดังกล่าว

React DOM Treeแสดงเหตุการณ์ที่ไม่จำเป็นต้องใช้Context

ตัวอย่างกรณีที่ไม่จำเป็นต้องใช้Context เช่นเรามี3Componentsที่ต้องใช้ข้อมูลร่วมกัน แต่ว่าแต่ละComponentอยู่ห่างกันไม่เกิน1Level เคสแบบนี้การประกาศStateที่Component 1และส่งPropsมาที่Component2,4อาจเหมาะสมกว่า เนื่องจากจะเห็นได้ว่าไม่มีComponentไหนที่ได้รับข้อมูลโดยที่ไม่จำเป็นเลย

How?

เมื่อเรารู้จักContextและรู้ว่าควรใช้งานมันเมื่อไหร่แล้ว ก็ถึงเวลาเรียนรู้ว่าจะใช้งานมันได้อย่างไร

ก่อนที่ผมจะแนะนำวิธีการใช้งานContext ต้องขออนุญาตแนะนำContext APIที่จำเป็นต้องรู้จักกันก่อน

React.createContext

APIตัวแรกที่ต้องรู้จักคือcreateContext มันเป็นตัวสร้างContext Objectขึ้นมาเพื่อเก็บข้อมูลที่ต้องการใช้ร่วมกัน, ใช้เป็นProvider, และใช้Subscribe Contextที่Componentsปลายทาง

Context.Provider

อย่างที่เรียนให้ทราบด้านบน การสร้างContext Objectจะมาพร้อมกับProvider ซึ่งเจ้าProviderจะช่วยให้Componentsปลายทางทั้งหลายสามารถSubscribeการเปลี่ยนแปลงข้อมูลภายในContextได้

Class.contextType

ใช้เพื่อSubscribeข้อมูลภายในContextที่Componentsปลายทาง แต่จะใช้ได้กับClass Componentเท่านั้น และหากSubscribe Contextด้วยวิธีนี้เพียงวิธีเดียวจะทำให้เราสามารถSubscribeได้เพียงหนึ่งContextเท่านั้น

Context.Consumer

ใช้เพื่อSubscribeข้อมูลภายในContextที่Componentปลายทาง โดยที่สามารถใช้ได้กับทั้งClassและFunctional Component การSubscribe Contextด้วยวิธีนี้จะทำให้เราสามารถSubscribeได้มากกว่าหนึ่งContext

React.useContext

ใช้เพื่อSubscribeข้อมูลภายในContextที่Componentปลายทาง ใช้ได้กับFunctional Componentเท่านั้น การSubscribe Contextด้วยวิธีนี้จะทำให้เราสามารถSubscribeได้มากกว่าหนึ่งContext

วิธีนี้เป็นวิธีที่ง่ายและนิยมที่สุดในการSubscribeข้อมูลจากContext

Example

เรารู้จักContextกันมามากพอแล้ว สิ่งต่อไปที่เราอยากเห็นคงหนีไม่พ้นตัวอย่างการใช้งาน

ต้องขอบอกว่าวิธีการใช้งานที่ผมกำลังจะนำเสนอเป็นหนึ่งในวิธีการใช้งานเท่านั้นและไม่การันตีว่าจะเป็นวิธีการที่ดีที่สุด

ภาพตัวอย่างการสร้างProvider

ในที่นี้เราจะสร้างCart Contextเพื่อใช้เก็บข้อมูลสินค้าในรถเข็น

เริ่มแรกเราต้องสร้างContext Objectขึ้นมาก่อน แล้วจึงสร้างCartProvider Componentเพื่อใช้เป็นที่เก็บStateและActionต่างๆของContext

ในCartProviderเราจะสร้างStateขึ้นมาเพื่อใช้เป็นที่เก็บข้อมูลต่างๆที่ต้องการใช้ร่วมกัน และสร้างAction Fucntionต่างๆที่เราจะอนุญาตให้Componentปลายทางสามารถทำได้เช่น addToCart

ต่อมาเราจะสร้างตัวแปรcartStoreขึ้นมาเพื่อเป็นValueให้แก่PropของProvider ซึ่งPropตัวนี้จะเป็นค่าที่เราจะส่งไปให้Consumerนั่นเอง

ซึ่งค่าของตัวแปรcartStoreผมดีไซน์แยกออกเป็นสองส่วนคือส่วนของStateและAction จากโค้ดจะเห็นได้ว่าStateจะอยู่ในLevelของroot ส่วนActionจะถูกwrapไว้ด้วยkey cartAction อีกที

วิธีนี้ทำให้เมื่อเราอ่านโค้ดที่Componentปลายทาง เราสามารถเข้าใจได้ในทันทีเลยว่ามันกำลังใช้ค่าจากStateหรือกำลังจะทำActionอะไรบางอย่างกับContextอยู่

สุดท้ายเราจะสร้างCustom Hookชื่อว่าuseCartContextขึ้นมา ประโยชน์ของHookตัวนี้คือทำให้เราไม่ต้องImport useContextและCartContextในทุกComponentปลายทาง ที่ต้องการSubscribe เพียงImport useCartContextไปตัวเดียวก็สามารถใช้ข้อมูลของContextได้แล้ว ทำให้โค้ดคลีนขึ้น

เมื่อสร้างCartProviderเสร็จแล้ว เราก็จะนำProvider ตัวนี้ไปครอบเป็นParent Component ที่ระดับApp levelเพื่อให้ทุกComponentในแอปของเราสามารถเรียกใช้งานContextตัวนี้ได้นั่นเอง

โค้ดตัวอย่างการใช้งานProvider

หากเรามีมากกว่าหนึ่งContextเราก็สามารถสร้างProviderอื่นๆขึ้นมาได้และนำมาใช้ในรูปแบบเดียวกัน

โค้ดตัวอย่างการSubscribe Contextที่Componentปลายทาง

หากComponentไหนต้องการใช้งานCartContextเพียงแค่Import useCartContextมาใช้ ก็จะสามารถใช้งานได้โดยที่ข้อมูลที่ได้รับจะเป็นข้อมูลที่แชร์กันระหว่างComponents

High Order Componentเพื่อใช้Subscribe Context

หากเราต้องการSubscribe Context Valueที่Class Componentเราอาจสร้างHigh Order Componentขึ้นมาตัวหนึ่งเพื่อใช้ส่งContext Valueไปเป็นPropให้กับComponentปลายทาง

Caution!!!

จุดที่ทุกคนควรรู้และระวังคือContextไม่มีการพิจารณาว่าComponentsปลายทางสมควรที่จะถูกRe-renderหรือไม่ เพราะฉะนั้นทุกครั้งที่ข้อมูลภายในContextเปลี่ยนแปลง Componentปลายทางจะถูกสั่งRe-renderด้วย

หากเราต้องการให้โค้ดบางส่วนทำงานก็ต่อเมื่อข้อมูลในContextบางตัวเปลี่ยนเท่านั้น ไม่ใช่ทุกตัว เราอาจใช้useMemoเข้ามาช่วยได้ครับ

หากสนใจศึกษาข้อมูลเกี่ยวกับContext APIเพิ่มเติม สามารถดูได้จากOfficial Documentเลยครับ

Source : https://medium.com/@pakpoom.thawee/%E0%B8%A1%E0%B8%B2%E0%B8%97%E0%B8%B3%E0%B8%84%E0%B8%A7%E0%B8%B2%E0%B8%A1%E0%B8%A3%E0%B8%B9%E0%B9%89%E0%B8%88%E0%B8%B1%E0%B8%81-react-context-api-%E0%B8%81%E0%B8%B1%E0%B8%99%E0%B9%80%E0%B8%96%E0%B8%AD%E0%B8%B0-511d1bd332f1


No comments:

Post a Comment