Truyền Props vào một Component

Các component trong React sử dụng props để giao tiếp với nhau. Mỗi component cha có thể truyền một số thông tin cho các component con của nó bằng cách cung cấp các props. Props có thể khiến bạn liên tưởng đến các thuộc tính trong HTML, nhưng bạn có thể truyền bất kỳ giá trị JavaScript nào thông qua chúng, bao gồm cả đối tượng (objects), mảng (arrays) và hàm (functions).

Bạn sẽ được học

  • Làm thế nào để truyền props vào một component
  • Làm thế nào để đọc props từ một component
  • Làm thế nào để đặt giá trị mặc định cho props
  • Làm thế nào để truyền JSX cho một component
  • Props thay đổi như thế nào theo thời gian

Những props thường gặp

Props là thông tin mà bạn truyền vào một thẻ JSX. Ví dụ như, className, src, alt, width, và height là một vài props mà bạn có thể truyền vào thẻ <img>:

function Avatar() {
  return (
    <img
      className="avatar"
      src="https://i.imgur.com/1bX5QH6.jpg"
      alt="Lin Lanying"
      width={100}
      height={100}
    />
  );
}

export default function Profile() {
  return (
    <Avatar />
  );
}

Các props mà bạn có thể truyền vào thẻ <img> đã được định nghĩa sẵn (ReactDOM tuân theo tiêu chuẩn HTML). Nhưng bạn có thể truyền bất kỳ props nào vào component do bạn tự tạo ra, chẳng hạn như <Avatar>, để tùy chỉnh chúng. Sau đây là cách thực hiện!

Truyền props vào một component

Trong đoạn code này, component Profile không truyền bất cứ props nào đến component con của nó, Avatar:

export default function Profile() {
return (
<Avatar />
);
}

Bạn có thể cung cấp vài props cho component Avatar qua hai bước.

Bước 1: Truyền props đến component con

Đầu tiên, truyền vài props đến component Avatar. Ví dụ, hãy truyền hai props: person (một đối tượng - object), và size (một con số - number):

export default function Profile() {
return (
<Avatar
person={{ name: 'Lin Lanying', imageId: '1bX5QH6' }}
size={100}
/>
);
}

Ghi chú

Nếu bạn thấy dấu ngoặc nhọn đôi sau person= gây nhầm lẫn, hãy nhớ rằng chúng chỉ đơn giản là một đối tượng - object bên trong dấu ngoặc nhọn của JSX.

Bây giờ bạn có thể đọc những props này bên trong component Avatar.

Bước 2: Đọc props trong component con

Bạn có thể đọc các props này bằng cách liệt kê tên của chúng là person, size cách nhau bằng dấu phẩy, bên trong ({}) ngay sau function Avatar. Cách này cho phép bạn sử dụng chúng bên trong code của Avatar, giống như bạn sử dụng các biến thông thường.

function Avatar({ person, size }) {
// person và size có sẵn ở đây
}

Thêm một số logic vào Avatar sử dụng các props personsize để hiển thị, và bạn hoàn tất.

Bây giờ bạn có thể cấu hình Avatar để hiển thị theo nhiều cách khác nhau với các props khác nhau. Hãy thử thay đổi các giá trị nhé!

import { getImageUrl } from './utils.js';

function Avatar({ person, size }) {
  return (
    <img
      className="avatar"
      src={getImageUrl(person)}
      alt={person.name}
      width={size}
      height={size}
    />
  );
}

export default function Profile() {
  return (
    <div>
      <Avatar
        size={100}
        person={{ 
          name: 'Katsuko Saruhashi', 
          imageId: 'YfeOqp2'
        }}
      />
      <Avatar
        size={80}
        person={{
          name: 'Aklilu Lemma', 
          imageId: 'OKS67lh'
        }}
      />
      <Avatar
        size={50}
        person={{ 
          name: 'Lin Lanying',
          imageId: '1bX5QH6'
        }}
      />
    </div>
  );
}

Props giúp bạn suy nghĩ về components cha và con một cách độc lập. Ví dụ, bạn có thể thay đổi các props person hoặc size bên trong component Profile mà không phải suy nghĩ về cách component Avatar sử dụng chúng. Tương tự, bạn có thể thay đổi cách component Avatar sử dụng những props này mà không cần phải xem xét về component Profile.

Bạn có thể nghĩ về props như những “núm điều chỉnh” mà bạn có thể tùy chỉnh. Chúng có vai trò giống như các đối số (arguments) của hàm — thực ra, props chính là đối số duy nhất của component của bạn! Các hàm component React nhận một đối số duy nhất, đó là một đối tượng (object) props:

function Avatar(props) {
let person = props.person;
let size = props.size;
// ...
}

Thông thường, bạn không cần toàn bộ đối tượng (object) props, vì vậy bạn sẽ dùng kỹ thuật destructuring để lấy riêng từng prop.

Chú Ý

Đừng bỏ sót cặp dấu ngoặc nhọn {} bên trong dấu ngoặc đơn () khi khai báo props:

function Avatar({ person, size }) {
// ...
}

Cú pháp này được gọi là “destructuring” và tương đương với việc đọc các thuộc tính từ một tham số của hàm (function):

function Avatar(props) {
let person = props.person;
let size = props.size;
// ...
}

Chỉ định một giá trị mặc định cho một prop

Nếu bạn muốn gán cho một prop một giá trị mặc định để sử dụng khi không có giá trị được chỉ định, bạn có thể làm điều đó bằng cách sử dụng destructuring và đặt dấu = cùng với giá trị mặc định ngay sau tham số:

function Avatar({ person, size = 100 }) {
// ...
}

Bây giờ, nếu <Avatar person={...} /> được hiển thị mà không có prop size, thì size sẽ được gán giá trị là 100.

Giá trị mặc định chỉ được dùng nếu prop size bị bỏ qua hoặc nếu bạn truyền size={undefined}. Nhưng nếu bạn truyền size={null} hoặc size={0} thì giá trị mặc định sẽ không được dùng.

Chuyển props bằng cú pháp spread của JSX

Đôi khi, việc truyền props trở nên rất lặp đi lặp lại:

function Profile({ person, size, isSepia, thickBorder }) {
return (
<div className="card">
<Avatar
person={person}
size={size}
isSepia={isSepia}
thickBorder={thickBorder}
/>
</div>
);
}

Việc viết code lặp lại không có gì sai—thậm chí còn giúp dễ đọc hơn. Tuy nhiên, đôi khi bạn có thể ưu tiên sự ngắn gọn. Một số component chuyển tiếp toàn bộ props của chúng đến các component con, giống như Profile chuyển tiếp cho Avatar. Vì những component này không sử dụng trực tiếp bất kỳ prop nào, nên việc sử dụng cú pháp “spread” ngắn gọn sẽ hợp lý hơn:

function Profile(props) {
return (
<div className="card">
<Avatar {...props} />
</div>
);
}

Dòng này sẽ chuyển tiếp tất cả các props của Profile sang Avatar mà không cần liệt kê từng tên prop một.

Hãy sử dụng cú pháp spread một cách cẩn trọng. Nếu bạn dùng nó trong hầu hết các component, có thể đang có vấn đề trong cách thiết kế. Thường thì điều đó cho thấy bạn nên tách nhỏ các component và truyền nội dung con dưới dạng JSX. Phần tiếp theo sẽ nói rõ hơn về điều này!

Truyền JSX dưới hình thức con (children)

Việc lồng các thẻ trình duyệt có sẵn (built-in browser tags) là điều rất phổ biến:

<div>
<img />
</div>

Đôi khi, bạn cũng sẽ muốn lồng các component do chính bạn tạo ra theo cách tương tự:

<Card>
<Avatar />
</Card>

Khi bạn lồng nội dung bên trong một thẻ JSX, component cha sẽ nhận nội dung đó thông qua một prop có tên là children. Ví dụ, component Card dưới đây sẽ nhận prop children<Avatar /> và hiển thị nó bên trong một thẻ div bao ngoài:

import Avatar from './Avatar.js';

function Card({ children }) {
  return (
    <div className="card">
      {children}
    </div>
  );
}

export default function Profile() {
  return (
    <Card>
      <Avatar
        size={100}
        person={{ 
          name: 'Katsuko Saruhashi',
          imageId: 'YfeOqp2'
        }}
      />
    </Card>
  );
}

Hãy thử thay thế <Avatar> bên trong <Card> bằng một đoạn văn bản để thấy cách component Card có thể bao bọc bất kỳ nội dung lồng nào. Nó không cần phải “biết” chính xác thứ gì đang được hiển thị bên trong. Bạn sẽ thấy mẫu thiết kế linh hoạt này được sử dụng rất phổ biến ở nhiều nơi.

Bạn có thể hình dung một component có prop children giống như một “chỗ trống” mà các component cha có thể “lấp đầy” bằng bất kỳ JSX nào. Bạn sẽ thường sử dụng prop children cho các thành phần bao bọc giao diện, chẳng hạn như: bảng (panel), lưới (grid), v.v.

