-
-
Notifications
You must be signed in to change notification settings - Fork 15.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add TypeScript definitions #1413
Changes from all commits
889030c
c01501f
4b09a3b
6b4f7f6
b19ebc1
a6b4d80
56ab522
1fa6636
6b61fca
16325a2
6317727
a5d44fa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,390 @@ | ||
/** | ||
* An *action* is a plain object that represents an intention to change the | ||
* state. Actions are the only way to get data into the store. Any data, | ||
* whether from UI events, network callbacks, or other sources such as | ||
* WebSockets needs to eventually be dispatched as actions. | ||
* | ||
* Actions must have a `type` field that indicates the type of action being | ||
* performed. Types can be defined as constants and imported from another | ||
* module. It’s better to use strings for `type` than Symbols because strings | ||
* are serializable. | ||
* | ||
* Other than `type`, the structure of an action object is really up to you. | ||
* If you’re interested, check out Flux Standard Action for recommendations on | ||
* how actions should be constructed. | ||
*/ | ||
export interface Action { | ||
type: any; | ||
} | ||
|
||
|
||
/* reducers */ | ||
|
||
/** | ||
* A *reducer* (also called a *reducing function*) is a function that accepts | ||
* an accumulation and a value and returns a new accumulation. They are used | ||
* to reduce a collection of values down to a single value | ||
* | ||
* Reducers are not unique to Redux—they are a fundamental concept in | ||
* functional programming. Even most non-functional languages, like | ||
* JavaScript, have a built-in API for reducing. In JavaScript, it's | ||
* `Array.prototype.reduce()`. | ||
* | ||
* In Redux, the accumulated value is the state object, and the values being | ||
* accumulated are actions. Reducers calculate a new state given the previous | ||
* state and an action. They must be *pure functions*—functions that return | ||
* the exact same output for given inputs. They should also be free of | ||
* side-effects. This is what enables exciting features like hot reloading and | ||
* time travel. | ||
* | ||
* Reducers are the most important concept in Redux. | ||
* | ||
* *Do not put API calls into reducers.* | ||
* | ||
* @template S State object type. | ||
*/ | ||
export type Reducer<S> = <A extends Action>(state: S, action: A) => S; | ||
|
||
/** | ||
* Object whose values correspond to different reducer functions. | ||
*/ | ||
export interface ReducersMapObject { | ||
[key: string]: Reducer<any>; | ||
} | ||
|
||
/** | ||
* Turns an object whose values are different reducer functions, into a single | ||
* reducer function. It will call every child reducer, and gather their results | ||
* into a single state object, whose keys correspond to the keys of the passed | ||
* reducer functions. | ||
* | ||
* @template S Combined state object type. | ||
* | ||
* @param reducers An object whose values correspond to different reducer | ||
* functions that need to be combined into one. One handy way to obtain it | ||
* is to use ES6 `import * as reducers` syntax. The reducers may never | ||
* return undefined for any action. Instead, they should return their | ||
* initial state if the state passed to them was undefined, and the current | ||
* state for any unrecognized action. | ||
* | ||
* @returns A reducer function that invokes every reducer inside the passed | ||
* object, and builds a state object with the same shape. | ||
*/ | ||
export function combineReducers<S>(reducers: ReducersMapObject): Reducer<S>; | ||
|
||
|
||
/* store */ | ||
|
||
/** | ||
* A *dispatching function* (or simply *dispatch function*) is a function that | ||
* accepts an action or an async action; it then may or may not dispatch one | ||
* or more actions to the store. | ||
* | ||
* We must distinguish between dispatching functions in general and the base | ||
* `dispatch` function provided by the store instance without any middleware. | ||
* | ||
* The base dispatch function *always* synchronously sends an action to the | ||
* store’s reducer, along with the previous state returned by the store, to | ||
* calculate a new state. It expects actions to be plain objects ready to be | ||
* consumed by the reducer. | ||
* | ||
* Middleware wraps the base dispatch function. It allows the dispatch | ||
* function to handle async actions in addition to actions. Middleware may | ||
* transform, delay, ignore, or otherwise interpret actions or async actions | ||
* before passing them to the next middleware. | ||
*/ | ||
export type Dispatch = (action: any) => any; | ||
|
||
/** | ||
* Function to remove listener added by `Store.subscribe()`. | ||
*/ | ||
export interface Unsubscribe { | ||
(): void; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Middleware definition below seems to be enough, but… interface Middleware<S> extends Function {
(store: MiddlewareAPI<S>): (next: Dispatch) => Dispatch;
} It actually does not return a dispatch but function mapping input of type A to output of type B: interface Middleware<S, A, B> extends Function {
(store: MiddlewareAPI<S>): (next: Dispatch) => (action: A) => B;
} But in this case interface Middleware extends Function {
<S, A, B>(store: MiddlewareAPI<S>): (next: Dispatch) => (action: A) => B;
} It's not always so easy to add static type definitions to code written in dynamically typed language… ;) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's try to implement const thunkMiddleware = ({dispatch, getState}) =>
(next) => (action) => {
return typeof action === function ? action(dispatch, getState) : next(action)
} Now what types can we add here? Keep in mind that there may me other middlewares applied before There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. // in our typings
interface Middleware<S, A, B> extends Function {
(store: MiddlewareAPI<S>): (next: Dispatch) => (action: A) => B;
} import { MyState } from './wherever-my-state-is-declared'
type ThunkAction = ((d: Dispatch, gs: () => MyState) => ThunkAction) | Object;
const thunkMiddleware: Middleware<MyStore, ThunkAction, ThunkAction> =
({dispatch, getState}) =>
(next) => (action) => {
return typeof action === function ? action(dispatch, getState) : next(action)
} Does it do the job ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It shows that elasticity of import { MyState } from './wherever-my-state-is-declared';
const thunkMiddleware: Middleware<MyStore, any, any> =
({dispatch, getState}) =>
(next) => (action) => {
return typeof action === function ? action(dispatch, getState) : next(action)
} ;) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The problem is — |
||
|
||
/** | ||
* A store is an object that holds the application’s state tree. | ||
* There should only be a single store in a Redux app, as the composition | ||
* happens on the reducer level. | ||
* | ||
* @template S State object type. | ||
*/ | ||
export interface Store<S> { | ||
/** | ||
* Dispatches an action. It is the only way to trigger a state change. | ||
* | ||
* The `reducer` function, used to create the store, will be called with the | ||
* current state tree and the given `action`. Its return value will be | ||
* considered the **next** state of the tree, and the change listeners will | ||
* be notified. | ||
* | ||
* The base implementation only supports plain object actions. If you want | ||
* to dispatch a Promise, an Observable, a thunk, or something else, you | ||
* need to wrap your store creating function into the corresponding | ||
* middleware. For example, see the documentation for the `redux-thunk` | ||
* package. Even the middleware will eventually dispatch plain object | ||
* actions using this method. | ||
* | ||
* @param action A plain object representing “what changed”. It is a good | ||
* idea to keep actions serializable so you can record and replay user | ||
* sessions, or use the time travelling `redux-devtools`. An action must | ||
* have a `type` property which may not be `undefined`. It is a good idea | ||
* to use string constants for action types. | ||
* | ||
* @returns For convenience, the same action object you dispatched. | ||
* | ||
* Note that, if you use a custom middleware, it may wrap `dispatch()` to | ||
* return something else (for example, a Promise you can await). | ||
*/ | ||
dispatch: Dispatch; | ||
|
||
/** | ||
* Reads the state tree managed by the store. | ||
* | ||
* @returns The current state tree of your application. | ||
*/ | ||
getState(): S; | ||
|
||
/** | ||
* Adds a change listener. It will be called any time an action is | ||
* dispatched, and some part of the state tree may potentially have changed. | ||
* You may then call `getState()` to read the current state tree inside the | ||
* callback. | ||
* | ||
* You may call `dispatch()` from a change listener, with the following | ||
* caveats: | ||
* | ||
* 1. The subscriptions are snapshotted just before every `dispatch()` call. | ||
* If you subscribe or unsubscribe while the listeners are being invoked, | ||
* this will not have any effect on the `dispatch()` that is currently in | ||
* progress. However, the next `dispatch()` call, whether nested or not, | ||
* will use a more recent snapshot of the subscription list. | ||
* | ||
* 2. The listener should not expect to see all states changes, as the state | ||
* might have been updated multiple times during a nested `dispatch()` before | ||
* the listener is called. It is, however, guaranteed that all subscribers | ||
* registered before the `dispatch()` started will be called with the latest | ||
* state by the time it exits. | ||
* | ||
* @param listener A callback to be invoked on every dispatch. | ||
* @returns A function to remove this change listener. | ||
*/ | ||
subscribe(listener: () => void): Unsubscribe; | ||
|
||
/** | ||
* Replaces the reducer currently used by the store to calculate the state. | ||
* | ||
* You might need this if your app implements code splitting and you want to | ||
* load some of the reducers dynamically. You might also need this if you | ||
* implement a hot reloading mechanism for Redux. | ||
* | ||
* @param nextReducer The reducer for the store to use instead. | ||
*/ | ||
replaceReducer(nextReducer: Reducer<S>): void; | ||
} | ||
|
||
/** | ||
* A store creator is a function that creates a Redux store. Like with | ||
* dispatching function, we must distinguish the base store creator, | ||
* `createStore(reducer, initialState)` exported from the Redux package, from | ||
* store creators that are returned from the store enhancers. | ||
* | ||
* @template S State object type. | ||
*/ | ||
export interface StoreCreator { | ||
<S>(reducer: Reducer<S>, enhancer?: StoreEnhancer): Store<S>; | ||
<S>(reducer: Reducer<S>, initialState: S, | ||
enhancer?: StoreEnhancer): Store<S>; | ||
} | ||
|
||
/** | ||
* A store enhancer is a higher-order function that composes a store creator | ||
* to return a new, enhanced store creator. This is similar to middleware in | ||
* that it allows you to alter the store interface in a composable way. | ||
* | ||
* Store enhancers are much the same concept as higher-order components in | ||
* React, which are also occasionally called “component enhancers”. | ||
* | ||
* Because a store is not an instance, but rather a plain-object collection of | ||
* functions, copies can be easily created and modified without mutating the | ||
* original store. There is an example in `compose` documentation | ||
* demonstrating that. | ||
* | ||
* Most likely you’ll never write a store enhancer, but you may use the one | ||
* provided by the developer tools. It is what makes time travel possible | ||
* without the app being aware it is happening. Amusingly, the Redux | ||
* middleware implementation is itself a store enhancer. | ||
*/ | ||
export type StoreEnhancer = (next: StoreCreator) => StoreCreator; | ||
|
||
/** | ||
* Creates a Redux store that holds the state tree. | ||
* The only way to change the data in the store is to call `dispatch()` on it. | ||
* | ||
* There should only be a single store in your app. To specify how different | ||
* parts of the state tree respond to actions, you may combine several | ||
* reducers | ||
* into a single reducer function by using `combineReducers`. | ||
* | ||
* @template S State object type. | ||
* | ||
* @param reducer A function that returns the next state tree, given the | ||
* current state tree and the action to handle. | ||
* | ||
* @param [initialState] The initial state. You may optionally specify it to | ||
* hydrate the state from the server in universal apps, or to restore a | ||
* previously serialized user session. If you use `combineReducers` to | ||
* produce the root reducer function, this must be an object with the same | ||
* shape as `combineReducers` keys. | ||
* | ||
* @param [enhancer] The store enhancer. You may optionally specify it to | ||
* enhance the store with third-party capabilities such as middleware, time | ||
* travel, persistence, etc. The only store enhancer that ships with Redux | ||
* is `applyMiddleware()`. | ||
* | ||
* @returns A Redux store that lets you read the state, dispatch actions and | ||
* subscribe to changes. | ||
*/ | ||
export const createStore: StoreCreator; | ||
|
||
|
||
/* middleware */ | ||
|
||
export interface MiddlewareAPI<S> { | ||
dispatch: Dispatch; | ||
getState(): S; | ||
} | ||
|
||
/** | ||
* A middleware is a higher-order function that composes a dispatch function | ||
* to return a new dispatch function. It often turns async actions into | ||
* actions. | ||
* | ||
* Middleware is composable using function composition. It is useful for | ||
* logging actions, performing side effects like routing, or turning an | ||
* asynchronous API call into a series of synchronous actions. | ||
*/ | ||
export interface Middleware { | ||
<S>(api: MiddlewareAPI<S>): (next: Dispatch) => (action: any) => any; | ||
} | ||
|
||
/** | ||
* Creates a store enhancer that applies middleware to the dispatch method | ||
* of the Redux store. This is handy for a variety of tasks, such as | ||
* expressing asynchronous actions in a concise manner, or logging every | ||
* action payload. | ||
* | ||
* See `redux-thunk` package as an example of the Redux middleware. | ||
* | ||
* Because middleware is potentially asynchronous, this should be the first | ||
* store enhancer in the composition chain. | ||
* | ||
* Note that each middleware will be given the `dispatch` and `getState` | ||
* functions as named arguments. | ||
* | ||
* @param middlewares The middleware chain to be applied. | ||
* @returns A store enhancer applying the middleware. | ||
*/ | ||
export function applyMiddleware(...middlewares: Middleware[]): StoreEnhancer; | ||
|
||
|
||
/* action creators */ | ||
|
||
/** | ||
* An *action creator* is, quite simply, a function that creates an action. Do | ||
* not confuse the two terms—again, an action is a payload of information, and | ||
* an action creator is a factory that creates an action. | ||
* | ||
* Calling an action creator only produces an action, but does not dispatch | ||
* it. You need to call the store’s `dispatch` function to actually cause the | ||
* mutation. Sometimes we say *bound action creators* to mean functions that | ||
* call an action creator and immediately dispatch its result to a specific | ||
* store instance. | ||
* | ||
* If an action creator needs to read the current state, perform an API call, | ||
* or cause a side effect, like a routing transition, it should return an | ||
* async action instead of an action. | ||
* | ||
* @template A Returned action type. | ||
*/ | ||
export interface ActionCreator<A> { | ||
(...args: any[]): A; | ||
} | ||
|
||
/** | ||
* Object whose values are action creator functions. | ||
*/ | ||
export interface ActionCreatorsMapObject { | ||
[key: string]: ActionCreator<any>; | ||
} | ||
|
||
/** | ||
* Turns an object whose values are action creators, into an object with the | ||
* same keys, but with every function wrapped into a `dispatch` call so they | ||
* may be invoked directly. This is just a convenience method, as you can call | ||
* `store.dispatch(MyActionCreators.doSomething())` yourself just fine. | ||
* | ||
* For convenience, you can also pass a single function as the first argument, | ||
* and get a function in return. | ||
* | ||
* @param actionCreator An object whose values are action creator functions. | ||
* One handy way to obtain it is to use ES6 `import * as` syntax. You may | ||
* also pass a single function. | ||
* | ||
* @param dispatch The `dispatch` function available on your Redux store. | ||
* | ||
* @returns The object mimicking the original object, but with every action | ||
* creator wrapped into the `dispatch` call. If you passed a function as | ||
* `actionCreator`, the return value will also be a single function. | ||
*/ | ||
export function bindActionCreators<A extends ActionCreator<any>>(actionCreator: A, dispatch: Dispatch): A; | ||
|
||
export function bindActionCreators< | ||
A extends ActionCreator<any>, | ||
B extends ActionCreator<any> | ||
>(actionCreator: A, dispatch: Dispatch): B; | ||
|
||
export function bindActionCreators<M extends ActionCreatorsMapObject>(actionCreators: M, dispatch: Dispatch): M; | ||
|
||
export function bindActionCreators< | ||
M extends ActionCreatorsMapObject, | ||
N extends ActionCreatorsMapObject | ||
>(actionCreators: M, dispatch: Dispatch): N; | ||
|
||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Action creators:I think that overloading is much better than union: // or maybe "<A extends Action>" but due to middleware issues better without "extends Action"
export interface ActionCreator<A> extends Function {
(...args: any[]): A;
}
export interface ActionCreatorsMapObject {
[key: string]: <A>(...args: any[]) => A;
}
export interface ActionCreatorsBinder {
<A>(actionCreator: ActionCreator<A>, dispatch: Dispatch): ActionCreator<A>;
<M extends ActionCreatorsMapObject>(actionCreators: M, dispatch: Dispatch): M;
(actionCreators: ActionCreatorsMapObject, dispatch: Dispatch): ActionCreatorsMapObject;
}
export function bindActionCreators: ActionCreatorsBinder; I've tested it with such piece of useless code: const oneCreatorA = bindActionCreators<Action>(a => a, store.dispatch);
const oneCreatorB = bindActionCreators(oneCreatorA, store.dispatch);
const oneCreatorC = bindActionCreators(oneCreatorB, store.dispatch);
interface Ott extends ActionCreatorsMapObject {
one: IActionCreator<Action>;
two: IActionCreator<Action>;
three: IActionCreator<Action>;
four: IActionCreator<Action>;
}
const xCreatorA = bindActionCreators({
one: a => a,
two(b: Action) {return b;},
three: oneCreatorA
}, store.dispatch);
const cMap: Ott = {
one: a => a,
two(b: Action) {return b;},
three: oneCreatorA,
four: oneCreatorC
};
const xCreatorB: ActionCreatorsMapObject = bindActionCreators(xCreatorA, store.dispatch);
const xCreatorC: Ott = bindActionCreators(cMap, store.dispatch); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Third overload seems redundant here, we can omit type parameter in second overload and it will be inferred as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also consider the difference between <A>(actionCreator: ActionCreator<A>, dispatch: Dispatch): ActionCreator<A>; and <A extends ActionCreator<any>>(actionCreator: A, dispatch: Dispatch): A; I guess the latter is stronger because let's you constraint not only return type of ActionCreator, but its full signature including argument types. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Second one here: <A extends ActionCreator<any>>(actionCreator: A, dispatch: Dispatch): A; is not typesafe as it enforces I think we should find better way… There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @aikoven also you said
And that is great :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand. This signature allows doing bindActionCreators<ActionCreator<MyAction>>(...) and even bindActionCreators<(text: string) => MyAction>(...) So type parameter is not enforced. In contrast, if we used this signature: <A>(actionCreator: ActionCreator<A>, dispatch: Dispatch): ActionCreator<A>; to bind action creator of type Also, I found that we don't cover cases when There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, you're right. My mistake: <A extends ActionCreator<any>>(actionCreator: A, dispatch: Dispatch): A; Is totally ok.
So maybe third overload: <A extends ActionCreator<any>, B extends ActionCreator<any>>(
actionCreator: A,
dispatch: Dispatch
): B; |
||
/* compose */ | ||
|
||
/** | ||
* Composes single-argument functions from right to left. The rightmost | ||
* function can take multiple arguments as it provides the signature for the | ||
* resulting composite function. | ||
* | ||
* @param funcs The functions to compose. | ||
* @returns R function obtained by composing the argument functions from right | ||
* to left. For example, `compose(f, g, h)` is identical to doing | ||
* `(...args) => f(g(h(...args)))`. | ||
*/ | ||
export function compose(): <R>(a: R, ...args: any[]) => R; | ||
|
||
export function compose<A, R>( | ||
f1: (b: A) => R, | ||
f2: (...args: any[]) => A | ||
): (...args: any[]) => R; | ||
|
||
export function compose<A, B, R>( | ||
f1: (b: B) => R, | ||
f2: (a: A) => B, | ||
f3: (...args: any[]) => A | ||
): (...args: any[]) => R; | ||
|
||
export function compose<A, B, C, R>( | ||
f1: (b: C) => R, | ||
f2: (a: B) => C, | ||
f3: (a: A) => B, | ||
f4: (...args: any[]) => A | ||
): (...args: any[]) => R; | ||
|
||
export function compose<R>( | ||
f1: (a: any) => R, | ||
...funcs: Function[] | ||
): (...args: any[]) => R; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be great if try to not use too much
any
- it's just like working without support of typscript… :(Consider more strict approach like:
Or even better
so anyone can implement his own strict actions:
or dynamic ones:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why isn't this sufficient?
If you want to have strict actions you can just do:
And it will be assignable to
Action
.Also, reducer should not be constrained to accept only some selected actions. It should accept any possible action and bypass ones it doesn't need to handle.