You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This PR rethinks actions and action creators. An action now simply becomes an
unbound function with two arguments, state and store (or only store, see
discussion below). More commonly a user will use action creators, which are
functions that return an action (function).
// without argumentexportconstincrement=()=>(state,store)=>({count: state.count+1,})// with argumentexportconstincrement=(amount)=>(state,store)=>({count: state.count+amount,})
I'm beginning to question whether the state argument is really needed. Calling getState() isn't that much extra work, and to avoid stale states it is
often required. The above example would then be:
I've also updated devtools to differentiate between unnamed actions and direct
calls to setState. Furthermore, the update argument passed to setState is now passed on to the subscribers. This was done to show the update object in devtools.
Caveats
The only problem with this change is we no longer get naming of actions in
devtools for free. Now you have to name both the action creator and the action
function if you want to get a descriptive name for the action in devtools.
I've also implemented the ability to return an action object instead
of an action function, merely for naming purposes.
exportconstincrement=(amount)=>functionincrement(state){return{count: state.count+amount},}// ORexportconstincrement=(amount)=>(state)=>({type: `increment by ${amount}`,action: (state)=>({count: state.count+amount}),});
Why?
1. Easier to compose action maps for connected components
Before:
importsomeActionsfrom'./someActions'importmoreActionsfrom'./moreActions'connect('stuffs',store=>({
...someActions(store),
...moreActions(store),}))(Component)// or even worse, if we dont want to expose all actions:connect('stuffs',store=>{const{ oneAction, twoAction }=someActions(store)const{ anotherAction }=moreActions(store)return{
oneAction,
twoAction,
anotherAction,}})(Component)
Now:
import*assomeActionsfrom'./someActions'import*asmoreActionsfrom'./moreActions'connect('stuffs',{
...someActions,
...moreActions,})(Component)// or even better:import{oneAction,twoAction}from'./someActions'import{anotherAction}from'./moreActions'connect('stuffs',{
oneAction,
twoAction,
anotherAction,})(Component)
Since actions no longer need to be bound it becomes a little easier to test.
Performance
I was initially worried creating new action functions every time an action is called would have negative performance impacts, but I was glad to see that the performance actually greatly improved. At least in modern browsers. The results in the table below are from https://jsperf.com/binding-vs-action-creators.
Bind
Bound
Create
Mapped Create
Firefox 70.0.1
14,317,110 ±0.69% 75% slower
17,701,729 ±1.76% 70% slower
58,379,915 ±0.80% fastest
45,373,182 ±0.48% 22% slower
Chrome 78.0.3904.108
21,238,688 ±0.79% 56% slower
30,728,667 ±0.80% 36% slower
47,844,616 ±0.90% fastest
47,107,563 ±1.00% 2% slower
Edge 44.18362.449.0
4,862,331 ±4.09 % 43% slower
8,476,679 ±4.08% fastest
5,315,648 ±3.16% 37% slower
1,689,862 ±2.47% 80% slower
IE 11
4,251,654 ±1.75% 29% slower
5,965,267 ±1.68% fastest
4,322,828 ±1.49% 27% slower
1,798,760 ±1.52% 70% slower
Build Size
The full preact and dist builds have shrunk quite a bit, while somehow the isolated preact build increased. File compression is a dark magic.
full/preact.js
dist/unistore.js
preact.js
master
760B
355B
546B
feature/new-action-creators
737B
327B
558B
Notes
I had to disable the prefer-spread eslint rule to allow the modifications of mapAction where .apply(actions, arguments) is used.
@Akiyamka 😍 really happy to hear you like the changes!
you only really need access to getState and action... access to setState becomes kind of redundant now that calling other actions is easier. i passed in store because that was how it was done before, but one of the following probably makes more sense now:
Since getState will more commonly be used, it should probably be the first argument:
This file contains hidden or 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
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR rethinks actions and action creators. An action now simply becomes an
unbound function with two arguments,
stateandstore(or onlystore, seediscussion below). More commonly a user will use action creators, which are
functions that return an action (function).
I'm beginning to question whether the state argument is really needed. Calling
getState()isn't that much extra work, and to avoid stale states it isoften required. The above example would then be:
I've also updated devtools to differentiate between unnamed actions and direct
calls to
setState. Furthermore, theupdateargument passed tosetStateis now passed on to the subscribers. This was done to show the update object in devtools.Caveats
The only problem with this change is we no longer get naming of actions in
devtools for free. Now you have to name both the action creator and the action
function if you want to get a descriptive name for the action in devtools.
I've also implemented the ability to return an action object instead
of an action function, merely for naming purposes.
Why?
1. Easier to compose action maps for connected components
Before:
Now:
2. Easier to call actions from other actions:
Before:
Now:
3. A little easier to test
Since actions no longer need to be bound it becomes a little easier to test.
Performance
I was initially worried creating new action functions every time an action is called would have negative performance impacts, but I was glad to see that the performance actually greatly improved. At least in modern browsers. The results in the table below are from https://jsperf.com/binding-vs-action-creators.
70.0.1
75% slower
70% slower
fastest
22% slower
78.0.3904.108
56% slower
36% slower
fastest
2% slower
44.18362.449.0
43% slower
fastest
37% slower
80% slower
29% slower
fastest
27% slower
70% slower
Build Size
The full preact and dist builds have shrunk quite a bit, while somehow the isolated preact build increased. File compression is a dark magic.
Notes
I had to disable the
prefer-spreadeslint rule to allow the modifications ofmapActionwhere.apply(actions, arguments)is used.