Thứ ba, 30/06/2020 | 00:00 GMT+7

Cách quản lý trạng thái trên các thành phần lớp React

Trong React , trạng thái đề 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ý trạng thái là một kỹ năng quan trọng trong React vì nó cho phép bạn tạo các thành phần tương tác và ứng dụng web động. Trạng thái đượ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ừ API. Trong hướng dẫn này, bạn sẽ xem qua một ví dụ về quản lý trạng thái trên cácthành phần dựa trên lớp.

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ý trạng thái với các thành phần chức năng khi viết mã mới, thay vì sử dụng các thành phần dựa trên lớp . 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ý trạng thái trên các thành phần dựa trên lớp. Học các khái niệm đằng sau quản lý nhà nước sẽ giúp bạn chuyển và khắc phục sự cố quản lý nhà nước dựa trên lớp trong các cơ sở mã hiện có và giúp bạn quyết định khi nào quản lý nhà nước dựa trên lớp là phù hợp hơn. Ngoài ra còn có một phương thức dựa trên lớp được gọi là componentDidCatch không có sẵn trong Hooks và sẽ yêu cầu cài đặt trạng thái bằng cách sử dụng các phương thức lớp.

Hướng dẫn này trước tiên sẽ chỉ cho bạn cách đặt trạng thái bằng cách sử dụng giá trị tĩnh, hữu ích cho các trường hợp trạng thái tiếp theo không phụ thuộc vào trạng thái đầu tiên, chẳng hạn như cài đặt dữ liệu từ một API overrides các giá trị cũ. Sau đó, nó sẽ chạy qua cách đặt trạng thái làm trạng thái hiện tại, điều này rất hữu ích khi trạng thái tiếp theo phụ thuộc vào trạng thái hiện tại, chẳng hạn như chuyển đổi một giá trị. Để khám phá các cách cài đặt trạng thái khác nhau này, bạn sẽ tạo thành phần 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.

Yêu cầu

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 Tạo ứng dụng React . Sau đó, bạn sẽ xóa dự án mẫu và các file 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 file đơn giản để tổ chức các thành phần của bạn . Điều này sẽ cung cấp cho bạn cơ sở vững chắc để xây dựng ứng dụng mẫu của hướng dẫn này để quản lý trạng thái trên các thành phần dựa trên lớp.

Để bắt đầu, hãy tạo một dự án mới. Trong terminal của bạn, hãy 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 folder :

  • cd state-class-tutorial

Trong cửa sổ hoặc tab terminal mới, hãy bắt đầu dự án bằng cách sử dụng tập lệnh bắt đầu Tạo ứng dụng React . Trình duyệt sẽ tự động làm mới các thay đổi, vì vậy hãy để tập lệnh này chạy trong khi bạn làm việc:

  • npm start

Bạn sẽ nhận được một server local đang chạy. Nếu dự án không mở trong cửa sổ trình duyệt, bạn có thể mở bằng http://localhost:3000/ . Nếu bạn đang chạy điều này từ một server 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 phản ứng

Bạn sẽ xây dựng một tập hợp các thành phần tùy chỉnh hoàn toàn mới, vì vậy bạn 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ở src/App.js trong editor . Đây là thành phần root được đưa vào trang. Tất cả các thành phần sẽ bắt đầu từ đây. Bạn có thể tìm thêm thông tin về App.js tại Cách cài đặt một dự án React với Tạo ứng dụng React .

Mở src/App.js bằng lệnh sau:

  • nano src/App.js

Bạn sẽ thấy một file như thế này:

state-class-tutorial / src / App.js
import React from 'react'; import logo from './logo.svg'; import './App.css';  function App() {   return (     <div className="App">       <header className="App-header">         <img src={logo} className="App-logo" alt="logo" />         <p>           Edit <code>src/App.js</code> and save to reload.         </p>         <a           className="App-link"           href="https://reactjs.org"           target="_blank"           rel="noopener noreferrer"         >           Learn React         </a>       </header>     </div>   ); }  export default App; 

