Thứ ba, 14/07/2020 | 00:00 GMT+7

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

Trong phát triển React , việc theo dõi dữ liệu ứng dụng của bạn thay đổi như thế nào theo thời gian được gọi là quản lý trạng thái . Bằng cách quản lý trạng thái ứng dụng của bạn , bạn có thể tạo các ứng dụng động phản hồi thông tin nhập của user . Có nhiều phương pháp quản lý trạng thái trong React, bao gồm quản lý trạng thái dựa trên lớp và các thư viện của bên thứ ba như Redux . Trong hướng dẫn này, bạn sẽ quản lý trạng thái trên các thành phần chức năng bằng một phương pháp được khuyến khích bởi tài liệu React chính thức : Hooks.

Hooks là một tập hợp rộng các công cụ chạy các chức năng tùy chỉnh khi đạo cụ của một thành phần thay đổi. Vì phương pháp quản lý trạng thái này không yêu cầu bạn sử dụng các lớp, các nhà phát triển có thể sử dụng Hooks để viết mã ngắn hơn, dễ đọc hơn, dễ chia sẻ và duy trì. Một trong những điểm khác biệt chính giữa Hooks và quản lý trạng thái dựa trên lớp là không có đối tượng duy nhất nào nắm giữ tất cả trạng thái. Thay vào đó, bạn có thể chia trạng thái thành nhiều phần mà bạn có thể cập nhật độc lập.

Trong suốt hướng dẫn này, bạn sẽ học cách cài đặt trạng thái bằng cách sử dụng useStateuseReducer Hooks. useState Hook có giá trị khi đặt giá trị mà không tham chiếu đến trạng thái hiện tại; useReducer Hook hữu ích khi bạn cần tham chiếu đến một giá trị trước đó hoặc khi bạn có các hành động khác nhau yêu cầu các thao tác dữ liệu phức tạp. Để 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 với giỏ hàng 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. Đến cuối hướng dẫn này, bạn sẽ cảm thấy thoải mái khi quản lý trạng thái trong một thành phần chức năng bằng Hooks và bạn sẽ có nền tảng cho các Hook nâng cao hơn như useEffect , useMemouseContext .

Yêu cầu

Bước 1 - Cài đặt trạng thái ban đầu trong một thành phần

Trong bước này, bạn sẽ đặt trạng thái ban đầu trên một thành phần bằng cách gán trạng thái ban đầu cho một biến tùy chỉnh bằng useState Hook. Để khám phá Hooks, bạn sẽ tạo trang sản phẩm có giỏ hàng, sau đó hiển thị các giá trị ban đầu dựa trên 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ị trạng thái bằng cách sử dụng Hooks và khi nào thì sử dụng trạng thái thay vì một giá trị chống đỡ hoặc một giá trị tĩnh.

Bắt đầu bằng cách tạo folder cho thành phần Product :

  • mkdir src/components/Product

Tiếp theo, mở một file có tên là Product.js trong folder Product :

  • nano src/components/Product/Product.js

Bắt đầu bằng cáchtạo một thành phần không có trạng thái. Thành phần sẽ bao gồm 2 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 hoặc bớt mặt hàng khỏi giỏ hàng. Hiện tại, các nút này sẽ không có chức năng.

Thêm mã sau vào file :

hooks-tutorial / src / components / Product / Product.js
import React from 'react'; import './Product.css';  export default function Product() {   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>   ) } 

Trong mã này, bạn đã sử dụng JSX để tạo các phần tử HTML cho thành phần Product , với một biểu tượng cảm xúc dạng kem để đại diện cho sản phẩm. Ngoài ra, hai trong số các phần tử <div> có tên lớp để bạn có thể thêm một số kiểu CSS cơ bản.

Lưu file , sau đó tạo một file mới có tên là Product.css trong folder Product :

  • nano src/components/Product/Product.css

Thêm một số kiểu để tăng kích thước phông chữ cho văn bản và biểu tượng cảm xúc:

hooks-tutorial / src / components / Product / Product.css
.product span {     font-size: 100px; }  .wrapper {     padding: 20px;     font-size: 20px; }  .wrapper button {     font-size: 20px;     background: none;     border: black solid 1px; } 

Biểu tượng cảm xúc cần font-size lớn hơn nhiều, vì nó hoạt động như hình ảnh sản phẩm. Ngoài ra, bạn đang xóa nền gradient mặc định trên nút bằng cách đặt background thành none .

