+
{
(theme ?? 'latte') as keyof typeof THEME_CATPUCCIN_MAP
]
}
+ options={{
+ showConsole: true,
+ showConsoleButton: true,
+ editorHeight: '512px',
+ showRefreshButton: true,
+ }}
/>
>
diff --git a/content/mdx-data.ts b/content/mdx-data.ts
index 6356b55..ebb4e97 100644
--- a/content/mdx-data.ts
+++ b/content/mdx-data.ts
@@ -24,6 +24,14 @@ import {
useSyncExternalStore,
useTransition,
} from './sandpack/react18-new-hooks';
+import {
+ app1,
+ app2,
+ app3,
+ signal1,
+ signal2,
+ signal3,
+} from './sandpack/automatic-dependency-collect';
const data = {
sandpack: {
@@ -58,6 +66,14 @@ const data = {
MultiStore,
Reducer,
},
+ 'automatic-dependency-collect': {
+ signal1,
+ app1,
+ signal2,
+ app2,
+ signal3,
+ app3,
+ },
},
};
diff --git a/content/posts/automatic-dependency-collect.mdx b/content/posts/automatic-dependency-collect.mdx
index 9f6bda4..2811a66 100644
--- a/content/posts/automatic-dependency-collect.mdx
+++ b/content/posts/automatic-dependency-collect.mdx
@@ -96,24 +96,17 @@ const useState: UseState = (value) => {
使用它的方式和官方 hooks 没有区别:
-```tsx
-function App() {
- console.log('App updated');
- const [count, setCount] = useState(0);
-
- return (
- <>
-
- >
- );
-}
-```
+
### useEffect
@@ -248,6 +241,18 @@ const useEffect = (callback: () => void) => {
};
```
+
+
### useMemo
通过 `useEffect` 实现的自动依赖追踪,我们就可以轻松的实现一个自动追踪依赖的 `useMemo`:
@@ -259,3 +264,15 @@ const useMemo =
(callback: () => T) => {
return s;
};
```
+
+
diff --git a/content/sandpack/automatic-dependency-collect.ts b/content/sandpack/automatic-dependency-collect.ts
new file mode 100644
index 0000000..fd60ad9
--- /dev/null
+++ b/content/sandpack/automatic-dependency-collect.ts
@@ -0,0 +1,299 @@
+export const signal1 = `import { useRef, useState as useUpdate } from "react";
+
+export type Getter = () => T;
+export type Setter = (newValue: T | ((oldValue: T) => T)) => void;
+export type UseState = (value: T) => [Getter, Setter];
+
+export const useState: UseState = (value) => {
+ const state = useRef(value);
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const [_, update] = useUpdate({});
+
+ const getter: Getter = () => {
+ return state.current;
+ };
+
+ const setter: Setter = (updater) => {
+ const newState =
+ updater instanceof Function ? updater(state.current) : updater;
+ if (state.current === newState) return;
+ state.current = newState;
+ update({});
+ };
+
+ return [getter, setter];
+};
+`;
+
+export const app1 = `import { useState } from "./signal";
+import Button from './Button';
+
+function App() {
+ console.log('App updated');
+ const [count, setCount] = useState(0);
+
+ return (
+ <>
+
+ >
+ );
+}
+
+export default App;
+`;
+
+export const signal2 = `/* eslint-disable react-hooks/exhaustive-deps */
+import { useRef, useState as useUpdate, useEffect as useMounted } from 'react';
+
+export type Getter = () => T;
+export type Setter = (newValue: T | ((oldValue: T) => T)) => void;
+export type UseState = (value: T) => [Getter, Setter];
+export type Subs = Set;
+
+export type Effect = {
+ // 用于执行 useEffect 回调函数
+ execute: () => void;
+ // 保存该 useEffect 依赖的 state 对应的 subs 集合
+ deps: Set;
+};
+const effectStack: Effect[] = [];
+
+const subscribe = (effect: Effect, subs: Subs) => {
+ // 建立订阅关系
+ subs.add(effect);
+ // 建立依赖关系
+ effect.deps.add(subs);
+};
+const useState: UseState = (value) => {
+ const state = useRef(value);
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const [_, update] = useUpdate({});
+
+ const subs = useRef(new Set());
+
+ const getter: Getter = () => {
+ // 获取刚刚送入栈中的 effect
+ const effect = effectStack[effectStack.length - 1];
+ if (effect) {
+ // 建立订阅发布关系
+ subscribe(effect, subs.current);
+ }
+ return state.current;
+ };
+
+ const setter: Setter = (updater) => {
+ const newState =
+ updater instanceof Function ? updater(state.current) : updater;
+ if (state.current === newState) return;
+
+ state.current = newState;
+ update({});
+ // 通知所有订阅 effect 状态变化
+ for (const sub of [...subs.current]) {
+ sub.execute();
+ }
+ };
+
+ return [getter, setter];
+};
+
+const cleanup = (effect: Effect) => {
+ for (const subs of effect.deps) {
+ subs.delete(effect);
+ }
+ effect.deps.clear();
+};
+
+const useEffect = (callback: () => void) => {
+ const execute = () => {
+ // 重制依赖
+ cleanup(effect);
+ // 添加到副作用列表
+ effectStack.push(effect);
+ try {
+ callback();
+ } finally {
+ effectStack.pop();
+ }
+ };
+ const effect: Effect = {
+ execute,
+ deps: new Set(),
+ };
+
+ useMounted(() => {
+ // 调用时执行
+ execute();
+ }, []);
+};
+
+export { useState, useEffect };
+`;
+
+export const app2 = `import { useState, useEffect } from "./signal";
+import Button from './Button';
+
+function App() {
+ console.log('App updated');
+ const [count, setCount] = useState(0);
+
+ console.log('App updated');
+ useEffect(() => {
+ console.log('count is ', count());
+ });
+ useEffect(() => {
+ console.log('effect update');
+ });
+
+ return (
+ <>
+
+ >
+ );
+}
+
+export default App;
+`;
+
+export const signal3 = `/* eslint-disable react-hooks/exhaustive-deps */
+import { useRef, useState as useUpdate, useEffect as useMounted } from 'react';
+
+export type Getter = () => T;
+export type Setter = (newValue: T | ((oldValue: T) => T)) => void;
+export type UseState = (value: T) => [Getter, Setter];
+export type Subs = Set;
+
+export type Effect = {
+ // 用于执行 useEffect 回调函数
+ execute: () => void;
+ // 保存该 useEffect 依赖的 state 对应的 subs 集合
+ deps: Set;
+};
+const effectStack: Effect[] = [];
+
+const subscribe = (effect: Effect, subs: Subs) => {
+ // 建立订阅关系
+ subs.add(effect);
+ // 建立依赖关系
+ effect.deps.add(subs);
+};
+const useState: UseState = (value) => {
+ const state = useRef(value);
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const [_, update] = useUpdate({});
+
+ const subs = useRef(new Set());
+
+ const getter: Getter = () => {
+ // 获取刚刚送入栈中的 effect
+ const effect = effectStack[effectStack.length - 1];
+ if (effect) {
+ // 建立订阅发布关系
+ subscribe(effect, subs.current);
+ }
+ return state.current;
+ };
+
+ const setter: Setter = (updater) => {
+ const newState =
+ updater instanceof Function ? updater(state.current) : updater;
+ if (state.current === newState) return;
+
+ state.current = newState;
+ update({});
+ // 通知所有订阅 effect 状态变化
+ for (const sub of [...subs.current]) {
+ sub.execute();
+ }
+ };
+
+ return [getter, setter];
+};
+
+const cleanup = (effect: Effect) => {
+ for (const subs of effect.deps) {
+ subs.delete(effect);
+ }
+ effect.deps.clear();
+};
+
+const useEffect = (callback: () => void) => {
+ const execute = () => {
+ // 重制依赖
+ cleanup(effect);
+ // 添加到副作用列表
+ effectStack.push(effect);
+ try {
+ callback();
+ } finally {
+ effectStack.pop();
+ }
+ };
+ const effect: Effect = {
+ execute,
+ deps: new Set(),
+ };
+
+ useMounted(() => {
+ // 调用时执行
+ execute();
+ }, []);
+};
+
+const useMemo = (callback: () => T) => {
+ const [s, set] = useState(callback());
+ useEffect(() => set(callback()));
+ return s;
+};
+
+export { useState, useEffect, useMemo };
+`;
+
+export const app3 = `import { useState, useEffect, useMemo } from "./signal";
+import Button from './Button';
+
+function App() {
+ console.log('App updated');
+ const [count, setCount] = useState(0);
+
+ console.log('App updated');
+ useEffect(() => {
+ console.log('count is ', count());
+ });
+ useEffect(() => {
+ console.log('effect update');
+ });
+
+ const double = useMemo(() => count() * 2);
+
+ return (
+ <>
+
+
+
+ >
+ );
+}
+
+export default App;
+`;