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 useState
và useReducer
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
, useMemo
và useContext
.
Yêu cầu
Bạn cần một môi trường phát triển chạy Node.js ; hướng dẫn này đã được thử nghiệm trên Node.js version 10.20.1 và NPM version 6.14.4. Để cài đặt phần mềm này trên macOS hoặc Ubuntu 18.04, hãy làm theo các bước trong Cách cài đặt Node.js và Tạo Môi trường Phát triển Cục bộ trên macOS hoặc phần Cài đặt Sử dụng PPA của Cách Cài đặt Node.js trên Ubuntu 18.04 .
Môi trường phát triển React được cài đặt với Create React App , với phần trình bày không cần thiết bị xóa. Để cài đặt điều này, hãy làm theo Bước 1 - Tạo một dự án trống của hướng dẫn Cách quản lý trạng thái trên các thành phần lớp React . Hướng dẫn này sẽ sử dụng
hooks-tutorial
làm tên dự án.Bạn cũng cần có kiến thức cơ bản về JavaScript, bạn có thể tìm thấy kiến thức này trong Cách viết mã trong JavaScript , cùng với kiến thức cơ bản về HTML và CSS. Một tài nguyên hữu ích cho HTML và CSS là Mạng nhà phát triển Mozilla .
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 node này sẽ không có chức năng.
Thêm mã sau vào file :
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:
.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:
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
:
Đế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
, useContext
và useReducer
. 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ụ: Redux có useSelector
và useStore
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:
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:
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:
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 total
và setTotal
:
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 minimumFractionDigits
và maximumFractionDigits
để 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ữ:
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:
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:
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 :
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:
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 :
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:
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 node Thêm và Xóa để nhường chỗ cho việc tái cấu trúc:
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:
Hiện tại, các node 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 setCart
và setTotal
, 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:
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:
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 state
và product
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:
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:
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:
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
:
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: type
và price
. Loại có thể add
hoặc remove
và price
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
add
và price
, 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 name
và type
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 setCart
và setTotal
với các đối tượng chứa type: 'remove'
và 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:
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:
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:
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 :
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ả useState
và useReducer
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 useState
và useReducer
, 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 .
Các tin liên quan