

GitHub - planttheidea/redux-slices: Manage slices of redux store in a concise, c...
source link: https://github.com/planttheidea/redux-slices
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

redux-slices
Manage slices of redux store in a concise, clear, and well-typed way
Usage
import { createSlice } from 'redux-slices'; import type { ActionCreatorMap } from 'redux-slices'; type Item = { complete: boolean; value: string }; type State = { items: Item[] }; const INITIAL_STATE: State = { items: [] }; const { createAction, createReducer, createSelector } = createSlice( 'todos', INITIAL_STATE, ); export const add = createAction('added', (todo: string) => todo); export const complete = createAction('completed', (item: Item) => item); export const remove = createAction('removed', (todo: Item) => todo); type HandledActions = ActionCreatorMap< [typeof add, typeof complete, typeof remove] >; export const reducer = createReducer<HandledActions>({ [add.type]: (state, { payload: value }) => ({ ...state, items: [...state.items, { complete: false, value }], }), [complete.type]: (state, { payload }) => ({ ...state, items: state.items.map((item) => item === payload ? { ...item, complete: true } : item, ), }), [remove.type]: (state, { payload }) => ({ ...state, items: state.items.map((item) => item !== payload), }), }); export const getTodos = createSelector((slice) => slice.items);
createSlice
function createSlice(sliceName: string, initialState?: object): Slice;
Creates a slice of state based on the name and initial state passed, and returns a suite of utilities that allow construction of common redux implementation methods. If initialState
is not passed, the slice will default to an empty object for its state.
Initial state typing
Clearly defining the contract of your Redux state is considered a best practice, and redux-slices
leans into this by making the type of your state object inferred from the initialState
value passed. However, there are some scenarios where you want more control over the typing:
- You defined your initial state with
as const
, and you want the state typing to be wider - You have dynamic population of the state, and you start with an empty object
In these cases, you can pass generics to createSlice
to ensure this typing is respected:
const slice = createSlice<'name', { dynamic?: boolean }>('name', {});
createAction
function createAction( actionType: string, getPayload?: (...args: any[]) => any, getMeta?: (...args: any[]) => any, ): ((...args: any[]) => any) & { type: string };
Creates an action creator that will construct the action in a Flux Standard Action format based on the type and getters passed:
- If both getters are passed, the action will contain both
payload
andmeta
- If only
getPayload
is passed, the action will containpayload
but notmeta
- If only
getMeta
is passed, the action will containmeta
but notpayload
- If no getters are passed, the action will not contain either
payload
ormeta
The action creator returned will also have a static type
property on it, which will allow easy mapping with createReducer
.
If using TypeScript, the action creator returned will create action objects that are narrowly-typed based on the sliceName
provided to createSlice
and the handler passed to createAction
. Example:
const { createAction } = createSlice('todos', { items: [] }); const add = createAction('added', (value: string) => value); // `action` is typed as { payload: string, type 'todos/added' } const action = add('stuff');
createReducer
type SliceReducer = (slice: StateSlice, action: Action) => StateSlice; function createReducer<ActionHandlerMap>( actionHandlerMap: ActionHandlerMap, ): SliceReducer; function createReducer(sliceReducer: SliceReducer): SliceReducer;
There are two ways to create a reducer:
- Pass a map of action-specific reducers, each of which will receive the slice of state and the action dispatched and return a new slice of state
- Pass a traditional reducer function, which will receive the slice of state and the action dispatched and return a new slice of state
Using the former is preferred, because redux-slices
will internally optimize the slice's reducer to only call handlers when the corresponding action type matches. redux-slices
makes this easier by including the type
property on any action creator generated with createAction
:
const add = createAction('added', (value) => value); const reducer = createReducer({ [add.type]: (state, { payload: value }) => ({ ...state, items: [...items, { complete: false, value }], }), });
If using TypeScript, you can pass a type map of type => handler, and it will narrowly-type all handlers for you:
const add = createAction('added', (value) => value); const actions = { [add.type]: add; }; const reducer = createReducer<typeof actions>({ // `payload` will automatically be typed as a `string`, as in the handler above [add.type]: (state, { payload: value }) => ({ ...state, items: [...items, { complete: false, value }], }), });
There is also a convenience type, ActionCreatorMap
, provided to simplify the generation of this map from the action creators.
ActionCreatorMap
If using TypeScript, an additional typing utility is provided to narrowly-type all handlers based on the actions for the slice without incurring any additional runtime cost.
const add = createAction('added', (value: string) => value); type ActionHandlers = ActionCreatorMap<[typeof add]>; const reducer = createReducer<ActionHandlers>({ [add.type]: (state, { payload: value }) => ({ ...state, items: [...items, { complete: false, value }], }), });
The action handler typing for the is also not specific to the slice the reducer is created for; you can easily leverage action creators from other slices.
import { reset } from './appSlice'; ... const add = createAction('added', (value: string) => value); type ActionHandlers = ActionCreatorMap<[typeof add, typeof reset]>; const reducer = createReducer<ActionHandlers>({ [add.type]: (state, { payload: value }) => ({ ...state, items: [...items, { complete: false, value }], }), [reset.type]: (state) => ({ ...state, items: [] }), });
createSelector
function createSelector( selector: (slice: StateSlice, ...args: any[]) => any, ): (state: State, ...args: any[]) => any;
Creates a selector that receives the full state and returns a value derived from the specific slice of state.
const { createSelector } = createSlice('todos', { items: [] }); const getItems = createSelector((slice) => slice.items);
NOTE: The selector created is not memoized. If you use the selector to derive a new object, it is recommended to use createMemoizedSelector
instead.
createMemoizedSelector
function createMemoizedSelector( selector: (slice: StateSlice, ...args: any[]) => any, isEqual?: (prevArg: any, nextArg: any) => boolean) ): (state: State, ...args: any[]) => any;
Creates a memoizedselector that receives the full state and returns a value derived from the specific slice of state.
const { createMemoizedSelector } = createSlice('todos', { items: [] }); const getOpenItems = createMemoizedSelector((slice) => slice.items.filter((item) => !item.completed), );
NOTE: Memoization has inherent runtime costs, which may not be worth if the values being returned from the selector have consistent references (e.g., if simply returning values from state). For simpler use-cases, it is recommended to use createSelector
instead.
Custom isEqual
Be default, createMemoizedSelector
will do a referential equality check on each argument to determine if the selector should be called again; if all arguments are equal, then the memoized value is returned. However, if you have needs for a custom equality check, you can past it as the second parameter when creating the selector.
import { createSlice } from 'redux-slices'; import { deepEqual } from 'fast-equal'; const { createSelector } = createSlice('todos', { items: [] }); const getOpenItems = createMemoizedSelector( (slice) => slice.items.filter(item) => !item.completed), deepEqual );
The above example would memoize based on the arguments being deeply equal.
Comparable libraries
There are libraries in the wild that try to solve the same problem redux-slices
solves, but there are some differences worth calling out. As a note, these comparisons are based on generating action creators and reducers; the scoped selector concept that redux-slices
provides do not exist in these libraries.
createSlice
from Redux Toolkit
Redux Toolkit, and the RTK team in general, garner much respect. For most projects RTK is a great way to hit the ground running. That said, there are a few limitations with the createSlice
API:
- Generated once through a large configuration object (readability can suffer with large slices)
- Forces use of
immer
for state changes - Typing of action payloads is manual
- Custom action creators are clunky, and do not conform to FSA standards
- Use with action creators for external slices via
extraReducers
can be challenging and confusing
redux-actions
While it is longer maintained, it has similar goals. Like redux-slices
, it follows FSA standards, and is agnostic about how state changes occur. The main difference is typing; the library was not written with first-class TS support in mind, and therefore the action creators and reducers require a lot of manual typing.
Recommend
-
180
TensorFlow Examples This tutorial was designed for easily diving into TensorFlow, through examples. For readability, it includes both notebooks and source codes with explanation, for both TF v1 & v2. It is suitable for beginners wh...
-
70
README.md fast-copy
-
52
Site not found · GitHub Pages There isn't a GitHub Pages site here. If you're trying to publish one, read t...
-
4
Member m-ou-se commented
-
11
Copy link Contributor AnthonyMikh...
-
13
Observable Plot Observable Plot is a JavaScript library for exploratory data visualization. Installing For use with Webpack, Rollup, or other Node-based bundlers, Plot is typically installed via a pa...
-
12
Copy link Contributor camsteffen commented
-
11
If you want to read more of these articles, don't hesitate to subscribe to my newsletter.😁 Writing tests in Redux may definitely sound counter-intuitive. It may seem eve...
-
4
Use Redux to Manage Authenticated State in a React App
-
9
USM is a universal state modular library, supports Redux(4.x), MobX(6.x), Vuex(4.x) and Angular(2.0+). Support Libraries/Frameworks None Redux MobX Vuex Angular2+
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK