| title | useId |
|---|
useId là một React Hook dùng để sinh ra các ID duy nhất có thể được dùng để truyền vào các thuộc tính accessibility.
const id = useId()Gọi useId ở top level của component để sinh ra một ID duy nhất:
import { useId } from 'react';
function PasswordField() {
const passwordHintId = useId();
// ...Xem nhiều ví dụ hơn ở bên dưới.
useId không nhận tham số.
useId trả về một ID dạng chuỗi duy nhất với lần gọi useId trong component hiện tại.
-
useIdlà một Hook, vì vậy bạn chỉ có thể gọi nó ở top level của component hoặc các Hook tuỳ chỉnh của bạn. Bạn không thể gọi nó bên trong vòng lặp hoặc trong câu lệnh điều kiện. Nếu bạn cần làm vậy, hãy tách nó thành một component mới và di chuyển state vào đó. -
useIdkhông nên được sử dụng để sinh key cho danh sách. Key nên được tạo từ dữ liệu của bạn. -
useIdhiện tại không thể sử dụng trong async Server Component.
Không gọi useId để sinh key trong một danh sách. Key nên được tạo từ dữ liệu của bạn.
Sinh ID duy nhất cho các thuộc tính accessibility {/generating-unique-ids-for-accessibility-attributes/}
Gọi useId tại top level của component để sinh ra một ID duy nhất:
import { useId } from 'react';
function PasswordField() {
const passwordHintId = useId();
// ...Sau đó bạn có thể truyền ID đã sinh ra vào các thuộc tính khác:
<>
<input type="password" aria-describedby={passwordHintId} />
<p id={passwordHintId}>
</>Hãy xem một ví dụ để thấy khi nào nó hữu ích.
HTML accessibility attributes như aria-describedby cho bạn chỉ định rằng hai thẻ có liên quan với nhau. Ví dụ, bạn có thể chỉ định rằng một phần tử (như input) được mô tả bởi một phần tử khác (như đoạn văn).
Trong HTML thuần, bạn sẽ viết như sau:
<label>
Mật khẩu:
<input
type="password"
aria-describedby="password-hint"
/>
</label>
<p id="password-hint">
Mật khẩu nên chứa ít nhất 18 ký tự
</p>Tuy nhiên, việc hardcode ID như thế không phải là một practice tốt trong React. một component có thể được render nhiều lần trên trang--nhưng ID thì phải là duy nhất! Thay vì hardcode một ID, hãy sinh ra một ID duy nhất với useId:
import { useId } from 'react';
function PasswordField() {
const passwordHintId = useId();
return (
<>
<label>
Mật khẩu:
<input
type="password"
aria-describedby={passwordHintId}
/>
</label>
<p id={passwordHintId}>
Mật khẩu nên chứa ít nhất 18 ký tự
</p>
</>
);
}Và giờ, ngay cả khi bạn render nhiều instance của PasswordField, các ID được sinh ra sẽ không bị trùng lặp:
import { useId } from 'react';
function PasswordField() {
const passwordHintId = useId();
return (
<>
<label>
Mật khẩu:
<input
type="password"
aria-describedby={passwordHintId}
/>
</label>
<p id={passwordHintId}>
Mật khẩu nên chứa ít nhất 18 ký tự
</p>
</>
);
}
export default function App() {
return (
<>
<h2>Tạo mật khẩu</h2>
<PasswordField />
<h2>Xác nhận mật khẩu</h2>
<PasswordField />
</>
);
}input { margin: 5px; }Xem video này để thấy sự khác biệt trong trải nghiệm người dùng với các công nghệ hỗ trợ.
Với server rendering, useId yêu cầu component tree phải giống hệt nhau trên server và client. Nếu cây bạn render trên server và client không khớp hoàn toàn, các ID được sinh ra sẽ không khớp.
Bạn có thể sẽ thắc mắc tại sao useId lại tốt hơn là tăng một biến toàn cục như nextId++.
Lợi ích chính của useId là React đảm bảo rằng nó hoạt động với server rendering. Trong server rendering, các component của bạn sinh ra output HTML. Sau đó, trên client, hydration gắn các event handler của bạn vào HTML đã sinh ra. Để hydration hoạt động, output trên client phải khớp với HTML trên server.
Điều này quá khó để đảm bảo với một biến đếm tăng dần vì thứ tự mà các Client Component được hydrated có thể không khớp với thứ tự mà HTML trên server được sinh ra. Bằng cách gọi useId, bạn đảm bảo rằng hydration sẽ hoạt động, và output sẽ khớp giữa server và client.
Trong React, useId được sinh ra từ "parent path" của component gọi nó. Đây là lý do tại sao, nếu cây trên client và server giống nhau, "parent path" sẽ khớp với nhau bất kể thứ tự render.
Nếu bạn cần gán ID cho nhiều phần tử liên quan, bạn có thể gọi useId để sinh ra một tiền tố (prefix) dùng chung cho chúng:
import { useId } from 'react';
export default function Form() {
const id = useId();
return (
<form>
<label htmlFor={id + '-lastName'}>Họ:</label>
<input id={id + '-lastName'} type="text" />
<hr />
<label htmlFor={id + '-firstName'}>Tên:</label>
<input id={id + '-firstName'} type="text" />
</form>
);
}label { min-width: 50px; display: inline-block; }
input { margin: 5px; }Điều này giúp bạn tránh phải gọi useId cho từng phần tử cần ID duy nhất.
Đặt tiền tố dùng chung cho tất cả ID được sinh ra {/specifying-a-shared-prefix-for-all-generated-ids/}
Nếu bạn render nhiều ứng dụng React độc lập trên một trang, hãy truyền identifierPrefix như một tuỳ chọn vào các lệnh gọi createRoot hoặc hydrateRoot. Điều này đảm bảo rằng các ID được sinh ra bởi hai ứng dụng khác nhau sẽ không bao giờ bị trùng lặp vì mỗi ID được sinh ra với useId sẽ bắt đầu với tiền tố riêng biệt mà bạn đã chỉ định.
<!DOCTYPE html>
<html>
<head><title>My app</title></head>
<body>
<div id="root1"></div>
<div id="root2"></div>
</body>
</html>import { useId } from 'react';
function PasswordField() {
const passwordHintId = useId();
console.log('Generated identifier:', passwordHintId)
return (
<>
<label>
Mật khẩu:
<input
type="password"
aria-describedby={passwordHintId}
/>
</label>
<p id={passwordHintId}>
Mật khẩu nên chứa ít nhất 18 ký tự
</p>
</>
);
}
export default function App() {
return (
<>
<h2>Tạo mật khẩu</h2>
<PasswordField />
</>
);
}import { createRoot } from 'react-dom/client';
import App from './App.js';
import './styles.css';
const root1 = createRoot(document.getElementById('root1'), {
identifierPrefix: 'my-first-app-'
});
root1.render(<App />);
const root2 = createRoot(document.getElementById('root2'), {
identifierPrefix: 'my-second-app-'
});
root2.render(<App />);#root1 {
border: 5px solid blue;
padding: 10px;
margin: 5px;
}
#root2 {
border: 5px solid green;
padding: 10px;
margin: 5px;
}
input { margin: 5px; }Sử dụng cùng một tiền tố ID trên client và server {/using-the-same-id-prefix-on-the-client-and-the-server/}
Nếu bạn render nhiều ứng dụng React độc lập trên cùng một trang, và một số trong các ứng dụng này được server-rendered, hãy đảm bảo rằng identifierPrefix bạn truyền vào lệnh gọi hydrateRoot ở phía client giống với identifierPrefix bạn truyền vào các API server như renderToPipeableStream.
// Server
import { renderToPipeableStream } from 'react-dom/server';
const { pipe } = renderToPipeableStream(
<App />,
{ identifierPrefix: 'react-app1' }
);// Client
import { hydrateRoot } from 'react-dom/client';
const domNode = document.getElementById('root');
const root = hydrateRoot(
domNode,
reactNode,
{ identifierPrefix: 'react-app1' }
);Bạn không cần truyền identifierPrefix nếu bạn chỉ có một ứng dụng React trên trang.