ReactJS: Cách quản lý state trên các component dạng class của React


Khóa học qua video:
Lập trình Python All Lập trình C# All SQL Server All Lập trình C All Java PHP HTML5-CSS3-JavaScript
Đăng ký Hội viên
Tất cả các video dành cho hội viên

Giới thiệu

Trong React, state đề cập đến một cấu trúc theo dõi cách dữ liệu thay đổi theo thời gian trong ứng dụng của bạn. Quản lý state là một kỹ năng quan trọng trong React vì nó cho phép bạn tạo các component tương tác và các ứng dụng web động. State được sử dụng cho mọi thứ, từ theo dõi đầu vào biểu mẫu đến thu thập dữ liệu động từ một API. Trong hướng dẫn này, bạn sẽ xem qua một ví dụ về quản lý state trên các component dạng class.

Khi viết hướng dẫn này, tài liệu React chính thức khuyến khích các nhà phát triển sử dụng React Hooks để quản lý state với các component dạng function khi viết mã mới, thay vì sử dụng các component dạng class. Mặc dù việc sử dụng React Hooks được coi là một phương pháp hiện đại hơn, nhưng điều quan trọng là bạn phải hiểu cách quản lý state trên các component dạng class. Nắm được bản chất đằng sau việc quản lý state sẽ giúp bạn điều hướng và khắc phục sự cố quản lý state dựa trên class trong các cơ sở mã hiện có và giúp bạn quyết định khi nào quản lý state dựa trên class phù hợp hơn. Ngoài ra còn có một phương thức dựa trên class được gọi là phương thức componentDidCatch không có sẵn trong Hooks và sẽ yêu cầu thiết lập trạng thái bằng cách sử dụng các phương thức class.

Hướng dẫn này trước tiên sẽ chỉ cho bạn cách đặt state bằng cách sử dụng giá trị tĩnh, hữu ích cho các trường hợp state tiếp theo không phụ thuộc vào state đầu tiên, chẳng hạn như thiết lập dữ liệu từ một API ghi đè các giá trị cũ. Sau đó, nó sẽ chạy qua cách đặt một state làm state hiện tại, điều này rất hữu ích khi state tiếp theo phụ thuộc vào state hiện tại, chẳng hạn như chuyển đổi một giá trị. Để khám phá các cách thiết lập state khác nhau này, bạn sẽ tạo một component trang sản phẩm mà bạn sẽ cập nhật bằng cách thêm các giao dịch mua từ danh sách các tùy chọn.

Bước 1 - Tạo một dự án trống

Trong bước này, bạn sẽ tạo một dự án mới bằng Create React App. Sau đó, bạn sẽ xóa dự án mẫu và các tệp liên quan được cài đặt khi bạn khởi động dự án. Cuối cùng, bạn sẽ tạo một cấu trúc tệp đơn giản để tổ chức các component của mình. Điều này sẽ cung cấp cho bạn một cơ sở vững chắc để xây dựng ứng dụng mẫu của hướng dẫn này để tạo style trong bước tiếp theo.

Để bắt đầu, hãy thực hiện một dự án mới. Bạn mở terminal và chạy tập lệnh sau để cài đặt một dự án mới bằng cách sử dụng create-react-app:

npx create-react-app state-class-tutorial

Sau khi dự án kết thúc, hãy thay đổi vào thư mục:

cd state-class-tutorial

Khởi động dự án với lệnh:

npm start
Bạn sẽ nhận được một máy chủ cục bộ đang chạy. Nếu dự án không mở trong cửa sổ trình duyệt, bạn có thể mở nó bằng http://localhost:3000/. Nếu bạn đang chạy điều này từ một máy chủ từ xa, địa chỉ sẽ là .http://your_domain:3000

Trình duyệt của bạn sẽ tải với một ứng dụng React đơn giản được bao gồm như một phần của Create React App:

Dự án mẫu React

Bạn sẽ xây dựng một tập hợp các component tùy chỉnh hoàn toàn mới, vì vậy bạn sẽ cần bắt đầu bằng cách xóa một số mã soạn sẵn để bạn có thể có một dự án trống.

Để bắt đầu, hãy mở component src/App.js. Đây là component gốc được đưa vào trang. Tất cả các component sẽ bắt đầu từ đây. Bạn sửa lại file để trong đó chỉ còn chứa như sau:

import './App.css';

function App() {
  return <></>;
}

export default App;

Mở một terminal khác và thực hiện thao tác xóa file logo.svg:

rm src/logo.svg

Tạo thư mục components:

mkdir src/components

Tạo thư mục App:

mkdir src/components/App

Di chuyển các file App.* vào thư mục App:

mv src/App.* src/components/App

Mở file index.js và chỉnh sửa:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './components/App/App';
import * as serviceWorker from './reportWebVitals';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

Lưu file lại và quay lại trang web ta được một trang web trống.

màn hình trống trong chrome

Bây giờ bạn đã hoàn thành dự án Tạo ứng dụng React mẫu, hãy tạo một cấu trúc tệp đơn giản. Điều này sẽ giúp bạn giữ cho các component của bạn cô lập và độc lập.

Tạo một thư mục được gọi components trong thư mục src, components sẽ là nơi chứa tất cả các component tùy chỉnh của bạn. 

Bước 2 - Sử dụng trạng thái trong một component

Trong bước này, bạn sẽ đặt trạng thái ban đầu của một component trên lớp của nó và tham chiếu trạng thái để hiển thị một giá trị. Sau đó, bạn sẽ tạo trang sản phẩm với giỏ hàng hiển thị tổng số mặt hàng trong giỏ hàng bằng giá trị trạng thái. Đến cuối bước, bạn sẽ biết các cách khác nhau để giữ một giá trị và khi nào bạn nên sử dụng trạng thái thay vì giá trị hỗ trợ hoặc giá trị tĩnh.

Xây dựng các component

Bắt đầu bằng cách tạo một thư mục cho Product:

mkdir src/components/Product

Bạn tạo file Product.js rồi mở ra. Bắt đầu bằng cách tạo một component không có state. Component sẽ có hai phần: Giỏ hàng, có số lượng mặt hàng và tổng giá, và sản phẩm, có nút thêm bớt một mặt hàng. Hiện tại, các nút sẽ không có hành động nào.

Thêm mã sau vào Product.js:

import { Component } from 'react';
import './Product.css';

export default class Product extends Component {
  render() {
    return(
      <div className="wrapper">
        <div>
          Shopping Cart: 0 total items.
        </div>
        <div>Total: 0</div>

        <div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
        <button>Add</button>
        <button>Remove</button>
      </div>
    )
  }
}

Như vậy ở đây bạn đã đưa vào một số phần tử div có tên class dạng JSX để bạn có thể thêm một số style cơ bản.

Lưu file lại.

Tiếp theo bạn tạo file Product.css và đưa vào đoạn code sau:

.product span {
    font-size: 100px;
}

.wrapper {
    padding: 20px;
    font-size: 20px;
}

.wrapper button {
    font-size: 20px;
    background: none;
}

Ở đây ta tạo một số style đơn giản để tăng font-size cho phần văn bản và biểu tượng cảm xúc. Biểu tượng cảm xúc sẽ cần kích thước phông chữ lớn hơn nhiều so với văn bản, vì nó hoạt động như hình ảnh sản phẩm trong ví dụ này. Ngoài ra, bạn đang xóa nền gradient mặc định trên các nút bằng cách đặt background thành none.

Lưu file lại.

Bây giờ, kết xuất component Product trong component App để bạn có thể xem kết quả trong trình duyệt. Mở App.js ra và update code như sau:

import Product from '../Product/Product';

function App() {
  return <Product />
}

export default App;

Lưu file lại và quay lại trang web bạn refresh và bạn sẽ thấy sự xuất hiện của component Product.

Trang sản phẩm

Đặt state ban đầu trên một component dạng class

Có hai giá trị trong các giá trị của component sẽ thay đổi trong màn hình của bạn: tổng số mặt hàng và tổng chi phí. Thay vì mã hóa chúng một cách khó khăn, trong bước này, bạn sẽ chuyển chúng vào một đối tượng được gọi là state.

Lớp state là một thuộc tính đặc biệt và có sẵn dùng để kiểm soát việc hiển thị một trang. Khi bạn thay đổi trạng thái, React biết rằng component đó đã lỗi thời và sẽ tự động hiển thị lại. Khi một component hiển thị lại, nó sẽ sửa đổi đầu ra được kết xuất để đưa vào state thông tin cập nhật nhất. Trong ví dụ này, component sẽ hiển thị lại bất cứ khi nào bạn thêm sản phẩm vào giỏ hàng hoặc xóa sản phẩm đó khỏi giỏ hàng. Bạn có thể thêm các thuộc tính khác vào một lớp React, nhưng chúng sẽ không có cùng khả năng kích hoạt kết xuất.

Mở file Product.js, thêm thuộc tính được gọi state vào lớp Product. Sau đó, thêm hai giá trị vào đối tượng statecart và totalcart sẽ là một mảng, vì nó cuối cùng có thể giữ nhiều mặt hàng. total sẽ chứa một số. Sau khi gán các giá trị này, hãy thay thế các tham chiếu đến các giá trị với this.state.property:

import { Component } from 'react';
import './Product.css';

export default class Product extends Component {

  state = {
    cart: [],
    total: 0
  }

  render() {
    return(
      <div className="wrapper">
        <div>
          Shopping Cart: {this.state.cart.length} total items.
        </div>
        <div>Total: {this.state.total}</div>

        <div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
        <button>Add</button>
        <button>Remove</button>
      </div>
    )
  }
}

Trong đoạn code trên, lưu ý rằng trong cả hai trường hợp, vì bạn đang tham chiếu JavaScript bên trong JSX của mình, bạn cần đặt mã trong cặp ngoặc xoắn {}. Bạn cũng đang hiển thị giá trị length của mảng cart để đếm số lượng mục trong mảng.

Lưu file lại và quay lại trang web bạn sẽ vẫn thấy trang như trước.

Thuộc tính state là một thuộc tính class tiêu chuẩn, có nghĩa là nó có thể được truy cập từ các phương thức khác của class component ngoài phương thức render.

Tiếp theo, thay vì hiển thị giá dưới dạng giá trị tĩnh, hãy chuyển đổi nó thành một chuỗi bằng cách sử dụng phương thức toLocaleString, sẽ chuyển đổi số thành một chuỗi phù hợp với cách số được hiển thị theo vùng miền/quốc gia của trình duyệt.

Tạo một phương thức được gọi là getTotal() để lấy state và chuyển đổi nó thành một chuỗi được địa phương hóa bằng cách sử dụng một đối tượng có tên currencyOptions. Sau đó, thay thế tham chiếu đến state trong JSX bằng một lời gọi phương thức:

import { Component } from 'react';
import './Product.css';

export default class Product extends Component {

  state = {
    cart: [],
    total: 0
  }

  currencyOptions = {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
  }

  getTotal = () => {
    return this.state.total.toLocaleString(undefined, this.currencyOptions)
  }

  render() {
    return(
      <div className="wrapper">
        <div>
          Shopping Cart: {this.state.cart.length} total items.
        </div>
        <div>Total {this.getTotal()}</div>

        <div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
        <button>Add</button>
        <button>Remove</button>
      </div>
    )
  }
}

Trong đoạn code trên, vì total là tổng tiền của các sản phẩm, nên bạn truyền giá trị currencyOptions để thiết lập số thập phân (độ chính xác) tối đa và tối thiểu cho total là hai. Lưu ý rằng điều này được đặt thành một thuộc tính riêng biệt. Thông thường, các nhà phát triển React mới bắt đầu sẽ đưa thông tin như thế này vào đôi tượng state, nhưng tốt nhất là chỉ thêm thông tin state mà bạn muốn thay đổi. Bằng cách này, thông tin trong state sẽ dễ dàng lưu giữ hơn khi ứng dụng của bạn mở rộng.

