-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Update changelog formatter and some scaffolding * Update scripts to match template * Refine docs
- Loading branch information
Showing
15 changed files
with
672 additions
and
323 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@dynamic-selectors/core": patch | ||
--- | ||
|
||
Clean up docs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,52 +1,104 @@ | ||
# Comparison with other selector libraries | ||
# Selector comparison | ||
|
||
This shows several different ways to write [Reselect's example selector](https://github.com/reduxjs/reselect#example), | ||
which filters `state.todos` based on a value in `state.visibilityFilter`. | ||
This shows several different ways to write a simple selector that filters a list of books (`state.books`) based on | ||
a author (`state.authorFilter`). | ||
|
||
#### Plain, unmemoized function | ||
## 1. Plain, unmemoized function | ||
|
||
State values are passed to a normal function. This is straightforward to write, but it will repeat the work every | ||
single time your state updates, with potentially bad performance. | ||
The simplest implementation is to use no selector at all. This is straightforward to write, but it will repeat the work | ||
every time your state updates -- and because array's `filter()` returns a new array every time it runs, this will always | ||
trigger a rerender. | ||
|
||
```javascript | ||
const getVisibleTodos = (todos, filter) => { | ||
// Snipped for brevity: filter `todos` by `filter` | ||
const getBooksForAuthor = (books, authorFilter) => { | ||
return books.filter((book) => book.author === authorFilter); | ||
}; | ||
|
||
const mapStateToProps = (state) => { | ||
return { | ||
todos: getVisibleTodos(state.todos, state.visibilityFilter), | ||
}; | ||
}; | ||
useSelector((state) => getBooksForAuthor(state.books, state.authorFilter)); | ||
``` | ||
|
||
#### Using Reselect | ||
## 2. Reselect selectors | ||
|
||
Reselect offers a standard way to memoize the operation, building up a dependency tree of functions. | ||
|
||
Each state value is wrapped in an accessor function, and `getVisibleTodos` is set to depend on those functions. It will | ||
rerun only when one of the functions returns a new value. The selector's dependencies must be listed ahead of time. | ||
Each state value is wrapped in an accessor function, and `getBooksForAuthor` is set to depend on those functions. | ||
It will rerun only when one of the functions returns a new value. The selector's dependencies must be registered at | ||
creation time. | ||
|
||
```javascript | ||
const getVisibilityFilter = (state) => state.visibilityFilter; | ||
const getTodos = (state) => state.todos; | ||
|
||
const getVisibleTodos = createSelector( | ||
[getVisibilityFilter, getTodos], | ||
(visibilityFilter, todos) => { | ||
// Snipped for brevity: filter `todos` by `filter` | ||
}, | ||
); | ||
const getBooks = (state) => state.books; | ||
const getAuthorFilter = (state) => state.authorFilter; | ||
|
||
const getBooksForAuthor = createSelector([getBooks, getAuthorFilter], (books, authorFilter) => { | ||
return books.filter((book) => book.author === authorFilter); | ||
}); | ||
``` | ||
|
||
#### Using Dynamic Selectors | ||
## 3. Dynamic Selector | ||
|
||
`getVisibleTodos` can retrieve values directly from the state, or from other dynamic selectors. It will rerun only when | ||
one of the functions returns a new value. You can retrieve values dynamically, or change the `getState` calls from run to run. | ||
`getBooksForAuthor` can retrieve values directly from the state, or from other dynamic selectors. It will rerun only | ||
when | ||
one of the functions returns a new value. For simple cases, this ultimately works the same as a Reselect selector -- | ||
just with less code and fewer functions. | ||
|
||
```javascript | ||
const getVisibleTodos = createDynamicSelector((getState) => { | ||
const visibilityFilter = getState('visibilityFilter'); | ||
const todos = getState('todos'); | ||
const getBooksForAuthor = createDynamicSelector((getState) => { | ||
const books = getState('books'); | ||
const authorFilter = getState('authorFilter'); | ||
|
||
// Snipped for brevity: filter `todos` by `filter` | ||
return books.filter((book) => book.author === authorFilter); | ||
}); | ||
``` | ||
|
||
### More complex cases | ||
|
||
#### Conditional dependency | ||
|
||
With a dynamic selector you can retrieve values dynamically, change the `getState` calls from run to run, and generally | ||
be flexible in ways that upfront selector registration doesn't allow. | ||
|
||
Here's an example which allows the author to be overridden by the caller. Results are memoized independently by params, | ||
so this will remain cached even when `state.authorFilter` changes: `state.authorFilter` only gets marked as a | ||
dependency if it's actually used. | ||
|
||
```javascript | ||
const getBooksForAuthor = createDynamicSelector((getState, authorFilterOverride) => { | ||
const books = getState('books'); | ||
const authorFilter = authorFilterOverride || getState('authorFilter'); | ||
|
||
return books.filter((book) => book.author === authorFilter); | ||
}); | ||
``` | ||
|
||
#### Loops | ||
|
||
You can build higher-level selectors on top of simpler selectors, without having to rewrite any accessors or other | ||
logic. | ||
|
||
In this example, `authorFilter` can be a single author or a list of authors. The caching all works as before, so | ||
if one of the authors in the list already has a list of books cached, it will not be reprocessed. | ||
|
||
```javascript | ||
const getBooksForAuthor = createDynamicSelector((getState, authorFilterOverride) => { | ||
const books = getState('books'); | ||
const authorFilter = authorFilterOverride || getState('authorFilter'); | ||
|
||
if (Array.isArray(authorFilter)) { | ||
// Accumulate books for each author, and combine them into a list of lists | ||
return authorFilter.map( | ||
// Recurse! | ||
(authorFilter) => getBooksForAuthor(authorFilter), | ||
); | ||
} else { | ||
return books.filter((book) => book.author === authorFilter); | ||
} | ||
}); | ||
``` | ||
|
||
The above solution is `O(n^2)` for a large number of authors: in practice you probably don't want to loop over | ||
the `books` multiple times. | ||
|
||
With traditional selectors, refactoring this to loop over `books` only once would require rewriting the entire selector. | ||
Instead, you could split the algorithm inside the selector: use `getBooksForAuthor` as before if the list is small, | ||
or for any authors who already have a cached result (see the [API docs](../README.md#additional-selector-properties) | ||
for `.hasCachedResult()`), and then process the remainder in a single loop. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"extends": "../../tsconfig-base.build.json", | ||
"compilerOptions": { | ||
"outDir": "dist", | ||
"declarationDir": "dist" | ||
}, | ||
"include": ["src"] | ||
} |
Oops, something went wrong.