Xóa import logo from './logo.svg'; dòng import logo from './logo.svg'; . Sau đó, thay thế mọi thứ trong câu lệnh return để trả về một tập hợp các thẻ trống: <></> . Điều này sẽ cung cấp cho bạn một trang hợp lệ mà không trả lại gì. Mã cuối cùng sẽ giống như sau:

state-class-tutorial / src / App.js
 import React from 'react'; import './App.css';  function App() {   return <></>; }  export default App; 

Lưu và thoát khỏi editor .

Cuối cùng, xóa logo. Bạn sẽ không sử dụng nó trong ứng dụng của bạn và bạn nên xóa các file không sử dụng khi làm việc. Nó sẽ giúp bạn khỏi nhầm lẫn về lâu dài.

Trong cửa sổ terminal , nhập lệnh sau:

  • rm src/logo.svg

Nếu bạn nhìn vào trình duyệt của bạn , bạn sẽ thấy một màn hình trống.

màn hình trống trong chrome

Đến đây 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 file đơn giản. Điều này sẽ giúp bạn giữ cho các thành phần của bạn bị cô lập và độc lập.

Tạo một folder được gọi là components trong folder src . Điều này sẽ chứa tất cả các thành phần tùy chỉnh của bạn.

  • mkdir src/components

Mỗi thành phần sẽ có folder riêng để lưu trữ file thành phần cùng với các kiểu, hình ảnh và bài kiểm tra.

Tạo folder cho App :

  • mkdir src/components/App

Di chuyển tất cả các file App vào folder đó. Sử dụng ký tự đại diện, * , để chọn các file nào bắt đầu bằng App. dù phần mở rộng file . Sau đó, sử dụng lệnh mv để đưa chúng vào folder mới:

  • mv src/App.* src/components/App

Tiếp theo, cập nhật đường dẫn nhập tương đối trong index.js , là thành phần root khởi động toàn bộ quá trình:

  • nano src/index.js

Câu lệnh nhập cần trỏ đến file App.js trong folder App , vì vậy hãy thực hiện thay đổi được đánh dấu sau:

state-class-tutorial / src / index.js
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './components/App/App'; import * as serviceWorker from './serviceWorker';  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 và thoát khỏi file .

Bây giờ dự án đã được cài đặt , bạn có thể tạo thành phần đầu tiên của bạn .

Bước 2 - Sử dụng trạng thái trong một thành phần

Trong bước này, bạn sẽ đặt trạng thái ban đầu của một thành phần 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 một 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 thành phần

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

  • mkdir src/components/Product

Tiếp theo, mở Product.js trong folder đó:

  • nano src/components/Product/Product.js

Bắt đầu bằng cách tạo một thành phần không có trạng thái. Thành phần 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 node sẽ không có hành động nào.

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

state-class-tutorial / src / components / Product / Product.js
import React, { 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>     )   } } 

Bạn cũng đã bao gồm một vài phần tử div có tên lớp JSX để bạn có thể thêm một số kiểu cơ bản.

Lưu file , sau đó mở Product.css :

  • nano src/components/Product/Product.css

Tạo kiểu nhẹ nhàng để tăng font-size cho văn bản và biểu tượng cảm xúc:

state-class-tutorial / src / components / Product / Product.css
.product span {     font-size: 100px; }  .wrapper {     padding: 20px;     font-size: 20px; }  .wrapper button {     font-size: 20px;     background: none; } 

Biểu tượng cảm xúc 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 node bằng cách đặt background thành none .

Lưu và đóng file .

Bây giờ, kết Product thành phần Product trong thành phần App để bạn có thể xem kết quả trong trình duyệt. Mở App.js :

  • nano src/components/App/App.js

Nhập thành phần và hiển thị nó. Bạn cũng có thể xóa nhập CSS vì bạn sẽ không sử dụng nó trong hướng dẫn này:

