diff --git a/docs/_sidebar.md b/docs/_sidebar.md index be817f7ad..895304d45 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -12,11 +12,12 @@ * [@reatom/debug](/packages/debug.md) - Guides - * [Naming Conventions](/guides/naming-conventions.md) - * [File Structure](/guides/file-structure.md) + * [Migration from Redux](/guides/migration-from-redux.md) * [Code Splitting](/guides/code-splitting.md) * [Server Side Rendering](/guides/server-side-rendering.md) - * [Migration from Redux](/guides/migration-from-redux.md) + * [Dependency injection](/guides/di.md) + * [Naming Conventions](/guides/naming-conventions.md) + * [File Structure](/guides/file-structure.md) * [Examples](/examples.md) diff --git a/docs/guides/di.md b/docs/guides/di.md new file mode 100644 index 000000000..ac5e91384 --- /dev/null +++ b/docs/guides/di.md @@ -0,0 +1,38 @@ +# Dependency injection + +## Explanation + +Dependency injection is a concept for reuse some dependencies based on the running context. Good for Reatom users - all basic entities (actions, atoms) are already working in a controlled context - a store. By the same token, any atom dependencies (as another atom) are automatically adding to a store when computed atom connecting to it. That means you already have dependencies resolving mechanism with Reatom. + +What is it mean in the conclusion? You can create not only reactive atoms (depended on actions) but just _static_ atoms and use it as a service. + +## Example + +> IMPORTANT NOTE: in order to prevent errors in serialization **use a symbol** as an atom key + +```ts +/* DECLARE API SERVICE */ + +import { declareAtom, declareAction, createStore } from '@reatom/core' +import axios from 'axios' + +export const API = Symbol('api') +export const apiAtom = declareAtom(API, axios, () => []) + +/* SOME FEATURE CODE */ + +export const recieveData = declareAction() +export const requestData = declareAction(async (payload, store) => { + const data = await store.getState(apiAtom).get(url, payload) + store.dispach(recieveData(data)) +}) + +/* TESTS */ + +// here we rewrite initial state (axios) of apiAtom by preloaded state +const store = createStore({ + [API]: mockedAxios +}) +// ... +store.dispatch(requestData()) +``` \ No newline at end of file diff --git a/packages/core/package.json b/packages/core/package.json index 56b21a70c..8a6e4239b 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,7 +1,7 @@ { "name": "@reatom/core", "private": false, - "version": "1.0.0", + "version": "1.0.1", "description": "State manager with a focus of all needs", "source": "src/index.ts", "main": "build/index.js", diff --git a/packages/core/src/declareAtom.ts b/packages/core/src/declareAtom.ts index 95a188670..1623c5324 100644 --- a/packages/core/src/declareAtom.ts +++ b/packages/core/src/declareAtom.ts @@ -17,6 +17,7 @@ const DEPS = Symbol('@@Reatom/DEPS') const _initAction = declareAction(['@@Reatom/init']) export const initAction = _initAction() +type AtomName = string | [TreeId] | symbol type AtomsMap = { [key: string]: Atom } type Reducer = (state: TState, value: TValue) => TState type DependencyMatcher = ( @@ -36,12 +37,12 @@ export function declareAtom( dependencyMatcher: DependencyMatcher, ): Atom export function declareAtom( - name: string | [TreeId], + name: AtomName, initialState: TState, dependencyMatcher: DependencyMatcher, ): Atom export function declareAtom( - name: string | [TreeId] | TState, + name: AtomName | TState, initialState: TState | DependencyMatcher, dependencyMatcher?: DependencyMatcher, ): Atom { @@ -51,7 +52,7 @@ export function declareAtom( name = 'atom' } - const _id = nameToId(name as string | [TreeId]) + const _id = nameToId(name as AtomName) if (initialState === undefined) throwError(`Atom "${_id}". Initial state can't be undefined`) diff --git a/packages/core/src/shared.ts b/packages/core/src/shared.ts index 626ae8dee..bee19faba 100644 --- a/packages/core/src/shared.ts +++ b/packages/core/src/shared.ts @@ -3,7 +3,7 @@ import { Atom } from './declareAtom' import { PayloadActionCreator } from './declareAction' export { TreeId } -export type GenId = (name: string | [string]) => TreeId +export type GenId = (name: string | [string] | symbol) => TreeId export const TREE = Symbol('@@Reatom/TREE') export type Unit = { [TREE]: Tree } @@ -14,8 +14,10 @@ export type Unit = { [TREE]: Tree } * type MyAtomType = InferType * type MyActionType = InferType */ -export type InferType = T extends (Atom | PayloadActionCreator) - ? R +export type InferType = T extends ( + | Atom + | PayloadActionCreator) + ? R : never export function noop() {} @@ -37,13 +39,16 @@ export function getIsAction(thing: any): thing is Atom { } let id = 0 -export function nameToIdDefault(name: string | [string]): TreeId { - return Array.isArray(name) +export function nameToIdDefault(name: string | [string] | symbol): TreeId { + return typeof name === 'symbol' + ? // TODO: https://github.com/microsoft/TypeScript/issues/1863 + ((name as unknown) as string) + : Array.isArray(name) ? safetyStr(name[0], 'name') : `${safetyStr(name, 'name')} [${++id}]` } let _nameToId: GenId -export function nameToId(name: string | [string]): TreeId { +export function nameToId(name: string | [string] | symbol): TreeId { return _nameToId ? _nameToId(name) : nameToIdDefault(name) } diff --git a/packages/core/tests/index.test.ts b/packages/core/tests/index.test.ts index 6dbceff8a..849e97682 100644 --- a/packages/core/tests/index.test.ts +++ b/packages/core/tests/index.test.ts @@ -472,6 +472,21 @@ describe('@reatom/core', () => { }) }) + test('DI example', () => { + class Api {} + const api = new Api() + const apiAtom = declareAtom(Symbol('API'), api, () => []) + + var store = createStore(apiAtom) + expect(store.getState()).toEqual({ + [getTree(apiAtom).id]: api, + }) + + var store = createStore({ [getTree(apiAtom).id]: api }) + expect(store.getState(apiAtom)).toBe(api) + expect(JSON.stringify(store.getState())).toBe('{}') + }) + test('declareAction reactions', async () => { const delay = () => new Promise(on => setTimeout(on, 10)) const setValue = declareAction()