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

Một số thứ trên màn hình cập nhật theo phản hồi đầu vào của người dùng. Ví dụ, nhấp vào thư viện ảnh sẽ chuyển đổi ảnh đang được hiển thị. Trong React, dữ liệu thay đổi theo thời gian được gọi là state. Bạn có thể thêm state vào bất kỳ component nào và cập nhật nó khi cần thiết. 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ị kết quả khác nhau theo thời gian.

Bắt sự kiện

React cho phép bạn thêm event handler vào JSX của mình. Event handler là những 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 chuột, di chuột, focus vào input của form, và nhiều hơn nữa.

Những component tích hợp sẵn như <button> chỉ hỗ trợ các sự kiện tích hợp sẵn của trình duyệt như onClick. Tuy nhiên, bạn cũng có thể tạo những component của riêng mình và đặt tên cho các props event handler của chúng theo bất kỳ tên cụ thể 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 Lên Ảnh
      </Button>
    </div>
  );
}

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

Ready to learn this topic?

Đọc Bắt Sự Kiện để học cách thêm event handler.

Read More

State: bộ nhớ của component

Những component thường cần thay đổi những gì hiển thị trên màn hình theo kết quả của một tương tác. Gõ vào form sẽ cập nhật trường input, nhấp “tiếp theo” trên carousel ảnh sẽ thay đổi ảnh nào được hiển thị, nhấp “mua” sẽ đưa sản phẩm vào giỏ hàng. Những component cần “nhớ” các thứ: giá trị input hiện tại, ảnh hiện tại, giỏ hàng. Trong React, loại bộ nhớ cụ thể của component này được gọi là state.

Bạn có thể thêm state vào một component với Hook useState. Hook là những function đặc biệt cho phép những component của bạn sử dụng các tính năng 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ó nhận 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 Component để học cách nhớ một giá trị và cập nhật nó khi tương tác.

Read More

Render và commit

Trước khi những 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 quá 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 những component của bạn là những đầu bếp trong nhà bếp, pha chế những món ăn ngon từ nguyên liệu. Trong kịch bản này, React là người phục vụ nhận yêu cầu từ khách hàng và mang đến cho họ đơn hàng. Quá trình yêu cầu và phục vụ UI có ba bước:

  1. Kích hoạt một render (chuyển đơn hàng của khách đến nhà bếp)
  2. Render component (chuẩn bị đơn hàng trong nhà 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.
    Trigger
  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 để học vòng đời của một lần cập nhật UI.

Read More

State như một snapshot

Không giống như những biến JavaScript thông thường, React state hoạt động giống như một snapshot hơn. Thiết lập nó không thay đổi biến state mà 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 một re-render với 1
console.log(count); // Vẫn là 0!

Hành vi này giúp bạn tránh những bug tinh vi. Đây là một ứng dụng chat nhỏ. Hãy thử đoán xem điều gì sẽ xảy ra nếu bạn nhấn “Gửi” trước rồi 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>
        Tới:{' '}
        <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 để học tại sao state có vẻ “cố định” và không thay đổi bên trong event handler.

Read More

Xếp hàng đợi một chuỗi cập nhật state

Component này có bug: nhấp “+3” chỉ tăng điểm số 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 số: {score}</h1>
    </>
  )
}

State Như Một Snapshot giải thích tại sao điều này xảy ra. Thiết lập state yêu cầu một re-render mới, nhưng không thay đổi nó trong code đang 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ể sửa điều này bằng cách truyền một updater function khi thiết lập state. Chú ý cách thay thế setScore(score + 1) bằng setScore(s => s + 1) sửa được nút “+3”. Điều này cho phép bạn xếp hàng đợi nhiều lần 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 số: {score}</h1>
    </>
  )
}

Ready to learn this topic?

Đọc Xếp Hàng Đợi Một Chuỗi Cập Nhật State để học cách xếp hàng đợi một chuỗi cập nhật state.

Read More

Cập nhật object trong state

State có thể chứa bất kỳ loại giá trị JavaScript nào, bao gồm object. Nhưng bạn không nên thay đổi những object và array mà bạn giữ trong React state một cách trực tiếp. 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ì, bạn sẽ sử dụng cú pháp spread ... để sao chép những object và array mà bạn muốn thay đổi. Ví dụ, cập nhật một nested object 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 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 thiểu code 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 Object Trong State để học cách cập nhật object một cách chính xác.

Read More

Cập nhật array trong state

Array là một loại object JavaScript có thể thay đổi (mutable) khác mà bạn có thể lưu trữ trong state, nhưng nên coi như chỉ đọc được (read-only). Giống như với 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 đó thiết lập 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 tôi muốn 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 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 thiểu code 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 Array Trong State để học cách cập nhật array một cách chính xác.

Read More

Tiếp theo là gì?

Hãy đến Bắt Sự Kiện để bắt đầu đọc chương này từng trang một!

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