Lưu và đóng file . Bây giờ, hãy thêm thành phần vào Thành phần App để hiển thị thành phần Product 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ó. Ngoài ra, hãy xóa nhập CSS vì bạn sẽ không sử dụng nó trong hướng dẫn này:

hooks-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

Đến đây bạn có một thành phần đang hoạt động, bạn có thể thay thế dữ liệu được mã hóa cứng bằng các giá trị động.

React xuất một số Hook mà bạn có thể nhập trực tiếp từ gói React chính. Theo quy ước, React Hooks bắt đầu bằng cách use từ, chẳng hạn như useState , useContextuseReducer . Hầu hết các thư viện của bên thứ ba đều tuân theo cùng một quy ước. Ví dụ: ReduxuseSelectoruseStore Hook .

Hook là các hàm cho phép bạn chạy các hành động như một phần của vòng đời React . Hook được kích hoạt bởi các hành động khác hoặc bởi các thay đổi trong đạo cụ của một thành phần và được sử dụng để tạo dữ liệu hoặc để kích hoạt các thay đổi tiếp theo. Ví dụ: useState Hook tạo ra một phần dữ liệu trạng thái cùng với một hàm để thay đổi phần dữ liệu đó và kích hoạt kết xuất lại. Nó sẽ tạo một đoạn mã động và kết nối vào vòng đời bằng cách kích hoạt kết xuất khi dữ liệu thay đổi. Trong thực tế, điều đó nghĩa là bạn có thể lưu trữ các phần dữ liệu động trong các biến bằng cách sử dụng useState Hook.

Ví dụ: trong thành phần này, bạn có hai phần dữ liệu sẽ thay đổi dựa trên hành động của user : giỏ hàng và tổng chi phí. Mỗi trong số này có thể được lưu trữ ở trạng thái bằng cách sử dụng Hook ở trên.

Để thử điều này, hãy mở Product.js :

  • nano src/components/Product/Product.js

Tiếp theo, nhập useState Hook từ React bằng cách thêm mã được đánh dấu:

hooks-tutorial / src / components / Product / Product.js
import React, { useState } from 'react'; import './Product.css';  export default function Product() {   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>   ) } 

useState là một hàm lấy trạng thái ban đầu làm đối số và trả về một mảng có hai mục. Mục đầu tiên là một biến chứa trạng thái mà bạn sẽ thường sử dụng trong JSX của bạn . Mục thứ hai trong mảng là một hàm sẽ cập nhật trạng thái. Vì React trả về dữ liệu dưới dạng một mảng, bạn có thể sử dụng cấu trúc hủy để gán giá trị cho bất kỳ tên biến nào bạn muốn. Điều đó nghĩa là bạn có thể gọi useState nhiều lần và không bao giờ phải lo lắng về xung đột tên, vì bạn có thể gán mọi trạng thái và hàm cập nhật cho một biến có tên rõ ràng.

Tạo Hook đầu tiên của bạn bằng cách gọi useState Hook với một mảng trống. Thêm vào mã được đánh dấu sau:

hooks-tutorial / src / components / Product / Product.js
import React, { useState } from 'react'; import './Product.css';  export default function Product() {   const [cart, setCart] = useState([]);   return(     <div className="wrapper">       <div>         Shopping Cart: {cart.length} 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>   ) } 

Ở đây bạn đã gán giá trị đầu tiên, trạng thái, cho một biến được gọi là cart . cart sẽ là một mảng chứa các sản phẩm trong giỏ hàng. Bằng cách chuyển một mảng trống làm đối số cho useState , bạn đặt trạng thái trống ban đầu làm giá trị đầu tiên của cart .

Ngoài biến cart , bạn đã gán chức năng cập nhật cho một biến có tên là setCart . Đến đây, bạn không sử dụng hàm setCart và bạn có thể thấy cảnh báo về việc có một biến không sử dụng. Bỏ qua cảnh báo này ngay bây giờ; trong bước tiếp theo, bạn sẽ sử dụng setCart để cập nhật trạng thái cart .

Lưu các file . Khi trình duyệt reload , bạn sẽ thấy trang không có thay đổi:

Trang sản phẩm