Một thay đổi quan trọng khác mà bạn đã thực hiện là tạo phương thức getTotal() bằng cách gán một hàm mũi tên cho một thuộc tính lớp. Nếu không sử dụng hàm mũi tên, phương thức này sẽ tạo ra một ràng buộc this mới, ràng buộc này sẽ can thiệp vào ràng buộc this hiện tại và đưa một lỗi vào mã của chúng ta. Bạn sẽ thấy thêm về điều này trong bước tiếp theo.

Lưu file lại và quay lại trang web bạn sẽ thấy giá trị được chuyển đổi thành số thập phân.

Giá được chuyển đổi thành số thập phân

Như vậy bạn đã thêm state vào một component và tham chiếu nó trong lớp của bạn. Bạn cũng đã truy cập các giá trị trong phương thức render và trong các phương thức lớp khác. Tiếp theo, bạn sẽ tạo các phương thức để cập nhật state và hiển thị các giá trị động.

Bước 3 - Thiết lập state từ giá trị tĩnh

Hiện tại bạn đã tạo được state cơ sở cho component và bạn đã tham chiếu state đó trong các hàm và mã JSX của mình. Trong bước này, bạn sẽ cập nhật trang sản phẩm của mình để sửa đổi state trong các lần nhấp vào nút. Bạn sẽ học cách truyền một đối tượng mới có chứa các giá trị cập nhật tới một phương thức đặc biệt được gọi là phương thức setState sau đó sẽ thiết lập state với dữ liệu cập nhật.

Để cập nhật state, các nhà phát triển React sử dụng một phương thức đặc biệt được gọi là phương thức setState kế thừa từ lớp cơ sở Component. Phương thức setState có thể mang hoặc một đối tượng hoặc một hàm như là đối số đầu tiên. Nếu bạn có một giá trị tĩnh không cần tham chiếu đến state, thì tốt nhất bạn nên truyền một đối tượng có chứa giá trị mới, vì nó dễ đọc hơn. Nếu bạn cần tham chiếu state hiện tại, bạn truyền một hàm để tránh bất kỳ tham chiếu nào đến state chứa giá trị cũ.

Hãy bắt đầu bằng cách thêm một sự kiện vào các nút. Nếu người dùng của bạn nhấp vào Add, thì chương trình sẽ thêm sản phẩm vào cart và cập nhật total. Nếu họ nhấp vào Remove, nó sẽ đặt lại giỏ hàng thành một mảng trống và total thành 0. Trong ví dụ này, chương trình sẽ không cho phép người dùng thêm nhiều hơn một mục giống nhau.

Mở file Product.js ra, bên trong component, hãy tạo một phương thức mới được gọi là add, sau đó truyền phương thức này đến prop onClick cho nút Add:

import { Component } from 'react';
import './Product.css';

export default class Product extends Component {

  state = {
    cart: [],
    total: 0
  }

  add = () => {
    this.setState({
      cart: ['ice cream'],
      total: 5
    })
  }

  currencyOptions = {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
  }

  getTotal = () => {
    return this.state.total.toLocaleString(undefined, this.currencyOptions)
  }

  render() {
    return(
      <div className="wrapper">
        <div>
          Shopping Cart: {this.state.cart.length} total items.
        </div>
        <div>Total {this.getTotal()}</div>

        <div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
        <button onClick={this.add}>Add</button>
        <button>Remove</button>
      </div>
    )
  }
}

Trong đoạn code trên, bên trong phương thức add, bạn gọi phương thức setState và truyền một đối tượng có chứa giỏ hàng cart là một mảng chứa một sản phẩm duy nhất là ice cream và giá total cập nhật 5. Lưu ý rằng bạn lại sử dụng một hàm mũi tên để tạo phương thức add. Như đã đề cập ở trên, điều này sẽ đảm bảo hàm có ngữ cảnh this thích hợp khi chạy bản cập nhật. Nếu bạn thêm hàm dưới dạng một phương thức mà không sử dụng hàm mũi tên, thì hàm setState sẽ không tồn tại nếu không ràng buộc hàm với ngữ cảnh hiện tại.