A puzzle-like Card tile with a slot for "children" pieces like text and Avatar

Illustrated by Rachel Lee Nabors

Props thay đổi như thế nào theo thời gian

Component Clock dưới đây nhận hai props từ component cha của nó: colortime. (Code của component cha được lược bỏ vì nó sử dụng state, phần mà chúng ta chưa đi sâu vào lúc này.)

Hãy thử thay đổi màu trong hộp chọn bên dưới:

export default function Clock({ color, time }) {
  return (
    <h1 style={{ color: color }}>
      {time}
    </h1>
  );
}

Ví dụ này minh họa rằng một component có thể nhận các props khác nhau theo thời gian. Props không phải lúc nào cũng tĩnh! Ở đây, prop time thay đổi mỗi giây, và prop color thay đổi khi bạn chọn một màu khác. Props phản ánh dữ liệu của component tại bất kỳ thời điểm nào, chứ không chỉ ở lúc khởi đầu.

Tuy nhiên, props là bất biến (immutable) — một thuật ngữ trong khoa học máy tính có nghĩa là “không thể thay đổi”. Khi một component cần thay đổi props của nó (ví dụ, phản ứng với tương tác người dùng hoặc dữ liệu mới), nó sẽ phải “yêu cầu” component cha truyền cho nó props khác — một đối tượng (object) mới! Các props cũ sẽ bị bỏ qua, và cuối cùng trình thông dịch JavaScript sẽ giải phóng bộ nhớ mà chúng chiếm dụng.

Đừng cố “thay đổi props”. Khi bạn cần phản hồi tương tác của người dùng (chẳng hạn như thay đổi màu được chọn), bạn sẽ cần phải “đặt state” (set state), phần mà bạn có thể tìm hiểu trong State: Một bộ nhớ của Component.

Tóm tắt

  • Để truyền props, bạn chỉ cần thêm chúng vào JSX, giống như cách bạn sử dụng các thuộc tính trong HTML.
  • Để đọc props, hãy sử dụng cú pháp destructuring function Avatar({ person, size }).
  • Bạn có thể chỉ định giá trị mặc định như size = 100, giá trị này sẽ được dùng khi prop bị thiếu hoặc có giá trị undefined.
  • Bạn có thể chuyển tiếp tất cả props bằng cú pháp JSX spread <Avatar {...props} />, nhưng đừng lạm dụng nó!
  • JSX lồng nhau như <Card><Avatar /></Card> sẽ xuất hiện dưới dạng prop children của component Card.
  • Props là các ảnh chụp (snapshot) chỉ đọc theo thời gian: mỗi lần render sẽ nhận một phiên bản mới của props.
  • Bạn không thể thay đổi props. Khi bạn cần tương tác với nó, bạn phải sử dụng set state.

Challenge 1 of 3:
Tách một component

Component Gallery này chứa một số đoạn mã rất giống nhau cho hai profile. Hãy tách một component Profile ra khỏi nó để giảm sự trùng lặp. Bạn sẽ cần quyết định các props nào sẽ truyền cho component đó.

import { getImageUrl } from './utils.js';

export default function Gallery() {
  return (
    <div>
      <h1>Notable Scientists</h1>
      <section className="profile">
        <h2>Maria Skłodowska-Curie</h2>
        <img
          className="avatar"
          src={getImageUrl('szV5sdG')}
          alt="Maria Skłodowska-Curie"
          width={70}
          height={70}
        />
        <ul>
          <li>
            <b>Profession: </b> 
            physicist and chemist
          </li>
          <li>
            <b>Awards: 4 </b> 
            (Nobel Prize in Physics, Nobel Prize in Chemistry, Davy Medal, Matteucci Medal)
          </li>
          <li>
            <b>Discovered: </b>
            polonium (chemical element)
          </li>
        </ul>
      </section>
      <section className="profile">
        <h2>Katsuko Saruhashi</h2>
        <img
          className="avatar"
          src={getImageUrl('YfeOqp2')}
          alt="Katsuko Saruhashi"
          width={70}
          height={70}
        />
        <ul>
          <li>
            <b>Profession: </b> 
            geochemist
          </li>
          <li>
            <b>Awards: 2 </b> 
            (Miyake Prize for geochemistry, Tanaka Prize)
          </li>
          <li>
            <b>Discovered: </b>
            a method for measuring carbon dioxide in seawater
          </li>
        </ul>
      </section>
    </div>
  );
}