Thêm Tính Tương Tác

Một số thứ trên màn hình cập nhật để phản hồi lại thao tác của người dùng. Ví dụ: nhấp vào thư viện ảnh sẽ chuyển ảnh đang hoạt động. Trong React, dữ liệu thay đổi theo thời gian được gọi là state (trạng thái). Bạn có thể thêm state vào bất kỳ component nào và cập nhật nó khi cần. Trong chương này, bạn sẽ học cách viết các component xử lý tương tác, cập nhật state của chúng và hiển thị các đầu ra khác nhau theo thời gian.

Phản hồi các event

React cho phép bạn thêm trình xử lý event vào JSX của mình. Trình xử lý event là các function của riêng bạn sẽ được kích hoạt để phản hồi các tương tác của người dùng như nhấp, di chuột, tập trung vào các đầu vào biểu mẫu, v.v.

Các component tích hợp sẵn như <button> chỉ hỗ trợ các event trình duyệt tích hợp sẵn như onClick. Tuy nhiên, bạn cũng có thể tạo các component của riêng mình và cung cấp cho các prop trình xử lý event của chúng bất kỳ tên dành riêng cho ứng dụng nào mà bạn thích.

export default function App() {
  return (
    <Toolbar
      onPlayMovie={() => alert('Đang phát!')}
      onUploadImage={() => alert('Đang tải lên!')}
    />
  );
}

function Toolbar({ onPlayMovie, onUploadImage }) {
  return (
    <div>
      <Button onClick={onPlayMovie}>
        Phát Phim
      </Button>
      <Button onClick={onUploadImage}>
        Tải Ảnh Lên
      </Button>
    </div>
  );
}

function Button({ onClick, children }) {
  return (
    <button onClick={onClick}>
      {children}
    </button>
  );
}

Ready to learn this topic?

Đọc Phản hồi các Event để tìm hiểu cách thêm trình xử lý event.

Đọc thêm

State: bộ nhớ của một component

Các component thường cần thay đổi những gì trên màn hình do kết quả của một tương tác. Nhập vào biểu mẫu sẽ cập nhật trường nhập liệu, nhấp vào “tiếp theo” trên băng chuyền hình ảnh sẽ thay đổi hình ảnh nào được hiển thị, nhấp vào “mua” sẽ đưa một sản phẩm vào giỏ hàng. Các component cần “ghi nhớ” mọi thứ: giá trị đầu vào hiện tại, hình ảnh hiện tại, giỏ hàng. Trong React, loại bộ nhớ dành riêng cho component này được gọi là state.

Bạn có thể thêm state vào một component bằng Hook useState. Hook là các function đặc biệt cho phép các component của bạn sử dụng các tính năng của React (state là một trong những tính năng đó). Hook useState cho phép bạn khai báo một biến state. Nó lấy state ban đầu và trả về một cặp giá trị: state hiện tại và một function setter state cho phép bạn cập nhật nó.

const [index, setIndex] = useState(0);
const [showMore, setShowMore] = useState(false);

Đây là cách một thư viện ảnh sử dụng và cập nhật state khi nhấp:

import { useState } from 'react';
import { sculptureList } from './data.js';

export default function Gallery() {
  const [index, setIndex] = useState(0);
  const [showMore, setShowMore] = useState(false);
  const hasNext = index < sculptureList.length - 1;

  function handleNextClick() {
    if (hasNext) {
      setIndex(index + 1);
    } else {
      setIndex(0);
    }
  }

  function handleMoreClick() {
    setShowMore(!showMore);
  }

  let sculpture = sculptureList[index];
  return (
    <>
      <button onClick={handleNextClick}>
        Tiếp theo
      </button>
      <h2>
        <i>{sculpture.name} </i>
        bởi {sculpture.artist}
      </h2>
      <h3>
        ({index + 1} trên {sculptureList.length})
      </h3>
      <button onClick={handleMoreClick}>
        {showMore ? 'Ẩn' : 'Hiện'} chi tiết
      </button>
      {showMore && <p>{sculpture.description}</p>}
      <img
        src={sculpture.url}
        alt={sculpture.alt}
      />
    </>
  );
}

Ready to learn this topic?

Đọc State: Bộ nhớ của một Component để tìm hiểu cách ghi nhớ một giá trị và cập nhật nó khi tương tác.

Đọc thêm

Render và commit

Trước khi các component của bạn được hiển thị trên màn hình, chúng phải được render bởi React. Hiểu các bước trong quy trình này sẽ giúp bạn suy nghĩ về cách code của bạn thực thi và giải thích hành vi của nó.

