1 | import * as Redux from 'redux'
|
2 | import {
|
3 | Action,
|
4 | ConfigRedux,
|
5 | ModelReducers,
|
6 | NamedModel,
|
7 | RematchBag,
|
8 | DevtoolOptions,
|
9 | Models,
|
10 | RematchRootState,
|
11 | } from './types'
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 | export default function createReduxStore<
|
19 | TModels extends Models<TModels>,
|
20 | TExtraModels extends Models<TModels>,
|
21 | RootState = RematchRootState<TModels, TExtraModels>
|
22 | >(bag: RematchBag<TModels, TExtraModels>): Redux.Store<RootState> {
|
23 | bag.models.forEach((model) => createModelReducer(bag, model))
|
24 |
|
25 | const rootReducer = createRootReducer<RootState, TModels, TExtraModels>(bag)
|
26 |
|
27 | const middlewares = Redux.applyMiddleware(...bag.reduxConfig.middlewares)
|
28 | const enhancers = bag.reduxConfig.devtoolComposer
|
29 | ? bag.reduxConfig.devtoolComposer(...bag.reduxConfig.enhancers, middlewares)
|
30 | : composeEnhancersWithDevtools(bag.reduxConfig.devtoolOptions)(
|
31 | ...bag.reduxConfig.enhancers,
|
32 | middlewares
|
33 | )
|
34 |
|
35 | const createStore = bag.reduxConfig.createStore || Redux.createStore
|
36 | const bagInitialState = bag.reduxConfig.initialState
|
37 | const initialState = bagInitialState === undefined ? {} : bagInitialState
|
38 |
|
39 | return createStore<RootState, Action, any, typeof initialState>(
|
40 | rootReducer,
|
41 | initialState,
|
42 | enhancers
|
43 | )
|
44 | }
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 | export function createModelReducer<
|
59 | TModels extends Models<TModels>,
|
60 | TExtraModels extends Models<TModels>,
|
61 | TState extends NamedModel<TModels>['state'] = any
|
62 | >(bag: RematchBag<TModels, TExtraModels>, model: NamedModel<TModels>): void {
|
63 | const modelReducers: ModelReducers<TState> = {}
|
64 |
|
65 |
|
66 | const modelReducerKeys = Object.keys(model.reducers)
|
67 | modelReducerKeys.forEach((reducerKey) => {
|
68 | const actionName = isAlreadyActionName(reducerKey)
|
69 | ? reducerKey
|
70 | : `${model.name}/${reducerKey}`
|
71 |
|
72 | modelReducers[actionName] = model.reducers[reducerKey]
|
73 | })
|
74 |
|
75 |
|
76 | const combinedReducer = (
|
77 | state: TState = model.state,
|
78 | action: Action
|
79 | ): TState => {
|
80 | if (action.type in modelReducers) {
|
81 | return modelReducers[action.type](
|
82 | state,
|
83 | action.payload,
|
84 | action.meta
|
85 |
|
86 |
|
87 | ) as TState
|
88 | }
|
89 |
|
90 | return state
|
91 | }
|
92 |
|
93 | const modelBaseReducer = model.baseReducer
|
94 |
|
95 |
|
96 | let reducer = !modelBaseReducer
|
97 | ? combinedReducer
|
98 | : (state: TState = model.state, action: Action): TState =>
|
99 | combinedReducer(modelBaseReducer(state, action), action)
|
100 |
|
101 | bag.forEachPlugin('onReducer', (onReducer) => {
|
102 | reducer = onReducer(reducer, model.name, bag) || reducer
|
103 | })
|
104 |
|
105 | bag.reduxConfig.reducers[model.name] = reducer
|
106 | }
|
107 |
|
108 |
|
109 |
|
110 |
|
111 |
|
112 |
|
113 |
|
114 | export function createRootReducer<
|
115 | TRootState,
|
116 | TModels extends Models<TModels>,
|
117 | TExtraModels extends Models<TModels>
|
118 | >(bag: RematchBag<TModels, TExtraModels>): Redux.Reducer<TRootState, Action> {
|
119 | const { rootReducers } = bag.reduxConfig
|
120 | const mergedReducers = mergeReducers<TRootState>(bag.reduxConfig)
|
121 | let rootReducer = mergedReducers
|
122 |
|
123 | if (rootReducers && Object.keys(rootReducers).length) {
|
124 | rootReducer = (
|
125 | state: TRootState | undefined,
|
126 | action: Action
|
127 | ): TRootState => {
|
128 | const actionRootReducer = rootReducers[action.type]
|
129 |
|
130 | if (actionRootReducer) {
|
131 | return mergedReducers(actionRootReducer(state, action), action)
|
132 | }
|
133 |
|
134 | return mergedReducers(state, action)
|
135 | }
|
136 | }
|
137 |
|
138 | bag.forEachPlugin('onRootReducer', (onRootReducer) => {
|
139 | rootReducer = onRootReducer(rootReducer, bag) || rootReducer
|
140 | })
|
141 |
|
142 | return rootReducer
|
143 | }
|
144 |
|
145 |
|
146 |
|
147 |
|
148 |
|
149 |
|
150 |
|
151 | function mergeReducers<TRootState>(
|
152 | reduxConfig: ConfigRedux<TRootState>
|
153 | ): Redux.Reducer<TRootState, Action> {
|
154 | const combineReducers = reduxConfig.combineReducers || Redux.combineReducers
|
155 |
|
156 | if (!Object.keys(reduxConfig.reducers).length) {
|
157 | return (state: any): TRootState => state
|
158 | }
|
159 |
|
160 | return combineReducers(reduxConfig.reducers as Redux.ReducersMapObject)
|
161 | }
|
162 |
|
163 |
|
164 |
|
165 |
|
166 |
|
167 | function composeEnhancersWithDevtools(
|
168 | devtoolOptions: DevtoolOptions = {}
|
169 | ): (...args: any[]) => Redux.StoreEnhancer {
|
170 | return !devtoolOptions.disabled &&
|
171 | typeof window === 'object' &&
|
172 | window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
|
173 | ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__(devtoolOptions)
|
174 | : Redux.compose
|
175 | }
|
176 |
|
177 |
|
178 |
|
179 |
|
180 |
|
181 | function isAlreadyActionName(reducerKey: string): boolean {
|
182 | return reducerKey.indexOf('/') > -1
|
183 | }
|