Một điểm khác biệt quan trọng giữa Hooks và quản lý nhà nước dựa trên lớp là, trong quản lý nhà nước dựa trên lớp, có một đối tượng trạng thái duy nhất. Với Hooks, các đối tượng trạng thái hoàn toàn độc lập với nhau, vì vậy bạn có thể có bao nhiêu đối tượng trạng thái tùy thích. Điều đó nghĩa là nếu bạn muốn một phần dữ liệu trạng thái mới, tất cả những gì bạn cần làm là gọi useState với một mặc định mới và gán kết quả cho các biến mới.

Bên trong Product.js , hãy thử điều này bằng cách tạo một phần trạng thái mới để giữ total . Đặt giá trị mặc định thành 0 và gán giá trị và hàm cho totalsetTotal :

hooks-tutorial / src / components / Product / Product.js
import React, { useState } from 'react'; import './Product.css';  export default function Product() {   const [cart, setCart] = useState([]);   const [total, setTotal] = useState(0);   return(     <div className="wrapper">       <div>         Shopping Cart: {cart.length} total items.       </div>       <div>Total: {total}</div>        <div className="product"><span role="img" aria-label="ice cream">🍦</span></div>       <button>Add</button> <button>Remove</button>     </div>   ) } 

Đến đây bạn có một số dữ liệu trạng thái, bạn có thể chuẩn hóa dữ liệu được hiển thị để tạo ra trải nghiệm dễ đoán hơn. Ví dụ: vì tổng trong ví dụ này là giá nên nó sẽ luôn có hai chữ số thập phân. Bạn có thể sử dụng phương thức toLocaleString để chuyển đổi total từ một số thành một chuỗi có hai chữ số thập phân. Nó cũng sẽ chuyển đổi số thành chuỗi theo quy ước số phù hợp với ngôn ngữ của trình duyệt. Bạn sẽ đặt các tùy chọn minimumFractionDigitsmaximumFractionDigits để cung cấp số lượng vị trí thập phân nhất quán.

Tạo một hàm có tên getTotal . Hàm này sẽ sử dụng total biến trong phạm vi và trả về một chuỗi được bản địa hóa mà bạn sẽ sử dụng để hiển thị tổng. Sử dụng undefined làm đối số đầu tiên cho toLocaleString để sử dụng ngôn ngữ hệ thống thay vì chỉ định ngôn ngữ:

hooks-tutorial / src / components / Product / Product.js
import React, { useState } from 'react'; import './Product.css';  const currencyOptions = {   minimumFractionDigits: 2,   maximumFractionDigits: 2, }  export default function Product() {   const [cart, setCart] = useState([]);   const [total, setTotal] = useState(0);    function getTotal() {     return total.toLocaleString(undefined, currencyOptions)   }    return(     <div className="wrapper">       <div>         Shopping Cart: {cart.length} total items.       </div>       <div>Total: {getTotal()}</div>        <div className="product"><span role="img" aria-label="ice cream">🍦</span></div>       <button>Add</button> <button>Remove</button>     </div>   ) } 

Đến đây bạn đã thêm một số xử lý chuỗi vào tổng số được hiển thị. Mặc dù getTotal là một hàm riêng biệt, nó có cùng phạm vi với hàm xung quanh, nghĩa là nó có thể tham chiếu đến các biến của thành phần.

Lưu các file . Trang sẽ reload và bạn sẽ thấy tổng số được cập nhật với hai chữ số thập phân:

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

Chức năng này hoạt động, nhưng hiện tại, getTotal chỉ có thể hoạt động trong đoạn mã này. Trong trường hợp này, bạn có thể chuyển đổi nó thành một chức năng thuần túy , cung cấp các kết quả giống nhau khi được cung cấp các đầu vào giống nhau và không dựa vào môi trường cụ thể để hoạt động. Bằng cách chuyển đổi chức năng thành một chức năng thuần túy, bạn làm cho nó có thể tái sử dụng nhiều hơn. Ví dụ, bạn có thể extract nó vào một file riêng biệt và sử dụng nó trong nhiều thành phần.

Cập nhật getTotal để lấy total làm đối số. Sau đó, di chuyển hàm ra bên ngoài thành phần:

hooks-tutorial / src / components / Product / Product.js
import React, { useState } from 'react'; import './Product.css';  const currencyOptions = {   minimumFractionDigits: 2,   maximumFractionDigits: 2, }  function getTotal(total) {   return total.toLocaleString(undefined, currencyOptions) }  export default function Product() {   const [cart, setCart] = useState([]);   const [total, setTotal] = useState(0);     return(     <div className="wrapper">       <div>         Shopping Cart: {cart.length} total items.       </div>       <div>Total: {getTotal(total)}</div><^>        <div className="product"><span role="img" aria-label="ice cream">🍦</span></div>       <button>Add</button> <button>Remove</button>     </div>   ) } 

