Hello, everyone. I am Karsong.
See what’s wrong with the following components:
// App.tsx
const id = Math.random();
export default function App() {
return <div id={id}>Hello</div>
}
Copy the code
If the application is CSR (Client rendering), the ID is stable and the App component is fine.
But if the application is SSR (server-side rendering), app.tsx will experience:
-
React renders on the server and generates a random ID (let’s say 0.1234) called dehydrate
-
Hello
is passed as HTML to the client as the first screen content
-
React renders on the client and generates a random ID (say 0.6789). This step is called hydrate
Client/server generated ID does not match!
In fact, there is a long-standing problem that the server and client cannot simply generate stable, unique ids. The issue was raised as early as 15 years ago:
Generating random/unique attributes server-side that don’t break client-side mounting
Until recently, Act18 released the official Hook, useId, to solve these problems. His usage is simple:
function Checkbox() {
// Generate a unique, stable ID
const id = useId();
return (
<>
<label htmlFor={id}>Do you like React?</label>
<input type="checkbox" name="react" id={id} />
</>); ;Copy the code
While simple to use, the principle behind it is interesting — each ID represents the component’s hierarchy in the component tree.
In this article, let’s take a look at useId.
Welcome to join the Human high quality front-end framework research group, Band fly
Here comes Act18, and everything changes
This problem has always existed, but it has always been possible to use an incremented global count variable as id. Consider the following example:
// Global universal count variable
let globalIdIndex = 0;
export default function App() {
const id = useState(() = > globalIdIndex++);
return <div id={id}>Hello</div>
}
Copy the code
As long as the React process is the same on the server and client, the ids generated on both ends are the same.
However, with the advent of React Fizz (React’s new server-side streaming renderer), rendering order is no longer fixed.
For example, there is a feature called Selective Chocolate that changes the hydrate order based on user interaction.
When the left part of the screenshot is on Hydrate, the user clicks on the bottom right part:
React gives priority to the lower right part of the hydrate:
For a more detailed explanation of Selective variations, see New Suspense SSR Architecture in React 18
If the application uses the increment global count variable as the ID, then obviously the component ID will be smaller, so the ID is unstable.
So, is there any sign that both the server and the client are stable?
The answer is: the hierarchy of components.
The principle of useId
Suppose the application component tree looks like this:
No matter who comes first, B or C’s hierarchy is constant, so the hierarchy itself acts as a constant identifier between the server and the client.
For example, B can use 2-1 as its ID and C can use 2-2 as its ID:
function B() {
/ / id is "2-1"
const id = useId();
return <div id={id}>B</div>;
}
Copy the code
In practice, two factors need to be considered:
1. Use multiple ids for the same component
Like this:
function B() {
const id0 = useId();
const id1 = useId();
return (
<ul>
<li id={id0}></li>
<li id={id1}></li>
</ul>
);
}
Copy the code
2. Skip the components that do not use useId
Consider this component tree:
If components A and D use useId, and components B and C do not, then only A and D need to be assigned A hierarchy, thus reducing the need for presentation hierarchy.
In the actual implementation of useId, the hierarchy is represented as a number in base 32.
Base 32 is chosen because choosing the largest possible base will make the resulting string as compact as possible. Such as:
const a = 18;
// "10010" length 5
a.toString(2)
// "i" length 1
a.toString(32)
Copy the code
For details about the useId hierarchy, see useId
conclusion
React source code has multiple stack structures (such as a stack for storing context data).
The useId stack logic is one of the more complex ones.
Who would have thought an API so simple to use would be so complex to implement?
The React team is working on concurrency features that aren’t easy…