Hãy tưởng tượng rằng các component của bạn là những đầu bếp trong bếp, lắp ráp các món ăn ngon từ các nguyên liệu. Trong kịch bản này, React là người phục vụ đưa các yêu cầu từ khách hàng và mang chúng đến cho họ. Quá trình yêu cầu và phục vụ UI này có ba bước:

  1. Kích hoạt render (giao đơn đặt hàng của khách hàng đến bếp)
  2. Rendering component (chuẩn bị đơn hàng trong bếp)
  3. Commit vào DOM (đặt đơn hàng lên bàn)
  1. React as a server in a restaurant, fetching orders from the users and delivering them to the Component Kitchen.
    Kích hoạt
  2. The Card Chef gives React a fresh Card component.
    Render
  3. React delivers the Card to the user at their table.
    Commit

Illustrated by Rachel Lee Nabors

Ready to learn this topic?

Đọc Render và Commit để tìm hiểu vòng đời của một bản cập nhật UI.

Đọc thêm

State như một snapshot

Không giống như các biến JavaScript thông thường, state của React hoạt động giống như một snapshot hơn. Việc đặt nó không thay đổi biến state bạn đã có, mà thay vào đó kích hoạt một re-render. Điều này có thể gây ngạc nhiên lúc đầu!

console.log(count); // 0
setCount(count + 1); // Yêu cầu re-render với 1
console.log(count); // Vẫn là 0!

Hành vi này giúp bạn tránh các lỗi nhỏ. Đây là một ứng dụng trò chuyện nhỏ. Hãy thử đoán điều gì sẽ xảy ra nếu bạn nhấn “Gửi” trước và sau đó thay đổi người nhận thành Bob. Tên của ai sẽ xuất hiện trong alert năm giây sau đó?

import { useState } from 'react';

export default function Form() {
  const [to, setTo] = useState('Alice');
  const [message, setMessage] = useState('Hello');

  function handleSubmit(e) {
    e.preventDefault();
    setTimeout(() => {
      alert(`Bạn đã nói ${message} với ${to}`);
    }, 5000);
  }

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Đến:{' '}
        <select
          value={to}
          onChange={e => setTo(e.target.value)}>
          <option value="Alice">Alice</option>
          <option value="Bob">Bob</option>
        </select>
      </label>
      <textarea
        placeholder="Tin nhắn"
        value={message}
        onChange={e => setMessage(e.target.value)}
      />
      <button type="submit">Gửi</button>
    </form>
  );
}

Ready to learn this topic?

Đọc State như một Snapshot để tìm hiểu lý do tại sao state xuất hiện “cố định” và không thay đổi bên trong các trình xử lý event.

Đọc thêm

Xếp hàng đợi một loạt các cập nhật state

Component này bị lỗi: nhấp vào “+3” chỉ tăng điểm một lần.

import { useState } from 'react';

export default function Counter() {
  const [score, setScore] = useState(0);

  function increment() {
    setScore(score + 1);
  }

  return (
    <>
      <button onClick={() => increment()}>+1</button>
      <button onClick={() => {
        increment();
        increment();
        increment();
      }}>+3</button>
      <h1>Điểm: {score}</h1>
    </>
  )
}

State như một Snapshot giải thích tại sao điều này xảy ra. Đặt state yêu cầu một re-render mới, nhưng không thay đổi nó trong code đã chạy. Vì vậy, score tiếp tục là 0 ngay sau khi bạn gọi setScore(score + 1).

console.log(score); // 0
setScore(score + 1); // setScore(0 + 1);
console.log(score); // 0
setScore(score + 1); // setScore(0 + 1);
console.log(score); // 0
setScore(score + 1); // setScore(0 + 1);
console.log(score); // 0

Bạn có thể khắc phục điều này bằng cách truyền một function cập nhật khi đặt state. Lưu ý cách thay thế setScore(score + 1) bằng setScore(s => s + 1) sẽ sửa nút “+3”. Điều này cho phép bạn xếp hàng đợi nhiều cập nhật state.

import { useState } from 'react';

export default function Counter() {
  const [score, setScore] = useState(0);

  function increment() {
    setScore(s => s + 1);
  }

  return (
    <>
      <button onClick={() => increment()}>+1</button>
      <button onClick={() => {
        increment();
        increment();
        increment();
      }}>+3</button>
      <h1>Điểm: {score}</h1>
    </>
  )
}

Ready to learn this topic?

Đọc Xếp hàng đợi một loạt các cập nhật state để tìm hiểu cách xếp hàng đợi một chuỗi các cập nhật state.

Đọc thêm

Cập nhật các object trong state

State có thể chứa bất kỳ loại giá trị JavaScript nào, bao gồm cả object. Nhưng bạn không nên thay đổi trực tiếp các object và array mà bạn giữ trong state của React. Thay vào đó, khi bạn muốn cập nhật một object và array, bạn cần tạo một cái mới (hoặc tạo một bản sao của một cái hiện có), và sau đó cập nhật state để sử dụng bản sao đó.

