7 เคล็ดลับเปลี่ยนโค๊ด React/Redux ให้อ่านง่ายและดูดีขึ้น
เพื่่อนๆที่เขียน React อย่างเพลิดเพลิน เคยไหมที่เวลาเขียนโค๊ดช่างสนุกสนานอย่างกับกำลังเล่นเครื่องเล่นที่ดิสนีย์แลนด์ แต่พอกลับมาดูโค๊ดในภายหลังช่างฝันร้ายดั่งโดนผีหลอกในบ้านผีสิง วันนี้ผมจะนำเสนอ 7 วิธีที่จะทำให้โค๊ด React ของเพื่อนๆอ่านง่ายและดูมีสไตล์มากขึ้นครับ
1. compose ช่วยชีวิต
ลองจินตนาการถึงคอมโพแนนท์ตัวนึงที่เราเขียนขึ้น เราต้องการใช้ connect
เพื่อผูก store เข้ากับ component ต้องการใช้ withRouter
เพื่อสามารถเรียก this.props.router ได้จากในคอมโพแนนท์เลย และสุดท้ายต้องการเรียก injectIntl
เพื่อให้สามารถทำ i18n ผ่าน this.props ได้ ด้วยเหตุนี้เราจึงเขียนคอมโพแนนท์พร้อม export ลักษณะนี้
1class MyComponent extends Component {2 ...3}45export default withRouter(6 connect(mapStateToProps, mapDispatchToProps)(7 injectIntl(MyComponent)8 )9)
เมื่อเราดูโค๊ดข้างบนแล้วช่างชวนสับสนยิ่งนัก การใส่ของซ้อนวงเล็บมันทำให้เราเริ่มคิดเป็นลำดับ ปุถุชนแบบเราจะเริ่มมองของที่อยู่ในวงเล็บในสุดก่อน แล้วจึงค่อยๆถอยออกมาชั้นนอก นั่นละฮะมันช่างยากที่จะเข้าใจ
ใน Redux เรามี compose
ที่ใช้ประกอบฟังก์ชันให้มีความสามารถเหมือนเอาฟังก์ชันแต่ละตัวมารวมกัน เราจึงสามารถผนวก connect, withRouter และ injectIntl เข้าด้วยกันผ่าน compose จึงทำให้รูปแบบการเขียนโค๊ดดูดีขึ้น ดังนี้
1import { compose } from 'redux'23class MyComponent extends Component {4 ...5}67export default compose(8 withRouter,9 connect(mapStateToProps, mapDispatchToProps),10 injectIntl11)(MyComponent)
ดูอ่านง่ายขึ้นเยอะเลย ไม่ต้องมีวงเล็บซ้อนไปซ้อนมาหลายชั้นอีกแล้ว สำหรับเพื่อนๆคนไหนที่งงว่าเห้ย compose คืออะไร ลองอ่านเพิ่มเติมได้ในหัวข้อ composition
จากบทความ พื้นฐาน funtional programming ใน JavaScript ครับ
2. แค่ใส่วงเล็บชีวิตก็เปลี่ยน
สมมติเรามีคอมโพแนนท์หน้าตาแบบนี้พร้อมประโยค return ที่คุ้นเคย
1class MyComponent extends Component {2 render() {3 return (4 <NextComponent5 property1={property1}6 property2={property2}7 property3={property3}8 >9 Children10 </NextComponent>11 )12 }13}
เมื่อพุ่งการสังเกตไปที่ render จะพบว่า NextComponent นั้นแท็กเปิดและแท็กปิดไม่อยู่ในระนาบแนวตั้งเดียวกันทำให้อ่านยาก ลองจัดใหม่ดังนี้ด้วยวงเล็บเพื่อให้ดูดีขึ้น
1class MyComponent extends Component {2 render() {3 return (4 <NextComponent5 property1={property1}6 property2={property2}7 property3={property3}8 >9 Children10 </NextComponent>11 )12 }13}
เพียงเท่านี้แท็กเปิดและปิดของ NextComponent ในประโยค return ก็จะตรงกันสวยงามแล้ว แต่ถ้าให้ดีกว่านี้ใช้ functional component ไปซะเลย
1const MyComponent = () => (2 <NextComponent3 property1={property1}4 property2={property2}5 property3={property3}6 >7 Children8 </NextComponent>9)
เห็นไหมครับเพียงแค่มีวงเล็บ ชีวิตก็ดูดี๊ดีขึ้นมาแล้ว
3. PropTypes หรือจะสู้ FlowType
ในโลกของ React เรานิยมใส่ PropTypes เพื่อเป็นการตรวจสอบ props
ของเราว่ามีชนิดข้อมูลตรงตามที่กำหนดหรือไม่ เช่น
1class MyComponent extends React {2 static propTypes = {3 // this.props.name ต้องเป็น string และจำเป็นต้องระบุเข้ามาในคอมโพแนนท์4 name: PropTypes.string.isRequired,5 // this.props.age ต้องเป็น number แต่จะมีหรือไม่มีก็ได้6 age: PropTypes.number,7 }8}
PropTypes มีปัญหาอยู่อย่างหนึ่งคือเราต้องการใช้มันเพื่อตรวจสอบชนิดข้อมูลของ props แค่ใน development เท่านั้น แต่บรรทัดที่2-6นั้นยังคงปรากฎอยู่แม้จะเป็นสภาพแวดล้อมแบบ production ก็ตาม แม้ใน production ตัว PropTypes จะไม่ทำงาน แต่เราก็คงไม่อยากให้โค๊ดมันไปปรากฎใช่ไหมครับ เพราะมันทำให้ไฟล์ใน production ของเราใหญ่ขึ้นโดยใช่เหตุ เราสามารถใช้ babel-plugin-transform-react-remove-prop-types เพื่อกำจัดคุณ PropTypes ออกจากโค๊ดของเราใน production build
แต่นั่นไม่ใช่ประเด็นที่เราจะกล่าวถึงในนี้ครับ PropTypes คือดีงาม มันช่วยเราหาข้อผิดพลาดเจอได้ง่ายเพราะมันตรวจสอบ props ของเราก่อน จะดีกว่าไหมถ้าเราเพิ่มความสามารถให้โค๊ดของเราอธิบายตัวเองได้
ใน TypeScript เราสามารถระบุชนิดข้อมูลได้ นั่นทำให้เรารู้ว่าตัวแปรตัวนี้จะมีค่าเป็นข้อมูลชนิดนั้นตลอดไปไม่เปลี่ยนแปลง ถ้าโค๊ดเราเผลอไปใช้ชนิดข้อมูลอื่น เราจะได้รับการแจ้งเตือนถึงสิ่งผิดพลาด ก่อนที่ข้อผิดพลาดนี้จะไปปรากฎบน production
1// person จะต้องเป็น string ตลอดฟังก์ชัน2function greeter(person: string) {3 return 'Hello, ' + person4}
ถ้าตอนนี้คุณใช้ ES2015 อยู่คุณสามารถใช้ Flow เพื่อเพิ่มความแกร่งในการตรวจสอบข้อมูลของคุณ ตัวอย่างการใช้ Flow เช่น
1/* @flow */23type Props = {4 // name ต้องเป็น string และจำเป็นต้องมี5 name: string,6 // ? หมายถึงมีหรือไม่มีก็ได้7 age: ?number8}910class MyComponent extends React {11 props: Props;1213 // ทำให้มั่นใจว่า render ต้องคืนค่ากลับเป็น React.Element เท่านั้น14 render(): React.Element {15 ...16 }17}
4. ใช้ classnames
ถ้าเรามีคอมโพแนนท์หนึ่งสำหรับแทน Article เราต้องการให้คอมโพแนนท์นี้มีคลาสของ CSS เป็น article เสมอ และให้มี article--published ด้วยถ้าบทความนั้นออกสู่สาธารณชนแล้ว ดังนี้
1import styles from './MyComponent.css'23class MyComponent extends Component {4 render() {5 const { status } = this.props67 return (8 <article9 className={10 `${styles['article'] ${status === 'published' ? styles['article--published'] : '']}`11 }12 )13 }14}
เพื่อนๆจะพบว่าเพียงเราต้องการระบุคลาสแค่นี้กลับกลายเป็นเรื่องยากเมื่อต้องกลับมาอ่านโค๊ดอีกครั้ง เพื่อนๆสามารถทำให้การระบุคลาสเป็นเรื่องสนุกได้ด้วยการใช้ classnames
1import classNames from 'classnames'2import styles from './MyComponent.css'34class MyComponent extends Component {5 render() {6 const { status } = this.props78 return (9 <article10 className={11 classNames(12 ${styles['article'],13 // ถ้าเป็น published จึงจะมี .article--published14 [styles['article--published']: status === 'published'15 )16 }17 )18 }19}
5. แยกค่าคงที่อิสระออกจากไฟล์
ถ้าเรามีค่าคงที่ แต่ค่าคงที่นั้นใช้ในหลายๆที่และไม่เกี่ยวข้องโดยตรงกับไฟล์นั้นเราควรแยกค่าคงที่นั้นออกจากไฟล์ เช่น action type ใน Redux เช่น
1// actions/Page.js2export const loadPages = () => ({3 [CALL_API]: {4 endpoint: PAGES_ENDPOINT,5 method: 'GET',6 types: ['LOAD_PAGES_REQUEST', 'LOAD_PAGES_SUCCESS', 'LOAD_PAGES_FAILURE'],7 },8})910// reducers/pages.js11const initialState = []1213export default (state = initialState, action) => {14 switch (action.type) {15 case 'LOAD_PAGES_SUCCESS':16 return action.payload17 default:18 return state19 }20}
จากตัวอย่างข้างบนทั้ง LOAD_PAGES_REQUEST LOAD_PAGES_SUCCESS และ LOAD_PAGES_FAILURE เราใช้ค่าคงที่ทั้งสามในหลายที่ ถ้าเราสะกดผิดในซักไฟล์นึงหละ? โค๊ดของเราก็จะทำงานไม่ถูกต้อง นอกจากนี้ถ้าสมาชิกในทีมอยากเพิ่ม action ใหม่เขาก็ต้องไปนั่งเปิดดูในแต่ละไฟล์ว่า action ที่จะสร้างมีการใช้งานแล้วหรือยัง ถ้ามีจะได้เปลี่ยนไปใช้ชื่ออื่น
ด้วยเหตุนี้เราจึงควรแยกค่าคงที่ทั้งสามออกไปไว้อีกไฟล์ แล้วเรียกค่าคงที่เหล่านี้มาใช้งานในไฟล์ปลายทาง ดังนี้
1// constants/actionTypes.js2export const LOAD_PAGES_REQUEST = 'LOAD_PAGES_REQUEST'3export const LOAD_PAGES_SUCCESS = 'LOAD_PAGES_SUCCESS'4export const LOAD_PAGES_FAILURE = 'LOAD_PAGES_FAILURE'
จากนั้นจึง import ค่าคงที่เหล่านี้ในแต่ละไฟล์
1import { PAGES_ENDPOINT } from '../constants/endpoints'2import {3 LOAD_PAGES_REQUEST,4 LOAD_PAGES_SUCCESS,5 LOAD_PAGES_FAILURE,6} from '../constants/actionTypes'78export const loadPages = () => ({9 [CALL_API]: {10 endpoint: PAGES_ENDPOINT,11 method: 'GET',12 types: [LOAD_PAGES_REQUEST, LOAD_PAGES_SUCCESS, LOAD_PAGES_FAILURE],13 },14})
6. โค๊ดดูดีเมื่อกดแท็บ
พยายามหลีกเลี่ยงการเขียนโค๊ดยาวติดกันเป็นพรืด โดยปกติเรานิยมตั้งค่าไม่ให้โค๊ดที่เราเขียนเกิน 80 ตัวอักษรต่อหนึ่งบรรทัด เมื่อโค๊ดยาวเกินไปให้ขึ้นบรรทัดใหม่ แล้วกดแท็บ ในกรณีของการเรียกคอมโพแนนท์พร้อมส่ง property เข้าไปด้วย ควรขึ้นบรรทัดใหม่ก่อนเขียนโค๊ดสำหรับการส่งค่าเหล่านั้น ดังนี้
1// ตัวอย่างที่ไม่ควรทำ2class MyComponent extends Component {3 render() {4 return <NextComponent prop1={prop1} prop2={prop2} prop3={prop3} />5 }6}78// แบบอย่างที่ควรปฏิบัติ9class MyComponent extends Component {10 render() {11 return <NextComponent prop1={prop1} prop2={prop2} prop3={prop3} />12 }13}
7. DRY ทุกสรรพสิ่ง
Don't repeat yourself หรือ DRY เป็นหลักการที่แนะนำให้อย่าทำอะไรซ้ำซาก เช่น อย่าเขียนโค๊ดที่มีขั้นตอนซ้ำไปๆมาๆ ในที่นี้เราจะลดขั้นตอนของสิ่งที่ไม่จำเป็นเพื่อให้โค๊ดของเราดู DRY สะอาด สวยงาม เป็นระเบียบมากขึ้น
7.1 บ๊ายบาย semicolon
ES2015 เราไม่ต้องใส่ ;
ก็ได้นะครับเพราะมันจะใส่ให้คุณอัตโนมัติในภายหลังเอง ในความคิดของผม ไร้ซึ่ง semicolon โค๊ดช่างดูสวยงามยิ่งนัก
7.2 ใช้ ES2015 เพื่อลดการทำซ้ำ
1// ตัวอย่างที่12// ก่อนปรับเปลี่ยน3const name = 'Nuttavut Thongjor'4const obj = {5 name: name6}78// หลังปรับเปลี่ยน9const name = 'Nuttavut Thongjor'10const obj = {11 name12}1314// ตัวอย่างที่215// ก่อนปรับเปลี่ยน16const obj = {17 display: function() {18 ...19 }20}2122// หลังปรับเปลี่ยน23const obj = {24 display() {25 ...26 }27}
จบแล้วครับกับ 7 วิธีทำให้โค๊ด React ของเราดูดีและอ่านง่ายขึ้น เพื่อนๆสามารถนำหลักการนี้ไปประยุกต์ใช้กับการเขียน JavaScript อื่นๆได้ครับ เพื่อนๆที่มีหลักการอะไรเพิ่มเติมอยากแนะนำ อย่าเก็บไว้คนเดียวครับ ร่วมกันแบ่งปันได้ข้างล่างนี้เลยฮะ
สารบัญ
- 1. compose ช่วยชีวิต
- 2. แค่ใส่วงเล็บชีวิตก็เปลี่ยน
- 3. PropTypes หรือจะสู้ FlowType
- 4. ใช้ classnames
- 5. แยกค่าคงที่อิสระออกจากไฟล์
- 6. โค๊ดดูดีเมื่อกดแท็บ
- 7. DRY ทุกสรรพสิ่ง