มารู้จักกับ Context API ของ React กัน

Tananan Tangthanachaikul
TakeMeTour Engineering
3 min readMar 11, 2018

เร็วๆ นี้มีโอกาสได้ทำ UI Library ไว้ใช้ใน product ของบริษัท โดยมีจุดประสงค์เพื่ออยากให้ตัว component นั้นสามารถ share ข้ามหลายๆ โปรเจคได้ ซึ่งเป็นไอเดียที่ผมเองผุดมาเพราะมีความปวดหัวเมื่อต้องแก้ component ครั้งนึงนั้น ก็จะต้องไปแก้ในหลายๆ โปรเจค (แถมบาง component ที่แก้นั้นมี API การใช้งานที่ต่างกันอีก!) การทำ UI Library ขึ้นมาจึงช่วยในจุดนี้ได้ โดยการเอา share component กองไว้ในที่เดียวกันเลย

หากใครยังงงๆ ก็ให้นึกถึง Ant Design ครับ สิ่งที่ผมทำก็คือสิ่งนั้นนั่นแหละ

ซึ่งในช่วงแรกก็จะ struggle ปวดหัวเรื่อง setup project ต่างๆ นาๆ ครับ ก็ผ่านพ้นมาได้ แต่สักพักก็ไปค้นพบปัญหาใหม่ที่คิดมาตั้งนานไม่รู้จะใช้ท่าไหนดี จนสุดท้ายได้มีโอกาสจับสิ่งที่เรียกว่า Context API ของ React และมันก็แก้ปัญหาที่ว่านั้นได้ เลยเอาเรื่องนี้มาแชร์ให้ฟังกัน

ปกติเราจะแชร์ data ข้าม component ผ่าน props

แน่นอนว่า use-case ร้อยละ 99.99% นั้นหากเราต้องการแชร์ data ข้าม component ก็จะทำผ่าน props ซึ่ง data flow ก็จะไหลจาก component พ่อสู่ลูก ตามคอนเซปของ React

ถ้ามันห่างไกลกันเหลือเกินล่ะ

แต่ปัญหาเกิดขึ้นคือ ถ้าสมมติว่า component ที่ต้องการ data นี้มีความห่างจาก component พ่อที่ provide data นั้นอยู่ไกลมากๆ เราสามารถทำได้หลายวิธี เช่น

  1. pass props ไล่ไปเรื่อยๆ (มี 5 ชั้นก็โยนข้ามหัวไป 5 ชั้น)
  2. ใช้ Redux, MobX (ดึงค่าจาก store เอา)
  3. Context API

Context API คือ?

Context API คือการที่เราสามารถบอก component ชั้น parent ว่าเราจะให้ค่าของ context มีค่าเป็นอย่างไร และ component ลูกที่อยู่ภายใต้ parent ที่ประกาศ context ไว้ สามารถหยิบค่าจาก context มาใช้งาน component ลูกก็จะบอกว่า อยากได้ context ค่าไหนมาใช้ แล้วนำไปใช้

ตัวอย่างเช่น (ยกมาจากเว็บ document ของ React) สมมติเดิมเรามี component 3 ตัว

  • MessageList: โชว์ list message ทั้งหมด
  • Message: โชว์ message และมีปุ่มลบ message
  • Button: ปุ่มธรรมดา

โดยพอประกอบรวมกันมีหน้าตาแบบนี้

ถ้าเราดูเราจะเห็นว่า ตัวระบุสีของปุ่ม (color) นั้นอยู่ใน component ของ MessageList แต่คนที่ใช้จริงๆ คือ Button ซึ่งห่างกันสองชั้นเลยทีเดียว ทำให้ตัว Message จึงเหมือนคนกลางที่ส่งค่าสีไปให้ Button

แต่ด้วย Context API เราสามารถให้ MessageList ระบุ context ได้ว่ามีตัวแปรสีให้ใช้ ด้วยการเขียน getChildContext() ใน MessageList

ซึ่งจริงๆ แล้วในฟังก์ชัน getChildContext() สามารถดึงค่า props หรือ state ของ component มาใช้ได้เหมือนกัน และนอกจากนี้ก็ต้องประกาศ childContextTypes ด้วย (แบบเดียวกับ PropTypes)

และในส่วนของ Button ที่จะเอาสีมาใช้นั้น

เพียงแค่ประกาศว่า จะใช้ context อะไรบ้าง ผ่าน contextTypes แล้วก็สามารถหยิบ context มาใช้ได้เลย

หรือถ้าใครไม่ชอบหน้าตาของ class component ใน functional component ก็ใช้งาน context ได้เหมือนกัน โดยเป็นพารามิเตอร์ตัวที่สองของฟังก์ชันครับ

Use case ที่เอา Context API มาใช้

