diff --git a/components/rua/rua-sandpack.tsx b/components/rua/rua-sandpack.tsx index bdacf08..34bf037 100644 --- a/components/rua/rua-sandpack.tsx +++ b/components/rua/rua-sandpack.tsx @@ -14,15 +14,22 @@ const RUASandpack = ({ ...rest }: Props) => { if (!mounted) { return ( -
- +
+
); } return ( <> -
+
{ (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; +`;