[React] ใช้เทคนิค Render Props จัดการ state และ lifecycle
จากบทความ รู้จัก Render Props อีกทางเลือกนอกเหนือจาก HOC ที่พูดถึงการแก้ปัญหาบางประการของ Higher-Order Components ด้วยการใช้เทคนิคของ Render Props จุดนี้เพื่อน ๆ บางคนจึงอาจรู้สึกเหมือนโดนฉีดแอมเฟตามีน (ยาบ้า) เข้าสู่กระแสเลือด ชีวิตนี้ฉันคงขาดเธอผู้เป็นดั่ง Render Props ไม่ได้แล้วหละ ทำยังไงดี!
ก็ฉันไม่อยากใช้ Recompose
สถานการณ์ที่เราต้องใช้ state หรือ lifecycle อันเป็นผลให้เสียความเป็น functional เช่นข้างล่างนี้
1class Aticles extends Component {2 state = {3 articles: [],4 }56 componentDidMount() {7 fetch('/api/v1/articles')8 .then((res) => res.json())9 .then((articles) => this.setState({ articles }))10 }1112 render() {13 return (14 <ul>15 {this.state.articles.map(({ id, title }) => (16 <li key={id}>{title}</li>17 ))}18 </ul>19 )20 }21}
เหล่าสาวก functional programming คงรู้สึกคันคะเยอ คันตามร่มผ้าระยะสุดท้าย จนมิอาจทนไหว ต้องขอแปลงกายไปใช้ท่า recompose ซะหน่อย ดังนี้
1import { compose, withState, lifecycle } from 'recompose'23const Articles = ({ articles }) => (4 <ul>5 {articles.map(({ id, title }) => (6 <li key={id}>{title}</li>7 ))}8 </ul>9)1011export default compose(12 withState('articles', 'setArticles', []),13 lifecycle({14 componentDidMount() {15 fetch('/api/v1/articles')16 .then((res) => res.json())17 .then((articles) => this.props.setArticles(articles))18 },19 })20)(Articles)
เริ่มจากใช้ withState
เพื่อกำหนดค่าสถานะเริ่มต้นของ articles ที่จะถูกนำไปใช้เพื่อแสดงผลด้วยค่าว่างของ []
พร้อมกำหนดฟังก์ชันสำหรับการตั้งค่า articles นี้ในชื่อของ setArticles
นอกจาก withState
เรายังใช้ HOC อีกหนึ่งตัวจาก Recompose นั่นคือ lifecycle
เพื่อใช้กำหนดว่าหากเมื่อใดที่คอมโพแนนท์ของเราเสนอหน้าเข้าสู่เบราเซอร์แล้ว เมื่อนั้นให้ทำการโหลดข้อมูลและกำหนดค่าของ articles ให้เป็นดั่งข้อมูลที่รับมาจากเซิฟเวอร์ ผ่านการเรียกใช้ setArticles
จากการใช้งาน Recompose ตรงนี้สิ่งหนึ่งที่เราพบคือ lifecycle
ของเราจะปราศจาก setArticles
ไม่ได้ นั่นเพราะสิ่งนี้จำเป็นต่อการตั้งค่าให้ articles เพื่อใช้ในการแสดงผล เพราะว่าลำดับของ HOC นั้นสำคัญ เราจึงมิอาจสลับให้ lifecycle ไปอยู่เหนือ withState ได้ มิเช่นนั้นภายใต้ lifecycle จะมองไม่เห็น setArticles
จาก withState นั่นเอง
1// แบบนี้ทำไม่ได้ น๊จ๊2export default compose(3 lifecycle({4 componentDidMount() {5 fetch('/api/v1/articles')6 .then((res) => res.json())7 .then((articles) => this.props.setArticles(articles))8 },9 }),10 withState('articles', 'setArticles', [])11)(Articles)
รู้จัก Reactions Component วิถีแห่ง Render Props
เพราะ Render Props เป็นเหมือนยาบ้า เราจึงดราม่าใส่ Recompose!
Reactions นั้นเป็นไลบรารี่ตัวจิ๋วที่ใช้คอนเซ็ปต์ของ Render Props ในการจัดการ state และ lifecycle โดยอาศัยการกำหนดพฤติกรรมต่าง ๆ ของคอมโพแนนท์ผ่านทางพร็อพเพอร์ตี้ เช่น didMount
ที่ใช้แทน componentDidMount และ initialState
แทนการกำหนดค่า state เริ่มต้นรวมถึงการจัดการ state ของคอมโพแนนท์ เป็นต้น
จากการใช้งาน Recompose ที่แสนเหี่ยวเฉา เปลี่ยนเป็นการเขียนโค้ดบนทุ่งลาเวนเดอร์ได้ง่ายด้วย Reactions ดังนี้
1import Component from '@reactions/component'23const Articles = (4 <Component5 initialState={{ articles: [] }}6 didMount={({ setState }) =>7 fetch('/api/v1/articles')8 .then((res) => res.json())9 .then((articles) => setState({ articles }))10 }11 >12 {({ state }) => (13 <ul>14 {state.articles.map(({ id, title }) => (15 <li key={id}>{title}</li>16 ))}17 </ul>18 )}19 </Component>20)
จากตัวอย่างข้างต้นจะพบว่าคอมโพแนนท์ Component ของ Reactions อนุญาตให้เราส่งค่า props ในชื่อของ initialState เพื่อทำการกำหนดค่า state ในชื่อ articles พร้อมกำหนดค่าเริ่มต้นเป็น []
ได้ ทำนองเดียวกับ didMount ที่ให้เรากำหนดพฤติกรรมเฉกเช่นเดียวกับ componentDidMount ได้
ด้วยการใช้เทคนิคของ Render Props เราจึงพบว่าส่วนของการแสดงผลนั้นเป็นหน้าที่ของ this.props.children
ของ Component หรือพูดง่าย ๆ ก็คือส่วนแสดงผลนั้นอยู่ใต้แท็กเปิดและแท็กปิดของ Component โดยส่วนแสดงผลนี้จะอยู่ในลักษณะของฟังก์ชันที่ได้รับค่าพารามิเตอร์มาจากการจัดส่งของ Component เพราะว่าเรามีการใช้ state ค่าพารามิเตอร์ตัวนึงที่เป็นไปได้จึงเป็น state ที่จัดเก็บ articles ตามการนิยามของเราไว้ก่อนหน้านั่นเอง
ใคร ๆ ก็สร้าง Reactions ได้ด้วยตนเอง
การใช้งาน Component ของ Reactions นั้นดูเรียบง่ายครับ ลักษณะของการโปรแกรมเพื่อให้ Component นี้ทำงานก็เรียบง่ายตามไปด้วย
ในหัวข้อนี้เราจะมาลองสร้าง Component โดยเลียนแบบจากสิ่งที่ Reactions ทำกัน
เริ่มแรกนั้นคอมโพแนนท์นี้ชื่อ Component และสามารถรับค่า initialState
เข้ามาได้ เราจึงต้องกำหนดค่านี้ให้เป็น state ของคอมโพแนนท์เรา
1class Component extends React.Component {2 state = this.props.initialState3}
ถัดมาเราต้องการรับค่า props ในชื่อของ didMount
เพื่อเอาไว้ใช้กับ componentDidMount เราจึงต้องเขียนกลไกนี้ไว้ใต้ componentDidMount ของ Component
1class Component extends React.Component {2 state = this.props.initialState34 componentDidMount() {5 if (this.props.didMount) this.props.didMount(/* TODO */)6 }7}
จากการใช้งานในตัวอย่าง เราพบว่า didMount
ของเราสามารถรับค่าเป็นฟังก์ชันได้ โดยฟังก์ชันดังกล่าวเป็นอ็อบเจ็กต์ที่มีพร็อพเพอร์ตีตัวหนึ่งชื่อ setState
ใช้สำหรับการตั้งค่า state ของ Component เราจึงต้องทำการโยนอ็อบเจ็กต์นี้ลงไปในจังหวะของการเรียกใช้ didMount
ด้วย ดังนี้
1class Component extends React.Component {2 state = this.props.initialState34 getArgs() {5 return {6 setState: (...args) => this.setState(...args),7 }8 }910 componentDidMount() {11 if (this.props.didMount) this.props.didMount(this.getArgs())12 }13}
ง่ายใช่ไหมละฮะ
อยากใช้ Render Props ให้เซียน ทำยังไงดี?
เพจ BabelCoder เปิดคอร์สสอนสด React Fundamentals รอบใหม่ วันที่ 16 - 17 มิย 2561 / สถานที่ Comscicafe BTS แบริ่ง / เวลา 09.00 - 17.00 ครับ
คอร์สนี้นอกจากจะสอนการใช้งาน React แล้ว เรายังสอนไปถึงเทคนิคของการใช้ Render Props อีกด้วย หากเพื่อน ๆ ท่านไหนสนใจข้อมูลเพิ่มเติมหรือต้องการจองคอร์สนี้ สามารถทักแชทเข้ามาที่เพจ Babel Coder ได้เลยครับ :)
สรุป
ย้ำอีกครั้ง Higher-Order Components ไม่ได้ตายนะครับ เพียงแต่ Render Props เป็นอีกทางเลือกของเทคนิคการสร้างคอมโพแนนท์เพื่อนำกลับมาใช้ใหม่เท่านั้น บางสถานการณ์การใช้ Render Props ก็ดีเข้าใจง่ายกว่า แต่บางเหตุการณ์ Recompose กลับทำให้ชีวิตดูแพงและมีสีสันมากกว่า ฉะนั้นแล้วจึงเข้าตำรา The Right Tool for the Right Job นั่นเอง
สารบัญ
- ก็ฉันไม่อยากใช้ Recompose
- รู้จัก Reactions Component วิถีแห่ง Render Props
- ใคร ๆ ก็สร้าง Reactions ได้ด้วยตนเอง
- อยากใช้ Render Props ให้เซียน ทำยังไงดี?
- สรุป