Babel Coder

[React] ใช้เทคนิค Render Props จัดการ state และ lifecycle

beginner

จากบทความ รู้จัก Render Props อีกทางเลือกนอกเหนือจาก HOC ที่พูดถึงการแก้ปัญหาบางประการของ Higher-Order Components ด้วยการใช้เทคนิคของ Render Props จุดนี้เพื่อน ๆ บางคนจึงอาจรู้สึกเหมือนโดนฉีดแอมเฟตามีน (ยาบ้า) เข้าสู่กระแสเลือด ชีวิตนี้ฉันคงขาดเธอผู้เป็นดั่ง Render Props ไม่ได้แล้วหละ ทำยังไงดี!

สารบัญ

ก็ฉันไม่อยากใช้ Recompose

สถานการณ์ที่เราต้องใช้ state หรือ lifecycle อันเป็นผลให้เสียความเป็น functional เช่นข้างล่างนี้

class Aticles extends Component {
  state = {
    articles: []
  }
  
  componentDidMount() {
    fetch('/api/v1/articles')
      .then(res => res.json())
      .then(articles => this.setState({ articles }))
  }
  
  render() {
    return (
      <ul>
        {
          this.state.articles.map(
            ({ id, title }) => <li key={id}>{title}</li>
          )
        }
      </ul>
    )
  }
}

เหล่าสาวก functional programming คงรู้สึกคันคะเยอ คันตามร่มผ้าระยะสุดท้าย จนมิอาจทนไหว ต้องขอแปลงกายไปใช้ท่า recompose ซะหน่อย ดังนี้

import { compose, withState, lifecycle } from 'recompose'

const Articles = ({ articles }) => (
  <ul>
    {
      articles.map(({ id, title }) => <li key={id}>{title}</li>)
    }
  </ul>
)

export default compose(
  withState('articles', 'setArticles', []),
  lifecycle({
    componentDidMount() {
      fetch('/api/v1/articles')
        .then(res => res.json())
        .then(articles => this.props.setArticles(articles))
    }
  })
)(Articles)

เริ่มจากใช้ withState เพื่อกำหนดค่าสถานะเริ่มต้นของ articles ที่จะถูกนำไปใช้เพื่อแสดงผลด้วยค่าว่างของ [] พร้อมกำหนดฟังก์ชันสำหรับการตั้งค่า articles นี้ในชื่อของ setArticles

นอกจาก withState เรายังใช้ HOC อีกหนึ่งตัวจาก Recompose นั่นคือ lifecycle เพื่อใช้กำหนดว่าหากเมื่อใดที่คอมโพแนนท์ของเราเสนอหน้าเข้าสู่เบราเซอร์แล้ว เมื่อนั้นให้ทำการโหลดข้อมูลและกำหนดค่าของ articles ให้เป็นดั่งข้อมูลที่รับมาจากเซิฟเวอร์ ผ่านการเรียกใช้ setArticles

จากการใช้งาน Recompose ตรงนี้สิ่งหนึ่งที่เราพบคือ lifecycle ของเราจะปราศจาก setArticles ไม่ได้ นั่นเพราะสิ่งนี้จำเป็นต่อการตั้งค่าให้ articles เพื่อใช้ในการแสดงผล เพราะว่าลำดับของ HOC นั้นสำคัญ เราจึงมิอาจสลับให้ lifecycle ไปอยู่เหนือ withState ได้ มิเช่นนั้นภายใต้ lifecycle จะมองไม่เห็น setArticles จาก withState นั่นเอง

// แบบนี้ทำไม่ได้ น๊จ๊
export default compose(
  lifecycle({
    componentDidMount() {
      fetch('/api/v1/articles')
        .then(res => res.json())
        .then(articles => this.props.setArticles(articles))
    }
  }),
  withState('articles', 'setArticles', [])
)(Articles)

รู้จัก Reactions Component วิถีแห่ง Render Props

เพราะ Render Props เป็นเหมือนยาบ้า เราจึงดราม่าใส่ Recompose!

Reactions นั้นเป็นไลบรารี่ตัวจิ๋วที่ใช้คอนเซ็ปต์ของ Render Props ในการจัดการ state และ lifecycle โดยอาศัยการกำหนดพฤติกรรมต่าง ๆ ของคอมโพแนนท์ผ่านทางพร็อพเพอร์ตี้ เช่น didMount ที่ใช้แทน componentDidMount และ initialState แทนการกำหนดค่า state เริ่มต้นรวมถึงการจัดการ state ของคอมโพแนนท์ เป็นต้น

จากการใช้งาน Recompose ที่แสนเหี่ยวเฉา เปลี่ยนเป็นการเขียนโค้ดบนทุ่งลาเวนเดอร์ได้ง่ายด้วย Reactions ดังนี้

import Component from '@reactions/component'

const Articles = (
  <Component
    initialState={{ articles: [] }}
    didMount={({ setState }) => (
      fetch('/api/v1/articles')
        .then(res => res.json())
        .then(articles => setState({ articles }))
    )}
  >
  	{
  	  ({ state }) => (
  	    <ul>
          {
            state.articles.map(({ id, title }) => <li key={id}>{title}</li>)
          }
        </ul>
  	  )
  	}
  </Component>
)

จากตัวอย่างข้างต้นจะพบว่าคอมโพแนนท์ 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 ของคอมโพแนนท์เรา

class Component extends React.Component {
  state = this.props.initialState
}

ถัดมาเราต้องการรับค่า props ในชื่อของ didMount เพื่อเอาไว้ใช้กับ componentDidMount เราจึงต้องเขียนกลไกนี้ไว้ใต้ componentDidMount ของ Component

class Component extends React.Component {
  state = this.props.initialState
  
  componentDidMount() {
    if (this.props.didMount) this.props.didMount(/* TODO */);
  }
}

จากการใช้งานในตัวอย่าง เราพบว่า didMount ของเราสามารถรับค่าเป็นฟังก์ชันได้ โดยฟังก์ชันดังกล่าวเป็นอ็อบเจ็กต์ที่มีพร็อพเพอร์ตีตัวหนึ่งชื่อ setState ใช้สำหรับการตั้งค่า state ของ Component เราจึงต้องทำการโยนอ็อบเจ็กต์นี้ลงไปในจังหวะของการเรียกใช้ didMount ด้วย ดังนี้

class Component extends React.Component {
  state = this.props.initialState
  
  getArgs() {
    return {
      setState: (...args) => this.setState(...args)
    }
  }
  
  componentDidMount() {
    if (this.props.didMount) this.props.didMount(this.getArgs())
  }
}

ง่ายใช่ไหมละฮะ

อยากใช้ 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 นั่นเอง


แสดงความคิดเห็นของคุณ


No any discussions