React Design Patterns: Presentational และ Container Components

ไทย

Photo by Mateusz Wacławek

ถ้าจะกล่าวถึง Design Patterns ใน React คงจะหนีไม่พ้น Pattern ที่ชื่อว่า Presentational และ Container Components อยู่ในรายการเป็นอันดับแรก ๆ เป็นแน่

 

ย้อนกลับไปเมื่อปี Mar, 2015 ในปีที่ Dan Abramov ได้ Publish บทความที่ยังอมตะหัวข้อหนึ่งที่ชื่อว่า Presentational and Container Components ซึ่งไม่นานนักหลัง ReactJS ได้คลอดออกมาเมื่อ May 29, 2013

 

ในบทความนั้น Dan กล่าวถึงหลักคิดในการแบ่ง Component ของเราเป็น 2 ส่วนคือ Presentational และ Container Components ไว้อย่างน่าสนใจทีเดียวครับ เอาละมาทำความรู้จักกันดีกว่าว่าแต่ละอย่างคืออะไรและมีหน้าที่อะไร

 

Presentational Components

Photo by James A. Molnar

Presentational Component คือ Component ที่รับผิดชอบเกี่ยวกับการแสดงผล โดยภาย Component จะมีโค้ดที่เก็บ Markup ของ DOM ไว้ภายในและไม่มีส่วนของ Logic ที่ไม่เกี่ยวข้องกับการแสดงผล เช่น การดึงข้อมูลจากแหล่งไหน การเปลี่ยนแปลงแก้ไขข้อมูล เป็นต้น

 

คุณลักษณะของ Presentation Component จะมีดังนี้

 

  • สนใจแค่สิ่งนั้นมีรูปร่างหน้าตาเป็นอย่างไร
  • มี DOM markup และ style ที่ใช้ในการแสดงผลไว้กับตัวเอง
  • ไม่มี dependencies ไปตลอดทั้งแอพ (Have no dependencies on the rest of the app, such as Flux actions or stores.)
  • ไม่มีการระบุว่าในตัว Component เองว่า data ควรจะถูกโหลดหรือถูกแก้ไขอย่างไร
  • มักจะไม่ค่อยมี state หรือมีข้อมูลเป็นของตนเอง ถึงแม้จะมีก็จะเก็บแค่ state ของ UI


*คุณลักษณะของ Presentational ในเวอร์ชันที่ยังไม่มี hooks

 

  • จะเขียนโดยใช้แค่ functional components เท่านั้นเว้นเสียแต่ต้องการใช้งาน state หรือ life cycle hooks ต่าง ๆ
  • รับ data และ callbacks ผ่านทาง props เท่านั้น

 

ชื่อที่คล้ายคลึงกัน: Dumb Component, Stateless Component

 

Container Components

Photo by Artur Tumasjan

Container Component คือ Component มีการระบุว่าควรจะดึงข้อมูลมาจากที่ไหน การเปลี่ยนแปลงแก้ไขข้อมูลควรจะเป็นอย่างไร เมื่อกดปุ่มนี้แล้วจะต้องส่งข้อมูลหรือเก็บข้อมูลไว้ที่ไหน เป็นต้น

 

คุณลักษณะของ Container Component

 

  • สนใจว่าสิ่งที่อยู่ในแอพควรจะทำงานอย่างไร เช่น ข้อมูลนี้ควรจะไปดึงมาจากที่ไหน ข้อมูลควรจะแก้ไขข้อมูลอย่างไร คลิกปุ่มนี้แล้วให้ไปหน้าไหนหรือทำอะไรต่อ เป็นต้น
  • มี state เก็บไว้ด้านในเนื่องจากตัว Component เองจะต้องเป็นที่เก็บรวบรวมข้อมูล ส่งต่อหรือคืนค่าต่าง ๆ กลับไปไปให้แก่ Component อื่น
  • มีหน้าที่เตรียม data และ behevior ทั้งหลายให้กับ Presentational Components หรือ Container Component อื่น (ในเวอร์ชันที่เราสามารถเขียน hooks ได้ เราสามารถมองว่าตัว hooks function ที่มี state อยู่ด้านในเป็น container ก็ย่อมได้)