ปัญหาที่ผมเจอคือ ในระบบของเราจะเก็บรูปทั้งหมดไว้ที่ Cloudfront แต่ทีนี้คือรูปจะถูกแยกเก็บอยู่ที่ 2 endpoint คือเป็นของ production ที่นึง และของ dev อีกที่นึง ซึ่งทำให้มันจะมีโค้ดจุดนึงที่บ่งบอกว่าจะเลือก path ไหนมาใช้ ประมาณนี้

ซึ่งตอน build บน production ตัว NODE_ENV ก็จะมีค่าเป็น production ทำให้ก็จะหยิบ path ของ production มาใช้ ถ้าตอน dev หรือบน staging ที่ไว้เทสงานก็จะหยิบ path ของ dev มาใช้ และตัว component เวลาจะใช้ ก็จะเอาฟังก์ชัน path() ไปใช้งานเพื่อดึง path ของรูปมาใช้

ปัญหาคือตัว thirdparty library ที่เราทำมานั้นไม่สามารถจับค่า NODE_ENV มาใช้ได้ เพราะว่าตัว library จะถูก pre-built มาก่อนจะถูกนำไปใช้ ดังนั้นค่า NODE_ENV จึงมีค่าในตอนจังหวะ build ตัว library ขึ้นมา (ซึ่งก็คือ production)

ดังนั้นวิธีแก้ที่ผมทำคือ ทำ component ชื่อ UIKitContext มา โดยให้มันรับ props ชื่อว่า prefixPath ไว้ระบุว่า path ของรูปเป็นที่ไหน และตัว component นี้จะประกาศ context ไว้แบบนี้

ซึ่งเราให้ context มีค่าเป็นฟังก์ชัน path ที่จะไว้ใช้ get path จริงๆ ของตัวรูปภาพจาก cloudfront

ดังนั้นเวลา component ไหนที่อยู่ภายใต้ context นี้ ก็จะสามารถหยิบฟังก์ชัน path ไปใช้ได้ ด้วยท่านี้

เวลาประกอบร่าง ก็อย่าลืมใช้ UIKitContext component เป็น parent ของ Image (ซึ่งจะเป็น parent ห่างไกลขนาดไหนก็ได้)

ข้อควรระวัง

ใน document ของ React บอกว่า ถ้าเป็นไปได้อยากให้ “หลีกเลี่ยง” การใช้งาน context (ดู section Why Not Use Context)

แต่ทั้งนั้น ก็มีข่าวว่า React 16.3 กำลังพัฒนา Context API ใหม่หมดจด ที่ safer มากขึ้นอยู่ และดีขึ้นมาก

Context ใน React 16.3?

Context API ใน React 16.3 นั้นจะใช้ผ่าน factory function ที่ชื่อว่า React.createContext() ครับ ซึ่งฟังก์ชันนี้จะสร้าง component 2 อันขึ้นมา ประกอบด้วย “Provider” กับ “Consumer”

ซึ่งหน้าที่ก็ล้อเลียนกับชื่อเลย Provider จะเป็นคนที่ “provide” data ทั้งหมดใน component ที่อยู่ภายใต้ provider ทั้งหมด และ Consumer คือคนที่จะ consume ใช้งาน data ที่ Provider provide มาให้

เช่นถ้าอย่างใน scenario ของผม ถ้าใช้ Context ของ React 16.3 จะสามารถทำได้แบบนี้ (ดูลำดับขั้นได้ตามโค้ดเลย)

จุดสังเกตใหญ่ๆ ที่เปลี่ยนไปเลยคือ เรามอง context ออกมาในรูปแบบของ component มากขึ้น และวิธีใช้ก็ดูง่ายดายขึ้น และไม่จำเป็นต้องประกาศ contextTypes อีกแล้วด้วย รวมถึงวิธีการใช้งาน context มาใช้แนวคิด renderProps ซึ่งเป็นแนวคิดที่เริ่มเป็นที่นิยมกันมากขึ้นแล้วในตอนนี้

น่าใช้แค่ไหน?

ใน context เก่านั้นเอาจริงๆ ถ้า use-case ไม่ match ขนาดนั้น หรือมี workaround ด้วยท่าสามัญธรรมดา ผมก็เลือกที่จะไม่ใช้ context แต่สำหรับอันใหม่นั้น context ถือว่าเป็นอะไรที่น่าสนใจมากๆ

สำหรับคนที่อยากขุดลึกว่าใน React 16.3 ที่กำลังจะออกมานั้นจะมี context api หน้าตาเป็นยังไง use-case ไหนที่น่าใช้งาน ก็สามารถติดตามต่อได้ตามลิ้งด้านล่างเลยครับ

สำหรับวันนี้ก็เช่นเดียวกับบล็อกก่อนหน้าครับ หากสงสัย หรือมีความเห็น หรือมีอะไรที่อยากจะ discuss กัน ทำได้เต็มที่เลยครับ :)

--

--

Tananan Tangthanachaikul
TakeMeTour Engineering

Senior Full-Stack Developer@TakeMeTour - A man who passionately craft software