state-class-tutorial / src / components / App / App.js
import React from 'react'; import Product from '../Product/Product';  function App() {   return <Product /> }  export default App; 

Lưu và đóng file . Khi bạn làm như vậy, trình duyệt sẽ làm mới và bạn sẽ thấy thành phần Product .

Trang sản phẩm

Đặt trạng thái ban đầu trên một thành phần lớp

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

state của lớp React là một thuộc tính đặc biệt 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 thành phần đó đã lỗi thời và sẽ tự động hiển thị lại. Khi một thành phần hiển thị lại, nó sẽ sửa đổi kết quả hiển thị để bao gồm thông tin cập nhật nhất ở state . Trong ví dụ này, thành phần sẽ hiển thị lại 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 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ở Product.js :

  • nano src/components/Product/Product.js

Thêm một thuộc tính được gọi là state vào lớp Product . Sau đó, thêm hai giá trị vào đối tượng state : carttotal . cart sẽ là một mảng , vì cuối cùng nó có thể chứa nhiều mặt hàng. total sẽ là một con 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ị bằng this.state. property :

state-class-tutorial / src / components / Product / Product.js
 import React, { 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>     )   } } 

Lưu ý trong cả hai trường hợp, vì bạn đang tham chiếu JavaScript bên trong JSX của bạn , bạn cần đặt mã trong dấu ngoặc nhọn. Bạn cũng đang hiển thị length của mảng cart để đếm số lượng mặt hàng trong mảng.

Lưu các file . Khi bạn làm như vậy, trình duyệt sẽ làm mới và bạn sẽ thấy trang giống như trước đây.

Trang sản phẩm

Các state bất động sản là một tài sản lớp tiêu chuẩn, nghĩa là nó có thể truy cập vào các phương pháp khác, không chỉ là render phương pháp.

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

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

state-class-tutorial / src / components / Product / Product.js
import React, { 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>     )   } } 

total là giá hàng hóa, bạn đang chuyển currencyOptions để đặt số thập phân tối đa và tối thiểu cho total của bạn thành hai. Lưu ý điều này được đặt làm 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 vào state mà bạn muốn thay đổi. Bằng cách này, thông tin ở 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 chức năng mũi tên, phương pháp này sẽ tạo ra một mới this ràng buộc , trong đó sẽ can thiệp vào hiện tại this ràng buộc và giới thiệu một lỗi vào mã của ta . Bạn sẽ thấy nhiều hơn về điều này trong bước tiếp theo.

Lưu các file . Khi bạn làm như vậy, trang sẽ được làm mới và 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

Đến đây bạn đã thêm trạng thái vào một thành phần 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 trạng thái và hiển thị các giá trị động.

Bước 3 - Đặt trạng thái từ giá trị tĩnh

Lúc này, bạn đã tạo trạng thái cơ sở cho thành phần và bạn đã tham chiếu trạng thái đó trong các hàm và mã JSX của bạn . Trong bước này, bạn sẽ cập nhật trang sản phẩm của bạn để sửa đổi state khi nhấp vào nút. Bạn sẽ học cách chuyển một đối tượng mới có chứa các giá trị cập nhật vào một phương thức đặc biệt có tên là setState , phương thức này sau đó sẽ đặt state với dữ liệu được 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 gọi là setState được kế thừa từ lớp Component cơ sở. Phương thức setState có thể lấy một đối tượng hoặc một hàm làm đối số đầu tiên. Nếu bạn có một giá trị tĩnh không cần tham chiếu state , tốt nhất bạn nên chuyể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 trạng thái hiện tại, bạn chuyển một hàm để tránh mọi tham chiếu đến state lỗi thời.

Bắt đầu bằng cách thêm một sự kiện vào các node . Nếu user của bạn nhấp vào Thêm , thì chương trình sẽ thêm mặt hàng vào cart và cập nhật total . Nếu họ nhấp vào Xóa , nó sẽ đặt lại giỏ hàng thành một mảng trống và total0 . Ví dụ, chương trình sẽ không cho phép user thêm một mục nữa sau đó một lần.