ขื่อที่คล้ายคลึงกัน Smart Component, Stateful Component

 

ตัวอย่างโค้ด
ลองมาดูแอพพลิเคชันง่ายตัวหนึ่งที่ชื่อว่า Counter App แอพนี้สามารถเพิ่มเลข ลดเลข และรีเซ็ทค่า count ได้


The simply Counter app can increase, decrease and reset the number
import { useState } from “react”;

const CounterApp = () => {
const [counter, setCounter] = useState(0);
const handleIncrement = () => setCounter(counter + 1);
const handleDecrement = () => setCounter(counter – 1);
const handleReset = () => setCounter(0);

return (
<>
<DisplayCounter counter={counter} />
<CounterController
onIncrementClick={handleIncrement}
onDecrementClick={handleDecrement}
onResetClick={handleReset}
/>
</>
);
};

const DisplayCounter = ({ counter }) => {
return <p>Counter is: {counter}</p>;
};

const CounterController = ({
onIncrementClick,
onDecrementClick,
onResetClick
}) => {
return (
<div>
<button onClick={onIncrementClick}>-1</button>
<button onClick={onDecrementClick}>-1</button>
<button onClick={onResetClick}>Reset</button>
</div>
);
};

export default CounterApp;


อธิบายโค้ดด้านบน มีแอพที่ชื่อ CounterApp ทำหน้าที่

  1. DisplayCounter ทำหน้ารับ counter ที่ได้มาจาก props และแสดงผลบนหน้าจอ
  2. CounterController ทำหน้าที่รับ callbacks จำนวน 3 ฟังก์ชันได้แก่ onIncrementClick , onDecrementClick , onResetClick และผูกตัว button แต่ละปุ่มไม่ว่าจะเป็น +1 , -1 , reset ให้เรียก callback ที่ได้รับ
  3. CounterApp ทำหน้าที่เป็นเสมือนแอพของเราที่หุ้ม Component ทั้งหมดไว้ รวมถึงเก็บ state และ function ที่ทำการเปลี่ยนแปลงค่า state เอาไว้


ถ้าอ่านจากคำอธิบาย Component ด้านบนคงจะพอมองออกว่าอะไรคือ Presentational หรือ Container Component ถ้าใครยังมองไม่ออกไม่เป็นไรครับ เรามาลองมองไปด้วยกัน

 

  1. ดูจากฟังก์ชันที่เห็นผ่านจะเห็นโค้ดส่วนที่ React
  2. DisplayCounter ทำหน้าที่เป็น Presentational Component โดยรับเอาค่า counter มาจาก props และแสดงผลบนหน้าจอ โดยตัวเองมีโค้ดที่เก็บ DOM markup ไว้ภายในตัวเอง
  3. CounterController ทำหน้าที่เป็น Presentational Component โดยรับเอา callbacks มาจาก props และทำการผูกปุ่มแต่ละปุ่มเข้ากับ callback แต่ละอัน โดยตัวมันเองไม่ได้มีโค้ดที่เกี่ยวกับการเปลี่ยนแปลงค่า counter หากแต่แค่บอกว่าถ้าเกิดคลิกปุ่มนี้จะต้องเรียก callback ไหน
  4. CounterApp ทำหน้าที่เป็น Container Component ที่เก็บ Logic ของแอพเราเอาไว้ ทั้งเก็บตัว state เองและสร้าง callback functions ที่ทำหน้าที่เปลี่ยนแปลง state ของ counter ไว้

 

ประโยชน์ที่รับได้จากการทำตาม Pattern นี้

Photo by Razvan Chisu
  • Reusability: การนำ Component กลับมาใช้ใหม่สามารถทำได้ง่ายขึ้นถ้าเราสร้าง Component ด้วย Pattern นี้
  • Testability: การทดสอบ Component ทำได้สะดวกขึ้นเพราะสามารถเขียนเทสแยกชิ้นส่วน เช่น การเทส Presentational Component เราสามารถจำลอง state แต่ละแบบและส่งไปให้ Component ทดสอบได้ทันที เช่น Snapshot Testing ก็
  • Maintainability: การบำรุงรักษาโค้ดใน Component เองก็ทำได้ง่ายขึ้นเพราะเรามี mindset ในการมองและแนวทางปฎิบัติที่เป็นไปแนวทางเดียวกันทำให้คนที่เข้ามาแก้ไขหรือเพิ่มฟีเจอร์ใหม่ ๆ สะดวกมาก(ที่ไม่เจ็บปวดรวดร้าวชอกช้ำทุกข์ทรมานชอกช้ำปางตายเพราะเขียน Component ที่โค้ดโครตจะยาวจะแก้อะไรหรือเพิ่มอะไรก็ย๊ากยาก …)

 