Lưu các file . Khi bạn làm như vậy, trang sẽ reload và bạn sẽ thấy thành phần như trước đây.

Các thành phần chức năng như thế này giúp di chuyển các chức năng xung quanh dễ dàng hơn. Miễn là không có xung đột phạm vi, bạn có thể di chuyển các hàm chuyển đổi này đến bất kỳ đâu bạn muốn.

Trong bước này, bạn đặt giá trị mặc định cho một phần dữ liệu trạng thái bằng useState . Sau đó, bạn đã lưu dữ liệu trạng thái và một hàm để cập nhật trạng thái cho các biến bằng cách sử dụng cấu trúc mảng. Trong bước tiếp theo, bạn sẽ sử dụng chức năng cập nhật để thay đổi giá trị trạng thái nhằm hiển thị lại trang với thông tin cập nhật.

Bước 2 - Đặt trạng thái với useState

Trong bước này, bạn sẽ cập nhật trang sản phẩm của bạn bằng cách đặt trạng thái mới với giá trị tĩnh. Bạn đã tạo hàm để cập nhật một phần trạng thái, vì vậy bây giờ bạn sẽ tạo một sự kiện để cập nhật cả hai biến trạng thái với các giá trị được định nghĩa . Khi kết thúc bước này, bạn sẽ có một trang với trạng thái mà user có thể cập nhật chỉ bằng một cú nhấp chuột.

Không giống như các thành phần dựa trên lớp, bạn không thể cập nhật một số phần trạng thái bằng một lệnh gọi hàm duy nhất. Thay vào đó, bạn phải gọi từng hàm riêng lẻ. Điều này nghĩa là có sự tách biệt nhiều hơn các mối quan tâm, giúp giữ tập trung vào các đối tượng trạng thái.

Tạo một chức năng để thêm một mặt hàng vào giỏ hàng và cập nhật tổng cộng với giá của mặt hàng, sau đó thêm chức năng đó vào nút Thêm :

hooks-tutorial / src / components / Product / Product.js
import React, { useState } from 'react';  ...  export default function Product() {   const [cart, setCart] = useState([]);   const [total, setTotal] = useState(0);    function add() {     setCart(['ice cream']);     setTotal(5);   }    return(     <div className="wrapper">       <div>         Shopping Cart: {cart.length} total items.       </div>       <div>Total: {getTotal(total)}</div>        <div className="product"><span role="img" aria-label="ice cream">🍦</span></div>       <button onClick={add}>Add</button><^>       <button>Remove</button>     </div>   ) } 

Trong đoạn mã này, bạn đã gọi setCart với một mảng chứa từ "kem" và gọi là setTotal với 5 . Sau đó, bạn đã thêm chức năng này vào trình xử lý sự kiện onClick cho nút Thêm .

Chú ý rằng hàm phải có cùng phạm vi với các hàm để cài đặt trạng thái, vì vậy nó phải được định nghĩa bên trong hàm thành phần.

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ì bạn không tham chiếu đến ngữ cảnh this , bạn có thể sử dụng hàm mũi tên hoặc khai báo hàm. Cả hai đều hoạt động tốt như nhau ở đây và mỗi nhà phát triển hoặc group có thể quyết định sử dụng kiểu nào. Bạn thậm chí có thể bỏ qua việc xác định một hàm bổ sung và chuyển trực tiếp hàm vào thuộc tính onClick .

Để thử điều này, hãy tạo một hàm để loại bỏ các giá trị bằng cách đặt giỏ hàng thành một đối tượng trống và tổng bằng 0 . Tạo chức năng trong phần hỗ trợ onClick của nút Xóa :

hooks-tutorial / src / component / Product / Product.js
import React, { useState } from 'react'; ... export default function Product() {   const [cart, setCart] = useState([]);   const [total, setTotal] = useState(0);    function add() {     setCart(['ice cream']);     setTotal(5);   }    return(     <div className="wrapper">       <div>         Shopping Cart: {cart.length} total items.       </div>       <div>Total: {getTotal(total)}</div>        <div className="product"><span role="img" aria-label="ice cream">🍦</span></div>       <button onClick={add}>Add</button>       <button         onClick={() => {           setCart([]);           setTotal(0);         }}       >         Remove       </button>     </div>   ) } 

