Dark mode
React Best Practices
การกําหนดค่าเริ่มต้นให้กับ props แบบเดิมใช้ defaultProps ซึ่งกําลังจะถูก deprecate แบบใหม่ใช้ default parameters ที่เป็น JavaScript standard
❌ แบบเดิม
tsx
// ใช้ defaultProps ซึ่งกําลังจะถูก deprecate
interface Props {
name?: string;
}
function Greeting({ name }: Props) {
return <h1>Hello, {name}</h1>;
}
Greeting.defaultProps = {
name: "Guest",
};
✅ แบบใหม่
tsx
// ใช้ default parameters
interface Props {
name?: string;
}
function Greeting({ name = "Guest" }: Props) {
return <h1>Hello, {name}</h1>;
}
Event Handling
การจัดการ event แบบเดิมมักสร้างฟังก์ชันใหม่ทุกครั้งที่ render แบบใหม่ใช้ useCallback เพื่อ memoize ฟังก์ชัน
❌ แบบเดิม
tsx
function Counter() {
const [count, setCount] = useState(0);
// สร้างฟังก์ชันใหม่ทุกครั้งที่ render
const handleClick = () => {
setCount(count + 1);
};
return <button onClick={handleClick}>Count: {count}</button>;
}
✅ แบบใหม่
tsx
function Counter() {
const [count, setCount] = useState(0);
// memoize ฟังก์ชันด้วย useCallback
const handleClick = useCallback(() => {
setCount(prev => prev + 1);
}, []); // ไม่มี dependencies เพราะใช้ updater function
return <button onClick={handleClick}>Count: {count}</button>;
}
Side Effects
การจัดการ side effects แบบเดิมอาจทําให้เกิด memory leak แบบใหม่มีการ cleanup ที่เหมาะสม
❌ แบบเดิม
tsx
function UserStatus() {
const [isOnline, setIsOnline] = useState(false);
useEffect(() => {
// ไม่มีการ cleanup อาจทําให้เกิด memory leak
const timer = setInterval(() => {
checkUserStatus();
}, 1000);
}, []);
return <div>{isOnline ? "Online" : "Offline"}</div>;
}
✅ แบบใหม่
tsx
function UserStatus() {
const [isOnline, setIsOnline] = useState(false);
useEffect(() => {
const timer = setInterval(() => {
checkUserStatus();
}, 1000);
// cleanup function
return () => clearInterval(timer);
}, []);
return <div>{isOnline ? "Online" : "Offline"}</div>;
}
Error Handling
การจัดการ error แบบเดิมใช้ try-catch ธรรมดา แบบใหม่ใช้ Error Boundary และ Suspense
❌ แบบเดิม
tsx
function UserProfile() {
const [error, setError] = useState(null);
const [data, setData] = useState(null);
useEffect(() => {
try {
const data = await fetchUserProfile();
setData(data);
} catch (err) {
setError(err);
}
}, []);
if (error) return <div>Error!</div>;
if (!data) return <div>Loading...</div>;
return <div>{data.name}</div>;
}
✅ แบบใหม่
tsx
// ErrorBoundary component
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return <div>Something went wrong!</div>;
}
return this.props.children;
}
}
// การใช้งาน
function App() {
return (
<ErrorBoundary>
<Suspense fallback={<div>Loading...</div>}>
<UserProfile />
</Suspense>
</ErrorBoundary>
);
}
Memoization
การ optimize performance แบบเดิมไม่ใช้ memoization แบบใหม่ใช้ useMemo และ memo เพื่อลดการ render ที่ไม่จําเป็น
❌ แบบเดิม
tsx
function ExpensiveList({ items }) {
// คํานวณใหม่ทุกครั้งที่ component render
const sortedItems = items.sort((a, b) => b - a);
return (
<ul>
{sortedItems.map(item => <li key={item}>{item}</li>)}
</ul>
);
}
✅ แบบใหม่
tsx
// ใช้ useMemo สําหรับการคํานวณที่ซับซ้อน
const MemoizedList = memo(function ExpensiveList({ items }) {
const sortedItems = useMemo(() => {
return items.sort((a, b) => b - a);
}, [items]);
return (
<ul>
{sortedItems.map(item => <li key={item}>{item}</li>)}
</ul>
);
});
Custom Hooks
การจัดการ logic แบบเดิมเขียนรวมในคอมโพเนนต์ แบบใหม่แยกเป็น custom hook เพื่อนํากลับมาใช้ใหม่ได้
❌ แบบเดิม
tsx
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetchUsers()
.then(data => setUsers(data))
.catch(err => setError(err))
.finally(() => setLoading(false));
}, []);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error!</div>;
return <ul>{users.map(user => <li key={user.id}>{user.name}</li>)}</ul>;
}
✅ แบบใหม่
tsx
// แยก logic เป็น custom hook
function useUsers() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetchUsers()
.then(data => setUsers(data))
.catch(err => setError(err))
.finally(() => setLoading(false));
}, []);
return { users, loading, error };
}
// การใช้งาน
function UserList() {
const { users, loading, error } = useUsers();
if (loading) return <div>Loading...</div>;
if (error) return <div>Error!</div>;
return <ul>{users.map(user => <li key={user.id}>{user.name}</li>)}</ul>;
}
Context Usage
การใช้ Context แบบเดิมอาจทําให้เกิดการ re-render ที่ไม่จําเป็น แบบใหม่แยก state และ dispatch เพื่อลดการ re-render
❌ แบบเดิม
tsx
const ThemeContext = createContext();
function ThemeProvider({ children }) {
const [theme, setTheme] = useState("light");
// ทุก component ที่ใช้ context จะ re-render เมื่อ theme เปลี่ยน
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
✅ แบบใหม่
tsx
const ThemeContext = createContext();
const ThemeDispatchContext = createContext();
function ThemeProvider({ children }) {
const [theme, setTheme] = useState("light");
return (
<ThemeContext.Provider value={theme}>
<ThemeDispatchContext.Provider value={setTheme}>
{children}
</ThemeDispatchContext.Provider>
</ThemeContext.Provider>
);
}
// แยก consumer ตาม use case
function useTheme() {
return useContext(ThemeContext);
}
function useThemeDispatch() {
return useContext(ThemeDispatchContext);
}
Async Components
การโหลด component แบบ async แบบเดิมไม่มีการจัดการ loading state ที่ดี แบบใหม่ใช้ Suspense และ lazy
❌ แบบเดิม
tsx
const MyComponent = React.lazy(() => import("./MyComponent"));
function App() {
return (
<div>
<MyComponent />
</div>
);
}
✅ แบบใหม่
tsx
const MyComponent = React.lazy(() => import("./MyComponent"));
function App() {
return (
<Suspense
fallback={<Spinner />}
// แสดง fallback หลังจาก 200ms
unstable_expectedLoadTime={200}
>
<MyComponent />
</Suspense>
);
}
Form Handling
การจัดการฟอร์มแบบเดิมใช้ controlled components ทั้งหมด แบบใหม่ใช้ uncontrolled components กับ Form API เพื่อเพิ่ม performance
❌ แบบเดิม
tsx
// ทุก keystroke ทําให้เกิดการ re-render
function Form() {
const [values, setValues] = useState({ name: "", email: "" });
const handleChange = (e) => {
setValues({ ...values, [e.target.name]: e.target.value });
};
return (
<form>
<input
name="name"
value={values.name}
onChange={handleChange}
/>
<input
name="email"
value={values.email}
onChange={handleChange}
/>
</form>
);
}
✅ แบบใหม่
tsx
// ใช้ uncontrolled components กับ Form API
function Form() {
const formRef = useRef<HTMLFormElement>(null);
const handleSubmit = (e: FormEvent) => {
e.preventDefault();
const formData = new FormData(formRef.current!);
const values = Object.fromEntries(formData);
// จัดการข้อมูลเมื่อ submit เท่านั้น
};
return (
<form ref={formRef} onSubmit={handleSubmit}>
<input name="name" defaultValue="" />
<input name="email" defaultValue="" />
</form>
);
}
State Updates
การอัพเดท state แบบเดิมอาจทําให้เกิด race condition แบบใหม่ใช้ updater function เพื่อป้องกันปัญหา
❌ แบบเดิม
tsx
function Counter() {
const [count, setCount] = useState(0);
// อาจเกิด race condition
const increment = () => {
setCount(count + 1);
setCount(count + 1);
};
return <button onClick={increment}>{count}</button>;
}
✅ แบบใหม่
tsx
function Counter() {
const [count, setCount] = useState(0);
// ใช้ updater function ป้องกัน race condition
const increment = () => {
setCount(prev => prev + 1);
setCount(prev => prev + 1);
};
return <button onClick={increment}>{count}</button>;
}
Component Composition
การส่งผ่าน props แบบเดิมอาจทําให้เกิด props drilling แบบใหม่ใช้ composition pattern
❌ แบบเดิม
tsx
// props drilling ผ่านหลาย component
function GrandParent({ user }) {
return <Parent user={user} />;
}
function Parent({ user }) {
return <Child user={user} />;
}
function Child({ user }) {
return <div>{user.name}</div>;
}
✅ แบบใหม่
tsx
// ใช้ composition pattern
function UserInfo({ children }) {
const user = useUser();
return children(user);
}
function App() {
return (
<UserInfo>
{user => <div>{user.name}</div>}
</UserInfo>
);
}