Thông thường, bạn sẽ sử dụng cú pháp spread ... để sao chép các object và array mà bạn muốn thay đổi. Ví dụ: cập nhật một object lồng nhau có thể trông như thế này:

import { useState } from 'react';

export default function Form() {
  const [person, setPerson] = useState({
    name: 'Niki de Saint Phalle',
    artwork: {
      title: 'Blue Nana',
      city: 'Hamburg',
      image: 'https://i.imgur.com/Sd1AgUOm.jpg',
    }
  });

  function handleNameChange(e) {
    setPerson({
      ...person,
      name: e.target.value
    });
  }

  function handleTitleChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        title: e.target.value
      }
    });
  }

  function handleCityChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        city: e.target.value
      }
    });
  }

  function handleImageChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        image: e.target.value
      }
    });
  }

  return (
    <>
      <label>
        Tên:
        <input
          value={person.name}
          onChange={handleNameChange}
        />
      </label>
      <label>
        Tiêu đề:
        <input
          value={person.artwork.title}
          onChange={handleTitleChange}
        />
      </label>
      <label>
        Thành phố:
        <input
          value={person.artwork.city}
          onChange={handleCityChange}
        />
      </label>
      <label>
        Hình ảnh:
        <input
          value={person.artwork.image}
          onChange={handleImageChange}
        />
      </label>
      <p>
        <i>{person.artwork.title}</i>
        {' bởi '}
        {person.name}
        <br />
        (tọa lạc tại {person.artwork.city})
      </p>
      <img
        src={person.artwork.image}
        alt={person.artwork.title}
      />
    </>
  );
}

Nếu việc sao chép các object trong code trở nên tẻ nhạt, bạn có thể sử dụng một thư viện như Immer để giảm code lặp đi lặp lại:

{
  "dependencies": {
    "immer": "1.7.3",
    "react": "latest",
    "react-dom": "latest",
    "react-scripts": "latest",
    "use-immer": "0.5.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  },
  "devDependencies": {}
}

Ready to learn this topic?

Đọc Cập nhật các Object trong State để tìm hiểu cách cập nhật các object một cách chính xác.

Đọc thêm

Cập nhật các array trong state

Array là một loại object JavaScript có thể thay đổi khác mà bạn có thể lưu trữ trong state và nên coi là chỉ đọc. Giống như với các object, khi bạn muốn cập nhật một array được lưu trữ trong state, bạn cần tạo một cái mới (hoặc tạo một bản sao của một cái hiện có), và sau đó đặt state để sử dụng array mới:

import { useState } from 'react';

const initialList = [
  { id: 0, title: 'Big Bellies', seen: false },
  { id: 1, title: 'Lunar Landscape', seen: false },
  { id: 2, title: 'Terracotta Army', seen: true },
];

export default function BucketList() {
  const [list, setList] = useState(
    initialList
  );

  function handleToggle(artworkId, nextSeen) {
    setList(list.map(artwork => {
      if (artwork.id === artworkId) {
        return { ...artwork, seen: nextSeen };
      } else {
        return artwork;
      }
    }));
  }

  return (
    <>
      <h1>Danh sách Nghệ thuật Mong muốn</h1>
      <h2>Danh sách nghệ thuật của tôi để xem:</h2>
      <ItemList
        artworks={list}
        onToggle={handleToggle} />
    </>
  );
}

function ItemList({ artworks, onToggle }) {
  return (
    <ul>
      {artworks.map(artwork => (
        <li key={artwork.id}>
          <label>
            <input
              type="checkbox"
              checked={artwork.seen}
              onChange={e => {
                onToggle(
                  artwork.id,
                  e.target.checked
                );
              }}
            />
            {artwork.title}
          </label>
        </li>
      ))}
    </ul>
  );
}

Nếu việc sao chép các array trong code trở nên tẻ nhạt, bạn có thể sử dụng một thư viện như Immer để giảm code lặp đi lặp lại:

{
  "dependencies": {
    "immer": "1.7.3",
    "react": "latest",
    "react-dom": "latest",
    "react-scripts": "latest",
    "use-immer": "0.5.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  },
  "devDependencies": {}
}

Ready to learn this topic?

Đọc Cập nhật các Array trong State để tìm hiểu cách cập nhật các array một cách chính xác.

Đọc thêm

Tiếp theo là gì?

Chuyển đến Phản hồi các Event để bắt đầu đọc trang chương này từng trang một!

Hoặc, nếu bạn đã quen thuộc với các chủ đề này, tại sao không đọc về Quản lý State?