Lưu các file . Khi bạn làm như vậy, bạn có thể thêm và xóa một mục:

Thêm và Xóa

Cả hai chiến lược để gán hàm đều hoạt động, nhưng có một số tác động hiệu suất nhỏ đối với việc tạo một hàm mũi tên trực tiếp trong một giá đỡ. Trong mỗi lần render, React sẽ tạo ra một hàm mới, hàm này sẽ kích hoạt một thay đổi hỗ trợ và khiến thành phần hiển thị lại. Khi bạn xác định một hàm bên ngoài một giá đỡ, bạn có thể tận dụng một Hook khác được gọi là useCallback . Điều này sẽ ghi nhớ hàm, nghĩa là nó sẽ chỉ tạo một hàm mới nếu các giá trị nhất định thay đổi. Nếu không có gì thay đổi, chương trình sẽ sử dụng bộ nhớ đệm của hàm thay vì tính toán lại. Một số thành phần có thể không cần mức tối ưu hóa đó, nhưng theo quy luật, một thành phần có khả năng nằm trong cây càng cao thì nhu cầu ghi nhớ càng lớn.

Trong bước này, bạn đã cập nhật dữ liệu trạng thái với các hàm được tạo bởi useState Hook. Bạn đã tạo các hàm gói để gọi cả hai hàm nhằm cập nhật trạng thái của một số phần dữ liệu cùng một lúc. Nhưng các hàm này bị hạn chế vì chúng thêm các giá trị tĩnh, được định nghĩa thay vì sử dụng trạng thái trước đó để tạo trạng thái mới. Trong bước tiếp theo, bạn sẽ cập nhật trạng thái bằng cách sử dụng trạng thái hiện tại với cả useState Hook và Hook mới được gọi là useReducer .

Bước 3 - Đặt trạng thái bằng trạng thái hiện tại

Trong bước trước, bạn đã cập nhật trạng thái với một giá trị tĩnh. Trạng thái trước đó không quan trọng — bạn luôn vượt qua cùng một giá trị. Nhưng một trang sản phẩm điển hình sẽ có nhiều mặt hàng mà bạn có thể thêm vào giỏ hàng và bạn cần cập nhật giỏ hàng trong khi vẫn giữ các mặt hàng trước đó.

Trong bước này, bạn sẽ cập nhật trạng thái bằng trạng thái hiện tại. Bạn sẽ mở rộng trang sản phẩm của bạn để bao gồm một số sản phẩm và bạn sẽ tạo các chức năng cập nhật giỏ hàng và tổng số dựa trên các giá trị hiện tại. Để cập nhật các giá trị, bạn sẽ sử dụng cả useState Hook và một Hook mới được gọi là useReducer .

Vì React có thể tối ưu hóa mã bằng cách gọi các hành động một cách không đồng bộ, bạn cần đảm bảo hàm của bạn có quyền truy cập vào trạng thái cập nhật nhất. Cách cơ bản nhất để giải quyết vấn đề này là truyền một hàm cho hàm cài đặt trạng thái thay vì một giá trị. Nói cách khác, thay vì gọi setState(5) , bạn sẽ gọi setState(previous => previous +5) .

Để bắt đầu triển khai điều này, hãy thêm một số mục khác vào trang sản phẩm bằng cách tạo một mảng products gồm các đối tượng , sau đó xóa trình xử lý sự kiện khỏi các nút ThêmXóa để nhường chỗ cho việc tái cấu trúc:

hooks-tutorial / src / component / Product / Product.js
import React, { useState } 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 function Product() {   const [cart, setCart] = useState([]);   const [total, setTotal] = useState(0);    function add() {     setCart(['ice cream']);     setTotal(5);   }    return(     <div className="wrapper">       <div>         Shopping Cart: {cart.length} total items.       </div>       <div>Total: {getTotal(total)}</div>         <div>         {products.map(product => (           <div key={product.name}>             <div className="product">               <span role="img" aria-label={product.name}>{product.emoji}</span>             </div>             <button>Add</button>             <button>Remove</button>           </div>         ))}       <^></div><^     </div>   ) } 

Đến đây bạn có một số JSX sử dụng phương thức .map để lặp qua mảng và hiển thị các sản phẩm.

Lưu các file . Khi bạn làm như vậy, trang sẽ reload và bạn sẽ thấy nhiều sản phẩm:

Danh sách sản phẩm

Hiện tại, các nút không có hành động nào. Vì bạn chỉ muốn thêm sản phẩm cụ thể khi nhấp chuột, bạn cần chuyển sản phẩm làm đối số cho hàm add . Trong hàm add , thay vì truyền trực tiếp mục mới tới các setCartsetTotal , bạn sẽ chuyển một hàm ẩn danh có trạng thái hiện tại và trả về một giá trị cập nhật mới:

hooks-tutorial / src / component / Product / Product.js
import React, { useState } from 'react'; import './Product.css'; ... export default function Product() {   const [cart, setCart] = useState([]);   const [total, setTotal] = useState(0);    function add(product) {     setCart(current => [...current, product.name]);     setTotal(current => current + product.price);   }    return(     <div className="wrapper">       <div>         Shopping Cart: {cart.length} total items.       </div>       <div>Total: {getTotal(total)}</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={() => add(product)}>Add</button>             <button>Remove</button>           </div>         ))}       </div>     </div>   ) } 

Hàm ẩn danh sử dụng trạng thái mới nhất — cart hoặc total — làm đối số mà bạn có thể sử dụng để tạo giá trị mới. Tuy nhiên, hãy cẩn thận, 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 giỏ hàng, bạn có thể thêm sản phẩm mới vào trạng thái bằng cách rải giá trị hiện tại và thêm giá trị mới vào cuối.

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

Có một Hook khác được gọi là useReducer được thiết kế đặc biệt để cập nhật trạng thái dựa trên trạng thái hiện tại, theo cách tương tự như phương thức mảng .reduce . useReducer Hook tương tự như useState , nhưng khi bạn khởi tạo Hook, bạn truyền vào một hàm mà Hook sẽ chạy khi bạn thay đổi trạng thái cùng với dữ liệu ban đầu. Hàm — được gọi là bộ reducer — có hai đối số: trạng thái và một đối số khác. Đối số khác là những gì bạn sẽ cung cấp khi bạn gọi hàm cập nhật.

Cấu trúc lại trạng thái giỏ hàng để sử dụng useReducer Hook. Tạo một funciton có tên cartReducer lấy stateproduct làm đối số. Thay thế useState bằng useReducer , sau đó chuyển hàm cartReducer làm đối số đầu tiên và một mảng trống làm đối số thứ hai, sẽ là dữ liệu ban đầu:

hooks-tutorial / src / component / Product / Product.js
import React, { useReducer, useState } from 'react';  ...  function cartReducer(state, product) {   return [...state, product] }  export default function Product() {   const [cart, setCart] = useReducer(cartReducer, []);   const [total, setTotal] = useState(0);    function add(product) {     setCart(product.name);     setTotal(current => current + product.price);   }    return( ...   ) } 

Bây giờ khi bạn gọi setCart , hãy nhập tên sản phẩm thay vì một hàm. Khi bạn gọi setCart , bạn sẽ gọi hàm giảm thiểu và sản phẩm sẽ là đối số thứ hai. Bạn có thể thực hiện một thay đổi tương tự với total trạng thái.

Tạo một hàm gọi là totalReducer có trạng thái hiện tại và thêm số tiền mới. Sau đó thay thế useState bằng useReducer và chuyển giá trị mới setCart thay vì một hàm:

hooks-tutorial / src / component / Product / Product.js
import React, { useReducer } from 'react';  ...  function totalReducer(state, price) {   return state + price; }  export default function Product() {   const [cart, setCart] = useReducer(cartReducer, []);   const [total, setTotal] = useReducer(totalReducer, 0);    function add(product) {     setCart(product.name);     setTotal(product.price);   }    return(     ...   ) } 

Vì bạn không còn sử dụng useState Hook nữa, bạn đã xóa nó khỏi quá trình nhập.

Lưu các file . Khi bạn làm như vậy, trang sẽ reload và bạn có thể thêm các mặt hàng vào giỏ hàng:

Thêm sản phẩm

Bây giờ là lúc để thêm chức năng remove . Nhưng điều này dẫn đến một vấn đề: Các hàm giảm thiểu có thể xử lý việc thêm các mục và cập nhật tổng số, nhưng không rõ nó sẽ có thể xử lý việc xóa các mục khỏi trạng thái như thế nào. Một mẫu phổ biến trong các hàm rút gọn là chuyển một đối tượng làm đối số thứ hai chứa tên của hành động và dữ liệu cho hành động. Bên trong bộ giảm, sau đó bạn có thể cập nhật tổng số dựa trên hành động. Trong trường hợp này, bạn sẽ thêm các mặt hàng vào giỏ hàng khi thực hiện hành động add và xóa chúng bằng hành động remove .

