Skip to content

Commit e302953

Browse files
Vittlyyarastqt
authored andcommitted
feat(di): make di able to keep anything
BREAKING CHANGE: `HOC` and `IRegistryComponents` aren't exported from di, and generic-param for `Registry.set` has different meaning
1 parent 655f2d8 commit e302953

3 files changed

Lines changed: 157 additions & 123 deletions

File tree

packages/di/README.md

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ DI package helps to solve similar tasks with minimum effort:
66

77
- decouple _desktop_ and _mobile_ versions of a component
88
- implement an _experimental_ version of a component alongside the common one
9+
- store components and their auxiliaries (like settings and functions) in a single place
910

1011
## Install
1112

@@ -160,19 +161,19 @@ export const App = () => (
160161
)
161162
```
162163

163-
with `useComponentRegistry` (_require react version 16.8.0+_)
164+
with `useRegistry` (_require react version 16.8.0+_)
164165

165166
```tsx
166167
import React from 'react'
167168
import { cn } from '@bem-react/classname'
168-
import { useComponentRegistry } from '@bem-react/di'
169+
import { useRegistry } from '@bem-react/di'
169170

170171
// No Header or Footer imports
171172

172173
const cnApp = cn('App')
173174

174175
export const App = () => {
175-
const { Header, Footer } = useComponentRegistry(cnApp())
176+
const { Header, Footer } = useRegistry(cnApp())
176177

177178
return (
178179
<>
@@ -228,9 +229,9 @@ import { AppDesktop, registryId } from './App@desktop'
228229
const expRegistry = new Registry({ id: registryId })
229230

230231
// extends original Header
231-
expRegistry.extends('Header', BaseHeader => props => (
232+
expRegistry.extends('Header', (BaseHeader) => (props) => (
232233
<div>
233-
<BaseHeader height={200} color={red}/>
234+
<BaseHeader height={200} color={red} />
234235
</div>
235236
))
236237

@@ -239,3 +240,32 @@ export const AppDesktopExperimental = withRegistry(expRegistry)(AppDesktop)
239240
```
240241

241242
_DI_ merges nested registries composing and ordinary components for you. So you always can get a reference to previous component's implementation.
243+
244+
## Storing other
245+
246+
_DI_ registry may keep not only components but also their settings and any other auxiliaries (like functions).
247+
248+
```tsx
249+
import { useRegistry } from '@bem-react/di'
250+
251+
const cnHeader = cn('Header')
252+
253+
export const Header = (props) => {
254+
const { theme, showNotification, prepareProps } = useRegistry(cnApp())
255+
256+
// one function is used to fulfill props
257+
const { title, username } = prepareProps(props)
258+
259+
useEffect(() => {
260+
// another function is used inside hook
261+
showNotification()
262+
})
263+
264+
return (
265+
<header className={cnHeader({ theme })}>
266+
<h1>{title}</h1>
267+
<h2>Greetings ${username}</h2>
268+
</header>
269+
)
270+
}
271+
```

packages/di/di.tsx

Lines changed: 67 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -90,154 +90,164 @@ export const useRegistries = () => {
9090
return useContext(registryContext)
9191
}
9292

93-
export const useComponentRegistry = <T extends {}>(id: string) => {
93+
export const useRegistry = <T extends {}>(id: string) => {
9494
const registries = useRegistries()
9595

9696
return registries[id].snapshot<T>()
9797
}
9898

99+
/**
100+
* @deprecated consider using 'useRegistry' instead
101+
*/
102+
export const useComponentRegistry = useRegistry
103+
99104
export interface IRegistryOptions {
100105
id: string
101106
overridable?: boolean
102107
}
103108

104-
const registryHocMark = 'RegistryHoc'
105-
export type HOC<T> = (WrappedComponent: ComponentType) => ComponentType<T>
109+
const registryOverloadMark = 'RegistryOverloadHMark'
106110

107-
type IRegistryEntity<T = any> = ComponentType<T> | IRegistryHOC<T>
108-
export type IRegistryComponents = Record<string, IRegistryEntity>
111+
type SimpleOverload<T> = (Base: T) => T
109112

110-
interface IRegistryHOC<T> extends React.FC<T> {
111-
$symbol: typeof registryHocMark
112-
hoc: HOC<T>
113+
interface IRegistryEntityOverload<T> {
114+
$symbol: typeof registryOverloadMark
115+
overload: SimpleOverload<T>
113116
}
114117

115-
function withBase<T>(hoc: HOC<T>): IRegistryHOC<T> {
116-
const fakeComponent: IRegistryHOC<T> = () => {
117-
throw new Error(`Not found base component for enhance HOC: ${hoc.toString()}`)
118-
}
119-
120-
fakeComponent.$symbol = registryHocMark as typeof registryHocMark
121-
fakeComponent.hoc = hoc
118+
type IRegistryEntity<T = any> = T | IRegistryEntityOverload<T>
119+
export type IRegistryEntities = Record<string, IRegistryEntity>
122120

123-
return fakeComponent
121+
function withOverload<T>(overload: SimpleOverload<T>): IRegistryEntityOverload<T> {
122+
return {
123+
$symbol: registryOverloadMark,
124+
overload,
125+
}
124126
}
125127

126-
function isHoc<T>(component: IRegistryEntity<T>): component is IRegistryHOC<T> {
127-
return (component as IRegistryHOC<T>).$symbol === registryHocMark
128+
function isOverload<T>(entity: IRegistryEntity<T>): entity is IRegistryEntityOverload<T> {
129+
return (entity as IRegistryEntityOverload<T>).$symbol === registryOverloadMark
128130
}
129131

130132
export class Registry {
131133
id: string
132134
overridable: boolean
133-
private components: IRegistryComponents = {}
135+
private entities: IRegistryEntities = {}
134136

135137
constructor({ id, overridable = true }: IRegistryOptions) {
136138
this.id = id
137139
this.overridable = overridable
138140
}
139141

140142
/**
141-
* Set react component in registry by id.
143+
* Set registry entry by id.
142144
*
143-
* @param id component id
144-
* @param component valid react component
145+
* @param id entry id
146+
* @param entity valid registry entity
145147
*/
146-
set<T>(id: string, component: ComponentType<T>) {
147-
this.components[id] = component
148+
set<T>(id: string, entity: T) {
149+
this.entities[id] = entity
148150

149151
return this
150152
}
151153

152154
/**
153-
* Set hoc for extends component in registry by id
155+
* Set extender for registry entry by id.
154156
*
155-
* @param id component id
156-
* @param hoc hoc for extends component
157+
* @param id entry id
158+
* @param overload valid registry entity extender
157159
*/
158-
extends<T>(id: string, hoc: HOC<T>) {
159-
this.components[id] = withBase(hoc)
160+
extends<T>(id: string, overload: SimpleOverload<T>) {
161+
this.entities[id] = withOverload(overload)
160162

161163
return this
162164
}
163165

164166
/**
165-
* Set react components in registry via object literal.
167+
* Set react entities in registry via object literal.
166168
*
167-
* @param componentsSet set of valid react components
169+
* @param entitiesSet set of valid registry entities
168170
*/
169-
fill(componentsSet: IRegistryComponents) {
170-
for (const key in componentsSet) {
171-
this.components[key] = componentsSet[key]
171+
fill(entitiesSet: IRegistryEntities) {
172+
for (const key in entitiesSet) {
173+
this.entities[key] = entitiesSet[key]
172174
}
173175

174176
return this
175177
}
176178

177179
/**
178-
* Get react component from registry by id.
180+
* Get entry from registry by id.
179181
*
180-
* @param id component id
182+
* @param id entry id
181183
*/
182184
get<T>(id: string): IRegistryEntity<T> {
183185
if (__DEV__) {
184-
if (!this.components[id]) {
185-
throw new Error(`Component with id '${id}' not found.`)
186+
if (!this.entities[id]) {
187+
throw new Error(`Entry with id '${id}' not found.`)
186188
}
187189
}
188190

189-
return this.components[id]
191+
return this.entities[id]
190192
}
191193

192194
/**
193-
* Returns list of components from registry.
195+
* Returns raw entities from registry.
194196
*/
195197
snapshot<RT>(): RT {
196-
return this.components as any
198+
return this.entities as any
197199
}
198200

199201
/**
200-
* Override components by external registry.
202+
* Override entities by external registry.
201203
* @internal
202204
*
203205
* @param otherRegistry external registry
204206
*/
205207
merge(otherRegistry?: Registry) {
206208
const clone = new Registry({ id: this.id, overridable: this.overridable })
207-
clone.fill(this.components)
209+
clone.fill(this.entities)
208210

209211
if (!otherRegistry) return clone
210212

211-
const otherRegistryComponents = otherRegistry.snapshot<IRegistryComponents>()
213+
const otherRegistryEntities = otherRegistry.snapshot<IRegistryEntities>()
212214

213-
for (const componentName in otherRegistryComponents) {
214-
if (!otherRegistryComponents.hasOwnProperty(componentName)) continue
215+
for (const entityName in otherRegistryEntities) {
216+
if (!otherRegistryEntities.hasOwnProperty(entityName)) continue
215217

216-
clone.components[componentName] = this.mergeComponents(
217-
clone.components[componentName],
218-
otherRegistryComponents[componentName],
218+
clone.entities[entityName] = this.mergeEntities(
219+
this.id,
220+
clone.entities[entityName],
221+
otherRegistryEntities[entityName],
219222
)
220223
}
221224

222225
return clone
223226
}
224227

225228
/**
226-
* Returns extended or replacing for base impleme
229+
* Returns extended or replaced entity
227230
*
231+
* @param id entity entry id
228232
* @param base base implementation
229233
* @param overrides overridden implementation
230234
*/
231-
private mergeComponents(base: IRegistryEntity, overrides: IRegistryEntity): IRegistryEntity {
232-
if (isHoc(overrides)) {
233-
if (!base) return overrides
235+
private mergeEntities(
236+
id: string,
237+
base: IRegistryEntity,
238+
overrides: IRegistryEntity,
239+
): IRegistryEntity {
240+
if (isOverload(overrides)) {
241+
if (!base && __DEV__) {
242+
throw new Error(`Overload has no base in Registry '${id}'.`)
243+
}
234244

235-
if (isHoc(base)) {
236-
// If both components are hocs, then create compose-hoc
237-
return withBase((Base) => overrides.hoc(base.hoc(Base)))
245+
if (isOverload(base)) {
246+
// If both entities are hocs, then create compose-hoc
247+
return withOverload((Base) => overrides.overload(base.overload(Base)))
238248
}
239249

240-
return overrides.hoc(base)
250+
return overrides.overload(base)
241251
}
242252

243253
return overrides

0 commit comments

Comments
 (0)