บันทึกแนวทางการวาง React File Structure

September 22, 2021

เมื่อเริ่มต้นพัฒนา React ไปได้สักพักจำนวน code เริ่มเยอะขึ้น หลายคนคงสงสัยว่าจะวางโครงสร้างของ file อย่างไรดีให้สะดวกต่อทีมทั้งสมาชิกใหม่และเก่า ถ้าเราเข้าไปอ่าน docs เองก็จะเห็นว่าทาง React ปล่อยให้เราจัดการตรงนี้ได้อย่างอิสระ แต่อย่างไรก็ตามทาง React ได้มีการแนะนำเรื่องของ “Avoid too much nesting” และ “Don’t overthink it” เพื่อเป็นแนวทางในการจัดการ structure ไว้ โดยผมก็ได้นำ 2 สิ่งนี้มาปรับใช้ด้วย

ในบทความนี้จะมาแชร์อีกหนึ่งแนวทางในหลาย ๆ แนวทางของการทำ file Structure โดย structure นี้ผมนำมาใช้งานบน production จริง ๆ และเรื่องของ maintainability ก็อยู่ในระดับที่น่าพอใจ จึงอยากนำเสนอเพื่อให้ทุกคนสามารถนำไปปรับใช้กับ application ของตัวเองได้

Tech Stack

ก่อนอื่นเลยเพื่อให้เห็นภาพว่าทำไมเราต้องสร้าง folder นี้หรือทำไมเราไม่มี folder นี้ เราต้องมาพูดถึง technology ที่ใช้กันก่อนเพราะมีผลต่อการ group File เข้าด้วยกัน

ยกตัวอย่าง ถ้าใช้ Redux เข้ามาช่วยทำในเรื่องของ state Management ก็จะต้องมี Actions และ Reducers เข้ามาเกี่ยวด้วย

File ที่สร้างขึ้นเป็นการยกตัวอย่างเฉพาะในบทความนี้เท่านั้น

เมื่อเรา initial project จาก Create React App จะได้ structure หน้าตาประมาณนี้

my-app
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── public
│   ├── favicon.ico
│   ├── index.html
│   ├── logo192.png
│   ├── logo512.png
│   ├── manifest.json
│   └── robots.txt
└── src
    ├── App.css
    ├── App.js
    ├── App.test.js
    ├── index.css
    ├── index.js
    ├── logo.svg
    ├── serviceWorker.js
    └── setupTests.js

จุดเริ่มต้นจะมีแค่ index.js อันเดียวแล้วสามารถเริ่มเขียน React ได้เลย สิ่งที่เราต้องจัดการก็คือภายใน src โดยเริ่มจากการลบสิ่งที่ไม่เกี่ยวข้องออก

src

ถ้าใช้ Create React App จะคุ้นเคยอยู่แล้วซึ่งใน src จะเป็น folder หลักที่เราจะมาดูเรื่องของ structure ข้างในกัน

src folder

โดยข้างในจะมี folder ที่แบ่งตามประเภทดังต่อไปนี้

action

เมื่อเราใช้ Redux สำหรับจัดการ state แน่นอนว่าต้องมี actions folder สำหรับเก็บ action creator หรือ function ที่เอาไว้เปลี่ยนแปลงค่า state ของ Redux นั่นเอง (อ่านเรื่อง action ต่อที่นี่)

actions folder

ข้างในจะมี sub-folders แบ่งแยกตาม feature

components

component-based เป็นคำที่เราคุ้นเคยอยู่แล้วเมื่อเราใช้ React ทำให้เราต้องสร้าง component จำนวนมาก ข้างในก็จะมี logic, state ตามที่เราได้ design ออกมา

conponents folder

sub-folders หรือ sub-files ข้างในจะถูกแบ่งตาม feature เมื่อเราเปิด components ออกมาก็จะเห็น feature ทั้งหมดภายใน

แนะนำให้ภายใน sub-folder ไม่ต้องมี sup-folder ข้างในอีกเพื่อหลีกเลี่ยง nesting

ข้อสังเกตในการแบ่ง components folder ตาม feature จะสอดคล้องกับ pages folder (เพิ่มเติมในหัวข้อ pages folder)

controls

reusable component หรือ control เป็น component สำหรับการนำไปใช้ในหลาย ๆ ส่วนใน application และเมื่อนำไปใช้งานสามารถ config ค่าต่าง ๆ ได้เช่น สี หรือ ขนาด เป็นต้น

controls folder

folder นี้จะไม่ยึดกับ feature สามารถยกไปใช้ที่ project อื่นได้ โดยภายในจะแบ่งตาม type ของ component เรียงกันมา

fonts

ตัวอย่างการนำ font ใช้งานที่ index.css

@font-face {
  font-family: "Prompt_Regular";
  src: local("Prompt_Regular"),
    url(./fonts/Prompt-Regular.ttf) format("truetype");
}

สามารถ import font ที่อยู่ภาย folder นี้ไปใช้งานได้เหมือน module ทั่วไป อ่านเรื่องเพิ่ม assets ต่อได้ที่นี่

hooks