Bắt đầu với totalReducer . Cập nhật hàm để thực hiện một action làm đối số thứ hai, sau đó thêm một điều kiện để cập nhật trạng thái dựa trên action.type :

hooks-tutorial / src / component / Product / Product.js
import React, { useReducer } from 'react'; import './Product.css';  ...  function totalReducer(state, action) {   if(action.type === 'add') {     return state + action.price;   }   return state - action.price }  export default function Product() {   const [cart, setCart] = useReducer(cartReducer, []);   const [total, setTotal] = useReducer(totalReducer, 0);    function add(product) {     const { name, price } = product;     setCart(name);     setTotal({ price, type: 'add' });   }    return(     ...   ) } 

action là một đối tượng có hai properite: typeprice . Loại có thể add hoặc removeprice là một con số. Nếu loại được add , nó sẽ làm tăng tổng số. Nếu nó bị remove , nó sẽ làm giảm tổng số. Sau khi cập nhật totalReducer , bạn gọi setTotal với một type addprice , mà bạn đặt bằng cách sử dụng gán cơ cấu.

Tiếp theo, bạn sẽ cập nhật cartReducer . Điều này phức tạp hơn một chút: Bạn có thể sử dụng điều kiện if/then , nhưng thông thường hơn là sử dụng câu lệnh switch . Câu lệnh switch đặc biệt hữu ích nếu bạn có một trình giảm thiểu có thể xử lý nhiều hành động khác nhau vì nó làm cho những hành động đó dễ đọc hơn trong mã của bạn.

Giống như với totalReducer , bạn sẽ chuyển một đối tượng làm thuộc tính nametype mục thứ hai. Nếu hành động bị remove , hãy cập nhật trạng thái bằng cách nối version đầu tiên của sản phẩm.

Sau khi cập nhật cartReducer , hãy tạo một hàm remove gọi setCartsetTotal với các đối tượng chứa type: 'remove'price hoặc name . Sau đó, sử dụng câu lệnh switch để cập nhật dữ liệu dựa trên loại hành động. Đảm bảo trả về trạng thái cuối cùng:

hooks-tutorial / src / complex / Product / Product.js
import React, { useReducer } from 'react'; import './Product.css';  ...  function cartReducer(state, action) {   switch(action.type) {     case 'add':       return [...state, action.name];     case 'remove':       const update = [...state];       update.splice(update.indexOf(action.name), 1);       return update;     default:       return state;   } }  function totalReducer(state, action) {   if(action.type === 'add') {     return state + action.price;   }   return state - action.price }  export default function Product() {   const [cart, setCart] = useReducer(cartReducer, []);   const [total, setTotal] = useReducer(totalReducer, 0);    function add(product) {     const { name, price } = product;     setCart({ name, type: 'add' });     setTotal({ price, type: 'add' });   }    function remove(product) {     const { name, price } = product;     setCart({ name, type: 'remove' });     setTotal({ price, type: 'remove' });   }    return(     <div className="wrapper">       <div>         Shopping Cart: {cart.length} total items.       </div>       <div>Total: {getTotal(total)}</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={() => add(product)}>Add</button>             <button onClick={() => remove(product)}>Remove</button>           </div>         ))}       </div>     </div>   ) } 

Khi bạn làm việc trên mã của bạn , hãy cẩn thận không trực tiếp làm thay đổi trạng thái trong các hàm giảm thiểu. Thay vào đó, hãy tạo một bản sao trước khi splicing đối tượng ra. Cũng xin lưu ý cách tốt nhất là thêm một hành động default trên câu lệnh switch để giải quyết các trường hợp cạnh không lường trước được. Trong trường hợp này, chỉ cần trả lại đối tượng. Các tùy chọn khác cho default là đặt ra một lỗi hoặc quay trở lại một hành động như thêm hoặc xóa.

Sau khi áp dụng các thay đổi , hãy lưu file . Khi trình duyệt làm mới, 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 nhỏ trong sản phẩm này. Trong phương pháp remove , bạn có thể trừ vào giá ngay cả khi mặt hàng không có trong giỏ hàng. 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 hiển thị của bạn sẽ là -5,00 .

