useState
useState
là một React Hook cho phép bạn thêm một biến trạng thái vào component của bạn.
const [state, setState] = useState(initialState)
- Tham khảo
- Cách sử dụng
- Khắc phục sự cố
- Tôi đã cập nhật trạng thái, nhưng việc ghi log lại cho tôi giá trị cũ
- Tôi đã cập nhật trạng thái, nhưng màn hình không cập nhật
- Tôi đang gặp lỗi: “Quá nhiều lần re-render”
- Hàm khởi tạo hoặc hàm cập nhật của tôi chạy hai lần
- Tôi đang cố gắng đặt trạng thái thành một hàm, nhưng nó lại được gọi thay vì được lưu trữ
Tham khảo
useState(initialState)
Gọi useState
ở cấp cao nhất của component để khai báo một biến trạng thái.
import { useState } from 'react';
function MyComponent() {
const [age, setAge] = useState(28);
const [name, setName] = useState('Taylor');
const [todos, setTodos] = useState(() => createTodos());
// ...
Quy ước là đặt tên các biến trạng thái như [something, setSomething]
bằng cách sử dụng destructuring mảng.
Tham số
initialState
: Giá trị bạn muốn trạng thái ban đầu là. Nó có thể là một giá trị của bất kỳ kiểu nào, nhưng có một hành vi đặc biệt đối với các hàm. Đối số này bị bỏ qua sau lần render ban đầu.- Nếu bạn truyền một hàm làm
initialState
, nó sẽ được coi là một hàm khởi tạo. Nó phải thuần khiết, không nhận bất kỳ đối số nào và phải trả về một giá trị thuộc bất kỳ kiểu nào. React sẽ gọi hàm khởi tạo của bạn khi khởi tạo component và lưu trữ giá trị trả về của nó làm trạng thái ban đầu. Xem một ví dụ bên dưới.
- Nếu bạn truyền một hàm làm
Trả về
useState
trả về một mảng chính xác hai giá trị:
- Trạng thái hiện tại. Trong quá trình render đầu tiên, nó sẽ khớp với
initialState
mà bạn đã truyền. - Hàm
set
cho phép bạn cập nhật trạng thái thành một giá trị khác và kích hoạt re-render.
Lưu ý
useState
là một Hook, vì vậy bạn chỉ có thể gọi nó ở cấp cao nhất của component hoặc Hook của riêng bạn. Bạn không thể gọi nó bên trong các vòng lặp hoặc điều kiện. Nếu bạn cần điều đó, hãy trích xuất một component mới và di chuyển trạng thái vào đó.- Trong Strict Mode, React sẽ gọi hàm khởi tạo của bạn hai lần để giúp bạn tìm thấy các tạp chất vô tình. Đây là hành vi chỉ dành cho quá trình phát triển và không ảnh hưởng đến sản xuất. Nếu hàm khởi tạo của bạn là thuần khiết (như nó phải vậy), điều này sẽ không ảnh hưởng đến hành vi. Kết quả từ một trong các lệnh gọi sẽ bị bỏ qua.
Các hàm set
, như setSomething(nextState)
Hàm set
được trả về bởi useState
cho phép bạn cập nhật trạng thái thành một giá trị khác và kích hoạt re-render. Bạn có thể truyền trạng thái tiếp theo trực tiếp hoặc một hàm tính toán nó từ trạng thái trước đó:
const [name, setName] = useState('Edward');
function handleClick() {
setName('Taylor');
setAge(a => a + 1);
// ...
Tham số
nextState
: Giá trị bạn muốn trạng thái là. Nó có thể là một giá trị của bất kỳ kiểu nào, nhưng có một hành vi đặc biệt đối với các hàm.- Nếu bạn truyền một hàm làm
nextState
, nó sẽ được coi là một hàm cập nhật. Nó phải thuần khiết, chỉ nhận trạng thái đang chờ xử lý làm đối số duy nhất và phải trả về trạng thái tiếp theo. React sẽ đặt hàm cập nhật của bạn vào một hàng đợi và re-render component của bạn. Trong quá trình render tiếp theo, React sẽ tính toán trạng thái tiếp theo bằng cách áp dụng tất cả các trình cập nhật được xếp hàng vào trạng thái trước đó. Xem một ví dụ bên dưới.
- Nếu bạn truyền một hàm làm
Trả về
Các hàm set
không có giá trị trả về.
Lưu ý
-
Hàm
set
chỉ cập nhật biến trạng thái cho lần render tiếp theo. Nếu bạn đọc biến trạng thái sau khi gọi hàmset
, bạn vẫn sẽ nhận được giá trị cũ đã có trên màn hình trước khi bạn gọi. -
Nếu giá trị mới bạn cung cấp giống hệt với
state
hiện tại, như được xác định bởi so sánhObject.is
, React sẽ bỏ qua việc re-render component và các children của nó. Đây là một tối ưu hóa. Mặc dù trong một số trường hợp, React vẫn có thể cần gọi component của bạn trước khi bỏ qua các children, nhưng nó không ảnh hưởng đến mã của bạn. -
React gom các bản cập nhật trạng thái. Nó cập nhật màn hình sau khi tất cả các trình xử lý sự kiện đã chạy và đã gọi các hàm
set
của chúng. Điều này ngăn chặn nhiều lần re-render trong một sự kiện duy nhất. Trong trường hợp hiếm hoi bạn cần buộc React cập nhật màn hình sớm hơn, ví dụ: để truy cập DOM, bạn có thể sử dụngflushSync
. -
Hàm
set
có một identity ổn định, vì vậy bạn sẽ thường thấy nó bị bỏ qua khỏi các dependency của Effect, nhưng việc bao gồm nó sẽ không khiến Effect kích hoạt. Nếu trình lint cho phép bạn bỏ qua một dependency mà không có lỗi, thì điều đó là an toàn. Tìm hiểu thêm về việc loại bỏ các dependency của Effect. -
Gọi hàm
set
trong quá trình render chỉ được phép từ bên trong component đang render. React sẽ loại bỏ đầu ra của nó và ngay lập tức cố gắng render lại với trạng thái mới. Mẫu này hiếm khi cần thiết, nhưng bạn có thể sử dụng nó để lưu trữ thông tin từ các lần render trước. Xem một ví dụ bên dưới. -
Trong Strict Mode, React sẽ gọi hàm cập nhật của bạn hai lần để giúp bạn tìm thấy các tạp chất vô tình. Đây là hành vi chỉ dành cho quá trình phát triển và không ảnh hưởng đến sản xuất. Nếu hàm cập nhật của bạn là thuần khiết (như nó phải vậy), điều này sẽ không ảnh hưởng đến hành vi. Kết quả từ một trong các lệnh gọi sẽ bị bỏ qua.
Cách sử dụng
Thêm trạng thái vào một component
Gọi useState
ở cấp cao nhất của component để khai báo một hoặc nhiều biến trạng thái.
import { useState } from 'react';
function MyComponent() {
const [age, setAge] = useState(42);
const [name, setName] = useState('Taylor');
// ...
Quy ước là đặt tên các biến trạng thái như [something, setSomething]
bằng cách sử dụng destructuring mảng.
useState
trả về một mảng chính xác hai mục:
- Trạng thái hiện tại của biến trạng thái này, ban đầu được đặt thành trạng thái ban đầu mà bạn đã cung cấp.
- Hàm
set
cho phép bạn thay đổi nó thành bất kỳ giá trị nào khác để đáp ứng với tương tác.
Để cập nhật những gì trên màn hình, hãy gọi hàm set
với một số trạng thái tiếp theo:
function handleClick() {
setName('Robin');
}
React sẽ lưu trữ trạng thái tiếp theo, render lại component của bạn với các giá trị mới và cập nhật UI.
Example 1 of 4: Bộ đếm (số)
Trong ví dụ này, biến trạng thái count
giữ một số. Nhấp vào nút sẽ tăng nó.
import { useState } from 'react'; export default function Counter() { const [count, setCount] = useState(0); function handleClick() { setCount(count + 1); } return ( <button onClick={handleClick}> You pressed me {count} times </button> ); }
Cập nhật trạng thái dựa trên trạng thái trước đó
Giả sử age
là 42
. Trình xử lý này gọi setAge(age + 1)
ba lần:
function handleClick() {
setAge(age + 1); // setAge(42 + 1)
setAge(age + 1); // setAge(42 + 1)
setAge(age + 1); // setAge(42 + 1)
}
Tuy nhiên, sau một lần nhấp, age
sẽ chỉ là 43
thay vì 45
! Điều này là do việc gọi hàm set
không cập nhật biến trạng thái age
trong mã đang chạy. Vì vậy, mỗi lệnh gọi setAge(age + 1)
trở thành setAge(43)
.
Để giải quyết vấn đề này, bạn có thể truyền một hàm cập nhật cho setAge
thay vì trạng thái tiếp theo:
function handleClick() {
setAge(a => a + 1); // setAge(42 => 43)
setAge(a => a + 1); // setAge(43 => 44)
setAge(a => a + 1); // setAge(44 => 45)
}
Ở đây, a => a + 1
là hàm cập nhật của bạn. Nó lấy trạng thái đang chờ xử lý và tính toán trạng thái tiếp theo từ đó.
React đặt các hàm cập nhật của bạn vào một hàng đợi. Sau đó, trong quá trình render tiếp theo, nó sẽ gọi chúng theo cùng một thứ tự:
a => a + 1
sẽ nhận42
làm trạng thái đang chờ xử lý và trả về43
làm trạng thái tiếp theo.a => a + 1
sẽ nhận43
làm trạng thái đang chờ xử lý và trả về44
làm trạng thái tiếp theo.a => a + 1
sẽ nhận44
làm trạng thái đang chờ xử lý và trả về45
làm trạng thái tiếp theo.
Không có bản cập nhật nào khác đang chờ xử lý, vì vậy React sẽ lưu trữ 45
làm trạng thái hiện tại cuối cùng.
Theo quy ước, người ta thường đặt tên đối số trạng thái đang chờ xử lý cho chữ cái đầu tiên của tên biến trạng thái, chẳng hạn như a
cho age
. Tuy nhiên, bạn cũng có thể gọi nó là prevAge
hoặc một cái gì đó khác mà bạn thấy rõ ràng hơn.
React có thể gọi các hàm cập nhật của bạn hai lần trong quá trình phát triển để xác minh rằng chúng thuần khiết.
Tìm hiểu sâu
Bạn có thể nghe một khuyến nghị là luôn viết mã như setAge(a => a + 1)
nếu trạng thái bạn đang đặt được tính từ trạng thái trước đó. Không có hại gì trong đó, nhưng nó cũng không phải lúc nào cũng cần thiết.
Trong hầu hết các trường hợp, không có sự khác biệt giữa hai cách tiếp cận này. React luôn đảm bảo rằng đối với các hành động cố ý của người dùng, chẳng hạn như nhấp chuột, biến trạng thái age
sẽ được cập nhật trước lần nhấp tiếp theo. Điều này có nghĩa là không có rủi ro nào khi trình xử lý nhấp chuột nhìn thấy một age
“cũ” khi bắt đầu trình xử lý sự kiện.
Tuy nhiên, nếu bạn thực hiện nhiều cập nhật trong cùng một sự kiện, thì hàm cập nhật có thể hữu ích. Chúng cũng hữu ích nếu việc truy cập chính biến trạng thái là bất tiện (bạn có thể gặp phải điều này khi tối ưu hóa việc render lại).
Nếu bạn thích tính nhất quán hơn là cú pháp dài dòng hơn một chút, thì việc luôn viết một hàm cập nhật nếu trạng thái bạn đang đặt được tính từ trạng thái trước đó là hợp lý. Nếu nó được tính từ trạng thái trước đó của một số biến trạng thái khác, bạn có thể muốn kết hợp chúng thành một đối tượng và sử dụng một reducer.
Example 1 of 2: Truyền hàm cập nhật
Ví dụ này truyền hàm cập nhật, vì vậy nút “+3” hoạt động.
import { useState } from 'react'; export default function Counter() { const [age, setAge] = useState(42); function increment() { setAge(a => a + 1); } return ( <> <h1>Your age: {age}</h1> <button onClick={() => { increment(); increment(); increment(); }}>+3</button> <button onClick={() => { increment(); }}>+1</button> </> ); }
Cập nhật các đối tượng và mảng trong trạng thái
Bạn có thể đặt các đối tượng và mảng vào trạng thái. Trong React, trạng thái được coi là chỉ đọc, vì vậy bạn nên thay thế nó thay vì mutate các đối tượng hiện có của bạn. Ví dụ: nếu bạn có một đối tượng form
trong trạng thái, đừng mutate nó:
// 🚩 Đừng mutate một đối tượng trong trạng thái như thế này:
form.firstName = 'Taylor';
Thay vào đó, hãy thay thế toàn bộ đối tượng bằng cách tạo một đối tượng mới:
// ✅ Thay thế trạng thái bằng một đối tượng mới
setForm({
...form,
firstName: 'Taylor'
});
Đọc cập nhật các đối tượng trong trạng thái và cập nhật các mảng trong trạng thái để tìm hiểu thêm.
Example 1 of 4: Form (đối tượng)
Trong ví dụ này, biến trạng thái form
giữ một đối tượng. Mỗi input có một trình xử lý thay đổi gọi setForm
với trạng thái tiếp theo của toàn bộ form. Cú pháp spread { ...form }
đảm bảo rằng đối tượng trạng thái được thay thế chứ không phải bị mutate.
import { useState } from 'react'; export default function Form() { const [form, setForm] = useState({ firstName: 'Barbara', lastName: 'Hepworth', email: 'bhepworth@sculpture.com', }); return ( <> <label> First name: <input value={form.firstName} onChange={e => { setForm({ ...form, firstName: e.target.value }); }} /> </label> <label> Last name: <input value={form.lastName} onChange={e => { setForm({ ...form, lastName: e.target.value }); }} /> </label> <label> Email: <input value={form.email} onChange={e => { setForm({ ...form, email: e.target.value }); }} /> </label> <p> {form.firstName}{' '} {form.lastName}{' '} ({form.email}) </p> </> ); }
Tránh tạo lại trạng thái ban đầu
React lưu trạng thái ban đầu một lần và bỏ qua nó trong các lần render tiếp theo.
function TodoList() {
const [todos, setTodos] = useState(createInitialTodos());
// ...
Mặc dù kết quả của createInitialTodos()
chỉ được sử dụng cho lần render ban đầu, bạn vẫn đang gọi hàm này trên mỗi lần render. Điều này có thể gây lãng phí nếu nó tạo ra các mảng lớn hoặc thực hiện các tính toán tốn kém.
Để giải quyết vấn đề này, bạn có thể truyền nó như một hàm khởi tạo cho useState
thay thế:
function TodoList() {
const [todos, setTodos] = useState(createInitialTodos);
// ...
Lưu ý rằng bạn đang truyền createInitialTodos
, là chính hàm, chứ không phải createInitialTodos()
, là kết quả của việc gọi nó. Nếu bạn truyền một hàm cho useState
, React sẽ chỉ gọi nó trong quá trình khởi tạo.
React có thể gọi các hàm khởi tạo của bạn hai lần trong quá trình phát triển để xác minh rằng chúng thuần khiết.
Example 1 of 2: Truyền hàm khởi tạo
Ví dụ này truyền hàm khởi tạo, vì vậy hàm createInitialTodos
chỉ chạy trong quá trình khởi tạo. Nó không chạy khi component re-render, chẳng hạn như khi bạn nhập vào input.
import { useState } from 'react'; function createInitialTodos() { const initialTodos = []; for (let i = 0; i < 50; i++) { initialTodos.push({ id: i, text: 'Item ' + (i + 1) }); } return initialTodos; } export default function TodoList() { const [todos, setTodos] = useState(createInitialTodos); const [text, setText] = useState(''); return ( <> <input value={text} onChange={e => setText(e.target.value)} /> <button onClick={() => { setText(''); setTodos([{ id: todos.length, text: text }, ...todos]); }}>Add</button> <ul> {todos.map(item => ( <li key={item.id}> {item.text} </li> ))} </ul> </> ); }
Đặt lại trạng thái bằng một key
Bạn sẽ thường gặp thuộc tính key
khi render danh sách. Tuy nhiên, nó cũng phục vụ một mục đích khác.
Bạn có thể đặt lại trạng thái của một component bằng cách truyền một key
khác cho component đó. Trong ví dụ này, nút Reset thay đổi biến trạng thái version
, mà chúng ta truyền dưới dạng key
cho Form
. Khi key
thay đổi, React sẽ tạo lại component Form
(và tất cả các children của nó) từ đầu, vì vậy trạng thái của nó sẽ được đặt lại.
Đọc duy trì và đặt lại trạng thái để tìm hiểu thêm.
import { useState } from 'react'; export default function App() { const [version, setVersion] = useState(0); function handleReset() { setVersion(version + 1); } return ( <> <button onClick={handleReset}>Reset</button> <Form key={version} /> </> ); } function Form() { const [name, setName] = useState('Taylor'); return ( <> <input value={name} onChange={e => setName(e.target.value)} /> <p>Hello, {name}.</p> </> ); }
Lưu trữ thông tin từ các lần render trước
Thông thường, bạn sẽ cập nhật trạng thái trong các trình xử lý sự kiện. Tuy nhiên, trong một số trường hợp hiếm hoi, bạn có thể muốn điều chỉnh trạng thái để đáp ứng với việc render — ví dụ: bạn có thể muốn thay đổi một biến trạng thái khi một prop thay đổi.
Trong hầu hết các trường hợp, bạn không cần điều này:
- Nếu giá trị bạn cần có thể được tính toán hoàn toàn từ các prop hiện tại hoặc trạng thái khác, hãy loại bỏ trạng thái dư thừa đó hoàn toàn. Nếu bạn lo lắng về việc tính toán lại quá thường xuyên, thì
useMemo Hook
có thể giúp ích. - Nếu bạn muốn đặt lại trạng thái của toàn bộ cây component, hãy truyền một
key
khác cho component của bạn. - Nếu có thể, hãy cập nhật tất cả các trạng thái liên quan trong các trình xử lý sự kiện.
Trong trường hợp hiếm hoi mà không có điều nào trong số này áp dụng, có một mẫu bạn có thể sử dụng để cập nhật trạng thái dựa trên các giá trị đã được render cho đến nay, bằng cách gọi một hàm set
trong khi component của bạn đang render.
Đây là một ví dụ. Component CountLabel
này hiển thị prop count
được truyền cho nó:
export default function CountLabel({ count }) {
return <h1>{count}</h1>
}
Giả sử bạn muốn hiển thị xem bộ đếm đã tăng hay giảm kể từ lần thay đổi cuối cùng. Prop count
không cho bạn biết điều này — bạn cần theo dõi giá trị trước đó của nó. Thêm biến trạng thái prevCount
để theo dõi nó. Thêm một biến trạng thái khác có tên là trend
để giữ xem số lượng đã tăng hay giảm. So sánh prevCount
với count
và nếu chúng không bằng nhau, hãy cập nhật cả prevCount
và trend
. Bây giờ bạn có thể hiển thị cả prop số lượng hiện tại và cách nó đã thay đổi kể từ lần render cuối cùng.
import { useState } from 'react'; export default function CountLabel({ count }) { const [prevCount, setPrevCount] = useState(count); const [trend, setTrend] = useState(null); if (prevCount !== count) { setPrevCount(count); setTrend(count > prevCount ? 'increasing' : 'decreasing'); } return ( <> <h1>{count}</h1> {trend && <p>The count is {trend}</p>} </> ); }
Lưu ý rằng nếu bạn gọi một hàm set
trong khi render, nó phải nằm trong một điều kiện như prevCount !== count
, và phải có một lệnh gọi như setPrevCount(count)
bên trong điều kiện đó. Nếu không, component của bạn sẽ re-render trong một vòng lặp cho đến khi nó bị crash. Ngoài ra, bạn chỉ có thể cập nhật trạng thái của component đang render theo cách này. Gọi hàm set
của một component khác trong khi render là một lỗi. Cuối cùng, lệnh gọi set
của bạn vẫn nên cập nhật trạng thái mà không cần mutation — điều này không có nghĩa là bạn có thể phá vỡ các quy tắc khác của hàm thuần khiết.
Mô hình này có thể khó hiểu và thường nên tránh. Tuy nhiên, nó tốt hơn là cập nhật trạng thái trong một effect. Khi bạn gọi hàm set
trong quá trình render, React sẽ re-render component đó ngay sau khi component của bạn thoát bằng một câu lệnh return
và trước khi render các children. Bằng cách này, children không cần phải render hai lần. Phần còn lại của hàm component của bạn vẫn sẽ thực thi (và kết quả sẽ bị loại bỏ). Nếu điều kiện của bạn nằm dưới tất cả các lệnh gọi Hook, bạn có thể thêm một return;
sớm để khởi động lại quá trình render sớm hơn.
Khắc phục sự cố
Tôi đã cập nhật trạng thái, nhưng việc ghi log lại cho tôi giá trị cũ
Gọi hàm set
không thay đổi trạng thái trong mã đang chạy:
function handleClick() {
console.log(count); // 0
setCount(count + 1); // Yêu cầu re-render với 1
console.log(count); // Vẫn là 0!
setTimeout(() => {
console.log(count); // Cũng là 0!
}, 5000);
}
Điều này là do trạng thái hoạt động như một snapshot. Cập nhật trạng thái yêu cầu một render khác với giá trị trạng thái mới, nhưng không ảnh hưởng đến biến JavaScript count
trong trình xử lý sự kiện đang chạy của bạn.
Nếu bạn cần sử dụng trạng thái tiếp theo, bạn có thể lưu nó trong một biến trước khi chuyển nó cho hàm set
:
const nextCount = count + 1;
setCount(nextCount);
console.log(count); // 0
console.log(nextCount); // 1
Tôi đã cập nhật trạng thái, nhưng màn hình không cập nhật
React sẽ bỏ qua bản cập nhật của bạn nếu trạng thái tiếp theo bằng với trạng thái trước đó, như được xác định bởi so sánh Object.is
. Điều này thường xảy ra khi bạn thay đổi trực tiếp một đối tượng hoặc một mảng trong trạng thái:
obj.x = 10; // 🚩 Sai: mutating đối tượng hiện có
setObj(obj); // 🚩 Không làm gì cả
Bạn đã mutate một đối tượng obj
hiện có và chuyển nó trở lại setObj
, vì vậy React đã bỏ qua bản cập nhật. Để khắc phục điều này, bạn cần đảm bảo rằng bạn luôn thay thế các đối tượng và mảng trong trạng thái thay vì mutating chúng:
// ✅ Đúng: tạo một đối tượng mới
setObj({
...obj,
x: 10
});
Tôi đang gặp lỗi: “Quá nhiều lần re-render”
Bạn có thể nhận được một lỗi cho biết: Too many re-renders. React limits the number of renders to prevent an infinite loop.
(Quá nhiều lần re-render. React giới hạn số lần render để ngăn chặn một vòng lặp vô hạn.) Thông thường, điều này có nghĩa là bạn đang đặt trạng thái trong quá trình render một cách vô điều kiện, vì vậy component của bạn đi vào một vòng lặp: render, đặt trạng thái (gây ra một render), render, đặt trạng thái (gây ra một render), v.v. Rất thường xuyên, điều này là do một sai lầm trong việc chỉ định một trình xử lý sự kiện:
// 🚩 Sai: gọi trình xử lý trong quá trình render
return <button onClick={handleClick()}>Click me</button>
// ✅ Đúng: chuyển trình xử lý sự kiện xuống
return <button onClick={handleClick}>Click me</button>
// ✅ Đúng: chuyển một hàm inline xuống
return <button onClick={(e) => handleClick(e)}>Click me</button>
Nếu bạn không thể tìm thấy nguyên nhân của lỗi này, hãy nhấp vào mũi tên bên cạnh lỗi trong bảng điều khiển và xem qua ngăn xếp JavaScript để tìm lệnh gọi hàm set
cụ thể chịu trách nhiệm cho lỗi.
Hàm khởi tạo hoặc hàm cập nhật của tôi chạy hai lần
Trong Strict Mode, React sẽ gọi một số hàm của bạn hai lần thay vì một lần:
function TodoList() {
// Hàm component này sẽ chạy hai lần cho mỗi lần render.
const [todos, setTodos] = useState(() => {
// Hàm khởi tạo này sẽ chạy hai lần trong quá trình khởi tạo.
return createTodos();
});
function handleClick() {
setTodos(prevTodos => {
// Hàm cập nhật này sẽ chạy hai lần cho mỗi lần nhấp.
return [...prevTodos, createTodo()];
});
}
// ...
Điều này được mong đợi và không nên phá vỡ mã của bạn.
Hành vi chỉ dành cho quá trình phát triển này giúp bạn giữ cho các component thuần khiết. React sử dụng kết quả của một trong các lệnh gọi và bỏ qua kết quả của lệnh gọi kia. Miễn là component, hàm khởi tạo và hàm cập nhật của bạn là thuần khiết, điều này sẽ không ảnh hưởng đến logic của bạn. Tuy nhiên, nếu chúng vô tình không thuần khiết, điều này sẽ giúp bạn nhận thấy những sai lầm.
Ví dụ: hàm cập nhật không thuần khiết này mutate một mảng trong trạng thái:
setTodos(prevTodos => {
// 🚩 Sai lầm: mutating trạng thái
prevTodos.push(createTodo());
});
Vì React gọi hàm cập nhật của bạn hai lần, bạn sẽ thấy todo đã được thêm hai lần, vì vậy bạn sẽ biết rằng có một sai lầm. Trong ví dụ này, bạn có thể sửa sai lầm bằng cách thay thế mảng thay vì mutating nó:
setTodos(prevTodos => {
// ✅ Đúng: thay thế bằng trạng thái mới
return [...prevTodos, createTodo()];
});
Bây giờ hàm cập nhật này là thuần khiết, việc gọi nó thêm một lần không tạo ra sự khác biệt trong hành vi. Đây là lý do tại sao React gọi nó hai lần giúp bạn tìm thấy những sai lầm. Chỉ các hàm component, hàm khởi tạo và hàm cập nhật cần phải thuần khiết. Các trình xử lý sự kiện không cần phải thuần khiết, vì vậy React sẽ không bao giờ gọi các trình xử lý sự kiện của bạn hai lần.
Đọc giữ cho các component thuần khiết để tìm hiểu thêm.
Tôi đang cố gắng đặt trạng thái thành một hàm, nhưng nó lại được gọi thay vì được lưu trữ
Bạn không thể đặt một hàm vào trạng thái như thế này:
const [fn, setFn] = useState(someFunction);
function handleClick() {
setFn(someOtherFunction);
}
Vì bạn đang chuyển một hàm, React giả định rằng someFunction
là một hàm khởi tạo và someOtherFunction
là một hàm cập nhật, vì vậy nó cố gắng gọi chúng và lưu trữ kết quả. Để thực sự lưu trữ một hàm, bạn phải đặt () =>
trước chúng trong cả hai trường hợp. Sau đó, React sẽ lưu trữ các hàm bạn chuyển.
const [fn, setFn] = useState(() => someFunction);
function handleClick() {
setFn(() => someOtherFunction);
}