Giữ Các Component Thuần Khiết
Một số hàm JavaScript là thuần khiết. Các hàm thuần khiết chỉ thực hiện một phép tính và không làm gì khác. Bằng cách chỉ viết các component của bạn như các hàm thuần khiết, bạn có thể tránh được cả một loạt các lỗi khó hiểu và hành vi không thể đoán trước khi codebase của bạn phát triển. Để có được những lợi ích này, bạn phải tuân theo một vài quy tắc.
Bạn sẽ được học
- Tính thuần khiết là gì và nó giúp bạn tránh lỗi như thế nào
- Làm thế nào để giữ cho các component thuần khiết bằng cách giữ các thay đổi bên ngoài giai đoạn render
- Cách sử dụng Strict Mode để tìm lỗi trong các component của bạn
Tính Thuần Khiết: Các Component Như Các Công Thức
Trong khoa học máy tính (và đặc biệt là thế giới của lập trình hàm), một hàm thuần khiết là một hàm có các đặc điểm sau:
- Nó chỉ lo việc của nó. Nó không thay đổi bất kỳ đối tượng hoặc biến nào đã tồn tại trước khi nó được gọi.
- Đầu vào giống nhau, đầu ra giống nhau. Với cùng một đầu vào, một hàm thuần khiết phải luôn trả về cùng một kết quả.
Bạn có thể đã quen thuộc với một ví dụ về các hàm thuần khiết: các công thức trong toán học.
Xem xét công thức toán học này: y = 2x.
Nếu x = 2 thì y = 4. Luôn luôn.
Nếu x = 3 thì y = 6. Luôn luôn.
Nếu x = 3, y sẽ không đôi khi là 9 hoặc –1 hoặc 2.5 tùy thuộc vào thời gian trong ngày hoặc trạng thái của thị trường chứng khoán.
Nếu y = 2x và x = 3, y sẽ luôn luôn là 6.
Nếu chúng ta biến điều này thành một hàm JavaScript, nó sẽ trông như thế này:
function double(number) {
return 2 * number;
}
Trong ví dụ trên, double
là một hàm thuần khiết. Nếu bạn truyền cho nó 3
, nó sẽ trả về 6
. Luôn luôn.
React được thiết kế dựa trên khái niệm này. React giả định rằng mọi component bạn viết là một hàm thuần khiết. Điều này có nghĩa là các component React bạn viết phải luôn trả về cùng một JSX với cùng một đầu vào:
function Recipe({ drinkers }) { return ( <ol> <li>Đun sôi {drinkers} cốc nước.</li> <li>Thêm {drinkers} thìa trà và {0.5 * drinkers} thìa gia vị.</li> <li>Thêm {0.5 * drinkers} cốc sữa vào đun sôi và đường tùy khẩu vị.</li> </ol> ); } export default function App() { return ( <section> <h1>Công Thức Trà Chai Cay</h1> <h2>Cho hai người</h2> <Recipe drinkers={2} /> <h2>Cho một buổi tụ tập</h2> <Recipe drinkers={4} /> </section> ); }
Khi bạn truyền drinkers={2}
cho Recipe
, nó sẽ trả về JSX chứa 2 cốc nước
. Luôn luôn.
Nếu bạn truyền drinkers={4}
, nó sẽ trả về JSX chứa 4 cốc nước
. Luôn luôn.
Giống như một công thức toán học.
Bạn có thể nghĩ về các component của mình như các công thức nấu ăn: nếu bạn làm theo chúng và không đưa thêm nguyên liệu mới trong quá trình nấu, bạn sẽ nhận được món ăn giống nhau mỗi lần. “Món ăn” đó là JSX mà component phục vụ cho React để render.