Ví dụ: nếu bạn tạo hàm add theo cách này:

export default class Product extends Component {
...
  add() {
    this.setState({
      cart: ['ice cream'],
      total: 5
    })
  }
...
}

Người dùng sẽ gặp lỗi khi họ nhấp vào nút Add.

Lỗi ngữ cảnh

Sử dụng hàm mũi tên sẽ đảm bảo rằng bạn sẽ có ngữ cảnh thích hợp để tránh lỗi này.

Lưu file lại và quay lại trang web và khi bạn nhấp vào nút Add, giỏ hàng sẽ cập nhật số tiền hiện tại.

Nhấp vào nút và xem trạng thái được cập nhật

Với phương thức add này, bạn đã truyền cả hai thuộc tính của đối tượng statecart và total. Tuy nhiên, không phải lúc nào bạn cũng cần phải truyền một đối tượng hoàn chỉnh. Bạn có quyền truyền một đối tượng có chứa các thuộc tính mà bạn muốn cập nhật.

Để xem cách React có thể xử lý một đối tượng nhỏ hơn, hãy tạo một hàm mới được gọi là remove. Truyền một đối tượng mới mà chỉ chứa cart với một mảng trống, sau đó thêm phương thức vào thuộc tính onClick của nút Remove:

import { Component } from 'react';
import './Product.css';

export default class Product extends Component {

  ...
  remove = () => {
    this.setState({
      cart: []
    })
  }

  render() {
    return(
      <div className="wrapper">
        <div>
          Shopping Cart: {this.state.cart.length} total items.
        </div>
        <div>Total {this.getTotal()}</div>

        <div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
        <button onClick={this.add}>Add</button>
        <button onClick={this.remove}>Remove</button>
      </div>
    )
  }
}

Lưu file lại và quay lại trang web, bạn refresh trang sau đó hãy nhấp vào các nút Add và Remove. Bạn sẽ thấy cập nhật giỏ hàng, nhưng không thấy cập nhật giá. Giá trị của state total vẫn được bảo tồn trong quá trình cập nhật. Điều này chứng tỏ ta hoàn toàn có thể truyền đi một đối tượng state mà không cần phải bao gồm đầy đủ các thuộc tính của nó. Nhưng bạn thường sẽ có các component với các thuộc tính trạng thái mà có các phản hồi khác nhau và bạn có thể làm cho chúng tồn tại bằng cách loại bỏ chúng khỏi đối tượng được cập nhật.

Thay đổi trong bước này là tĩnh. Và bạn đã biết chính xác các giá trị sẽ có trước và chúng không cần phải được tính toán lại từ state. Nhưng nếu trang sản phẩm có nhiều sản phẩm và bạn muốn có thể thêm chúng nhiều lần, thì việc truyền một đối tượng tĩnh sẽ không đảm bảo tham chiếu đến thông tin cập nhật nhất của state, ngay cả khi đối tượng của bạn có sử dụng this.state. Trong trường hợp này, thay vào đó bạn có thể sử dụng một hàm.

Trong bước tiếp theo, bạn sẽ cập nhật state bằng các hàm tham chiếu trạng thái hiện tại.

Bước 4 - Thiết lập state bằng state hiện thời

Có nhiều lúc bạn cần tham chiếu trạng thái trước đó để cập nhật trạng thái hiện tại, chẳng hạn như cập nhật mảng, thêm số hoặc sửa đổi đối tượng. Để chính xác nhất có thể, bạn cần tham chiếu tới đối tượng state. Không giống như cập nhật state với một giá trị được xác định trước, trong bước này, bạn sẽ truyền một hàm cho phương thức setState, phương thức này sẽ lấy trạng thái hiện tại làm đối số. Sử dụng phương pháp này, bạn sẽ cập nhật trạng thái của một component bằng trạng thái hiện tại.

