คู่มือแนะนำการเขียน React และ JSX ที่เข้าท่ามากที่สุด โดย Airbnb
คู่มือนี้ผมแปลโดยใส่คำอธิบายและตัวอย่างเพิ่มเติม (ไม่แปลตรงตัว) เพื่อให้ผู้อ่านสามารถเข้าใจเนื้อหาต่าง ๆ ได้ดียิ่งขึ้น ในกรณีที่เจอข้อผิดพลาดใด ๆ กรุณา Fork และ PR ถ้ามีคำถามสามารถเปิด Issue ได้เลยครับ หวังว่าคู่มือนี้จะมีประโยชน์ต่อผู้อ่านไม่มากก็น้อย 🙏
- Basic Rules
- Class vs
React.createClass
vs stateless - Naming
- Declaration
- Alignment
- Quotes
- Spacing
- Props
- Parentheses
- Tags
- Methods
- Ordering
isMounted
- หนึ่งคอมโพเน้นท์ต่อหนึ่งไฟล์เท่านั้น
- ยกเว้นคอมโพเน้นท์ประเภท Stateless หรือ Pure Components (คอมโพเน้นท์ที่ไม่มีการจัดการ State ในตัวเอง) สามารถที่จะเขียนรวมในไฟล์เดียวกันได้ อ่านเพิ่มเติมจากกฎของ Eslint:
react/no-multi-comp
.
- ยกเว้นคอมโพเน้นท์ประเภท Stateless หรือ Pure Components (คอมโพเน้นท์ที่ไม่มีการจัดการ State ในตัวเอง) สามารถที่จะเขียนรวมในไฟล์เดียวกันได้ อ่านเพิ่มเติมจากกฎของ Eslint:
- ใช้ JSX ร่วมกับ React เสมอ
- อย่าใช้
React.createElement
ยกเว้นในการณีที่ต้องจัดการกับแอพพลิเคชั่นที่มีไฟล์ที่ไม่ได้ใช้ JSX เท่านั้น
-
ถ้าภายในคอมโพเน้นท์มีการจัดการ State และ refs ให้เลือกใช้
class extends React.Component
แทนการใช้React.createClass
ยกเว้นเราต้องการใช้งาน Mixins (Mixins ใช้งานได้กับ React.createClass เท่านั้น ไม่สามารถใช้ร่วมกับคลาสของ ES6 ได้) อ่านเพิ่มเติมจากกฎของ Eslint:react/prefer-es6-class
react/prefer-stateless-function
// ไม่ดี const Listing = React.createClass({ // ... render() { return <div>{this.state.hello}</div>; } }); // ดี class Listing extends React.Component { // ... render() { return <div>{this.state.hello}</div>; } }
แต่ถ้าภายในคอมโพเน้นท์ไม่มีการจัดการ State หรือ refs เราควรจะใช้ Function declaration แทนการสร้างคลาส
// แย่ class Listing extends React.Component { render() { return <div>{this.props.hello}</div>; } } // แย่ (เพราะ Arrow function ไม่สามารถกำหนดชื่อฟังก์ชันได้ (Arrow function เป็นการเขียนแบบย่อของ Anonymous function จึงไม่สามารถกำหนดชื่อได้)) const Listing = ({ hello }) => ( <div>{hello}</div> ); // ดี function Listing({ hello }) { return <div>{hello}</div>; }
-
นามสกุลไฟล์: ใช้
.jsx
เสมอ สำหรับคอมโพเน้นท์ของ React. -
ชื่อไฟล์: ควรตั้งชื่อในรูปแบบของ PascalCase (ขึ้นต้นทุกคำด้วยตัวใหญ่) ตัวอย่างเช่น
ReservationCard.jsx
-
ชื่อตัวแปร: ควรตั้งชื่อในรูปแบบของ PascalCase สำหรับคอมโพเน้นท์ของ React และ camelCase (ขึ้นต้นด้วยตัวเล็กและคำต่อไปขึ้นต้นด้วยตัวใหญ่) สำหรับ Instance อ่านเพิ่มเติมจากกฎของ Eslint:
react/jsx-pascal-case
// ไม่ดี import reservationCard from './ReservationCard'; // ควรใช้ PascalCase // ดี import ReservationCard from './ReservationCard'; // ไม่ดี const ReservationItem = <ReservationCard />; // ควรใช้ camelCase // ดี const reservationItem = <ReservationCard />;
-
ชื่อคอมโพเน้นท์: ควรตั้งให้เหมือนชื่อไฟล์ ตัวอย่างเช่น ไฟล์ชื่อ
ReservationCard.jsx
ควรตั้งชื่อคอมโพเน้นท์เป็นReservationCard
อย่างไรก็ตาม คอมโพเน้นท์ที่เป็นคอมโพเน้นท์หลักของแต่ละโฟเดอร์ให้ตั้งชื่อไฟล์ว่าindex.jsx
และตั้งชื่อคอมโพเน้นท์เป็นชื่อของโฟเดอร์แทน (เพราะสะดวกต่อการอิมพอร์ต เนื่องจากสามารถอิมพอร์ตจากชื่อโฟเดอร์ได้เลย)// ไม่ดี import Footer from './Footer/Footer'; // ไม่ดี import Footer from './Footer/index'; // ดี import Footer from './Footer'; // ระบบจะทำการอิมพอร์ตไฟล์ ./Footer/index.js ให้อัตโนมัติ
-
ไม่ควรใช้พรอพเพอร์ตี้
displayName
ในการตั้งชื่อคอมโพเน้นท์ แต่ควรตั้งชื่อคอมโพเน้นท์ตอนที่ทำการเอ็กพอร์ตแทน// ไม่ดี export default React.createClass({ displayName: 'ReservationCard', // โค้ดอื่น ๆ }); // ดี export default class ReservationCard extends React.Component { }
-
การจัดรูปแบบโค้ดของ JSX นั้นให้ทำตามกฎของ Eslint:
react/jsx-closing-bracket-location
// ไม่ดี <Foo superLongParam="bar" anotherSuperLongParam="baz" /> // ดี <Foo superLongParam="bar" anotherSuperLongParam="baz" /> // ถ้าพรอพเพอร์ตี้ไม่ยาวเกินไปสามารถใส่ไว้ในบรรทัดเดียวกันได้ <Foo bar="bar" /> // อิลิเม้นท์ภายในคอมโพเน้นท์ให้จัดย่อหน้าตามปกติ <Foo superLongParam="bar" anotherSuperLongParam="baz" > <Quux /> // อิลิเม้นท์ย่อหน้าเข้ามาตามปกติ </Foo>
-
ใช้เขี้ยวคู่ (Double quotes)
""
สำหรับ JSX เสมอ แต่สำหรับโค้ดจาวาสคริปต์ทั่วไปให้ใช้เขี้ยวเดี่ยว (Single quotes)''
อ่านเพิ่มเติมจากกฎของ Eslint:jsx-quotes
- ทำไม? ในกรณีที่มีอักขระพิเศษภายในแอททริบิวต์ของ JSX จะไม่สามารถใส่ Escaped quotes ได้ (ปกติในภาษาจาวาสคริปต์จะสามารถใส่สัญลักษณ์ \ เพื่อทำการ Escape อักขระพิเศษนั้น ๆ แต่ใน JSX ไม่สามารถใช้ได้) และในภาษาอังกฤษมีคำที่มีอักขระพิเศษเขี้ยวเดี่ยวอยู่เยอะพอสมควร จึงควรใช้เขี้ยวคู่เพื่อให้ง่ายต่อการพิมพ์ ตัวอย่างเช่น
"don't"
- ปกติแล้วแอททริบิวต์ของ HTML จะใช้เขี้ยวคู่เสมอ ดังนั้น JSX ควรจะทำตามกฎนั้นเช่นกัน
// ไม่ดี <Foo bar='bar' /> // ดี <Foo bar="bar" /> // ไม่ดี <Foo style={{ left: "20px" }} /> // ดี <Foo style={{ left: '20px' }} />
- ทำไม? ในกรณีที่มีอักขระพิเศษภายในแอททริบิวต์ของ JSX จะไม่สามารถใส่ Escaped quotes ได้ (ปกติในภาษาจาวาสคริปต์จะสามารถใส่สัญลักษณ์ \ เพื่อทำการ Escape อักขระพิเศษนั้น ๆ แต่ใน JSX ไม่สามารถใช้ได้) และในภาษาอังกฤษมีคำที่มีอักขระพิเศษเขี้ยวเดี่ยวอยู่เยอะพอสมควร จึงควรใช้เขี้ยวคู่เพื่อให้ง่ายต่อการพิมพ์ ตัวอย่างเช่น
-
ควรเว้นวรรคหนึ่งทีก่อนทำการปิดแท็ก (Tag)เสมอ
// ไม่ดี <Foo/> // แย่ <Foo /> // ไม่ดี <Foo /> // ดี <Foo />
-
ควรตั้งชื่อพรอพเพอร์ตี้ในรูปแบบ camelCase เสมอ
// ไม่ดี <Foo UserName="hello" phone_number={12345678} /> // ดี <Foo userName="hello" phoneNumber={12345678} />
-
พรอพเพอร์ตี้ที่มีค่าเป็น
true
ควรใส่แค่ชื่อพรอพเพอร์ตี้อย่างเดียวโดยไม่ต้องระบุค่า (React จะใส่ค่าtrue
ให้อัตโนมัติ) อ่านเพิ่มเติมจากกฎของ Eslint:react/jsx-boolean-value
// ไม่ดี <Foo hidden={true} /> // ดี <Foo hidden />
-
ควรใส่วงเล็บครอบ JSX ไว้ ในกรณีที่โค้ดมีมากกว่าหนึ่งบรรทัด อ่านเพิ่มเติมจากกฎของ Eslint:
react/wrap-multilines
// ไม่ดี render() { return <MyComponent className="long body" foo="bar"> <MyChild /> </MyComponent>; } // ดี render() { return ( <MyComponent className="long body" foo="bar"> <MyChild /> </MyComponent> ); } // ดี render() { const body = <div>hello</div>; // มีแค่บรรทัดเดียว ไม่ใส่วงเล็บจะทำให้อ่านง่ายขึ้น return <MyComponent>{body}</MyComponent>; }
-
ควรปิดแท็ก (Tag) ในตัวเองโดยใช้
/>
ในกรณีที่ภายในแท็กนั้นไม่ประกอบไปด้วยแท็กอื่น อ่านเพิ่มเติมจากกฎของ Eslint:react/self-closing-comp
// ไม่ดี <Foo className="stuff"></Foo> // ไม่มีแท็กอื่นภายใน Foo เพราะฉะนั้นควรจะปิดแท็กในตัวมันเอง // ดี <Foo className="stuff" />
-
ถ้าคอมโพเน้นท์มีพรอพเพอร์ตี้หลายบรรทัด ควรจะปิดแท็กในบรรทัดใหม่เสมอ อ่านเพิ่มเติมจากกฎของ Eslint:
react/jsx-closing-bracket-location
// ไม่ดี <Foo bar="bar" baz="baz" /> // ดี <Foo bar="bar" baz="baz" />
-
ใช้ Arrow function เพื่อครอบตัวแปรภายใน (Local variable)
function ItemList(props) { return ( <ul> {props.items.map((item, index) => ( <Item key={item.key} onClick={() => doSomethingWith(item.name, index)} /> ))} </ul> ); }
-
เมื่อต้องการใช้ฟังก์ชัน
bind()
ในการผูกอีเว้นท์ ควรทำการเรียกใช้ฟังก์ชันในคอนสตรัคเตอร์เสมอ อ่านเพิ่มเติมจากกฎของ Eslint:react/jsx-no-bind
ทำไม? การเรียกฟังก์ชัน bind() ภายในเมท็อต
render()
จะสร้างฟังชันท์ใหม่ทุกครั้งเมื่อมีการเรียกใช้เมท็อต ซึ่งส่งผลกระทบต่อประสิทธิภาพของแอพพลิเคชั่น// ไม่ดี class extends React.Component { onClickDiv() { // โค้ดอื่น ๆ } render() { return <div onClick={this.onClickDiv.bind(this)} /> } } // ดี class extends React.Component { constructor(props) { super(props); this.onClickDiv = this.onClickDiv.bind(this); } onClickDiv() { // โค้ดอื่น ๆ } render() { return <div onClick={this.onClickDiv} /> } }
-
อย่าใส่ขีดล่างนำหน้าเมท็อตในคอมโพเน้นท์ของ React
// ไม่ดี React.createClass({ _onClickSubmit() { // โค้ดอื่น ๆ }, // โค้ดอื่น ๆ }); // ดี class extends React.Component { onClickSubmit() { // โค้ดอื่น ๆ } // โค้ดอื่น ๆ }
- ควรเรียงลำดับเมท็อตภายใน
class extends React.Component
ดังต่อไปนี้:
- optional
static
methods constructor
getChildContext
componentWillMount
componentDidMount
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
componentDidUpdate
componentWillUnmount
- clickHandlers or eventHandlers like
onClickSubmit()
oronChangeDescription()
- getter methods for
render
likegetSelectReason()
orgetFooterContent()
- Optional render methods like
renderNavigation()
orrenderProfilePicture()
render
-
วิธีการประกาศ
propTypes
,defaultProps
,contextTypes
และอื่น ๆimport React, { PropTypes } from 'react'; const propTypes = { id: PropTypes.number.isRequired, url: PropTypes.string.isRequired, text: PropTypes.string, }; const defaultProps = { text: 'Hello World', }; class Link extends React.Component { static methodsAreOk() { return true; } render() { return <a href={this.props.url} data-id={this.props.id}>{this.props.text}</a> } } Link.propTypes = propTypes; Link.defaultProps = defaultProps; export default Link;
-
หากสร้างคอมโพนเน้นท์ด้วย
React.createClass
ควรเรียงลำดับพรอพเพอร์ตี้ดังต่อไปนี้ อ่านเพิ่มเติมจากกฎของ Eslint:react/sort-comp
displayName
propTypes
contextTypes
childContextTypes
mixins
statics
defaultProps
getDefaultProps
getInitialState
getChildContext
componentWillMount
componentDidMount
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
componentDidUpdate
componentWillUnmount
- clickHandlers or eventHandlers like
onClickSubmit()
oronChangeDescription()
- getter methods for
render
likegetSelectReason()
orgetFooterContent()
- Optional render methods like
renderNavigation()
orrenderProfilePicture()
render
- อย่าใช้ฟังก์ชัน
isMounted
อ่านเพิ่มเติมจากกฎของ Eslint:react/no-is-mounted
ทำไม? เพราะว่า
isMounted
เป็นแพทเทิร์นที่ควรหลีกเลี่ยง ซึ่งมันไม่สามารถใช้ได้ในคลาสของ ES6 นอกจากนั้นฟังก์ชันนี้จะถูกลบในอนาคต