- Define Store
If using npm
:
npm install fysics
If using yarn
:
yarn install fysics
If using pnpm
:
pnpm install fysics
Fysics is a powerful state management tool. It seeks to work as a more minimalist version of Redux with undo/redo history and side effects following the redux-saga pattern, powered by RxJS and Immer. Decide upon your types for State, Payload, and SagaResponse, write the logic for initial state and actions, initialize the store, and go!
This is the type describing the contents of the store.
Example:
type StateType = {
count: number;
};
While it is possible to add additional properties to the State
after the store is initialized, it's recommended that the type used here contains all the properties you ever expect to have in your store. This better follows the intended state management design pattern, and will give the full benefits of using Typescript.
This is the type describing the parameter that is passed to the saga and/or reducer.
Example:
type PayloadType = {
amount: number | undefined;
};
Each action may have its own payload shape and corresponding type - the Payload
type should be a union of all of these, and within the code for the reducer and/or saga, the payload should be typecast to the type of the expected payload.
This is the type describing the return value from the saga, which is optionally passed to the reducer.
Example:
type SagaResponseType = number;
As with the payload, each action that has a saga may have its own sagaResponse type. The SagaResponse
type should be a union of all of these, and within the code for the reducer, the sagaResponse should be typecast to the type of the expected sagaResponse.
The Logic
is where the actions and initial state of your store are defined. The type is below:
type Logic<State, Payload, SagaResponse> = {
initialState: State;
actions: {
[ACTION_NAME: string]: Action<State, Payload, SagaResponse>;
};
};
The initialState
property should contain initial values for every property you plan on having in the store. Even if the value is undefined, the property should still be listed explicitly.
Each property is a name of an action, with the corresponding value describing that action using the Action
type:
type Action<State, Payload, SagaResponse> = {
skipUndo?: boolean;
scope?: string | Array<string>;
reducer: Reducer<State, Payload, SagaResponse>;
saga?: Saga<State, Payload, SagaResponse>;
};
The only required property for an Action
is the Reducer
, which is a function modifying a draft version of the current state following some logic, potentially using the payload and sagaResponse passed in.
type Reducer<State, Payload, SagaResponse> = (
state: Draft<State>,
payload: Payload,
sagaResponse?: SagaResponse,
) => void;
If the action has some side effect, the side effect is handled using a Saga
:
type Saga<State, Payload, SagaResponse> = (
state: State,
payload: Payload,
) => Promise<SagaResponse>;
The saga runs before the reducer to perform some (usually asynchronous) action outside the scope of the store, for example writing to local storage or querying a database. The return value of the saga is then passed to the reducer of this action in the case it has an impact on the state.
If the action should not be considered as an individual action to be undone or redone, set skipUndo
to true. For example, if this action happens in the background without the user's knowledge after the most recent action the user has performed, initiating an undo should undo both the background action and the last action the user actually initiated.
The scope
property is used for abstracting parts of the logic away from individual actions - it is not used by the store itself, but may be useful inside reducers or sagas to avoid code duplication.
The store implements the Svelte store contract so the $
syntactic sugar is available.
The Store
returned by the createStore
function is contains methods to interface with two internal components: the store's present state (of type State
), and the store's full state, which is defined as
FullState<State, Payload>;
where
type FullState<State, Payload> = {
past: Array<TimelineEvent<PayloadType>>;
present: State;
future: Array<TimelineEvent<PayloadType>>;
};
type TimelineEvent<Payload> = {
action: DispatchAction<Payload>;
patches: Array<Patch>;
inversePatches: Array<Patch>;
};
type DispatchAction<Payload> = {
name: string;
payload?: Payload;
};
are the relevant type definitions. See here to understand the Patch
type imported from immer.
subject: () => Subject<State>;
Gets the subject for the store's present state.
subject: () => Subject<FullState<State, Payload>>;
Gets the subject for the store's full state.
subscribe: (observer?: Partial<Observer<State>> | ((value: State) => void)) =>
Subscription;
Creates a subscription to the store's present state with the specified observer or function to run when the store's present state subject pushes a new value (i.e. when the store's present state changes).
subscribe: (observer?: Partial<Observer<State>> | ((value: State) => void)) =>
Subscription;
Creates a subscription to the store's full state with the specified observer or function to run when the store's full state subject pushes a new value (i.e. when the store's full state changes).
get: () => State;
Returns a deep copy of the store's present state.
getAll: () => FullState<State, Payload>;
Returns a deep copy of the store's full state.
dispatch: (action: DispatchAction<Payload>) => void
Dispatches an Action
(see the Usage section) by name, with the provided payload.
undo: () => void
Undoes actions in the store's past until an action with skipUndo: false
is undone, or does nothing if all the actions in the store's past have skipUndo: true
.
redo: () => void
Redoes actions in the store's future until the second most recent action with skipUndo: false
is on top of the stack, or redoes everything if there is no such action.
rebase: () => void
Clears the past and future of the store's full state.
Please make sure to read the Contributing Guide before making a pull request.
Thank you to all the people who already contributed to this project!
Anubis π» |
jkeegan2 π» |
MIT