Bạn có thể sửa lỗi này bằng cách kiểm tra xem một mục có tồn tại trước khi trừ đi hay không, nhưng một cách hiệu quả hơn là giảm thiểu các phần trạng thái khác nhau bằng cách chỉ lưu dữ liệu liên quan ở một nơi. Nói cách khác, cố gắng tránh các tham chiếu kép đến cùng một dữ liệu, trong trường hợp này là sản phẩm. Thay vào đó, hãy lưu trữ dữ liệu thô trong một biến trạng thái — toàn bộ đối tượng sản phẩm — sau đó thực hiện các phép tính bằng dữ liệu đó.

Cấu trúc lại thành phần để hàm add() chuyển toàn bộ sản phẩm đến bộ giảm và hàm remove() loại bỏ toàn bộ đối tượng. Phương thức getTotal sẽ sử dụng giỏ hàng và do đó bạn có thể xóa hàm totalReducer . Sau đó, bạn có thể chuyển giỏ hàng đến getTotal() , bạn có thể cấu trúc lại để giảm mảng thành một giá trị duy nhất:

hooks-tutorial / src / component / Product / Product.js
import React, { useReducer } from 'react'; import './Product.css';  const currencyOptions = {   minimumFractionDigits: 2,   maximumFractionDigits: 2, }  function getTotal(cart) {   const total = cart.reduce((totalCost, item) => totalCost + item.price, 0);   return total.toLocaleString(undefined, currencyOptions) }  ...  function cartReducer(state, action) {   switch(action.type) {     case 'add':       return [...state, action.product];     case 'remove':       const productIndex = state.findIndex(item => item.name === action.product.name);       if(productIndex < 0) {         return state;       }       const update = [...state];       update.splice(productIndex, 1)       return update     default:       return state;   } }  export default function Product() {   const [cart, setCart] = useReducer(cartReducer, []);    function add(product) {     setCart({ product, type: 'add' });   }    function remove(product) {     setCart({ product, type: 'remove' });   }     return(     <div className="wrapper">       <div>         Shopping Cart: {cart.length} total items.       </div>       <div>Total: {getTotal(cart)}</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={() => add(product)}>Add</button>             <button onClick={() => remove(product)}>Remove</button>           </div>         ))}       </div>     </div>   ) } 

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ó giỏ hàng cuối cùng của bạn :

Thêm và xóa sản phẩm

Bằng cách sử dụng useReducer Hook, bạn đã giữ cho phần thân thành phần chính của bạn được tổ chức tốt và dễ đọc, vì logic phức tạp để phân tích cú pháp và nối mảng nằm ngoài thành phần. Bạn cũng có thể di chuyển bộ giảm tốc ra bên ngoài các thành phần nếu bạn muốn sử dụng lại nó hoặc bạn có thể tạo một Hook tùy chỉnh để sử dụng trên nhiều thành phần. Bạn có thể tạo các Hook tùy chỉnh dưới dạng các hàm xung quanh các Hook cơ bản, chẳng hạn như useState , useReducer hoặc useEffect .

Hooks cung cấp cho bạn cơ hội để di chuyển logic trạng thái vào và ra khỏi thành phần, trái ngược với các lớp, nơi bạn thường bị ràng buộc với thành phần. Lợi thế này cũng có thể mở rộng sang các thành phần khác. Vì Hook là các hàm, bạn có thể nhập chúng vào nhiều thành phần thay vì sử dụng kế thừa hoặc các dạng phức tạp khác của thành phần lớp.

Trong bước này, bạn đã học cách cài đặt trạng thái bằng cách sử dụng trạng thái hiện tại. Bạn đã tạo một thành phần cập nhật trạng thái bằng cách sử dụng cả useStateuseReducer Hooks, đồng thời bạn đã cấu trúc lại thành phần thành các Hook khác nhau để ngăn chặn lỗi và cải thiện khả năng sử dụng lại.

Kết luận

Hooks là một thay đổi lớn đối với React, tạo ra một cách mới để chia sẻ logic và cập nhật các thành phần mà không cần sử dụng các lớp. Đến đây bạn có thể tạo các thành phần bằng useStateuseReducer , bạn có các công cụ để tạo các dự án phức tạp đáp ứng user và thông tin động. Bạn cũng có kiến thức nền tảng mà bạn có thể sử dụng để khám phá các Hook phức tạp hơn hoặc tạo các Hook tùy chỉnh.

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