Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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ăngClass ComponentFunctional Component
SyntaxVerboseNgắn gọn
Statethis.stateuseState
LifecycleMethods cụ thểuseEffect
thisCần bindKhông có
Tái sử dụng logicHOC, Render PropsCustom Hooks
PerformanceTương đươngTươ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>
    </>
  );
}