Xin chào👋🏻,
Bài viết này được tạo riêng cho những người mới bắt đầu muốn tìm hiểu các phương pháp hiệu quả hơn để quản lý trạng thái giữa nhiều thành phần. Nó cũng nhằm mục đích giải quyết vấn đề phổ biến về việc khoan chống đỡ có thể khiến mã của bạn khó bảo trì và khó hiểu hơn. Hãy bắt đầu với loại vấn đề mà API ngữ cảnh giải quyết.
Nếu bạn thích định dạng video thì đây là hướng dẫn mà bạn có thể xem trên kênh YouTube của tôi.👇🏻
Bạn có biết đôi khi bạn cần truyền dữ liệu từ thành phần cha mẹ xuống thành phần con và cuối cùng bạn lại truyền đạo cụ qua một loạt thành phần ở giữa không? Đó gọi là khoan chống đỡ và nó có thể trở nên lộn xộn nhanh chóng. Hãy xem qua một ví dụ để làm rõ điều này.
Như được hiển thị trong sơ đồ, hãy tưởng tượng bạn đã tìm nạp một số dữ liệu trong thành phần App
nằm ở thư mục gốc của ứng dụng. Bây giờ, nếu một thành phần được lồng sâu, chẳng hạn như thành phần Grandchild
, cần truy cập dữ liệu này, thì bạn thường chuyển nó qua các thành phần Parent
và Child
dưới dạng đạo cụ trước khi đến được Grandchild
. Điều này có thể trở nên tồi tệ khi ứng dụng của bạn phát triển.
Đây là một cách trình bày trực quan khác:
Trong ví dụ trên, thành phần Profile
cần dữ liệu người dùng, nhưng dữ liệu này trước tiên phải truyền qua các thành phần App
và Navigation
, mặc dù bản thân các thành phần trung gian này không sử dụng dữ liệu. Vì vậy, làm thế nào để chúng ta làm sạch điều này? Đó là lúc API ngữ cảnh phát huy tác dụng.
Đạo cụ khoan:
API ngữ cảnh trong React.js cho phép bạn truyền dữ liệu giữa các thành phần mà không cần truyền dữ liệu dưới dạng đạo cụ qua từng cấp độ của cây thành phần. Nó hoạt động giống như một hệ thống quản lý trạng thái toàn cầu, nơi bạn xác định trạng thái của mình trong một đối tượng ngữ cảnh và sau đó bạn có thể dễ dàng truy cập nó ở bất kỳ đâu trong cây thành phần. Hãy hiểu điều này bằng một ví dụ.
Như bạn có thể thấy trong sơ đồ, chúng ta có một đối tượng ngữ cảnh lưu trữ dữ liệu để nhiều thành phần có thể truy cập. Dữ liệu này được lấy từ API hoặc dịch vụ của bên thứ ba. Trước khi truy cập dữ liệu ngữ cảnh này trong bất kỳ thành phần nào, chúng ta cần bao bọc tất cả các thành phần yêu cầu dữ liệu này trong thành phần nhà cung cấp ngữ cảnh.
Nếu chúng ta chỉ cần truy cập dữ liệu trong các thành phần điều hướng và hồ sơ, thì chúng ta không cần phải gói thành phần ứng dụng. Khi bạn đã gói các thành phần có liên quan bằng ContextProvider
, bạn có thể truy cập trực tiếp vào dữ liệu ngữ cảnh trong bất kỳ thành phần nào sử dụng nó. Đừng lo lắng nếu bạn vẫn chưa hiểu nó; hãy đi sâu vào mã và xem nó hoạt động.
Đầu tiên, hãy tạo một ứng dụng React bằng Vite.js . Chỉ cần sao chép các lệnh sau để thiết lập dự án.
npm create vite@latest
cd project_name // to change to project directory npm install npm run dev
Sau đó, bạn có thể mở máy chủ phát triển http://localhost:5173
trong trình duyệt của mình.
Đầu tiên, hãy tạo các thư mục cần thiết. Đây là cấu trúc thư mục của dự án của chúng tôi.
src | components | context
Trong thư mục thành phần, hãy tạo tệp Profile.jsx
và thêm mã sau đây.
import React from 'react' const Profile = () => { return ( <div>Profile</div> ) } export default Profile
Tạo thêm một thành phần có tên Navbar.jsx
trong thư mục thành phần.
import Profile from './Profile' const Navbar = () => { return ( <nav style={{ display: "flex", justifyContent: "space-between", alignItems: "center", width: "90%", height: "10vh", backgroundColor: theme === "light" ? "#fff" : "#1b1b1b", color: theme === "light" ? "#1b1b1b" : "#fff", border: "1px solid #fff", borderRadius: "5px", padding: "0 20px", marginTop: "40px", }}> <h1>LOGO</h1> <Profile /> </nav> ) } export default Navbar
Hãy nhập thành phần <Navbar />
này vào tệp App.jsx
.
import Navbar from "./components/Navbar"; function App() { return ( <main style={{ display: "flex", flexDirection: "column", justifyContent: "start", alignItems: "center", height: "100vh", width: "100vw", }} > <Navbar /> </main> ); } export default App;
Như vậy về cơ bản, thành phần <Profile />
là con của <Navbar />
và <Navbar />
là con của thành phần <App />
.
Hãy tạo tệp UserContext.jsx
trong thư mục context
. Thêm mã sau vào tập tin.
import { createContext, useEffect, useState } from "react"; export const UserContext = createContext(); export const UserProvider = ({ children }) => { const [user, setUser] = useState(null); const fetchUserData = async (id) => { const response = await fetch( `https://jsonplaceholder.typicode.com/users/${id}` ).then((response) => response.json()); console.log(response); setUser(response); }; useEffect(() => { fetchUserData(1); }, []); return ( <UserContext.Provider value={{ user, fetchUserData }} > {children} </UserContext.Provider> ); };
UserContext
trống bằng cách sử dụng createContext
. Chúng tôi đảm bảo nhập nó từ react
. Chúng ta có thể thêm các giá trị mặc định bên trong đối tượng bối cảnh, nhưng hiện tại chúng ta vẫn giữ nó là null.
UserProvider
để trả về một nhà cung cấp bằng cách sử dụng UserContext
, như UserContext.Provider
. Nó bao quanh các thành phần con và trong giá trị, chúng ta có thể chuyển bất cứ thứ gì chúng ta muốn sử dụng trong các thành phần con.
fetchUserData
chấp nhận id
và sử dụng ID đó để tìm nạp dữ liệu người dùng. Sau đó, chúng tôi lưu trữ phản hồi ở trạng thái user
.
fetchUserData
trong useEffect
nên khi tải trang, nó sẽ gọi hàm và đưa dữ liệu vào trạng thái user
.
Bây giờ, hãy sử dụng bối cảnh này trong thành phần <App />
. Gói thành phần <NavBar />
bằng cách sử dụng <UserProvider />
; giống như đoạn mã sau:
<UserProvider> <Navbar /> </UserProvider>
Hãy sử dụng trạng thái user
trong thành phần <Profile />
. Để làm được điều đó, chúng tôi sẽ sử dụng hook useContext
. Điều đó lấy UserContext
và cung cấp các giá trị mà chúng ta đã chuyển trong UserProvider
, chẳng hạn như trạng thái user
và hàm fetchUserData
. Hãy nhớ rằng, chúng ta không cần bọc thành phần <Profile />
vì nó đã có trong thành phần <Navbar />
vốn đã được gói cùng với nhà cung cấp.
Mở Profile.jsx
và thêm đoạn mã sau.
const { user } = useContext(UserContext); if (user) { return ( <span style={{ fontWeight: "bold", }} > {user.name} </span> ); } else { return <span>Login</span>; }
Ở đây, chúng tôi đang sử dụng trạng thái user
từ UserContext
. Chúng tôi sẽ hiển thị tên người dùng nếu có user
, nếu không chúng tôi sẽ chỉ hiển thị thông báo đăng nhập. Bây giờ, nếu bạn thấy kết quả đầu ra thì phải có tên người dùng trong thành phần thanh điều hướng. Đây là cách chúng ta có thể trực tiếp sử dụng bất kỳ trạng thái nào trong ngữ cảnh của bất kỳ thành phần nào. Thành phần sử dụng trạng thái này phải được gói trong <Provider />
.
Bạn cũng có thể sử dụng nhiều bối cảnh. Bạn chỉ cần bọc các thành phần nhà cung cấp trong một thành phần nhà cung cấp khác như trong ví dụ sau.
<ThemeProvider> <UserProvider> <Navbar /> </UserProvider> </ThemeProvider>
Trong ví dụ trên, chúng tôi đang sử dụng <ThemeProvider />
để quản lý trạng thái chủ đề.
Bạn có thể xem video youtube ở trên để xem ví dụ đầy đủ về việc sử dụng nhiều nhà cung cấp ngữ cảnh.
Có một vấn đề xảy ra khi bạn sử dụng API ngữ cảnh trong nhiều thành phần. Bất cứ khi nào trạng thái hoặc giá trị thay đổi trong API ngữ cảnh, nó sẽ hiển thị lại tất cả các thành phần đã đăng ký với ngữ cảnh cụ thể đó, ngay cả khi không phải tất cả các thành phần đều đang sử dụng trạng thái đã thay đổi. Để hiểu vấn đề kết xuất lại này, hãy tạo một thành phần <Counter />
sử dụng ngữ cảnh để lưu trữ và hiển thị các giá trị đếm.
Hãy xem ví dụ sau. Bạn có thể tạo tệp Counter.jsx
trong thư mục thành phần và dán đoạn mã sau.
import { createContext, memo, useContext, useState } from "react"; const CountContext = createContext(); const CountProvider = ({ children }) => { const [count, setCount] = useState(0); return ( <CountContext.Provider value={{ count, setCount }}> {children} </CountContext.Provider> ); }; function CountTitle() { console.log("This is Count Title component"); return <h1>Counter Title</h1>; } function CountDisplay() { console.log("This is CountDisplay component"); const { count } = useContext(CountContext); return <div>Count: {count}</div>; } function CounterButton() { console.log("This is CounterButton component"); const { count, setCount } = useContext(CountContext); return ( <> <CountTitle /> <CountDisplay /> <button onClick={() => setCount(count + 1)}>Increase</button> </> ); } export default function Counter() { return ( <CountProvider> <CounterButton /> </CountProvider> ); }
Trong đoạn mã trên:
CountContext
bằng createContext.
CountProvider,
chúng ta có một trạng thái để lưu trữ các giá trị đếm. Chúng ta đang gửi count
và phương thức setCount
tới các thành phần con thông qua value prop.
Chúng tôi đã tạo các thành phần riêng biệt để xem số lần hiển thị lại các thành phần riêng lẻ.
<CountTitle />
: Thành phần này chỉ hiển thị tiêu đề và thậm chí không sử dụng bất kỳ giá trị nào từ ngữ cảnh.
<CountDisplay />
: Thành phần này hiển thị các giá trị đếm và sử dụng trạng thái count
từ ngữ cảnh.
<CounterButton />
: Thành phần này hiển thị cả thành phần trên và nút tăng giá trị đếm bằng cách sử dụng setCount
.
Cuối cùng, chúng tôi gói thành phần <CounterButton />
trong thành phần CountProvider
để các thành phần khác có thể truy cập vào các giá trị đếm.
Bây giờ, nếu bạn chạy mã và nhấp vào nút Increase
, bạn sẽ thấy trong nhật ký rằng mọi thành phần đều hiển thị lại mỗi khi trạng thái thay đổi. <CountTitle />
thậm chí không sử dụng các giá trị đếm nhưng nó đang hiển thị lại. Điều này xảy ra vì thành phần chính của <CountTitle />
là <CounterButton />
đang sử dụng và cập nhật giá trị của count và đó là lý do hiển thị lại.
Làm thế nào chúng ta có thể tối ưu hóa hành vi này? Câu trả lời là memo
. memo
React cho phép bạn bỏ qua việc hiển thị lại một thành phần khi đạo cụ của nó không thay đổi. Sau thành phần <CountTitle />
, hãy thêm dòng sau.
const MemoizedCountTitle = React.memo(CountTitle)
Bây giờ, trong thành phần <CounterButton />
, nơi chúng ta đang hiển thị thành phần <CountTitle />
, hãy thay thế <CountTitle />
bằng <MemoizedCountTitle />
như trong đoạn mã sau:
<> <MemoizedCountTitle /> <CountDisplay /> <button onClick={() => setCount(count + 1)}>Increase</button> </>
Bây giờ, nếu bạn tăng số lượng và kiểm tra nhật ký, bạn sẽ có thể thấy rằng nó không hiển thị thành phần <CountTitle />
nữa.
Redux
là một thư viện quản lý trạng thái để quản lý trạng thái phức tạp với các chuyển đổi trạng thái dễ dự đoán hơn. Trong khi API bối cảnh được thiết kế để quản lý trạng thái đơn giản và truyền dữ liệu qua cây thành phần mà không cần khoan prop. Vậy khi nào nên chọn cái nào?
Ngoài ra còn có một thư viện nữa cũng là một lựa chọn phổ biến cho việc quản lý state. Phản ứng giật lại .
Nếu bạn muốn tìm hiểu thêm về React Recoil , hãy cho tôi biết trong phần nhận xét và tôi sẽ tạo các hướng dẫn chuyên sâu về chủ đề này dựa trên phản hồi của bạn.
API bối cảnh React.js
cung cấp một cách mạnh mẽ và hiệu quả để quản lý trạng thái trên nhiều thành phần, giải quyết hiệu quả vấn đề khoan chống đỡ. Bằng cách sử dụng API ngữ cảnh, bạn có thể đơn giản hóa mã của mình, giảm việc hiển thị lại không cần thiết và cải thiện hiệu suất ứng dụng tổng thể.
Mặc dù API bối cảnh lý tưởng cho việc quản lý trạng thái đơn giản, nhưng các ứng dụng phức tạp hơn có thể được hưởng lợi từ việc sử dụng Redux
hoặc các thư viện quản lý trạng thái khác như React Recoil
. Hiểu thời điểm và cách sử dụng các công cụ này sẽ cho phép bạn xây dựng các ứng dụng React dễ bảo trì và mở rộng hơn.
Cảm ơn bạn đã đọc bài viết này, tôi hy vọng bạn thấy nó hữu ích. Nếu bạn quan tâm đến việc học và xây dựng các dự án bằng React, Redux và Next.js, bạn có thể truy cập kênh YouTube của tôi tại đây: CodeBucks
Dưới đây là các bài viết khác của tôi mà bạn có thể muốn đọc:
Cách triển khai tính năng cuộn mượt mà trong Next.js bằng Lenis và GSAP
Top 10 Theme VS Code Phổ Biến Bạn Nên Thử
Ghé thăm blog cá nhân của tôi: DevDreaming