Một lợi ích khác của việc cài đặt state với một phương thức là tăng độ tin cậy. Để cải thiện hiệu suất, React có thể thực hiện hàng loạt các lời gọi setState, có nghĩa là this.state.value có thể không hoàn toàn đáng tin cậy. Ví dụ: nếu bạn cập nhật state nhanh chóng ở một số nơi, thì có thể một giá trị nào đó cũng sẽ bị lỗi thời. Điều này có thể xảy ra trong quá trình tìm nạp dữ liệu, xác thực biểu mẫu hoặc bất kỳ tình huống nào trong đó một số hành động diễn ra song song. Nhưng việc sử dụng một hàm có cập nhật state nhất làm đối số để đảm bảo rằng lỗi này sẽ không ảnh hưởng đến mã của bạn.

Để thể hiện hình thức quản lý state này, hãy thêm một số sản phẩm khác vào trang sản phẩm. Đầu tiên, hãy mở file Product.js ra, sau đó tạo một mảng các đối tượng sản phẩm khác nhau. Mảng này sẽ chứa biểu tượng cảm xúc, tên và giá của sản phẩm. Sau đó lặp qua mảng để hiển thị từng sản phẩm kèm cac nút Add và Remove:

import { Component } from 'react';
import './Product.css';

const products = [
  {
    emoji: '🍦',
    name: 'ice cream',
    price: 5
  },
  {
    emoji: '🍩',
    name: 'donuts',
    price: 2.5,
  },
  {
    emoji: '🍉',
    name: 'watermelon',
    price: 4
  }
];

export default class Product extends Component {

  ...

  render() {
    return(
      <div className="wrapper">
        <div>
          Shopping Cart: {this.state.cart.length} total items.
        </div>
        <div>Total {this.getTotal()}</div>
        <div>
          {products.map(product => (
            <div key={product.name}>
              <div className="product">
                <span role="img" aria-label={product.name}>{product.emoji}</span>
              </div>
              <button onClick={this.add}>Add</button>
              <button onClick={this.remove}>Remove</button>
            </div>
          ))}
        </div>
      </div>
    )
  }
}

Trong đoạn mã này, bạn đã sử dụng phương thức của mảng là map() để lặp qua mảng products và trả về JSX sẽ hiển thị từng phần tử trong trình duyệt của bạn.

Lưu file lại và quay lại trang web, bạn refresh lại trang và sẽ thấy kết quả dạng như sau:

Danh sách sản phẩm

Bây giờ bạn sẽ tiến hành cập nhật các phương thức của mình. Đầu tiên, thay đổi phương thức add() trong đó lấy product làm đối số. Sau đó, thay vì truyền một đối tượng đến setState(), hãy truyền một hàm nhận state làm đối số và trả về một đối tượng cart đã cập nhật sản phẩm mới và total đã cập nhật giá mới:

import { Component } from 'react';
import './Product.css';

...

export default class Product extends Component {

  state = {
    cart: [],
    total: 0
  }

  add = (product) => {
    this.setState(state => ({
      cart: [...state.cart, product.name],
      total: state.total + product.price
    }))
  }

  currencyOptions = {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
  }

  getTotal = () => {
    return this.state.total.toLocaleString(undefined, this.currencyOptions)
  }

  remove = () => {
    this.setState({
      cart: []
    })
  }

  render() {
    return(
      <div className="wrapper">
        <div>
          Shopping Cart: {this.state.cart.length} total items.
        </div>
        <div>Total {this.getTotal()}</div>

        <div>
          {products.map(product => (
            <div key={product.name}>
              <div className="product">
                <span role="img" aria-label={product.name}>{product.emoji}</span>
              </div>
              <button onClick={() => this.add(product)}>Add</button>
              <button onClick={this.remove}>Remove</button>
            </div>
          ))}
        </div>
      </div>
    )
  }
}

