我規劃如何替換我現在服務的小型新創公司,它其中的一個產品如何繼續順利調整結構,讓開發者能夠有更簡潔容易的開發體驗,因為現在仍使用 overkill 的 redux observable
做狀態管理,產生很多樣版程式碼,但很少需要中途取消 request (redux-observable
的長處)的需求。這個產品是做雲端檔案管理,你可以想像成是 Google 雲端硬碟,又客製化與擴充成特定領域的作業需求。我希望新的狀態管理函式庫是 zustand
,所以文章內容用它舉例。
我想到的方法是設計模式(Design pattern)裡的 Adapter 模式,因為產品需要持續正常運作,且依照頁面、功能與元件逐步上線,所以想到 proxy-state 的升級和重寫方式。
架構的使用情境:
Component → Hook → StoreManager → Adapter → Redux/Zustand
這是一篇 Adapter 模式在 SOLID 原則中的實作與架構解析,以下用 AI 工具產生內容,已經過我潤飾。
Adapter 模式屬於 結構型設計模式,主要用於:
// 經典的 Adapter 模式範例
interface Target {
request(): string;
}
class Adaptee {
specificRequest(): string {
return "Special behavior";
}
}
class Adapter implements Target {
private adaptee: Adaptee;
constructor(adaptee: Adaptee) {
this.adaptee = adaptee;
}
request(): string {
return `Adapter: ${this.adaptee.specificRequest()}`;
}
}
// ✅ 每個 Adapter 只負責一個轉換職責
class ReduxObservableAdapter implements StoreAdapter<UserState> {
// 只負責:Redux Observable ↔ 統一介面的轉換
constructor(private store: Store<UserState>) {}
getState(): UserState { /* 只做狀態獲取轉換 */ }
setState(partial: Partial<UserState>): void { /* 只做狀態轉換 */ }
subscribe(listener: Function): Function { /* 只做訂閱轉換 */ }
}
class ZustandAdapter implements StoreAdapter<UserState> {
// 只負責:Zustand ↔ 統一介面的轉換
constructor(private store: any) {}
getState(): UserState { /* 只做狀態獲取轉換 */ }
setState(partial: Partial<UserState>): void { /* 只做狀態轉換 */ }
subscribe(listener: Function): Function { /* 只做訂閱轉換 */ }
}
// ✅ 對擴展開放,對修改封閉
interface StoreAdapter<T> {
getState(): T;
setState(partial: Partial<T>): void;
subscribe(listener: (state: T) => void): () => void;
}
// 可以新增新的 Adapter 而不修改現有程式碼
class JotaiAdapter<T> implements StoreAdapter<T> {
// 新增 Jotai 支援,不影響現有的 Redux/Zustand Adapter
}
class ValtioAdapter<T> implements StoreAdapter<T> {
// 新增 Valtio 支援,不影響現有程式碼
}
// 使用方的程式碼完全不需要修改
function useStore<T>(storeKey: string): [T, (partial: Partial<T>) => void] {
const store = storeManager.getStore<T>(storeKey); // 不變
// ...使用邏輯完全相同
}
// ✅ 所有 Adapter 都可以互相替換而不影響功能
function testStoreAdapter(adapter: StoreAdapter<UserState>) {
// 無論是 ReduxAdapter 還是 ZustandAdapter,行為都一致
const initialState = adapter.getState();
adapter.setState({ name: 'John' });
expect(adapter.getState().name).toBe('John');
let callbackInvoked = false;
const unsubscribe = adapter.subscribe(() => {
callbackInvoked = true;
});
adapter.setState({ age: 25 });
expect(callbackInvoked).toBe(true);
unsubscribe();
}
// 所有實作都能通過相同的測試
testStoreAdapter(new ReduxObservableAdapter(reduxStore));
testStoreAdapter(new ZustandAdapter(zustandStore));
testStoreAdapter(new JotaiAdapter(jotaiStore));
分成讀取、寫入和訂閱狀態的介面,元件和 hook 可以依照自己的使用情境,取得自己需要用的函式,getState()
, setState()
和 subscribe()
。
// ✅ 客戶端只依賴它需要的介面
interface StateReader<T> {
getState(): T;
}
interface StateWriter<T> {
setState(partial: Partial<T>): void;
}
interface StateSubscriber<T> {
subscribe(listener: (state: T) => void): () => void;
}
// 組合成完整介面
interface StoreAdapter<T> extends StateReader<T>, StateWriter<T>, StateSubscriber<T> {}
// 客戶端可以只使用需要的部分
function ReadOnlyComponent<T>(reader: StateReader<T>) {
// 只能讀取狀態,符合元件需求
const state = reader.getState();
return <div>{JSON.stringify(state)}</div>;
}
function SubscriberComponent<T>(subscriber: StateSubscriber<T>) {
// 只需要訂閱功能
useEffect(() => {
return subscriber.subscribe((state) => {
console.log('State changed:', state);
});
}, [subscriber]);
return <div>Listening...</div>;
}
// ✅ 高層模組不依賴低層模組,都依賴抽象
class StoreManager {
private adapters = new Map<string, StoreAdapter<any>>(); // 依賴抽象介面
// 不依賴具體的 Redux 或 Zustand,只依賴 StoreAdapter 介面
registerStore<T>(key: string, adapter: StoreAdapter<T>): void {
this.adapters.set(key, adapter);
}
getStore<T>(key: string): StoreAdapter<T> | undefined {
return this.adapters.get(key);
}
}
// Hook 層也只依賴抽象
function useStore<T>(storeKey: string): [T, (partial: Partial<T>) => void] {
const store = storeManager.getStore<T>(storeKey); // 依賴抽象介面
if (!store) {
throw new Error(`Store ${storeKey} not found`);
}
// 不關心底層是 Redux 還是 Zustand
const [state, setState] = useState<T>(store.getState());
useEffect(() => {
return store.subscribe((newState) => {
setState(newState);
});
}, [store]);
const updateState = useCallback((partial: Partial<T>) => {
store.setState(partial);
}, [store]);
return [state, updateState];
}
Component → Hook → StoreManager → Adapter → Redux/Zustand
詳細說明每一層:
function UserProfile() {
// 只關心業務邏輯,不知道底層實作
const [userState, updateUser] = useUserStore();
const handleLogin = () => {
updateUser({ isLoading: true });
// ...業務邏輯
};
return (
<div>
{userState.isLoading ? 'Loading...' : userState.user?.name}
</div>
);
}
function useUserStore() {
// 提供統一的 API,隱藏底層複雜性
return useStore<UserState>('user');
}
function useStore<T>(storeKey: string): [T, (partial: Partial<T>) => void] {
// 通用的狀態管理邏輯
const store = storeManager.getStore<T>(storeKey);
const [state, setState] = useState<T>(store.getState());
useEffect(() => {
return store.subscribe(setState);
}, [store]);
const updateState = useCallback((partial: Partial<T>) => {
store.setState(partial);
}, [store]);
return [state, updateState];
}
class StoreManager {
private adapters = new Map<string, StoreAdapter<any>>();
// 管理不同的 store instance
registerStore<T>(key: string, adapter: StoreAdapter<T>): void {
this.adapters.set(key, adapter);
}
// 提供統一的獲取方式
getStore<T>(key: string): StoreAdapter<T> {
return this.adapters.get(key);
}
}
// 將不同的狀態管理庫轉換成統一介面
class ReduxObservableAdapter implements StoreAdapter<UserState> {
constructor(private reduxStore: Store<UserState>) {}
getState(): UserState {
return this.reduxStore.getState();
}
setState(partial: Partial<UserState>): void {
this.reduxStore.dispatch({
type: 'UPDATE_USER_STATE',
payload: partial
});
}
subscribe(listener: (state: UserState) => void): () => void {
return this.reduxStore.subscribe(() => {
listener(this.reduxStore.getState());
});
}
}
class ZustandAdapter implements StoreAdapter<UserState> {
constructor(private zustandStore: any) {}
getState(): UserState {
return this.zustandStore.getState();
}
setState(partial: Partial<UserState>): void {
this.zustandStore.setState(partial);
}
subscribe(listener: (state: UserState) => void): () => void {
return this.zustandStore.subscribe(listener);
}
}
// Redux Observable 實作
const reduxStore = createStore(userReducer);
const reduxAdapter = new ReduxObservableAdapter(reduxStore);
// Zustand 實作
const zustandStore = create((set) => ({
user: null,
isLoading: false,
setState: (partial) => set(partial)
}));
const zustandAdapter = new ZustandAdapter(zustandStore);
// 註冊到管理器
storeManager.registerStore('user', reduxAdapter); // 初期使用 Redux
// 後來遷移
storeManager.replaceStore('user', zustandAdapter); // 替換為 Zustand
使用者操作 → Component → Hook → StoreManager → Adapter → Redux/Zustand
↑ ↓
UI 更新 ← Hook ← StoreManager ← Adapter ← 狀態變化通知
這個架構實作了:
這是經典的分層架構 + Adapter 模式的實際應用。