Skip to content

Commit d08d5f6

Browse files
committed
docs(react): add batched useState demo
1 parent 0eb8ef4 commit d08d5f6

1 file changed

Lines changed: 135 additions & 1 deletion

File tree

docs/posts/React源码学习/useState.md

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,4 +189,138 @@
189189
</html>
190190
```
191191

192-
上面这个实现,已经可以满足我的需求了,那为啥 React 用的不是数组 + 下标,而是链表?这个到底有啥好处
192+
上面这个实现,已经可以满足我的需求了,那为啥 React 用的不是数组 + 下标,而是链表?这个到底有啥好处。
193+
先不着急,我发现了一个问题。我上面这个写法,每次setState的时候就会render一次。我先来解决这个问题。
194+
``` html
195+
<!DOCTYPE html>
196+
<html lang="en">
197+
<head>
198+
<meta charset="UTF-8" />
199+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
200+
<title>Mini useState</title>
201+
</head>
202+
<body>
203+
<div id="root"></div>
204+
205+
<script>
206+
let hooks = [];
207+
let hookIndex = 0;
208+
let renderCount = 0;
209+
let isBatching = false;
210+
211+
function batch(fn) {
212+
isBatching = true;
213+
fn();
214+
isBatching = false;
215+
render();
216+
}
217+
218+
function useState(initialState) {
219+
const currentIndex = hookIndex;
220+
221+
if (hooks[currentIndex] === undefined) {
222+
hooks[currentIndex] = {
223+
memoizedState:
224+
typeof initialState === 'function' ? initialState() : initialState,
225+
queue: [],
226+
};
227+
}
228+
229+
const hook = hooks[currentIndex];
230+
231+
if (hook.queue.length > 0) {
232+
let newState = hook.memoizedState;
233+
234+
for (const action of hook.queue) {
235+
newState =
236+
typeof action === 'function'
237+
? action(newState)
238+
: action;
239+
}
240+
241+
hook.memoizedState = newState;
242+
hook.queue = [];
243+
}
244+
245+
function setState(action) {
246+
hook.queue.push(action);
247+
248+
if (!isBatching) {
249+
render();
250+
}
251+
}
252+
253+
hookIndex++;
254+
255+
return [hook.memoizedState, setState];
256+
}
257+
258+
function App() {
259+
const [age, setAge] = useState(18);
260+
const [name, setName] = useState('Jack');
261+
const [address, setAddress] = useState('Github Repo');
262+
const [techStack, setTechStack] = useState('JavaScript');
263+
264+
function handleAgeClick() {
265+
setAge((prev) => prev + 1);
266+
setAge((prev) => prev + 1);
267+
setAge((prev) => prev + 1);
268+
}
269+
270+
function handleNameClick() {
271+
setName('Tom');
272+
}
273+
274+
function handleAddressClick() {
275+
setAddress('Gitlab Repo');
276+
}
277+
278+
function handleTechStackClick() {
279+
setTechStack((prev) =>
280+
prev === 'JavaScript' ? 'React' : 'JavaScript'
281+
);
282+
}
283+
284+
window.handleAgeClick = handleAgeClick;
285+
window.handleNameClick = handleNameClick;
286+
window.handleAddressClick = handleAddressClick;
287+
window.handleTechStackClick = handleTechStackClick;
288+
window.batch = batch;
289+
290+
console.log('hooks:', hooks);
291+
292+
return `
293+
<div>
294+
<button onclick="batch(window.handleAgeClick)">
295+
age: ${age}
296+
</button>
297+
298+
<div onclick="batch(window.handleNameClick)">
299+
name: ${name}
300+
</div>
301+
302+
<div onclick="batch(window.handleAddressClick)">
303+
address: ${address}
304+
</div>
305+
306+
<div onclick="batch(window.handleTechStackClick)">
307+
techStack: ${techStack}
308+
</div>
309+
</div>
310+
`;
311+
}
312+
313+
function render() {
314+
hookIndex = 0;
315+
renderCount++;
316+
317+
console.log('渲染了', renderCount, '');
318+
319+
document.getElementById('root').innerHTML = App();
320+
}
321+
322+
render();
323+
</script>
324+
</body>
325+
</html>
326+
```

0 commit comments

Comments
 (0)