Trong đoạn code trên, bên trong hàm ẩn danh mà bạn truyền tới setState(), hãy đảm bảo rằng bạn tham chiếu đối số — state —mà không phải trạng thái của component— this.state. Nếu không, bạn vẫn có nguy cơ nhận được một đối tượng state lỗi thời, khi đó trong hàm state của bạn dữ liệu sẽ giống hệt nhau.

Chú ý không để trạng thái đột biến (mutate) trực tiếp. Thay vào đó, khi thêm giá trị mới vào cart, bạn có thể thêm giá trị mới product vào state bằng cách sử dụng cú pháp spread trên giá trị hiện tại và thêm giá trị mới vào cuối.

Cuối cùng, cập nhật lời gọi đến this.add bằng cách thay đổi prop onClick() để thực hiện một hàm ẩn danh gọi this.add() với sản phẩm có liên quan.

Lưu file lại và khi bạn refresh trang web thì kết quả sẽ có dạng như sau:

Thêm sản phẩm

Tiếp theo ta sẽ cập nhật phương thức remove(). Thực hiện theo các bước tương tự: chuyển đổi setState để nhận một hàm, cập nhật các giá trị mà không thay đổi và cập nhật phần prop onChange():

import { Component } from 'react';
import './Product.css';

...

export default class Product extends Component {

...

  remove = (product) => {
    this.setState(state => {
      const cart = [...state.cart];
      cart.splice(cart.indexOf(product.name))
      return ({
        cart,
        total: state.total - product.price
      })
    })
  }

  render() {
    return(
      <div className="wrapper">
        <div>
          Shopping Cart: {this.state.cart.length} total items.
        </div>
        <div>Total {this.getTotal()}</div>
        <div>
          {products.map(product => (
            <div key={product.name}>
              <div className="product">
                <span role="img" aria-label={product.name}>{product.emoji}</span>
              </div>
              <button onClick={() => this.add(product)}>Add</button>
              <button onClick={() => this.remove(product)}>Remove</button>
            </div>
          ))}
        </div>
      </div>
    )
  }
}

Trong đoạn code trên, để tránh thay đổi đối tượng state, trước tiên bạn phải tạo một bản sao của nó bằng toán tử spread. Sau đó, bạn có thể ghép ra mục mà bạn muốn từ các bản sao và trả về bản copy trong đối tượng mới. Bằng cách sao chép state như bước đầu tiên, bạn có thể chắc chắn rằng bạn sẽ không thay đổi đối tượng state.

Lưu các tập tin. Khi bạn làm như vậy, trình duyệt sẽ làm mới và bạn có thể thêm và xóa các mục:

Loại bỏ các mục

Vẫn còn một lỗi trong ứng dụng này: Trong phương thức remove, người dùng có thể trừ total ngay cả khi sản phẩm không có trong cart. Nếu bạn nhấp vào Remove trên cây kem chẳng hạn mà không thêm nó vào giỏ hàng của bạn, thì tổng tiền của bạn sẽ là -5,00.

Bạn có thể sửa lỗi bằng cách kiểm tra sự tồn tại của một mặt hàng trước khi trừ đi, nhưng có một cách dễ dàng hơn đó là chỉ giữ các tham chiếu đến sản phẩm và không tách các tham chiếu đến sản phẩm và tổng chi phí. Cố gắng tránh các tham chiếu kép đến cùng một dữ liệu. Thay vào đó, hãy lưu trữ dữ liệu thô trong state - trong trường hợp này là toàn bộ đối tượng product - sau đó thực hiện các phép tính bên ngoài state.

Cấu trúc lại component để phương thức add() thêm toàn bộ đối tượng, phương thức remove() loại bỏ toàn bộ đối tượng và phương thức getTotal sử dụng cart:

import { Component } from 'react';
import './Product.css';

...

export default class Product extends Component {

  state = {
    cart: [],
  }
  currencyOptions = {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
  }
  add = (product) => {
    this.setState(state => ({
      cart: [...state.cart, product],
    }))
  }

