Skip to content

Commit

Permalink
refactor(store): actionObserver and stateObserver use
Browse files Browse the repository at this point in the history
store enhancers
  • Loading branch information
exuanbo committed Dec 5, 2023
1 parent b2f68e1 commit 6351692
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 29 deletions.
12 changes: 9 additions & 3 deletions src/app/actionObserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@ import {
type Middleware,
type PayloadAction,
type PayloadActionCreator,
type StoreEnhancer,
} from '@reduxjs/toolkit'
import { filter, map, type Observable, Subject } from 'rxjs'

import { extendStore } from './storeEnhancer'

type OnAction = <TPayload>(actionCreator: PayloadActionCreator<TPayload>) => Observable<TPayload>

interface ActionObserver {
middleware: Middleware
on: OnAction
enhancer: StoreEnhancer<{ onAction: OnAction }>
}

const matchType =
Expand All @@ -32,8 +35,11 @@ export const createActionObserver = (): ActionObserver => {
return result
}

const on: OnAction = (actionCreator) =>
const onAction: OnAction = (actionCreator) =>
action$.pipe(filter(matchType(actionCreator)), map(getPayload))

return { middleware, on }
return {
middleware,
enhancer: extendStore({ onAction }),
}
}
3 changes: 2 additions & 1 deletion src/app/selector.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
/* eslint-disable @typescript-eslint/unbound-method */

import type { Selector } from '@reduxjs/toolkit'
import { useDebugValue } from 'react'
import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector'

import { type RootState, store } from './store'

export type StateSelector<TSelected> = (state: RootState) => TSelected
type StateSelector<TSelected> = Selector<RootState, TSelected>

export const applySelector = <TSelected>(selector: StateSelector<TSelected>): TSelected => {
const state = store.getState()
Expand Down
24 changes: 13 additions & 11 deletions src/app/stateObserver.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
import type { Middleware } from '@reduxjs/toolkit'
import type { Middleware, Selector, StoreEnhancer } from '@reduxjs/toolkit'
import { distinctUntilChanged, identity, map, type Observable, ReplaySubject, skip } from 'rxjs'

import type { StateSelector } from './selector'
import type { RootState } from './store'
import { extendStore } from './storeEnhancer'

interface OnStateOptions {
initial?: boolean
}

type OnState = <TSelected>(
selector: StateSelector<TSelected>,
type OnState<TState> = <TSelected>(
selector: Selector<TState, TSelected>,
options?: OnStateOptions,
) => Observable<TSelected>

interface StateObserver {
interface StateObserver<TState> {
middleware: Middleware
on: OnState
enhancer: StoreEnhancer<{ onState: OnState<TState> }>
}

export const createStateObserver = (): StateObserver => {
const state$ = new ReplaySubject<RootState>(1)
export const createStateObserver = <TState>(): StateObserver<TState> => {
const state$ = new ReplaySubject<TState>(1)
const distinctState$ = state$.pipe(distinctUntilChanged())

const middleware: Middleware = (api) => {
Expand All @@ -40,8 +39,11 @@ export const createStateObserver = (): StateObserver => {
}
}

const on: OnState = (selector, { initial = false } = {}) =>
const onState: OnState<TState> = (selector, { initial = false } = {}) =>
distinctState$.pipe(map(selector), distinctUntilChanged(), initial ? identity : skip(1))

return { middleware, on }
return {
middleware,
enhancer: extendStore({ onState }),
}
}
26 changes: 12 additions & 14 deletions src/app/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,17 @@ const getPreloadedState = (): Partial<RootState> => {
}

const actionObserver = createActionObserver()
const stateObserver = createStateObserver()
const stateObserver = createStateObserver<RootState>()

export const store = Object.assign(
configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) => {
const defaultMiddleware = getDefaultMiddleware()
return defaultMiddleware.prepend(stateObserver.middleware, actionObserver.middleware)
},
preloadedState: getPreloadedState(),
}),
{
onAction: actionObserver.on,
onState: stateObserver.on,
export const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) => {
const defaultMiddleware = getDefaultMiddleware()
return defaultMiddleware.prepend(stateObserver.middleware, actionObserver.middleware)
},
)
preloadedState: getPreloadedState(),
enhancers: (getDefaultEnhancers) => {
const defaultEnhancers = getDefaultEnhancers()
return defaultEnhancers.concat(stateObserver.enhancer, actionObserver.enhancer)
},
})
12 changes: 12 additions & 0 deletions src/app/storeEnhancer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { StoreEnhancer } from '@reduxjs/toolkit'

export const extendStore =
<Ext extends {}>(extension: Ext): StoreEnhancer<Ext> =>
(createStore) =>
(...args) => {
const store = createStore(...args)
return {
...store,
...extension,
}
}

0 comments on commit 6351692

Please sign in to comment.