Mở Product.js :

  • nano src/components/Product/Product.js

Bên trong thành phần, hãy tạo một phương thức mới được gọi là add , sau đó chuyển phương thức này đến onClick prop cho nút Thêm :

state-class-tutorial / src / components / Product / Product.js
import React, { 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>     )   } } 

Bên trong phương thức add , bạn gọi phương thức setState và chuyển một đối tượng chứa cart được cập nhật với một món ice cream duy nhất và giá cập nhật là 5 . Lưu ý bạn lại sử dụng hàm mũi tên để tạo phương thức add . Như đã đề cập trước đây, điều này sẽ đảm bảo các chức năng có thích this bối cảnh 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, setState sẽ không tồn tại nếu không liên kết 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     })   } ... } 

User sẽ gặp lỗi khi họ nhấp vào nút Thêm .

Lỗi ngữ cảnh

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

Lưu các file . Khi bạn làm như vậy, trình duyệt sẽ reload và khi bạn nhấp vào nút Thêm , 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 , bạn đã chuyển cả hai thuộc tính của đối tượng state : carttotal . Tuy nhiên, không phải lúc nào bạn cũng cần phải chuyển một đối tượng hoàn chỉnh. Bạn chỉ cầ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 và mọi thứ khác sẽ giữ nguyên.

Để 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ó tên là remove . Chuyển một đối tượng mới 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 Xóa :

state-class-tutorial / src / components / Product / Product.js
import React, { 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 các file . Khi trình duyệt làm mới, hãy nhấp vào nút ThêmXóa . Bạn sẽ thấy cập nhật giỏ hàng, nhưng không thấy giá. total giá trị trạng thái được giữ nguyên trong quá trình cập nhật. Giá trị này chỉ được bảo tồn cho các mục đích ví dụ; với ứng dụng này, bạn cần cập nhật cả hai thuộc tính của đối tượng state . Nhưng bạn thường sẽ có các thành phần với các thuộc tính trạng thái có các trách nhiệm 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ập nhật.

Thay đổi trong bước này là tĩnh. Bạn đã biết chính xác các giá trị sẽ có trước thời hạn 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 chuyển một đối tượng tĩnh sẽ không đảm bảo tham chiếu đến state cập nhật nhất, ngay cả khi đối tượng của bạn sử dụng giá trị 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 - Đặt trạng thái bằng trạng thái hiện tạ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ột mảng, thêm một số hoặc sửa đổi một đối tượng. Để chính xác nhất có thể, bạn cần tham khảo đối tượng state cập nhật nhất. Không giống như cập nhật state với giá trị được định nghĩa , 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 thành phần 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 chức năng 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 cuộc gọi setState , nghĩa là this.state. value có thể không hoàn toàn tin cậy . Ví dụ: nếu bạn cập nhật state nhanh chóng ở một số nơi, có thể một giá trị có thể 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ó state cập nhật nhất làm đối số đảm bảo lỗi này sẽ không nhập mã của bạn.

Để thể hiện hình thức quản lý nhà nước này, hãy thêm một số mục khác vào trang sản phẩm. Đầu tiên, hãy mở file Product.js :

  • nano src/components/Product/Product.js

Tiếp theo, tạo một mảng đối tượng cho các 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 bằng nút ThêmXóa :

state-class-tutorial / src / components / Product / Product.js
import React, { 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 đang sử dụng phương thức mảng 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 các file . Khi trình duyệt reload , bạn sẽ thấy danh sách sản phẩm được cập nhật:

Danh sách sản phẩm

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

state-class-tutorial / src / components / Product / Product.js
import React, { 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>     )   } } 

Bên trong hàm ẩn danh mà bạn chuyển đến setState() , hãy đảm bảo bạn tham chiếu đến đối this.state state — chứ không phải state của thành 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. state trong chức năng của bạn sẽ giống hệt nhau.

Chú ý không để trạng thái đột biến trực tiếp. Thay vào đó, khi thêm giá trị mới vào cart , bạn có thể thêm product mới 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ệnh gọi this.add bằng cách thay đổi onClick() để sử dụng một hàm ẩn danh gọi this.add() với sản phẩm có liên quan.

Lưu các file . Khi bạn làm như vậy, trình duyệt sẽ reload và bạn có thể thêm nhiều sản phẩm.

Thêm sản phẩm

Tiếp theo, 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 thành một hàm, cập nhật các giá trị mà không thay đổi và cập nhật onChange() prop:

state-class-tutorial / src / components / Product / Product.js
import React, { 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>     )   } } 

Để tránh làm thay đổi đối tượng trạng thái, 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à gửi bản 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 file . 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 pháp remove , user có thể trừ ra khỏi total ngay cả khi mặt hàng không có trong cart . Nếu bạn nhấp vào Xóa trên cây kem mà không thêm nó vào giỏ hàng, tổng số 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 một cách dễ dàng hơn là giữ cho đối tượng trạng thái của bạn nhỏ bằng cách 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ô ở state - trong trường hợp này là toàn bộ đối tượng product - sau đó thực hiện các tính toán bên ngoài state .

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

state-class-tutorial / src / components / Product / Product.js
import React, { Component } from 'react'; import './Product.css';  ...  export default class Product extends Component {    state = {     cart: [],   }    add = (product) => {     this.setState(state => ({       cart: [...state.cart, product],     }))   }    currencyOptions = {     minimumFractionDigits: 2,     maximumFractionDigits: 2,   }    getTotal = () => {     const total = this.state.cart.reduce((totalCost, item) => totalCost + item.price, 0);     return total.toLocaleString(undefined, this.currencyOptions)   }    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       })     })   }    render() {     ...   } } 

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

Khi sử dụng phương thức splice() , bạn đang chuyển 1 làm đối số thứ hai, phương thức 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 total bằng cách sử dụng phương thức mảng reduce() .

Lưu các file . Khi bạn làm như vậy, trình duyệt sẽ làm mới và bạn sẽ có cart cuối cùng của bạn :

Thêm và bớt

Hàm setState mà bạn truyền có thể có một đối số bổ sung của các đạo cụ hiện tại, điều này có thể hữu ích nếu bạn có trạng thái cần tham chiếu đến các đạo cụ hiện tại. Bạn cũng có thể truyền một hàm gọi lại cho setState làm đối số thứ hai, dù 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 cài đặt 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 cập nhật state hoàn tất.

Trong bước này, bạn đã học cách cập nhật trạng thái mới dựa trên trạng thái hiện tại. Bạn đã chuyển một hàm cho hàm setState và tính toán các giá trị mới mà không làm thay đổi trạng thái hiện tại. Bạn cũng đã học cách thoát khỏi một hàm setState nếu không có bản cập nhật nào theo cách có thể ngăn kết xuất lại, thêm một chút cải tiến hiệu suất.

Kết luận

Trong hướng dẫn này, bạn đã phát triển một thành phần dựa trên lớp có trạng thái động mà bạn đã cập nhật tĩnh và sử dụng trạng thái hiện tại. Như vậy, bạn có các công cụ để thực hiện các dự án phức tạp đáp ứng user 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 thành phần nếu bạn cần làm việc với các thành phần phải dựa trên lớp, chẳng hạn như những componentDidCatch sử dụng phương thức componentDidCatch .

Quản lý trạng thái là key cho gần như tất cả các thành phần 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 thành phần 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 các ứng dụng bằng cách sử dụng hook hoặc phát triển các thành phần lấy dữ liệu động từ các API.

Nếu bạn muốn xem thêm các hướng dẫn về React, hãy xem trang Chủ đề React của ta hoặc quay lại trang Cách viết mã trong chuỗi React.js .


Tags:

Các tin trước