Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
570 additions
and
1 deletion.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
/modules | ||
/luacov.* | ||
/installer.lua | ||
/installer.lua | ||
site/ |
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,158 @@ | ||
# Rodux API Reference | ||
|
||
## Store | ||
The Store class is the core piece of Rodux. It is the state container that you create and use. | ||
|
||
### Store.new | ||
``` | ||
Store.new(reducer, [initialState, [middlewares]]) -> Store | ||
``` | ||
|
||
Creates and returns a new Store. | ||
|
||
* `reducer` is the store's root reducer function, and is invokved whenever an action is dispatched. It must be a pure function. | ||
* `initialState` is the store's initial state. This should be used to load a saved state from storage. | ||
* `middlewares` is a list of middleware to apply to the store. See [the middleware guide](/guide/middleware.md) for more details. | ||
|
||
The store will automatically dispatch an initialization action with a `type` of `@@INIT`. | ||
|
||
!!! note | ||
The initialization action does not pass through any middleware prior to reaching the reducer. | ||
|
||
### Store.changed | ||
```lua | ||
Store.changed:connect(function(newState, oldState) | ||
-- do something with newState or oldState | ||
end) | ||
``` | ||
|
||
A [Signal](#Signal) that is fired when the store's state is changed. | ||
|
||
!!! danger | ||
Do not yield within any listeners on `changed`; an error will be thrown. | ||
|
||
### Store:dispatch | ||
``` | ||
Store:dispatch(action) -> nil | ||
``` | ||
|
||
Dispatches an action. The action will travel through all of the store's middlewares before reaching the store's reducer. | ||
|
||
The action must contain a `type` field to indicate what type of action it is. No other fields are required. | ||
|
||
### Store:getState | ||
``` | ||
Store:getState() -> table | ||
``` | ||
|
||
Gets the store's current state. | ||
|
||
!!! warning | ||
Do not modify this value. Doing so may cause serious bugs in Rodux, your code, or both! | ||
|
||
### Store:destruct | ||
``` | ||
Store:destruct() -> nil | ||
``` | ||
|
||
Destroys the store, disconnecting all connections it may possess. | ||
|
||
!!! danger | ||
Attempting to use the store after `destruct` has been called will cause problems. | ||
|
||
### Store:flush | ||
``` | ||
Store:flush() -> nil | ||
``` | ||
|
||
Flushes the store's pending actions, firing the `changed` event. You should not need to use this method; it is called automatically from within Rodux. | ||
|
||
## Signal | ||
The Signal class in Rodux represents a simple, predictable event that is controlled from within Rodux. It is not publicly exposed and cannot be created outside of Rodux. | ||
|
||
### Signal:connect | ||
``` | ||
Signal:connect(listener) -> { disconnect } | ||
``` | ||
|
||
Connects a listener to the signal. The listener will be invoked whenever the signal is fired. | ||
|
||
`connect` returns a table with a `disconnect` function that can be used to disconnect the listener from the signal. | ||
|
||
## Helper functions | ||
Rodux supplies some helper functions to make creating complex reducers easier. | ||
|
||
### Rodux.combineReducers | ||
A helper function that can be used to combine multiple reducers into a new reducer. | ||
|
||
```lua | ||
local reducer = combineReducers({ | ||
key1 = reducer1, | ||
key2 = reducer2, | ||
}) | ||
``` | ||
|
||
`combineReducers` is functionally equivalent to writing: | ||
|
||
```lua | ||
local function reducer(state, action) | ||
return { | ||
key1 = reducer1(state.key1, action), | ||
key2 = reducer2(state.key2, action), | ||
} | ||
end | ||
``` | ||
|
||
### Rodux.createReducer | ||
``` | ||
Rodux.createReducer(actionHandlers) -> reducer | ||
``` | ||
|
||
A helper function that can be used to create reducers. | ||
|
||
Unlike JavaScript, Lua has no `switch` statement, which can make writing reducers that respond to lots of actions verbose. | ||
|
||
```lua | ||
local reducer = createReducer({ | ||
action1 = function(state, action) | ||
-- Handle actions of type "action1" | ||
end, | ||
|
||
action2 = function(state, action) | ||
-- Handle actions of type "action2" | ||
end, | ||
|
||
-- ... | ||
}) | ||
``` | ||
|
||
## Middleware | ||
Rodux ships with several middleware that address common use-cases. | ||
|
||
### Rodux.loggerMiddleware | ||
``` | ||
loggerMiddleware(outputFunction = print) -> middlewareFunction | ||
``` | ||
|
||
A middleware that logs actions and the new state that results from them. | ||
|
||
This middleware supports changing the output function. By default, it is `print`, and state changes are printed to the output. As a consequence of this, `loggerMiddleware` must be called when using it: | ||
|
||
```lua | ||
local store = Store.new(reducer, initialState, { loggerMiddleware() }) | ||
``` | ||
|
||
### Rodux.thunkMiddleware | ||
A middleware that allows thunks to be dispatched. Thunks are functions that perform asynchronous tasks or side effects, and can dispatch actions. | ||
|
||
```lua | ||
local store = Store.new(reducer, initialState, { thunkMiddleware }) | ||
|
||
store:dispatch(function(store) | ||
print("Hello from a thunk!") | ||
|
||
store:dispatch({ | ||
type = "thunkAction", | ||
}) | ||
end) | ||
``` |
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,36 @@ | ||
# Actions | ||
**Actions** are bundles of data sent from your game to your store. They are tables with a single required field: `type`. The `type` field is used to describe the type of the action, which determines what it does. It is recommended, but not required, that `type` be a string, for easy debugging. You can dispatch actions to the store using its [dispatch](/api-reference.md#storedispatch) method. | ||
|
||
Here's an example action that tells the store that a player joined: | ||
|
||
```lua | ||
local PLAYER_JOIN = "playerJoin" | ||
|
||
local action = { | ||
type = PLAYER_JOIN, | ||
player = newPlayer, | ||
} | ||
``` | ||
|
||
!!! question | ||
#### Do types have to be defined as variables first? | ||
No, not at all! It is a good practice, however, because it is easier to change type names and to catch mistakes. | ||
|
||
## Action creators | ||
**Action creators** are functions that create actions for you. This allows you to validate your action arguments before the action is dispatched, add extra data to the action, and normalize your inputs. Here's an action creator for the player join action above: | ||
|
||
```lua | ||
local function playerJoin(player) | ||
return { | ||
type = PLAYER_JOIN, | ||
player = player, | ||
} | ||
end | ||
``` | ||
|
||
And here's it in use: | ||
|
||
```lua | ||
-- somewhere inside an event handler | ||
store:dispatch(playerJoin(player)) | ||
``` |
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,171 @@ | ||
# Middleware | ||
Middleware is a way to modify the behavior of a store by altering the dispatch behavior. Middleware can modify an action, consume it entirely (stopping it from being dispatched), or just do something else on top of the action. They do this by overriding the store's `dispatch` method entirely. A middleware is a factory for new `dispatch` methods, to rephrase things. | ||
|
||
## Using middlewares | ||
Middleware can be used by specifying them in an array in the third argument of `Store.new`: | ||
|
||
```lua | ||
local store = Rodux.Store.new( | ||
reducer, | ||
initialState, | ||
{ middleware3, middleware2, middleware1 } | ||
) | ||
``` | ||
|
||
!!! question | ||
#### Why are the middlewares in reverse order? | ||
Rodux evaluates middleware in last-in-first-out order: the last argument is the one that's invoked first. The order of your middleware is important. | ||
|
||
Once you've done this, the middlewares are active and will take effect whenever you use the store's `dispatch` method. | ||
|
||
## Built-in middlewares | ||
Rodux comes with two built-in middleware: `loggerMiddleware` and `thunkMiddleware`. | ||
|
||
### Rodux.loggerMiddleware | ||
`loggerMiddleware` is a very simple middleware that lets you log changes to your state. Whenever an action is dispatched, it will print two things: | ||
|
||
* The action in its entirety | ||
* The state after the action was reduced | ||
|
||
To use it, specify it like this: | ||
|
||
```lua | ||
local store = Rodux.Store.new( | ||
reducer, | ||
initialState, | ||
{ Rodux.loggerMiddleware() } | ||
) | ||
``` | ||
|
||
!!! question | ||
#### Why is `loggerMiddleware` called? | ||
`loggerMiddleware` is called because it allows you to change the function used to print to the output. For example, if you wanted to print all the changes to your store as warnings, you could do this: | ||
|
||
```lua | ||
local store = Rodux.Store.new( | ||
reducer, | ||
initialState, | ||
{ Rodux.loggerMiddleware(warn) } | ||
) | ||
``` | ||
|
||
Now, whenever you dispatch an action, you'll see something like the following in the output window: | ||
|
||
``` | ||
Action dispatched: { | ||
type = "test"; (string) | ||
payload = 1; (number) | ||
} | ||
State changed to: { | ||
testValue = 1; (number) | ||
} | ||
``` | ||
|
||
### Rodux.thunkMiddleware | ||
`thunkMiddleware` is a middleware that lets you use thunks - it lets you dispatch a function to your store, which will be run. The function can do anything, and can dispatch new actions at will. Thunks are commonly used for asynchronous, long-running operations, like reading from a data store or performing a HTTP request. | ||
|
||
To use it, just include it in your `middlewares` table: | ||
|
||
```lua | ||
local store = Rodux.Store.new( | ||
reducer, | ||
initialState, | ||
{ Rodux.thunkMiddleware } | ||
) | ||
``` | ||
|
||
Once you've done that, you can dispatch a function just like you would an action with the store's `dispatch` method: | ||
|
||
```lua | ||
store:dispatch(function(store) | ||
-- Do something that takes a while | ||
|
||
-- Then dispatch an action to tell the store about the result! | ||
store:dispatch({ | ||
type = "someAction" | ||
}) | ||
end) | ||
``` | ||
|
||
## Writing your own middleware | ||
There's nothing magic about writing middleware! Here's how you can write your own. | ||
|
||
### A simple example: printing the type field | ||
Here's a simple middleware that just prints the action's `type` field: | ||
|
||
```lua | ||
local function printType(next) | ||
return function(store, action) | ||
print(action.type) | ||
next(store, action) | ||
end | ||
end | ||
``` | ||
|
||
Breaking it down: | ||
|
||
* `printType` is a function that takes one argument: `next`. This is the next middleware in the chain. At the end of the chain lies the original `dispatch` method. | ||
* `printType` returns a new function that takes two arguments: `store` and `action`. These arguments are the *exact signature* of the original `dispatch` method. | ||
* The function returned from `printType` prints the action's type, then calls `next` to pass the action on. | ||
|
||
To use this function, specify it in the third argument to `Store.new`: | ||
|
||
```lua | ||
local function reducer(state, action) | ||
-- Just return the same state, for demonstrational purposes. | ||
return state | ||
end | ||
|
||
local store = Store.new(reducer, {}, { printType }) | ||
|
||
store:dispatch({ | ||
type = "testAction" | ||
}) | ||
``` | ||
|
||
Run this code and you'll see this in the output: | ||
``` | ||
testAction | ||
``` | ||
|
||
### Canceling actions | ||
Nothing says you *have* to call `next` at all! Here's a middleware that just swallows up any action that it comes across. These actions never modify the store's state. | ||
|
||
```lua | ||
local function swallowAction(next) | ||
return function(store, action) | ||
-- Do nothing! Since next is not called, the action never moves on. | ||
end | ||
end | ||
``` | ||
|
||
### Modifying actions: PascalCased Type | ||
Similarly, you don't always have to call `next` with the same action. Say you prefer using `PascalCase` for your actions. Rodux requires that your actions have a `type` field, so your code style is being broken! Middlewares to the rescue - you can replace the action so that it fits the structure Rodux is expecting, without having to make compromises about your casing. | ||
|
||
```lua | ||
local function pascalCaseType(next) | ||
return function(store, action) | ||
-- If the action has a Type field, substitute it with an identical action | ||
-- that substitutes type for Type! | ||
if action.Type then | ||
local newAction = {} | ||
|
||
for key, value in pairs(action) do | ||
-- Change the casing on the Type field | ||
if key == "Type" then | ||
newAction.type = value | ||
-- Everything else can stay as-is | ||
else | ||
newAction[key] = value | ||
end | ||
end | ||
|
||
-- Pass the new action on! | ||
next(store, newAction) | ||
-- Otherwise, just send the action on! | ||
else | ||
next(store, action) | ||
end | ||
end | ||
end | ||
``` |
Oops, something went wrong.