ตั้งแต่ React 16.8 ที่ทุกคนได้รู้จัก hooks ซึ่งถูกเพิ่มเข้ามาเพื่อความสามารถในการ reuse component ที่มี state หรือ stateful component ได้ง่ายขึ้น built-in hooks ที่เราคุ้นเคยเช่น useState และ useEffect เป็นต้น

แต่ใน folder นี้จะเก็บ custom hooks ที่สร้างขึ้นเพื่อจุดประสงค์ของการ reuse สามารถอ่านเพิ่มเติมได้ที่ Building Your Own Hooks

hooks folder

ภายในจะถูกแบ่งเป็น 2 ประเภทหลัก ๆ คือ

  1. custom hooks ที่เป็น utilities ทั่ว ๆ ไป เช่น useClickOutside หรือ useToggle สามารถย้ายไปใช้ project อื่นได้
  2. custom hooks ที่เป็น api layer หรือ useEffect() + axios เมื่อต้องการที่ fetch data จาก api แทนที่เราจะ import axios และสร้าง request ภายใน component เลยเนี่ย เราก็จัดการย้ายมาทำใน custom hooks และใน component ก็เรียกใช้งาน custom hooks นี้แทน

แนะนำการตั้งชื่อ custom hooks ในข้อ 2 ให้ล้อตามชื่อของ endpoint ที่ไปเรียกจะสวกต่อการใช้งาน

อ้างอิงจากวิดิโอนี้ The Ultimate UI Abstraction Layer - Tanner Linsley ลองศึกษาเพิ่มเติมได้

inputs

ถ้าใน project มีการนำ library เข้ามาช่วยจัดการ form เช่น Formik หรือ Final Form เป็นต้น code ด้านล่างเป็นตัวอย่างของ final-form

import { Form, Field } from "react-final-form"

...

<Form>
   ...
   <Field
   component={TextInput}
   />
</Form>

เราจะต้องสร้าง component เพื่อผูกกับ field state (value,error) และ callback function (onChange, onBlur) โดยการส่งผ่าน component prop ของ <Field /> และนำไปใช้ใน <Form />

inputs folder

inputs component ข้างในจะเรียกใช้งาน control อีกที เช่น ใน CheckboxInput จะเรียก Checkbox ที่อยู่ใน controls folder ที่กล่าวถึงในหัวข้อก่อนหน้านี้ เพราะบางครั้งการใช้งาน Checkbox เราไม่ได้เอาไปผูกกับ Form เสมอไป

pages

page ก็คือ component ที่เชื่อมโยงกับ react-router-dom path โดยตรง เป็น top-level ของ component folder ที่ชื่อเดียวกันกับ page ภายในสามารถแบ่งเป็น sub-folder สำหรับจัดกลุ่มของ pages ได้

pages folder

เมื่อเราย้อนไปดู ที่ components folder จะเห็นว่า invoice page เป็น top-level ของ components > invoice folder

ยกตัวอย่างเมื่อเราต้องการแก้ไขเรื่องเกี่ยวกับ invoice เราสามารถมองหา invoce page และ invoice ใน components folder ได้เลย

⭐⭐ pages folder ผมยกให้เป็นอันดับหนึ่ง ที่ผมแนะนำให้เพิ่มเข้ามา ช่วยในเรื่องของการแบ่งขอบเขตการทำงานของ component ได้ชัดเจนขึ้น เมื่อสิ่งที่เกี่ยวข้องกันระหว่าง page กับ เหล่า components ลูก ๆ ถูกเชื่อมโยงเข้าหากันทำให้หาของได้ง่ายขึ้น

reducers

ผมขอยกคำจากหัวข้อ actions มาคือถ้าเรา Redux แน่นอนว่าเราต้องมี reducer ควบคู่กับ action ซึ่ง reducer ก็คือ function ที่รับ state, action และ return state ใหม่ออกไป (อ่านเรื่อง reducer ต่อที่นี่)

โดยปกติแล้ว โครงสร้างภายในจะเหมือนกับของ action

utils

ทุกคนรู้จัก folder นี้อยู่แล้ว เกือบในทุกโครงการน่าจะมีสิ่งนี้ สิ่งที่มีไว้เก็บ function ทั่วไปที่ไม่เกี่ยวกับ component หรือ business ใด ๆ สามารถนำ function ไป reuse ได้ทุกที่

utils folder

ข้อสังเกต: ถ้าเคยใช้ JavaScript แต่ไม่เคยเขียน React มาก่อนต้องอ่าน code ใน folder นี้แล้วเข้าใจการทำงาน

Conclusion

ทั้งหมดนี้ขึ้นอยู่กับว่าการนับไปปรับใช้และ pain point ที่แต่ละทีมเจอมา หรืออนาคตก็อาจจะเปลี่ยนแปลงไปอีกเหมือนก่อนที่ช่วงที่มี Hooks เข้ามาทุกคนก็คงจะไม่ได้ใช้ Structure อย่างที่ทุกคนใช้ทุกวันนี้

แนวทางนี้เป็นการแบ่งตามประเภทของ file และมีปรับเปลี่ยนอีกเล็กน้อยจากหัวข้อ File structure ของ React

ได้แรงบันดาลใจจาก Fractal — Nodejs app structure






web picture

I'm Bulagorn Sasanthei
a lad who is interested in React, including TypeScript.