Illustrated by Rachel Lee Nabors
Tác Dụng Phụ: Hậu Quả (Không) Mong Muốn
Quá trình render của React phải luôn thuần khiết. Các component chỉ nên trả về JSX của chúng, và không thay đổi bất kỳ đối tượng hoặc biến nào đã tồn tại trước khi render—điều đó sẽ làm cho chúng không thuần khiết!
Đây là một component vi phạm quy tắc này:
let guest = 0; function Cup() { // Sai: thay đổi một biến đã tồn tại! guest = guest + 1; return <h2>Cốc trà cho khách #{guest}</h2>; } export default function TeaSet() { return ( <> <Cup /> <Cup /> <Cup /> </> ); }
Component này đang đọc và ghi một biến guest
được khai báo bên ngoài nó. Điều này có nghĩa là gọi component này nhiều lần sẽ tạo ra JSX khác nhau! Và hơn thế nữa, nếu các component khác đọc guest
, chúng cũng sẽ tạo ra JSX khác nhau, tùy thuộc vào thời điểm chúng được render! Điều đó không thể đoán trước được.
Quay trở lại công thức của chúng ta y = 2x, bây giờ ngay cả khi x = 2, chúng ta không thể tin rằng y = 4. Các bài kiểm tra của chúng ta có thể thất bại, người dùng của chúng ta sẽ bối rối, máy bay sẽ rơi khỏi bầu trời—bạn có thể thấy điều này sẽ dẫn đến những lỗi khó hiểu như thế nào!
Bạn có thể sửa component này bằng cách truyền guest
như một prop:
function Cup({ guest }) { return <h2>Cốc trà cho khách #{guest}</h2>; } export default function TeaSet() { return ( <> <Cup guest={1} /> <Cup guest={2} /> <Cup guest={3} /> </> ); }
Bây giờ component của bạn là thuần khiết, vì JSX mà nó trả về chỉ phụ thuộc vào prop guest
.
Nói chung, bạn không nên mong đợi các component của mình được render theo bất kỳ thứ tự cụ thể nào. Không quan trọng nếu bạn gọi y = 2x trước hay sau y = 5x: cả hai công thức sẽ giải quyết độc lập với nhau. Tương tự, mỗi component chỉ nên “tự suy nghĩ cho bản thân”, và không cố gắng phối hợp với hoặc phụ thuộc vào những người khác trong quá trình render. Render giống như một bài kiểm tra ở trường: mỗi component nên tự tính toán JSX!
Tìm hiểu sâu
Mặc dù bạn có thể chưa sử dụng tất cả chúng, nhưng trong React có ba loại đầu vào mà bạn có thể đọc trong khi render: props, state, và context. Bạn nên luôn coi các đầu vào này là chỉ đọc.
Khi bạn muốn thay đổi một cái gì đó để đáp ứng với đầu vào của người dùng, bạn nên set state thay vì ghi vào một biến. Bạn không bao giờ nên thay đổi các biến hoặc đối tượng đã tồn tại trong khi component của bạn đang render.
React cung cấp một “Strict Mode” trong đó nó gọi hàm của mỗi component hai lần trong quá trình phát triển. Bằng cách gọi các hàm component hai lần, Strict Mode giúp tìm các component vi phạm các quy tắc này.
Lưu ý cách ví dụ ban đầu hiển thị “Khách #2”, “Khách #4” và “Khách #6” thay vì “Khách #1”, “Khách #2” và “Khách #3”. Hàm ban đầu không thuần khiết, vì vậy gọi nó hai lần đã làm hỏng nó. Nhưng phiên bản thuần khiết đã sửa hoạt động ngay cả khi hàm được gọi hai lần mỗi lần. Các hàm thuần khiết chỉ tính toán, vì vậy gọi chúng hai lần sẽ không thay đổi bất cứ điều gì—giống như gọi double(2)
hai lần không thay đổi những gì được trả về, và giải y = 2x hai lần không thay đổi y là gì. Đầu vào giống nhau, đầu ra giống nhau. Luôn luôn.
Strict Mode không có hiệu lực trong sản xuất, vì vậy nó sẽ không làm chậm ứng dụng cho người dùng của bạn. Để chọn tham gia Strict Mode, bạn có thể bọc component gốc của mình trong <React.StrictMode>
. Một số framework thực hiện điều này theo mặc định.
Thay Đổi Cục Bộ: Bí Mật Nhỏ Của Component Của Bạn
Trong ví dụ trên, vấn đề là component đã thay đổi một biến đã tồn tại trong khi render. Điều này thường được gọi là “mutation” để làm cho nó nghe có vẻ đáng sợ hơn một chút. Các hàm thuần khiết không mutate các biến bên ngoài phạm vi của hàm hoặc các đối tượng được tạo trước khi gọi—điều đó làm cho chúng không thuần khiết!
Tuy nhiên, hoàn toàn ổn khi thay đổi các biến và đối tượng mà bạn vừa tạo trong khi render. Trong ví dụ này, bạn tạo một mảng []
, gán nó cho một biến cups
, và sau đó push
một tá cốc vào nó:
function Cup({ guest }) { return <h2>Cốc trà cho khách #{guest}</h2>; } export default function TeaGathering() { let cups = []; for (let i = 1; i <= 12; i++) { cups.push(<Cup key={i} guest={i} />); } return cups; }
Nếu biến cups
hoặc mảng []
được tạo bên ngoài hàm TeaGathering
, đây sẽ là một vấn đề lớn! Bạn sẽ thay đổi một đối tượng đã tồn tại bằng cách đẩy các mục vào mảng đó.
Tuy nhiên, điều đó ổn vì bạn đã tạo chúng trong cùng một lần render, bên trong TeaGathering
. Không có mã nào bên ngoài TeaGathering
sẽ biết rằng điều này đã xảy ra. Điều này được gọi là “local mutation”—nó giống như bí mật nhỏ của component của bạn.
Nơi Bạn Có Thể Gây Ra Tác Dụng Phụ
Mặc dù lập trình hàm dựa nhiều vào tính thuần khiết, nhưng tại một thời điểm nào đó, ở đâu đó, một cái gì đó phải thay đổi. Đó là mục đích của lập trình! Những thay đổi này—cập nhật màn hình, bắt đầu hoạt ảnh, thay đổi dữ liệu—được gọi là tác dụng phụ. Chúng là những thứ xảy ra “ở bên cạnh”, không phải trong quá trình render.
Trong React, tác dụng phụ thường thuộc về bên trong trình xử lý sự kiện. Trình xử lý sự kiện là các hàm mà React chạy khi bạn thực hiện một số hành động—ví dụ: khi bạn nhấp vào một nút. Mặc dù trình xử lý sự kiện được xác định bên trong component của bạn, nhưng chúng không chạy trong quá trình render! Vì vậy, trình xử lý sự kiện không cần phải thuần khiết.
Nếu bạn đã cạn kiệt tất cả các tùy chọn khác và không thể tìm thấy trình xử lý sự kiện phù hợp cho tác dụng phụ của mình, bạn vẫn có thể đính kèm nó vào JSX được trả về của mình bằng một lệnh gọi useEffect
trong component của bạn. Điều này cho React biết để thực thi nó sau đó, sau khi render, khi các tác dụng phụ được cho phép. Tuy nhiên, cách tiếp cận này nên là phương sách cuối cùng của bạn.
Khi có thể, hãy cố gắng thể hiện logic của bạn chỉ bằng cách render. Bạn sẽ ngạc nhiên về mức độ bạn có thể đạt được!
Tìm hiểu sâu
Viết các hàm thuần khiết cần một số thói quen và kỷ luật. Nhưng nó cũng mở ra những cơ hội tuyệt vời:
- Các component của bạn có thể chạy trong một môi trường khác—ví dụ: trên máy chủ! Vì chúng trả về cùng một kết quả cho cùng một đầu vào, một component có thể phục vụ nhiều yêu cầu của người dùng.
- Bạn có thể cải thiện hiệu suất bằng cách bỏ qua việc render các component có đầu vào không thay đổi. Điều này an toàn vì các hàm thuần khiết luôn trả về cùng một kết quả, vì vậy chúng an toàn để lưu vào bộ nhớ cache.
- Nếu một số dữ liệu thay đổi ở giữa quá trình render một cây component sâu, React có thể khởi động lại quá trình render mà không lãng phí thời gian để hoàn thành quá trình render đã lỗi thời. Tính thuần khiết giúp bạn an toàn khi dừng tính toán bất cứ lúc nào.
Mọi tính năng React mới mà chúng tôi đang xây dựng đều tận dụng tính thuần khiết. Từ tìm nạp dữ liệu đến hoạt ảnh đến hiệu suất, việc giữ cho các component thuần khiết sẽ mở ra sức mạnh của mô hình React.
Tóm tắt
- Một component phải thuần khiết, có nghĩa là:
- Nó chỉ lo việc của nó. Nó không nên thay đổi bất kỳ đối tượng hoặc biến nào đã tồn tại trước khi render.
- Đầu vào giống nhau, đầu ra giống nhau. Với cùng một đầu vào, một component phải luôn trả về cùng một JSX.
- Quá trình render có thể xảy ra bất cứ lúc nào, vì vậy các component không nên phụ thuộc vào trình tự render của nhau.
- Bạn không nên mutate bất kỳ đầu vào nào mà các component của bạn sử dụng để render. Điều đó bao gồm props, state và context. Để cập nhật màn hình, hãy “set” state thay vì mutate các đối tượng đã tồn tại.
- Cố gắng thể hiện logic của component của bạn trong JSX mà bạn trả về. Khi bạn cần “thay đổi mọi thứ”, bạn thường sẽ muốn thực hiện nó trong một trình xử lý sự kiện. Là phương sách cuối cùng, bạn có thể sử dụng
useEffect
. - Viết các hàm thuần khiết cần một chút luyện tập, nhưng nó sẽ mở ra sức mạnh của mô hình React.
Challenge 1 of 3: Sửa Một Chiếc Đồng Hồ Bị Hỏng
Component này cố gắng đặt CSS class của <h1>
thành "night"
trong khoảng thời gian từ nửa đêm đến sáu giờ sáng, và "day"
vào tất cả các thời điểm khác. Tuy nhiên, nó không hoạt động. Bạn có thể sửa component này không?
Bạn có thể xác minh xem giải pháp của bạn có hoạt động hay không bằng cách tạm thời thay đổi múi giờ của máy tính. Khi thời gian hiện tại là từ nửa đêm đến sáu giờ sáng, đồng hồ sẽ có màu đảo ngược!
export default function Clock({ time }) { let hours = time.getHours(); if (hours >= 0 && hours <= 6) { document.getElementById('time').className = 'night'; } else { document.getElementById('time').className = 'day'; } return ( <h1 id="time"> {time.toLocaleTimeString()} </h1> ); }