 remove = (product) => {
    this.setState(state => {
      const cart = [...state.cart];
      const productIndex = cart.findIndex(p => p.name === product.name);
      if(productIndex < 0) {
        return;
      }
      cart.splice(productIndex, 1)
      return ({
        cart
      })
    })
  }
  ​getTotal = () => {
   const total = this.state.cart.reduce((totalCost, item) => totalCost + item.price, 0);
   ​return total.toLocaleString(undefined, this.currencyOptions)
 }
  render() {
    ...
  }
}

Trong đoạn code trên, phương thức add() tương tự như những gì nó đã có trước đó, chỉ khác là ta bỏ đi thuộc tính total. Trong phương thức remove(), bạn tìm thấy chỉ mục của product với bằng cách dùng phương thức findByIndex. Nếu chỉ mục không tồn tại, phương thức sẽ trả về -1. Trong trường hợp đó, bạn sử dụng một câu lệnh điều kiện để trả về không gì cả. Bằng cách không trả về gì, React sẽ biết rằng state đã không thay đổi và sẽ không kích hoạt hiển thị lại. Nếu bạn trả về state hoặc một đối tượng trống, nó sẽ vẫn kích hoạt kết xuất lại.

Khi sử dụng phương thức splice(), bạn truyền 1 làm đối số thứ hai, đối số này sẽ loại bỏ một giá trị và giữ phần còn lại.

Cuối cùng, bạn tính toán total bằng cách sử dụng phương thức mảng đó là reduce().

Lưu file lại và quay lại trang web, bạn refresh và sẽ được kết quả dạng như sau:

Thêm và bớt

Phương thức setState bạn truyền có thể có một đối số là các props hiện tại, mà có thể hữu ích nếu bạn có state cần để tham chiếu tới các prop hiện tại. Bạn cũng có thể truyền một hàm callback tới setState làm đối số thứ hai, bất kể bạn có truyền một đối tượng hay hàm cho đối số đầu tiên hay không. Điều này đặc biệt hữu ích khi bạn đang thiết lập state sau khi tìm nạp dữ liệu từ API và bạn cần thực hiện một hành động mới sau khi việc cập nhật state được hoàn tất.

Như vậy trong bước này, bạn đã học cách cập nhật state mới dựa trên state hiện tại. Bạn đã truyền một hàm cho phương thức setState và tính toán các giá trị mới mà không làm thay đổi state hiện thời. Bạn cũng đã học cách thoát khỏi một phương thức setState nếu không có bản cập nhật theo cách sẽ ngăn việc hiển thị lại, thêm một chút cải tiến hiệu suất.

Phần kết luận

Trong bài hướng dẫn này, bạn đã phát triển một component dạng class có state động mà bạn đã cập nhật tĩnh và sử dụng state hiện tại. Giờ đây, bạn có các công cụ để thực hiện các dự án phức tạp đáp ứng người dùng và thông tin động.

React thực sự có một cách để quản lý trạng thái với Hooks, nhưng sẽ rất hữu ích nếu bạn hiểu cách sử dụng trạng thái trên các component nếu bạn cần làm việc với các component dạng class, chẳng hạn như những component sử dụng phương thức componentDidCatch.

Quản lý state là chìa khóa cho gần như tất cả các component và cần thiết để tạo các ứng dụng tương tác. Với kiến ​​thức này, bạn có thể tạo lại nhiều compnent web phổ biến, chẳng hạn như thanh trượt, đàn accordion, biểu mẫu, v.v. Sau đó, bạn sẽ sử dụng các khái niệm tương tự như khi bạn xây dựng ứng dụng bằng cách sử dụng hook hoặc phát triển các component lấy dữ liệu động từ các API.

» Tiếp: Cách quản lý state bằng Hook trên các component React
« Trước: Cách tạo style cho các component React
Khóa học qua video:
Lập trình Python All Lập trình C# All SQL Server All Lập trình C All Java PHP HTML5-CSS3-JavaScript
Đăng ký Hội viên
Tất cả các video dành cho hội viên
Copied !!!