แต่ Pattern มันหมดยุคไปแล้วไม่ใช่หรือ

Decorated photo by Walter Martin

จริงอยู่ที่ Dan เขียนในบล็อกของตัวเองว่าไม่แนะนำ Pattern นี้แล้วหลังจากที่มี React Hooks เข้ามา แต่ผมมองว่ารูปแบบนี้ไม่ได้ล้าหลังไปสะทีเดียวหากแต่มีความจำเป็นอย่างยิ่งเพราะการมอง Component นี้ช่วยให้เรามีเลนส์ในการมอง Component และแบ่งแยกตามบทบาทหน้าที่ของตัวเองได้อย่างถูกต้องเหมาะสม

หากท่านต้องไป migrate โค้ดโปรเจคเก่า ๆ หรือตามอ่าน Tutorial ตามอินเทอร์เน็ตที่เขียนไว้ตั้งแต่ยุคก่อนยังไม่ได้อัพเดท ยังคงมีประโยชน์เป็นอย่างยิ่งครับ หรือต้องไป migrate โปรเจคที่ยังคงเป็นพระอิฐพระปูนอยู่ตั้งแต่ก่อนที่ยังไม่มี Hooks และต้อง refactor มาเป็นเวอร์ชันใหม่ดิบกิ๊บเก๋นั้น การมีมุมมองนี้ติดตัวคงจะดีกว่าไม่มีอะไรเลย

 

สรุป

Photo by Stephen Kraakmo

ถ้าจะสรุปทั้งบทความเป็นเพียงคำเดียวคงหนีไม่พ้นคำว่า “การแยกเรื่อง” โดยเรื่องที่เราจะต้องแยกจะมีด้วยกัน 2 เรื่องคือ 1. การแสดงผล 2. การคิด

การแสดงผล: สนใจแค่รูปร่างของตัวเองนั้นมีอะไรแสดงอย่างไร และโค้ดส่วนใหญ่ที่อยู่ใน Component นั้นจะเกี่ยวข้องกับการแสดงผลเป็นหลัก
การคิด: สนใจว่าสิ่งต่าง ๆ ควรจะทำงานอย่างไร ฉะนั้นโค้ดด้านในของตัว Component เองจะมี Logic ต่าง ๆ ที่เกี่ยวข้องกับ การดึงข้อมูล การแก้ไขข้อมูล การเก็บข้อมูล และ การอัพเดทข้อมูล
หากมองว่า Pattern นี้เปรียบเสมือนการใส่เลนส์เพื่อเพิ่มมุมมองของเราให้ฉุกคิดสักนิดรู้ว่า Component ที่เราจะเขียนหรือปรับแก้นั้นกำลังทำเรื่องอะไรอยู่นั้นการมีเลนส์นี้ติดตัวถือว่าเป็นสิ่งล้ำค่าเลยครับ

Related Content

  • ทั้งหมด
  • Blogs
  • Insights
  • News
  • Uncategorized
  • Jobs
    •   Back
    • Partnership
    • Services & Products
    • Others
    • Events
    • PointX Products
    • Joint ventures
    • Leadership
    •   Back
    • Tech innovation
    • Finance
    • Blockchain
    •   Back
    • User experience
    • Technology
    • Strategy
    • Product
    • Lifestyle
    • Data science
    • Careers

Your consent required

If you want to message us, please give your consent to SCB TechX to collect, use, and/or disclose your personal data.

| The withdrawal of consent

If you want to withdraw your consent to the collection, use, and/or disclosure of your personal data, please send us your request.

Vector

Message sent

We have receive your message and We will get back to you shortly.