Components & Props
Functional Components
Functional components là cách hiện đại và được khuyến nghị để viết React components.
// Component đơn giản
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
// Arrow function syntax
const Button = ({ label, onClick, disabled = false }) => {
return (
<button onClick={onClick} disabled={disabled}>
{label}
</button>
);
};
// Component với TypeScript
interface UserCardProps {
id: number;
name: string;
email: string;
avatarUrl?: string;
}
const UserCard = ({ id, name, email, avatarUrl }: UserCardProps) => {
return (
<div className="user-card">
<img src={avatarUrl ?? '/default-avatar.png'} alt={name} />
<h3>{name}</h3>
<p>{email}</p>
</div>
);
};
Props
Cơ bản
// Truyền props
function App() {
return (
<UserCard
id={1}
name="Alice"
email="alice@example.com"
avatarUrl="/alice.png"
/>
);
}
// Destructuring props
function Product({ name, price, category, inStock = true }) {
return (
<div>
<h2>{name}</h2>
<p>Price: ${price}</p>
<p>Category: {category}</p>
<p>Status: {inStock ? 'In Stock' : 'Out of Stock'}</p>
</div>
);
}
// Spread props
const buttonProps = { type: 'submit', disabled: false, className: 'btn-primary' };
<button {...buttonProps}>Submit</button>
Props đặc biệt
// children prop
function Card({ title, children }) {
return (
<div className="card">
<h3 className="card-title">{title}</h3>
<div className="card-body">{children}</div>
</div>
);
}
// Sử dụng
function App() {
return (
<Card title="My Card">
<p>This is the card content</p>
<button>Action</button>
</Card>
);
}
// render prop pattern
function DataFetcher({ url, render }) {
const [data, setData] = useState(null);
useEffect(() => {
fetch(url).then(r => r.json()).then(setData);
}, [url]);
return render(data);
}
// Sử dụng
<DataFetcher
url="/api/users"
render={(data) => data ? <UserList users={data} /> : <Spinner />}
/>
Callback Props
// Truyền function qua props
function Counter({ onCountChange }) {
const [count, setCount] = useState(0);
function increment() {
const newCount = count + 1;
setCount(newCount);
onCountChange?.(newCount); // Optional chaining
}
return (
<div>
<span>{count}</span>
<button onClick={increment}>+</button>
</div>
);
}
// Parent component
function App() {
function handleCountChange(newCount) {
console.log('Count changed to:', newCount);
}
return <Counter onCountChange={handleCountChange} />;
}
Class Components (Legacy)
Cần biết để đọc code cũ. Trong dự án mới hãy dùng functional components.
import { Component } from 'react';
class UserProfile extends Component {
constructor(props) {
super(props);
this.state = {
isExpanded: false,
};
// Bind this nếu không dùng arrow function
this.toggleExpand = this.toggleExpand.bind(this);
}
// Lifecycle methods
componentDidMount() {
// Gọi sau khi component mount (tương đương useEffect với [])
this.fetchUserData();
}
componentDidUpdate(prevProps, prevState) {
// Gọi khi props hoặc state thay đổi
if (prevProps.userId !== this.props.userId) {
this.fetchUserData();
}
}
componentWillUnmount() {
// Cleanup trước khi unmount
this.subscription?.unsubscribe();
}
toggleExpand = () => {
this.setState((prevState) => ({
isExpanded: !prevState.isExpanded,
}));
};
async fetchUserData() {
const response = await fetch(`/api/users/${this.props.userId}`);
const data = await response.json();
this.setState({ user: data });
}
render() {
const { user, isExpanded } = this.state;
const { className } = this.props;
return (
<div className={className}>
<h2>{user?.name}</h2>
{isExpanded && <p>{user?.bio}</p>}
<button onClick={this.toggleExpand}>
{isExpanded ? 'Show Less' : 'Show More'}
</button>
</div>
);
}
}
So sánh Class vs Functional
| Tính năng | Class Component | Functional Component |
|---|---|---|
| Syntax | Verbose | Ngắn gọn |
| State | this.state | useState |
| Lifecycle | Methods cụ thể | useEffect |
this | Cần bind | Không có |
| Tái sử dụng logic | HOC, Render Props | Custom Hooks |
| Performance | Tương đương | Tương đương |
| Khuyến nghị | Legacy | ✅ Hiện đại |
Component Composition
// Tránh prop drilling với composition
// ❌ Prop drilling
function App({ user }) {
return <Dashboard user={user} />;
}
function Dashboard({ user }) {
return <Sidebar user={user} />;
}
function Sidebar({ user }) {
return <UserMenu user={user} />;
}
// ✅ Composition pattern
function App({ user }) {
const userMenu = <UserMenu user={user} />;
return <Dashboard sidebar={<Sidebar>{userMenu}</Sidebar>} />;
}
// Slot pattern
function Layout({ header, sidebar, children, footer }) {
return (
<div className="layout">
<header>{header}</header>
<div className="content">
<aside>{sidebar}</aside>
<main>{children}</main>
</div>
<footer>{footer}</footer>
</div>
);
}
// Sử dụng
function App() {
return (
<Layout
header={<NavBar />}
sidebar={<SideNav />}
footer={<Footer />}
>
<HomePage />
</Layout>
);
}
Controlled vs Uncontrolled Components
// Controlled - React quản lý state
function ControlledInput() {
const [value, setValue] = useState('');
return (
<input
value={value}
onChange={(e) => setValue(e.target.value)}
/>
);
}
// Uncontrolled - DOM quản lý state qua ref
function UncontrolledInput() {
const inputRef = useRef(null);
function handleSubmit() {
console.log('Value:', inputRef.current.value);
}
return (
<>
<input ref={inputRef} defaultValue="initial" />
<button onClick={handleSubmit}>Submit</button>
</>
);
}