Tóm Tắt
Giới thiệu
Trong bài hướng dẫn này, bạn sẽ học cách chia sẻ state trên nhiều component bằng cách sử dụng ngữ cảnh React. React context là một giao diện để chia sẻ thông tin với các component khác mà không cần truyền dữ liệu làm prop một cách tường minh. Điều này có nghĩa là bạn có thể chia sẻ thông tin giữa component cha và component con lồng nhau sâu hoặc lưu trữ dữ liệu trên toàn trang web ở một nơi duy nhất và truy cập chúng ở bất kỳ đâu trong ứng dụng. Bạn thậm chí có thể cập nhật dữ liệu từ các compnent lồng nhau bằng cách cung cấp các chức năng cập nhật cùng với dữ liệu.
Ngữ cảnh (context) React đủ linh hoạt để sử dụng như một hệ thống quản lý state tập trung cho dự án của bạn hoặc bạn có thể mở rộng phạm vi nó đến các phần nhỏ hơn trong ứng dụng của mình. Với ngữ cảnh, bạn có thể chia sẻ dữ liệu trên toàn ứng dụng mà không cần bất kỳ công cụ bổ sung nào của bên thứ ba và với một lượng nhỏ cấu hình. Điều này cung cấp một giải pháp thay thế trọng lượng nhẹ hơn cho các công cụ như Redux, có thể trợ giúp với các ứng dụng lớn hơn nhưng có thể yêu cầu quá nhiều thiết lập cho các dự án quy mô vừa.
Trong suốt hướng dẫn này, bạn sẽ sử dụng ngữ cảnh để xây dựng một ứng dụng sử dụng các tập dữ liệu chung trên các component khác nhau. Để minh họa điều này, bạn sẽ tạo một trang web nơi người dùng có thể tạo các món salad tùy chỉnh. Trang web sẽ sử dụng ngữ cảnh để lưu trữ thông tin khách hàng, các món yêu thích và món salad tùy chỉnh. Sau đó, bạn sẽ truy cập dữ liệu đó và cập nhật nó trong toàn bộ ứng dụng mà không cần truyền dữ liệu qua prop. Đến cuối hướng dẫn này, bạn sẽ học cách sử dụng ngữ cảnh để lưu trữ dữ liệu ở các cấp độ khác nhau của dự án và cách truy cập và cập nhật dữ liệu trong các component lồng nhau.
Bước 1 – Tạo một dự án trống
Trong bước này, bạn sẽ tạo một dự án mới bằng Create React App. Sau đó, bạn sẽ xóa dự án mẫu và các tệp liên quan được cài đặt khi bạn khởi động dự án. Cuối cùng, bạn sẽ tạo một cấu trúc tệp đơn giản để tổ chức các component của mình. Điều này sẽ cung cấp cho bạn một cơ sở vững chắc để xây dựng ứng dụng mẫu của hướng dẫn này để tạo style trong bước tiếp theo.
Để bắt đầu, hãy thực hiện một dự án mới. Bạn mở terminal và chạy tập lệnh sau để cài đặt một dự án mới bằng cách sử dụng create-react-app
:
npx create-react-app context-tutorial
Sau khi dự án kết thúc, hãy thay đổi vào thư mục:
cd
context-tutorial
Khởi động dự án với lệnh:
npm
start
http://
your_domain
:3000
Bạn sẽ nhận được một máy chủ cục bộ đang chạy. Nếu dự án không mở trong cửa sổ trình duyệt, bạn có thể mở nó bằng http://localhost:3000/
. Nếu bạn đang chạy điều này từ một máy chủ từ xa, địa chỉ sẽ là .
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:
Bạn sẽ xây dựng một tập hợp các component tùy chỉnh hoàn toàn mới, vì vậy bạn sẽ cần bắt đầu bằng cách xóa một số mã soạn sẵn để bạn có thể có một dự án trống.
Để bắt đầu, hãy mở component src/App.js
. Đây là component gốc được đưa vào trang. Tất cả các component sẽ bắt đầu từ đây. Bạn sửa lại file để trong đó chỉ còn chứa như sau:
import
'./App.css'
;
function
App
(
)
{
return
<
>
<
/
>
;
}
export
default
App;
Mở một terminal khác và thực hiện thao tác xóa file logo.svg:
rm
src/logo.svg
Tạo thư mục components:
mkdir
src/components
Tạo thư mục App:
mkdir
src/components/App
Di chuyển các file App.* vào thư mục App:
mv
src/App.* src/components/App
Mở file index.js và chỉnh sửa:
import
React from
'react'
;
import
ReactDOM from
'react-dom'
;
import
'./index.css'
;
import
App from
'.
/components/App
/App';
import
*
as
serviceWorker from
'./reportWebVitals'
;
ReactDOM.
render
(
<
React.
StrictMode>
<
App /
>
<
/
React.
StrictMode>
,
document
.
getElementById
(
'root'
)
)
;
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.
unregister
(
)
;
Lưu file lại và quay lại trang web ta được một trang web trống.
Bây giờ bạn đã hoàn thành dự án Tạo ứng dụng React mẫu, hãy tạo một cấu trúc tệp đơn giản. Điều này sẽ giúp bạn giữ cho các component của bạn cô lập và độc lập.
Tạo một thư mục được gọi components
trong thư mục src
, components sẽ là nơi chứa tất cả các component tùy chỉnh của bạn.
Bước 2 – Xây dựng cơ sở cho ứng dụng của bạn
Trong bước này, bạn sẽ xây dựng cấu trúc chung của trình tạo salad tùy chỉnh của mình. Bạn sẽ tạo các component để hiển thị các lớp phủ có thể có, danh sách các lớp phủ đã chọn và thông tin khách hàng. Khi bạn xây dựng ứng dụng với dữ liệu tĩnh, bạn sẽ thấy cách các phần thông tin khác nhau được sử dụng trong nhiều component khác nhau và cách xác định các phần dữ liệu sẽ hữu ích trong một ngữ cảnh.
Đây là một ví dụ về ứng dụng bạn sẽ xây dựng:
Lưu ý cách có thông tin mà bạn có thể cần sử dụng trên các component. Ví dụ: tên người dùng (đối với mẫu này là Kwame) hiển thị dữ liệu người dùng trong khu vực điều hướng, nhưng bạn cũng có thể cần thông tin người dùng để xác định các mục yêu thích hoặc cho trang thanh toán. Thông tin người dùng sẽ cần phải được truy cập bởi bất kỳ component nào trong ứng dụng. Nhìn vào chính trình tạo salad, mỗi thành phần salad sẽ cần có thể cập nhật danh sách Your Salad ở cuối màn hình, vì vậy bạn sẽ cần lưu trữ và cập nhật dữ liệu đó từ một vị trí có thể truy cập được cho từng component.
Bắt đầu bằng cách mã hóa cứng tất cả dữ liệu để bạn có thể tìm ra cấu trúc ứng dụng của mình. Sau đó, bạn sẽ thêm ngữ cảnh bắt đầu trong bước tiếp theo. Ngữ cảnh cung cấp nhiều giá trị nhất khi các ứng dụng bắt đầu phát triển, vì vậy trong bước này, bạn sẽ xây dựng một số component để hiển thị cách ngữ cảnh hoạt động trên một cây component. Đối với các component hoặc thư viện nhỏ hơn, bạn thường có thể sử dụng các component wrapper và kỹ thuật quản lý state cấp thấp hơn, như React Hooks và quản lý dựa trên lớp.
Vì bạn đang xây dựng một ứng dụng nhỏ với nhiều component, hãy cài đặt JSS để đảm bảo rằng sẽ không có bất kỳ xung đột tên lớp nào và để bạn có thể thêm các kiểu trong cùng một tệp như một component. Để biết thêm về JSS, hãy xem Cách tạo style cho các component.
Chạy lệnh sau:
npm
install
react-jss
Bây giờ bạn đã cài đặt JSS, hãy xem xét các component khác nhau mà bạn sẽ cần. Ở đầu trang, bạn sẽ có một component Navigation
để lưu trữ thông điệp chào mừng. Componet tiếp theo sẽ là SaladMaker
, nó có nhiệm vụ giữ tiêu đề cùng với trình tạo và danh sách Your Salad ở dưới cùng. Phần có các thành phần sẽ là một component riêng biệt được gọi là SaladBuilder
, được lồng vào bên trong SaladMaker
. Mỗi thành phần sẽ là một thể hiện của một component SaladItem
. Cuối cùng, danh sách dưới cùng sẽ là một component được gọi SaladSummary
.
Lưu ý: Các component không cần phải được chia theo cách này. Khi bạn làm việc trên các ứng dụng của mình, cấu trúc của bạn sẽ thay đổi và phát triển khi bạn thêm nhiều chức năng hơn. Ví dụ này nhằm cung cấp cho bạn một cấu trúc để khám phá cách ngữ cảnh ảnh hưởng đến các component khác nhau trong cây.
Bây giờ bạn đã có ý tưởng về các component bạn sẽ cần, hãy tạo một thư mục cho từng component:
mkdir
src/components/Navigationmkdir
src/components/SaladMakermkdir
src/components/SaladItemmkdir
src/components/SaladBuildermkdir
src/components/SaladSummary
Tiếp theo ta xây dựng các component từ trên xuống bắt đầu từ Navigation
. Đầu tiên, mở file component trong trình soạn thảo văn bản:
Tạo một component có tên Navigation.js
và thêm một số style để tạo đường viền và đệm cho Navigation
:
import
{
createUseStyles }
from
'react-jss'
;
const
useStyles =
createUseStyles
(
{
wrapper:
{
borderBottom:
'black solid 1px'
,
padding:
[
15
,
10
]
,
textAlign:
'right'
,
}
}
)
;
export
default
function
Navigation
(
)
{
const
classes =
useStyles
(
)
;
return
(
<
div className=
{
classes.
wrapper}
>
Welcome,
Kwame
<
/
div>
)
}
Trong đoạn code trên, vì bạn đang sử dụng JSS, bạn có thể tạo các đối tượng style trực tiếp trong component thay vì dùng file CSS. Thẻ div
sẽ có một phần đệm, một đường viền solid
black
và căn chỉnh văn bản ở bên phải với textAlign
.
Lưu file lại. Tiếp theo, hãy mở App.js
, đây là component gốc của dự án, sau đó import component Navigation
và hiển thị nó bên trong các thẻ trống bằng cách thêm các dòng được đánh dấu như sau:
import
Navigation
from
'../Navigation/Navigation'
;
function
App
(
)
{
return
(
<
>
<
Navigation
/
>
<
/
>
)
;
}
export
default
App;
Lưu file lại và quay lại trang web, bạn refresh sẽ được kết quả dạng như sau:
Hãy coi thanh điều hướng như một component chung, vì trong ví dụ này, nó đóng vai trò như một component mẫu sẽ được sử dụng lại trên mọi trang.
Component tiếp theo sẽ là SaladMaker
. Nó sẽ chỉ hiển thị trên một số trang nhất định hoặc ở một số trạng thái nhất định.
Mở file SaladMaker.js
ra và đưa vào đoạn code sau:
import
{
createUseStyles }
from
'react-jss'
;
const
useStyles =
createUseStyles
(
{
wrapper:
{
textAlign:
'center'
,
}
}
)
;
export
default
function
SaladMaker
(
)
{
const
classes =
useStyles
(
)
;
return
(
<
>
<
h1 className=
{
classes.
wrapper}
>
<
span role=
"img"
aria-
label=
"salad"
>
🥗 <
/
span>
Build Your Custom Salad!
<
span role=
"img"
aria-
label=
"salad"
>
🥗<
/
span>
<
/
h1>
<
/
>
)
}
Trong đoạn code trên, bạn đang sử dụng textAlign
để căn giữa component trên trang. Các thuộc tính role
và aria-label
của phần tử span
sẽ giúp hỗ trợ khả năng truy cập bằng Ứng dụng Internet đa dạng có thể truy cập (ARIA).
Lưu file lại, sau đó mở App.js
ra để import component SaladMaker
và hiển thị sau component Navigation
:
import
Navigation from
'../Navigation/Navigation'
;
import
SaladMaker
from
'../SaladMaker/SaladMaker'
;
function
App
(
)
{
return
(
<
>
<
Navigation /
>
<
SaladMaker
/
>
<
/
>
)
;
}
export
default
App;
Lưu file lại và quay lại trang web ta được kết quả dạng như sau:
Tiếp theo, ta tạo component SaladItem
. Đây sẽ là một thẻ (card) cho từng thành phần riêng lẻ.
Mở file SaladItem.js ra. Component này sẽ có ba phần: tên của món, một biểu tượng hiển thị món đó có phải là món yêu thích của người dùng hay không và một biểu tượng cảm xúc được đặt bên trong một nút sẽ thêm món đó vào món salad khi nhấp chuột. Thêm các dòng sau vào SaladItem.js
:
import
PropTypes from
'prop-types'
;
import
{
createUseStyles }
from
'react-jss'
;
const
useStyles =
createUseStyles
(
{
add:
{
background:
'none'
,
border:
'none'
,
cursor:
'pointer'
,
}
,
favorite:
{
fontSize:
20
,
position:
'absolute'
,
top:
10
,
right:
10
,
}
,
image:
{
fontSize:
80
}
,
wrapper:
{
border:
'lightgrey solid 1px'
,
margin:
20
,
padding:
25
,
position:
'relative'
,
textAlign:
'center'
,
textTransform:
'capitalize'
,
width:
200
,
}
}
)
;
export
default
function
SaladItem
(
{
image,
name }
)
{
const
classes =
useStyles
(
)
;
const
favorite =
true
;
return
(
<
div className=
{
classes.
wrapper}
>
<
h3>
{
name}
<
/
h3>
<
span className=
{
classes.
favorite}
aria-
label=
{
favorite ?
'Favorite'
:
'Not Favorite'
}
>
{
favorite ?
'😋'
:
''
}
<
/
span>
<
button className=
{
classes.
add}
>
<
span className=
{
classes.
image}
role=
"img"
aria-
label=
{
name}
>
{
image}
<
/
span>
<
/
button>
<
/
div>
)
}
SaladItem.
propTypes =
{
image:
PropTypes.
string.
isRequired,
name:
PropTypes.
string.
isRequired,
}
Trong đoạn code trên, image
và name
sẽ là các prop. Trong đoạn code cũng có sử dụng biến favorite
và toán tử ba ngôi để xác định xem biểu tượng favorite
có xuất hiện hay không. Biến favorite
sau này sẽ được xác định với bối cảnh là một phần của hồ sơ. Bây giờ, hãy đặt nó thành true
. Tạo style để đặt biểu tượng yêu thích ở góc trên bên phải của thẻ và xóa đường viền và nền mặc định trên nút. Class wrapper
sẽ bổ sung thêm một đường viền nhỏ và biến đổi một số văn bản. Cuối cùng, PropTypes thêm một hệ thống kiểu yếu để cung cấp một số thực thi để đảm bảo kiểu prop sai không được truyền đi.
Lưu file lại. Bây giờ, bạn sẽ cần kết xuất các mục khác nhau. Bạn sẽ làm điều đó với một component tên SaladBuilder
, sẽ chứa danh sách các mục mà nó sẽ chuyển đổi thành một loạt các component SaladItem
:
Mở SaladBuilder.js
ra, nếu đây là một ứng dụng production, thì dữ liệu này thường đến từ Giao diện lập trình ứng dụng (API). Nhưng hiện tại, hãy sử dụng danh sách các component được mã hóa cứng:
import
SaladItem from
'../SaladItem/SaladItem'
;
import
{
createUseStyles }
from
'react-jss'
;
const
useStyles =
createUseStyles
(
{
wrapper:
{
display:
'flex'
,
flexWrap:
'wrap'
,
padding:
[
10
,
50
]
,
justifyContent:
'center'
,
}
}
)
;
const
ingredients =
[
{
image:
'🍎'
,
name:
'apple'
,
}
,
{
image:
'🥑'
,
name:
'avocado'
,
}
,
{
image:
'🥦'
,
name:
'broccoli'
,
}
,
{
image:
'🥕'
,
name:
'carrot'
,
}
,
{
image:
'🍷'
,
name:
'red wine dressing'
,
}
,
{
image:
'🍚'
,
name:
'seasoned rice'
,
}
,
]
;
export
default
function
SaladBuilder
(
)
{
const
classes =
useStyles
(
)
;
return
(
<
div className=
{
classes.
wrapper}
>
{
ingredients.
map
(
ingredient =>
(
<
SaladItem
key=
{
ingredient.
name}
image=
{
ingredient.
image}
name=
{
ingredient.
name}
/
>
)
)
}
<
/
div>
)
}
Đoạn mã trên sử dụng phương thức mảng map()
để ánh xạ từng mục trong danh sách, truyền name
và image
dưới dạng các prop cho một component SaladItem
. Hãy chắc chắn thêm key
vào từng mục khi bạn lập bản đồ. Style cho component này với việc thêm cách thức hiển thị là flex
sử dụng bố cục flexbox, bao ngoài các component và căn giữa chúng.
Lưu file lại.
Cuối cùng, kết xuất component SaladMaker
để nó sẽ xuất hiện trong trang.
Mở SaladMaker
ra sau đó import SaladBuilder
và hiển thị sau tiêu đề <h1>:
import
{
createUseStyles }
from
'react-jss'
;
import
SaladBuilder
from
'../SaladBuilder/SaladBuilder'
;
const
useStyles =
createUseStyles
(
{
wrapper:
{
textAlign:
'center'
,
}
}
)
;
export
default
function
SaladMaker
(
)
{
const
classes =
useStyles
(
)
;
return
(
<
>
<
h1 className=
{
classes.
wrapper}
>
<
span role=
"img"
aria-
label=
"salad"
>
🥗 <
/
span>
Build Your Custom Salad!
<
span role=
"img"
aria-
label=
"salad"
>
🥗<
/
span>
<
/
h1>
<
SaladBuilder
/
>
<
/
>
)
}
Lưu file lại và quay lại trang web ta được kết quả như sau:
Bước cuối cùng là thêm phần tóm tắt của món salad đang thực hiện. Component này sẽ hiển thị danh sách các mục mà người dùng đã chọn. Hiện tại, bạn sẽ mã hóa các mặt hàng một cách khó khăn. Bạn sẽ cập nhật chúng với ngữ cảnh trong Bước 3.
Mở SaladSummary.js
ra. Component sẽ là một tiêu đề và một danh sách các mục chưa được sắp xếp. Bạn sẽ sử dụng flexbox để bao chúng:
import
{
createUseStyles }
from
'react-jss'
;
const
useStyles =
createUseStyles
(
{
list:
{
display:
'flex'
,
flexDirection:
'column'
,
flexWrap:
'wrap'
,
maxHeight:
50
,
'& li'
:
{
width:
100
}
}
,
wrapper:
{
borderTop:
'black solid 1px'
,
display:
'flex'
,
padding:
25
,
}
}
)
;
export
default
function
SaladSummary
(
)
{
const
classes =
useStyles
(
)
;
return
(
<
div className=
{
classes.
wrapper}
>
<
h2>
Your Salad<
/
h2>
<
ul className=
{
classes.
list}
>
<
li>
Apple<
/
li>
<
li>
Avocado<
/
li>
<
li>
Carrots<
/
li>
<
/
ul>
<
/
div>
)
}
Lưu file lại sau mở SaladMaker.js
để hiển thị mục. Import và thêm SaladSummary
vào sau SaladBuilder
:
import
{
createUseStyles }
from
'react-jss'
;
import
SaladBuilder from
'../SaladBuilder/SaladBuilder'
;
import
SaladSummary
from
'../SaladSummary/SaladSummary'
;
const
useStyles =
createUseStyles
(
{
wrapper:
{
textAlign:
'center'
,
}
}
)
;
export
default
function
SaladMaker
(
)
{
const
classes =
useStyles
(
)
;
return
(
<
>
<
h1 className=
{
classes.
wrapper}
>
<
span role=
"img"
aria-
label=
"salad"
>
🥗 <
/
span>
Build Your Custom Salad!
<
span role=
"img"
aria-
label=
"salad"
>
🥗<
/
span>
<
/
h1>
<
SaladBuilder /
>
<
SaladSummary
/
>
<
/
>
)
}
Lưu file lại và quay lại trang web và đóng tập tin. Khi bạn làm như vậy, trang sẽ làm mới và bạn sẽ tìm thấy ứng dụng đầy đủ:
Như vậy, ở đây có dữ liệu được chia sẻ trong toàn bộ ứng dụng. Component Navigation
và SaladItem
đều cần biết điều gì đó về người dùng: tên của họ và danh sách yêu thích của họ. SaladItem
cũng cần phải cập nhật dữ liệu có thể truy cập vào các component SaladSummary
. Các component có chung một gốc (cha), nhưng việc truyền dữ liệu xuống thông qua cây sẽ khó khăn và dễ xảy ra lỗi.
Đó là lúc ta cần đến ngữ cảnh. Bạn có thể khai báo dữ liệu trong một nơi chung và sau đó truy cập mà không cần truyền nó xuống hệ thống phân cấp của các component một cách rõ ràng.
Trong bước này, bạn đã tạo một ứng dụng để cho phép người dùng tạo món salad từ danh sách các tùy chọn. Bạn đã tạo một tập hợp các component cần truy cập hoặc cập nhật dữ liệu được kiểm soát bởi các component khác. Trong bước tiếp theo, bạn sẽ sử dụng ngữ cảnh để lưu trữ dữ liệu và truy cập nó trong các component con.
Bước 3 – Cung cấp dữ liệu từ component gốc
Trong bước này, bạn sẽ sử dụng ngữ cảnh để lưu trữ thông tin khách hàng ở gốc của component. Bạn sẽ tạo một ngữ cảnh tùy chỉnh, sau đó sử dụng một component wrapper đặc biệt được gọi là a Provider
sẽ lưu trữ thông tin ở gốc của dự án. Sau đó, bạn sẽ sử dụng Hoock useContext
để kết nối với trình cung cấp trong các component lồng nhau để bạn có thể hiển thị thông tin tĩnh. Đến cuối bước này, bạn sẽ có thể cung cấp kho thông tin tập trung và sử dụng thông tin được lưu trữ trong ngữ cảnh ở nhiều component khác nhau.
Bối cảnh cơ bản nhất của nó là một giao diện để chia sẻ thông tin. Nhiều ứng dụng có một số thông tin chung mà chúng cần chia sẻ trên toàn ứng dụng, chẳng hạn như tùy chọn của người dùng, thông tin chủ đề và các thay đổi ứng dụng trên toàn bộ trang web. Với ngữ cảnh, bạn có thể lưu trữ thông tin đó ở cấp cơ sở sau đó truy cập nó ở bất kỳ đâu. Vì bạn đặt thông tin ở gốc, nên bạn biết thông tin đó sẽ luôn có sẵn và sẽ luôn được cập nhật.
Để thêm ngữ cảnh, hãy tạo một component mới có tên User
. User
sẽ không phải là một component thông thường, trong đó bạn sẽ sử dụng nó như một component và như một phần dữ liệu cho một Hook đặc biệt được gọi useContext
. Hiện tại, hãy giữ cấu trúc tệp phẳng, nhưng nếu bạn sử dụng nhiều ngữ cảnh, có thể đáng để chuyển chúng sang một cấu trúc thư mục khác.
Tiếp theo, mở User.js
ra, import createContext
, sau đó thực thi hàm và xuất kết quả:
import
{
createContext }
from
'react'
;
const
UserContext =
createContext
(
)
;
export
default
UserContext;
Bằng cách thực thi hàm createContext(), bạn đã đăng ký ngữ cảnh. Kết quả là bạn có UserContext
và bạn sẽ sử dụng trong các component của mình.
Lưu file lại.
Bước tiếp theo là áp dụng ngữ cảnh cho một tập hợp các phần tử. Để làm điều đó, bạn sẽ sử dụng một component được gọi là a Provider
. Provider
sẽ thiết lập dữ liệu và sau đó bao bọc (wrapper) một số component con. Bất kỳ component con nào được bao bọc sẽ có quyền truy cập vào dữ liệu từ Provider
với useContext
Hook.
Vì dữ liệu người dùng sẽ không đổi trong toàn bộ dự án, hãy đặt nó lên cây compnent càng cao càng tốt. Trong ứng dụng này, bạn sẽ đặt nó ở cấp gốc trong component App
:
Mở App
ra, thêm các dòng mã được đánh dấu sau để import ngữ cảnh và chuyển dữ liệu:
import
Navigation from
'../Navigation/Navigation'
;
import
SaladMaker from
'../SaladMaker/SaladMaker'
;
import
UserContext
from
'../User/User'
;
const
user
=
{
name
:
'Kwame'
,
favorites
:
[
'avocado'
,
'carrot'
]
}
function
App
(
)
{
return
(
<
UserContext
.
Provider value
=
{
user
}
>
<
Navigation /
>
<
SaladMaker /
>
<
/
UserContext
.
Provider
>
)
;
}
export
default
App;
Trong một ứng dụng điển hình, bạn sẽ tìm nạp dữ liệu người dùng hoặc lưu trữ dữ liệu đó trong quá trình hiển thị phía máy chủ. Trong trường hợp này, bạn đã mã hóa cứng một số dữ liệu mà bạn có thể nhận được từ API. Bạn đã tạo một đối tượng có tên user
chứa tên người dùng dưới dạng một chuỗi và một mảng các thành phần yêu thích.
Tiếp theo, bạn import UserContext
, sau đó bọc Navigation
và SaladMaker
bởi một component được gọi là UserContext.Provider
. Chú ý là trong trường hợp này UserContext
hoạt động như một component React tiêu chuẩn. Component này sẽ nhận một prop duy nhất được gọi là value
. Prop đó sẽ là dữ liệu bạn muốn chia sẻ, trong trường hợp này là đối tượng user
.
Lưu file lại. Bây giờ dữ liệu đã có sẵn trong toàn bộ ứng dụng. Tuy nhiên, để sử dụng dữ liệu, bạn sẽ cần import và truy cập ngữ cảnh một lần nữa.
Bây giờ bạn đã thiết lập ngữ cảnh, bạn có thể bắt đầu thay thế dữ liệu được mã hóa cứng trong component của bạn bằng các giá trị động. Bắt đầu bằng cách thay thế tên được mã hóa cứng trong Navigation
bằng dữ liệu người dùng mà bạn đã thiết lập với UserContext.Provider
.
Mở file Navigation.js
ra, bên trong Navigation
, import Hook useContext
từ React và UserContext
từ thư mục component. Sau đó, gọi useContext
bằng cách sử dụng UserContext
như một đối số. Không giống như UserContext.Provider
, bạn không cần phải kết xuất UserContext
trong JSX. Hook sẽ trả về dữ liệu mà bạn đã cung cấp trong phần prop value
. Lưu dữ liệu vào một biến mới được gọi, user
, đây là một đối tượng chứa name
và favorites
. Sau đó, bạn có thể thay thế tên được mã hóa cứng bằng user.name
:
import
{
useContext
}
from
'react'
;
import
{
createUseStyles }
from
'react-jss'
;
import
UserContext
from
'../User/User'
;
const
useStyles =
createUseStyles
(
{
wrapper:
{
outline:
'black solid 1px'
,
padding:
[
15
,
10
]
,
textAlign:
'right'
,
}
}
)
;
export
default
function
Navigation
(
)
{
const
user
=
useContext
(
UserContext
)
;
const
classes =
useStyles
(
)
;
return
(
<
div className=
{
classes.
wrapper}
>
Welcome,
{
user
.
name
}
<
/
div>
)
}
UserContext
hoạt động như một component trong App.js
, nhưng ở đây bạn đang sử dụng nó như một phần dữ liệu. Tuy nhiên, nó vẫn có thể hoạt động như một component nếu bạn muốn. Bạn có thể truy cập cùng một dữ liệu bằng cách sử dụng Consumer
là một phần của UserContext
. Bạn truy xuất dữ liệu bằng cách thêm UserContext.Consumer
vào JSX của mình, sau đó sử dụng một hàm như là con để truy cập dữ liệu.
Mặc dù có thể sử dụng component Consumer
, nhưng sử dụng Hook thường có thể ngắn hơn và dễ đọc hơn, trong khi vẫn cung cấp cùng một thông tin cập nhật. Đây là lý do tại sao hướng dẫn này sử dụng cách tiếp cận Hook.
Lưu file lại và quay lại trang web, bạn refresh và bạn sẽ thấy kết quả tương tự, nhưng lần này dữ liệu đã là động:
Trong trường hợp này, dữ liệu không di chuyển qua nhiều component. Cây component đại diện cho đường dẫn mà dữ liệu được truyền đi sẽ trông giống như sau:
| UserContext.Provider
| Navigation
Bạn có thể truyền tên người dùng như là prop và ở quy mô này, đó có thể là một chiến lược hiệu quả. Nhưng khi ứng dụng phát triển lên, có khả năng component Navigation
sẽ cần phải di chuyển trong cây component. Có thể có một component được gọi là Header
hao bọc component Navigation
và một component khác chẳng hạn như a TitleBar
, hoặc có thể bạn sẽ tạo một component Template
và sau đó lồng Navigation
vào đó. Bằng cách sử dụng ngữ cảnh, bạn sẽ không phải cấu trúc lại Navigation
miễn Provider
nằm phía trên trong cây component, giúp cho việc tái cấu trúc trở nên dễ dàng hơn.
Component tiếp theo cần dữ liệu người dùng là SaladItem
. Trong SaladItem
, bạn sẽ cần mảng favourites của người dùng. Bạn sẽ hiển thị biểu tượng cảm xúc có điều kiện nếu thành phần được người dùng yêu thích.
Mở file SaladItem.js
ra, import useContext
và UserContext
sau đó gọi useContext
với UserContext
. Sau đó, hãy kiểm tra xem thành phần có trong mảng favorites
hay không bằng cách sử dụng phương thức includes
:
import
{
useContext
}
from
'react'
;
import
PropTypes from
'prop-types'
;
import
{
createUseStyles }
from
'react-jss'
;
import
UserContext
from
'../User/User'
;
const
useStyles =
createUseStyles
(
{
...
}
)
;
export
default
function
SaladItem
(
{
image,
name }
)
{
const
classes =
useStyles
(
)
;
const
user
=
useContext
(
UserContext
)
;
const
favorite =
user
.
favorites
.
includes
(
name
)
;
return
(
<
div className=
{
classes.
wrapper}
>
<
h3>
{
name}
<
/
h3>
<
span className=
{
classes.
favorite}
aria-
label=
{
favorite ?
'Favorite'
:
'Not Favorite'
}
>
{
favorite ?
'😋'
:
''
}
<
/
span>
<
button className=
{
classes.
add}
>
<
span className=
{
classes.
image}
role=
"img"
aria-
label=
{
name}
>
{
image}
<
/
span>
<
/
button>
<
/
div>
)
}
SaladItem.
propTypes =
{
image:
PropTypes.
string.
isRequired,
name:
PropTypes.
string.
isRequired,
}
Lưu file lại và quay lại trang web bạn sẽ thấy rằng chỉ những mục yêu thích mới có biểu tượng cảm xúc:
Không giống như Navigation
, ngữ cảnh đang đi xa hơn nhiều. Cây component sẽ trông giống như sau:
| User.Provider
| SaladMaker
| SaladBuilder
| SaladItem
Thông tin bỏ qua hai component trung gian mà không có bất kỳ prop nào. Nếu bạn phải truyền dữ liệu dưới dạng prop qua toàn bộ cây, thì sẽ rất nhiều công việc và bạn có nguy cơ gặp phải một nhà phát triển trong tương lai cấu trúc lại mã và quên truyền prop xuống. Với ngữ cảnh, bạn có thể tin tưởng mã sẽ hoạt động khi ứng dụng phát triển và phát triển.
Trong bước này, bạn đã tạo một ngữ cảnh và sử dụng a Provider
để đặt dữ liệu trong cây component. Bạn cũng đã truy cập ngữ cảnh với Hook useContext
và ngữ cảnh được sử dụng trên nhiều component. Dữ liệu này là tĩnh và do đó không bao giờ thay đổi sau khi thiết lập ban đầu, nhưng sẽ có lúc bạn cần chia sẻ dữ liệu và cũng có thể sửa đổi dữ liệu trên nhiều component. Trong bước tiếp theo, bạn sẽ cập nhật dữ liệu lồng nhau bằng ngữ cảnh.
Bước 4 – Cập nhật dữ liệu từ các component lồng nhau
Trong bước này, bạn sẽ sử dụng ngữ cảnh và Hook useReducer
để tạo dữ liệu động mà các component lồng nhau có thể sử dụng và cập nhật. Bạn sẽ cập nhật component SaladItem
của mình để thiết lập dữ liệu SaladSummary
sẽ sử dụng và hiển thị. Bạn cũng sẽ đặt các trình cung cấp ngữ cảnh bên ngoài component gốc. Đến cuối bước này, bạn sẽ có một ứng dụng có thể sử dụng và cập nhật dữ liệu trên một số component và bạn sẽ có thể thêm nhiều trình cung cấp ngữ cảnh ở các cấp độ khác nhau của một ứng dụng.
Tại thời điểm này, ứng dụng của bạn đang hiển thị dữ liệu người dùng trên nhiều component, nhưng nó thiếu bất kỳ sự tương tác nào của người dùng. Trong bước trước, bạn đã sử dụng ngữ cảnh để chia sẻ một phần dữ liệu, nhưng bạn cũng có thể chia sẻ một tập hợp dữ liệu, bao gồm cả các hàm. Điều đó có nghĩa là bạn có thể chia sẻ dữ liệu và cũng có thể chia sẻ hàm để cập nhật dữ liệu.
Trong ứng dụng của bạn, mỗi SaladItem
cần cập nhật danh sách được chia sẻ. Sau đó, component SaladSummary
của bạn sẽ hiển thị các mục mà người dùng đã chọn và thêm nó vào danh sách. Vấn đề là các component này không phải là con cháu trực tiếp, vì vậy bạn không thể truyền dữ liệu và các hàm cập nhật dưới dạng các prop được. Nhưng chúng lại chia sẻ với một cha chung là: SaladMaker
.
Một trong những điểm khác biệt lớn giữa ngữ cảnh và các giải pháp quản lý state khác như Redux là ngữ cảnh không nhằm mục đích trở thành một nơi lưu trữ trung tâm. Bạn có thể sử dụng nó nhiều lần trong một ứng dụng và khởi tạo nó ở cấp gốc hoặc sâu trong cây component. Nói cách khác, bạn có thể dàn trải các ngữ cảnh của mình trong toàn bộ ứng dụng, tạo ra các bộ sưu tập dữ liệu tập trung mà không lo xung đột.
Để giữ cho ngữ cảnh được tập trung, hãy tạo Providers
để gộp cha được chia sẻ gần nhất khi có thể. Trong trường hợp này, điều đó có nghĩa là, thay vì thêm một ngữ cảnh khác vào App
, bạn sẽ thêm ngữ cảnh trong component SaladMaker
.
Mở file SaladMaker.js
ra, sau đó, tạo và xuất (export) một ngữ cảnh mới được gọi là SaladContext
:
import
{
createContext
}
from
'react'
;
import
{
createUseStyles }
from
'react-jss'
;
import
SaladBuilder from
'../SaladBuilder/SaladBuilder'
;
import
SaladSummary from
'../SaladSummary/SaladSummary'
;
const
useStyles =
createUseStyles
(
{
wrapper:
{
textAlign:
'center'
,
}
}
)
;
export
const
SaladContext
=
createContext
(
)
;
export
default
function
SaladMaker
(
)
{
const
classes =
useStyles
(
)
;
return
(
<
>
<
h1 className=
{
classes.
wrapper}
>
<
span role=
"img"
aria-
label=
"salad"
>
🥗 <
/
span>
Build Your Custom Salad!
<
span role=
"img"
aria-
label=
"salad"
>
🥗<
/
span>
<
/
h1>
<
SaladBuilder /
>
<
SaladSummary /
>
<
/
>
)
}
Trong bước trước, bạn đã tạo một component riêng biệt cho ngữ cảnh của mình, nhưng trong trường hợp này, bạn đang tạo nó trong cùng một tệp mà bạn đang sử dụng nó. Vì User
dường như không liên quan trực tiếp đến App
, nên có thể có ý nghĩa hơn nếu giữ chúng tách biệt. Tuy nhiên, vì mã SaladContext
được gắn chặt với component SaladMaker
, nên việc giữ chúng lại với nhau sẽ tạo ra mã dễ đọc hơn.
Ngoài ra, bạn có thể tạo một ngữ cảnh chung chung hơn được gọi là ngữ cảnh OrderContext
mà bạn có thể sử dụng lại trên nhiều component. Trong trường hợp đó, bạn cần tạo một component riêng biệt. Còn bây giờ, hãy giữ chúng lại với nhau. Bạn luôn có thể cấu trúc lại sau nếu bạn quyết định chuyển sang một mẫu khác.
Trước khi bạn thêm Provider
, hãy suy nghĩ về dữ liệu mà bạn muốn chia sẻ. Bạn sẽ cần một mảng các mục và một hàm để thêm các mục. Không giống như các công cụ quản lý state tập trung khác, ngữ cảnh không xử lý các cập nhật cho dữ liệu của bạn. Nó chỉ giữ dữ liệu để sử dụng sau này. Để cập nhật dữ liệu, bạn sẽ cần sử dụng các công cụ quản lý state khác như Hook. Nếu bạn đang thu thập dữ liệu cho cùng một component, bạn sẽ sử dụng Hook useState
hoặc useReducer
. Nếu bạn chưa quen với các Hook này, hãy xem Cách quản lý state bằng Hook trên các component.
Hook useReducer
là một sự phù hợp tốt vì bạn sẽ cần phải cập nhật trạng thái gần đây nhất trên tất cả các hành động.
Tạo một hàm reducer
để thêm một mục mới vào một mảng state
, sau đó sử dụng Hook useReducer
để tạo một mảng salad
và một hàm setSalad
:
import
{
useReducer
,
createContext }
from
'react'
;
import
{
createUseStyles }
from
'react-jss'
;
import
SaladBuilder from
'../SaladBuilder/SaladBuilder'
;
import
SaladSummary from
'../SaladSummary/SaladSummary'
;
const
useStyles =
createUseStyles
(
{
wrapper:
{
textAlign:
'center'
,
}
}
)
;
export
const
SaladContext =
createContext
(
)
;
function
reducer
(
state
,
item
)
{
return
[
...
state
,
item
]
}
export
default
function
SaladMaker
(
)
{
const
classes =
useStyles
(
)
;
const
[
salad
,
setSalad
]
=
useReducer
(
reducer
,
[
]
)
;
return
(
<
>
<
h1 className=
{
classes.
wrapper}
>
<
span role=
"img"
aria-
label=
"salad"
>
🥗 <
/
span>
Build Your Custom Salad!
<
span role=
"img"
aria-
label=
"salad"
>
🥗<
/
span>
<
/
h1>
<
SaladBuilder /
>
<
SaladSummary /
>
<
/
>
)
}
Bây giờ bạn đã có một component chứa dữ liệu salad
bạn muốn chia sẻ, một hàm được gọi là setSalad
để cập nhật dữ liệu và SaladContext
để chia sẻ dữ liệu. Lúc này, bạn cần kết hợp chúng lại với nhau.
Để kết hợp, bạn cần tạo một Provider
. Vấn đề là ở chỗ Provider
chỉ mang một giá trị đơn value
làm prop thôi, vậy nên bạn không thể truyền đồng thời cả salad
và setSalad
, cho nên bạn sẽ cần kết hợp chúng thành một đối tượng và truyền đối tượng dưới dạng value
:
import
{
useReducer,
createContext }
from
'react'
;
import
{
createUseStyles }
from
'react-jss'
;
import
SaladBuilder from
'../SaladBuilder/SaladBuilder'
;
import
SaladSummary from
'../SaladSummary/SaladSummary'
;
const
useStyles =
createUseStyles
(
{
wrapper:
{
textAlign:
'center'
,
}
}
)
;
export
const
SaladContext =
createContext
(
)
;
function
reducer
(
state,
item)
{
return
[
...
state,
item]
}
export
default
function
SaladMaker
(
)
{
const
classes =
useStyles
(
)
;
const
[
salad,
setSalad]
=
useReducer
(
reducer,
[
]
)
;
return
(
<
SaladContext
.
Provider value
=
{
{
salad
,
setSalad
}
}
>
<
h1 className=
{
classes.
wrapper}
>
<
span role=
"img"
aria-
label=
"salad"
>
🥗 <
/
span>
Build Your Custom Salad!
<
span role=
"img"
aria-
label=
"salad"
>
🥗<
/
span>
<
/
h1>
<
SaladBuilder /
>
<
SaladSummary /
>
<
/
SaladContext
.
Provider
>
)
}
Lưu file SaladMaker.js lại. Cũng giống như Navigation
, nó có vẻ không cần thiết để tạo ra một ngữ cảnh khi SaladSummary
nằm trong component tương tự như ngữ cảnh. Việc truyền salad
như một prop là hoàn toàn hợp lý, nhưng bạn có thể sẽ cần cấu trúc lại nó sau đó. Sử dụng ngữ cảnh ở đây sẽ giúp giữ thông tin cùng ở một nơi duy nhất.
Tiếp theo, ta vào component SaladItem
và kéo hàm setSalad
ra khỏi ngữ cảnh.
Mở SaladItem.js ra, import ngữ cảnh từ SaladMaker
, sau đó kéo hàm setSalad
ra bằng cách sử dụng hàm hủy. Thêm sự kiện nhấp chuột vào nút sẽ gọi hàm setSalad
. Vì bạn muốn người dùng có thể thêm một sản phẩm nhiều lần, nên bạn cũng cần tạo một id duy nhất cho từng mục để hàm map
có thể chỉ định một key
duy nhất:
import
{
useReducer
,
useContext }
from
'react'
;
import
PropTypes from
'prop-types'
;
import
{
createUseStyles }
from
'react-jss'
;
import
UserContext from
'../User/User'
;
import
{
SaladContext
}
from
'../SaladMaker/SaladMaker'
;
const
useStyles =
createUseStyles
(
{
...
}
)
;
const
reducer
=
key
=>
key
+
1
;
export
default
function
SaladItem
(
{
image,
name }
)
{
const
classes =
useStyles
(
)
;
const
{
setSalad
}
=
useContext
(
SaladContext
)
const
user =
useContext
(
UserContext)
;
const
favorite =
user.
favorites.
includes
(
name)
;
const
[
id
,
updateId
]
=
useReducer
(
reducer
,
0
)
;
function
update
(
)
{
setSalad
(
{
name
,
id
:
`
${
name
}
-
${
id
}
`
}
)
updateId
(
)
;
}
;
return
(
<
div className=
{
classes.
wrapper}
>
<
h3>
{
name}
<
/
h3>
<
span className=
{
classes.
favorite}
aria-
label=
{
favorite ?
'Favorite'
:
'Not Favorite'
}
>
{
favorite ?
'😋'
:
''
}
<
/
span>
<
button className=
{
classes.
add}
onClick
=
{
update
}
>
<
span className=
{
classes.
image}
role=
"img"
aria-
label=
{
name}
>
{
image}
<
/
span>
<
/
button>
<
/
div>
)
}
...
Để tạo id duy nhất, bạn sẽ sử dụng Hook useReducer
để tăng giá trị trên mỗi lần nhấp chuột. Đối với lần nhấp đầu tiên, id sẽ là 0
; thứ hai sẽ là 1
, cứ như vậy. Bạn sẽ không bao giờ hiển thị giá trị này cho người dùng; điều này sẽ chỉ tạo ra một giá trị duy nhất cho hàm ánh xạ sau này.
Sau khi tạo id duy nhất, bạn đã tạo một hàm được gọi là update
để tăng id và gọi setSalad
. Cuối cùng, bạn gắn hàm vào nút bằng prop onClick
.
Lưu file lại. Bước cuối cùng là kéo dữ liệu động từ ngữ cảnh trong SaladSummary
.
Mở SaladSummary.js
ra, import component SaladContext
vào, sau đó lấy dữ liệu salad
ra bằng cách sử dụng hàm hủy. Thay thế các mục danh sách được mã hóa cứng bằng một hàm ánh xạ salad
, chuyển đổi các đối tượng thành các phần tử <li>
. Hãy chắc chắn sử dụng id
như key
:
import
{
useContext
}
from
'react'
;
import
{
createUseStyles }
from
'react-jss'
;
import
{
SaladContext
}
from
'../SaladMaker/SaladMaker'
;
const
useStyles =
createUseStyles
(
{
...
}
)
;
export
default
function
SaladSummary
(
)
{
const
classes =
useStyles
(
)
;
const
{
salad
}
=
useContext
(
SaladContext
)
;
return
(
<
div className=
{
classes.
wrapper}
>
<
h2>
Your Salad<
/
h2>
<
ul className=
{
classes.
list}
>
{
salad
.
map
(
(
{
name
,
id
}
)
=>
(
<
li key
=
{
id
}
>
{
name
}
<
/
li
>
)
)
}
<
/
ul>
<
/
div>
)
}
Lưu file lại và quay lại trang web ta sẽ làm được như hình dưới đây:
Lưu ý cách ngữ cảnh cung cấp cho bạn khả năng chia sẻ và cập nhật dữ liệu trong các component khác nhau. Ngữ cảnh không tự cập nhật các mục, nhưng nó cung cấp cho bạn một cách để sử dụng Hook useReducer
trên nhiều component. Ngoài ra, bạn cũng có thể tự do đặt ngữ cảnh thấp hơn trong cây. Có vẻ như tốt nhất là luôn giữ ngữ cảnh ở gốc, nhưng bằng cách giữ ngữ cảnh thấp hơn, bạn không phải lo lắng về state không sử dụng vẫn tồn tại trong khu vực lưu trữ trung tâm. Ngay sau khi bạn ngắt kết nối một component, dữ liệu sẽ biến mất. Đó có thể là một vấn đề nếu bạn muốn lưu dữ liệu, nhưng trong trường hợp đó, bạn chỉ cần nâng ngữ cảnh lên mức gốc cao hơn.
Một lợi thế khác của việc sử dụng ngữ cảnh thấp hơn trong cây ứng dụng của bạn là bạn có thể sử dụng lại ngữ cảnh mà không lo xung đột. Giả sử bạn có một ứng dụng lớn hơn có máy làm bánh mì sandwich và máy làm salad. Bạn có thể tạo một ngữ cảnh chung được gọi là OrderContext
và sau đó bạn có thể sử dụng nó ở nhiều vị trí trong component của mình mà không cần lo lắng về xung đột dữ liệu hoặc tên. Nếu bạn có một SaladMaker
và một SandwichMaker
, cây sẽ trông giống như sau:
| App
| Salads
| OrderContext
| SaladMaker
| Sandwiches
| OrderContext
| SandwichMaker
Ở đây OrderContext
xuất hiện hai lần, nhưng điều này không sao cả vì Hook useContext
sẽ tìm kiếm Provider gần nhất.
Trong bước này, bạn đã chia sẻ và cập nhật dữ liệu bằng cách sử dụng ngữ cảnh. Bạn cũng đã đặt ngữ cảnh bên ngoài phần tử gốc để nó gần với các component cần thông tin mà không làm lộn xộn component gốc. Cuối cùng, bạn đã kết hợp ngữ cảnh với các Hook quản lý state để tạo dữ liệu động và có thể truy cập trên một số component.
Phần kết luận
Ngữ cảnh là một công cụ mạnh mẽ và linh hoạt cung cấp cho bạn khả năng lưu trữ và sử dụng dữ liệu trên một ứng dụng. Nó cung cấp cho bạn khả năng xử lý dữ liệu phân tán bằng các công cụ tích hợp mà không yêu cầu bất kỳ cài đặt hoặc cấu hình bổ sung nào của bên thứ ba.
Việc tạo ngữ cảnh có thể sử dụng lại rất quan trọng trên nhiều component chung, chẳng hạn như biểu mẫu cần truy cập dữ liệu trên các phần tử hoặc chế độ xem tab cần ngữ cảnh chung cho cả tab và màn hình. Bạn có thể lưu trữ nhiều loại thông tin trong các ngữ cảnh bao gồm chủ đề, dữ liệu biểu mẫu, thông báo cảnh báo, v.v. Ngữ cảnh cho phép bạn tự do xây dựng các component có thể truy cập dữ liệu mà không cần lo lắng về cách truyền dữ liệu qua các component trung gian hoặc cách lưu trữ dữ liệu trong một kho lưu trữ mà không làm cho kho trở lên quá lớn.