peterpeter5 / pyredux

12 stars 1 forks source link

Suggestions on action creators #2

Open mondwan opened 7 years ago

mondwan commented 7 years ago

I suggest action creators should cache and return corresponding namedtuple for the same action name in order to combine best practices from Javscript (JS) and Python (PY).

In Javascript, files for reducers, actions are placed separately while the single dispatch mechanism in python requires the same object reference.

Currently, in order to glue up reducers and actions, I have to do something below

from pyredux import default_reducer
from pyrsistent import pmap

import xxx.actions.common as Common
import xxx.constants as CONST

DEFAULT_STATE = {
    'isLoading': False,
}

@default_reducer
def main(action, state=pmap(DEFAULT_STATE)):
    return state

@main.register(Common.TYPES[CONST.TYPES['UPDATE_IS_LOADING']])
def updateIsLoading(action, state):
    return state.set('isLoading', action.payload)
import pyredux

import xxx.constants as CONST

TYPES = {
    CONST.TYPES['UPDATE_IS_LOADING']: pyredux.create_action_type(
        CONST.TYPES['UPDATE_IS_LOADING']
    ),
}

def updateIsLoading(isLoading=False):
    return TYPES[CONST.TYPES['UPDATE_IS_LOADING']](isLoading)

It it little bit tedious for exposing those TYPES as they need to be same reference for single dispatch?

I am wondering if the built-in action creator provide a caching mechanism I can simply create an action object every time instead of caching them myself

peterpeter5 commented 7 years ago

I dont know if I understand your suggestion the right way, so will try give some examples and please correct me if I got something wrong.

In js you would declare actions and import them like this:

actions.js

const UPDATING = 'UPDATE_IS_LOADING'
{
  type: UPDATING,
  state: true
}

when you placed these actions in a differnet file you have to import the type in your reducer like this:

reducer.js:

import { UPDATING } from '../actionTypes'

In python this could translate into this:

actions.py

UpdateIsLoading = create_action_type("UPDATE_IS_LOADING")

def updateIsLoading(isLoading=False):
    return UpdateIsLoading(isLoading) 

and into:

reducer.py

from actions import UpdateIsLoading

This would reduce your overhead with TYPES and CONST and is providing the same functionality, flexibility and maintainebility. The name / string you pass into the create_action_type is just used for "namespacing" your action-types and is never used directly from pyredux or singledispatch and therefore there is nothing that should be maintained in an extra const-file. You will always match against the imported type and not the string that you used creating this type. That is different in javascript. The reducer will match against a string. So you SHOULD store your action-types(-strings) in a different file and use them like you showed above.

For a caching-mechanism: There is the create_types_action_creator function that should provide a shorthand-function for creating simple actions like this:

UpdateIsLoading, updateIsLoading = create_typed_action_creator("UPDATE_IS_LOADING")

# create new action
new_action = updateIsLoading(True)

# register type at reducer
@main.register(UpdateIsLoading)
def _(action, state):
   pass
mondwan commented 7 years ago

Sorry for late reply.

After studied the snippets you provided a while, I think I get the essences of how to write and model reducer and actions in python. Thanks.

However, there are some statements I need a clarifications.

The name / string you pass into the create_action_type is just used for "namespacing" your
action-types and is never used directly from pyredux or singledispatch and therefore
there is nothing that should be maintained in an extra const-file.

What do you mean by extra const-file?

The reducer will match against a string.
So you SHOULD store your action-types(-strings) in a different file and use
them like you showed above.

Do you mean I should write following?

reducer.py

from constant import UPDATE_IS_LOADING
# register type at reducer
@main.register(UPDATE_IS_LOADING)
def _(action, state):
   pass

constant.py

UPDATE_IS_LOADING = create_action_type("UPDATE_IS_LOADING")

action.py

from constant import UPDATE_IS_LOADING
def updateIsLoading(isLoading=False):
    return UPDATE_IS_LOADING(isLoading) 
There is the create_types_action_creator function that should provide a
shorthand-function for creating simple actions like this:

In my opinion, I do not know the purpose of create_types_action_creator at all. Can you show more examples about how to use this function efficiently?

peterpeter5 commented 7 years ago

To your first question:

To your second question (general usage):

To your third question:

actions.py

UPDATE_IS_LOADING, updateIsLoading = create_typed_action_creator("UPDATE_IS_LOADING") 

By using create_typed_action_creator you get the type to register at your reducer and a function to create instances of this action-type. This function is more ore less (except for the default arguments) equivalent to your example-function updateIsLoading from above.

The created type/class UPDATE_IS_LOADING can than be registered at the reducer:

reducer.py

from actions import UPDATE_IS_LOADING
# register type at reducer
@main.register(UPDATE_IS_LOADING)
 def _(action, state):
   pass

The returned function from create_typed_action_creator is a short-hand-function to avoid writing these simple creator-functions all the time.

mondwan commented 7 years ago

Hey, it's me again. Thanks for the reply.

More on question 3:

By the way, discussion here is valuable. Will there any wikis to record these stuff? Maybe, I can create a PR about adding an example to summarize discussion here :D?