diff --git a/types/flux/flux-tests.ts b/types/flux/flux-tests.ts deleted file mode 100644 index 57c8fb3b20..0000000000 --- a/types/flux/flux-tests.ts +++ /dev/null @@ -1,126 +0,0 @@ -import flux = require('flux') -import FluxUtils = require('flux/utils') -import React = require('react') - -var Component = React.Component -var Container = FluxUtils.Container - -// -// Basic dispatcher usage -// - -var basicDispatcher = new flux.Dispatcher() - -// register(callback: (payload: any) => void): string -var id: string = basicDispatcher.register((payload) => { - // payload is type: any - payload.anything -}) - -// unregister(id: string): void -basicDispatcher.unregister(id) - -// waitFor(ids: string[]): void -basicDispatcher.waitFor([id]) - -// dispatch(payload: any): void -basicDispatcher.dispatch({ msg: 'hello' }) - -// isDispatching(): boolean -var dispatcherIsDispatching: boolean = basicDispatcher.isDispatching() - -// -// Typed payload -// - -enum ActionSource { Server, View } -enum ActionType { Create, Update, Delete } - -interface Action { - source: ActionSource - type: ActionType - data: Object -} - -var typedDispatcher = new flux.Dispatcher() - -var typedPayload: Action - -var typedStore = { - dispatcherID: typedDispatcher.register((payload) => { - typedPayload = payload - }) -} - -typedDispatcher.dispatch(typedPayload) - -// -// Derived dispatcher -// - -class CustomDispatcher extends flux.Dispatcher { - - // Dispatch an action with server as source - handleServerAction(type: ActionType, data: Object) { - this.dispatch({ - source: ActionSource.Server, - type: type, - data: data, - }) - } - - // Dispatch an action with view as source - handleViewAction(type: ActionType, data: Object) { - this.dispatch({ - source: ActionSource.View, - type: type, - data: data, - }) - } -} - -var customDispatcher = new CustomDispatcher() - -export = customDispatcher - - -// Sample Reduce Store -class CounterStore extends FluxUtils.ReduceStore { - getInitialState(): number { - return 0; - } - - reduce(state: number, action: any): number { - switch (action.type) { - case 'increment': - return state + 1; - - case 'square': - return state * state; - - default: - return state; - } - } -} - -const Store = new CounterStore(basicDispatcher); - -// Sample Flux container with CounterStore -class CounterContainer extends Component { - static getStores() { - return [Store]; - } - - static calculateState(prevState: any) { - return { - counter: Store.getState(), - }; - } - - render() { - return this.state.counter; - } -} - -const container = Container.create(CounterContainer); diff --git a/types/flux/index.d.ts b/types/flux/index.d.ts index 54fecd3a4e..7380613ce7 100644 --- a/types/flux/index.d.ts +++ b/types/flux/index.d.ts @@ -1,69 +1,12 @@ -// Type definitions for Flux 3.0 +// Type definitions for Flux 3.1 // Project: http://facebook.github.io/flux/ -// Definitions by: Steve Baker , Giedrius Grabauskas +// Definitions by: Steve Baker +// Giedrius Grabauskas // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped -// TypeScript Version: 2.1 +// TypeScript Version: 2.2 -/// -/// +import * as Dispatcher from "./lib/Dispatcher"; -export = Flux; - -declare namespace Flux { - - /** - * Dispatcher class - * Create an instance to use throughout the application. - * Or extend it to create a derived dispatcher class. - * - * Specify a type in the 'TPayload' generic argument to use strongly-typed payloads, - * otherwise specify 'any' - * - * Examples: - * var dispatcher = new flux.Dispatcher() - * var typedDispatcher = new flux.Dispatcher() - * class DerivedDispatcher extends flux.Dispatcher { } - */ - export class Dispatcher { - - /** - * Create an instance of the Dispatcher class to use throughout the application. - * - * Specify a type in the 'TPayload' generic argument to use strongly-typed payloads, - * otherwise specify 'any' - * - * Examples: - * var dispatcher = new flux.Dispatcher() - * var typedDispatcher = new flux.Dispatcher() - */ - constructor(); - - /** - * Registers a callback that will be invoked with every payload sent to the dispatcher. - * Returns a string token to identify the callback to be used with waitFor() or unregister. - */ - register(callback: (payload: TPayload) => void): string; - - /** - * Unregisters a callback with the given ID token - */ - unregister(id: string): void; - - /** - * Waits for the callbacks with the specified IDs to be invoked before continuing execution - * of the current callback. This method should only be used by a callback in response - * to a dispatched payload. - */ - waitFor(IDs: string[]): void; - - /** - * Dispatches a payload to all registered callbacks - */ - dispatch(payload: TPayload): void; - - /** - * Gets whether the dispatcher is currently dispatching - */ - isDispatching(): boolean; - } -} +export { + Dispatcher +}; diff --git a/types/flux/lib/Dispatcher.d.ts b/types/flux/lib/Dispatcher.d.ts new file mode 100644 index 0000000000..045d633088 --- /dev/null +++ b/types/flux/lib/Dispatcher.d.ts @@ -0,0 +1,45 @@ +declare namespace Dispatcher { } + +declare class Dispatcher { + /** + * Create an instance of the Dispatcher class to use throughout the application. + * + * Specify a type in the 'TPayload' generic argument to use strongly-typed payloads, + * otherwise specify 'any' + * + * Examples: + * var dispatcher = new flux.Dispatcher() + * var typedDispatcher = new flux.Dispatcher() + */ + constructor(); + + /** + * Registers a callback that will be invoked with every payload sent to the dispatcher. + * Returns a string token to identify the callback to be used with waitFor() or unregister. + */ + register(callback: (payload: TPayload) => void): string; + + /** + * Unregisters a callback with the given ID token + */ + unregister(id: string): void; + + /** + * Waits for the callbacks with the specified IDs to be invoked before continuing execution + * of the current callback. This method should only be used by a callback in response + * to a dispatched payload. + */ + waitFor(IDs: string[]): void; + + /** + * Dispatches a payload to all registered callbacks + */ + dispatch(payload: TPayload): void; + + /** + * Gets whether the dispatcher is currently dispatching + */ + isDispatching(): boolean; +} + +export = Dispatcher; diff --git a/types/flux/lib/FluxContainer.d.ts b/types/flux/lib/FluxContainer.d.ts new file mode 100644 index 0000000000..54b578cc1d --- /dev/null +++ b/types/flux/lib/FluxContainer.d.ts @@ -0,0 +1,65 @@ +import * as Dispatcher from "./Dispatcher"; +import * as FluxStore from "./FluxStore"; +import * as React from "react"; + +/** + * A FluxContainer is used to subscribe a react component to multiple stores. + * The stores that are used must be returned from a static `getStores()` method. + * + * The component receives information from the stores via state. The state + * is generated using a static `calculateState()` method that each container must implement. + */ +/** + * Default options to create a Container. + * + * @interface RealOptions + */ +export interface RealOptions { + /** + * Default value: true + * + * @type {boolean} + */ + pure?: boolean; + /** + * Default value: false + * + * @type {boolean} + */ + withProps?: boolean; + /** + * Default value: false + * + * @type {boolean} + */ + withContext?: boolean; +} + +export type ComponentConstructor = React.ComponentClass | React.StatelessComponent; + +export interface ComponentStatic { + getStores(maybeProps?: TProps, maybeContext?: TContext): Array>; + calculateState(prevState: TState, maybeProps?: TProps, maybeContext?: TContext): TState; +} + +export type Component = ComponentConstructor & ComponentStatic; + +/** + * Create is used to transform a react class into a container + * that updates its state when relevant stores change. + * The provided base class must have static methods getStores() and calculateState(). + */ +export function create(base: Component, options?: RealOptions): Component; +export function create(base: Component, options?: RealOptions): Component; +export function create(base: Component, options?: RealOptions): Component; +export function create(base: Component & TStatic, options?: RealOptions): Component & TStatic; + +/** + * This is a way to connect stores to a functional stateless view. + */ +export function createFunctional( + viewFn: (props: TProps) => React.ReactElement, + getStores: (maybeProps?: TProps, maybeContext?: any) => Array>, + calculateState: (prevState?: TState, maybeProps?: TProps, maybeContext?: any) => TState, + options?: RealOptions +): Component; diff --git a/types/flux/lib/FluxMixinLegacy.d.ts b/types/flux/lib/FluxMixinLegacy.d.ts new file mode 100644 index 0000000000..2ce825e3da --- /dev/null +++ b/types/flux/lib/FluxMixinLegacy.d.ts @@ -0,0 +1,26 @@ +import * as FluxStore from "./FluxStore"; + +declare namespace FluxMixinLegacy { + interface Options { + withProps?: boolean; + } +} + +/** + * `FluxContainer` should be preferred over this mixin, but it requires using + * react with classes. So this mixin is provided where it is not yet possible + * to convert a container to be a class. + * + * This mixin should be used for React components that have state based purely + * on stores. `this.props` will not be available inside of `calculateState()`. + * + * This mixin will only `setState` not replace it, so you should always return + * every key in your state unless you know what you are doing. + * + * On the second calculateState when prevState is not null, the state will be + * updated to contain the previous foo AND the bar that was just returned. Only + * returning bar will not delete foo. + */ +declare function FluxMixinLegacy(stores: Array>, options?: FluxMixinLegacy.Options): any; + +export = FluxMixinLegacy; diff --git a/types/flux/lib/FluxReduceStore.d.ts b/types/flux/lib/FluxReduceStore.d.ts new file mode 100644 index 0000000000..71a7968812 --- /dev/null +++ b/types/flux/lib/FluxReduceStore.d.ts @@ -0,0 +1,36 @@ +import * as Store from "./FluxStore"; + +declare namespace FluxReduceStore { } + +/** + * This is the basic building block of a Flux application. All of your stores + * should extend this class. + */ +declare abstract class FluxReduceStore extends Store { + /** + * Getter that exposes the entire state of this store. + * If your state is not immutable you should override this and not expose state directly. + */ + getState(): TState; + + /** + * Constructs the initial state for this store. + * This is called once during construction of the store. + */ + abstract getInitialState(): TState; + + /** + * Reduces the current state, and an action to the new state of this store. + * All subclasses must implement this method. + * This method should be pure and have no side-effects. + */ + abstract reduce(state: TState, action: TPayload): TState; + + /** + * Checks if two versions of state are the same. + * You do not need to override this if your state is immutable. + */ + areEqual(one: TState, two: TState): boolean; +} + +export = FluxReduceStore; diff --git a/types/flux/lib/FluxStore.d.ts b/types/flux/lib/FluxStore.d.ts new file mode 100644 index 0000000000..a0b2384c72 --- /dev/null +++ b/types/flux/lib/FluxStore.d.ts @@ -0,0 +1,74 @@ +import * as Dispatcher from "./Dispatcher"; +import * as fbEmitter from "fbemitter"; + +declare namespace FluxStore { } + +/** + * This class represents the most basic functionality for a FluxStore. Do not + * extend this store directly; instead extend FluxReduceStore when creating a + * new store. + */ +declare abstract class FluxStore { + /** + * Constructs and registers an instance of this store with the given dispatcher. + */ + constructor(dispatcher: Dispatcher); + + /** + * Adds a listener to the store, when the store changes the given callback will be called. + * A token is returned that can be used to remove the listener. + * Calling the remove() function on the returned token will remove the listener. + */ + addListener(callback: () => void): fbEmitter.EventSubscription; + + /** + * Returns the dispatcher this store is registered with. + */ + getDispatcher(): Dispatcher; + + /** + * Returns the dispatch token that the dispatcher recognizes this store by. + * Can be used to waitFor() this store. + */ + getDispatchToken(): string; + + /** + * Ask if a store has changed during the current dispatch. + * Can only be invoked while dispatching. + * This can be used for constructing derived stores that depend on data from other stores. + */ + hasChanged(): boolean; + + /** + * Emit an event notifying all listeners that this store has changed. + * This can only be invoked when dispatching. + * Changes are de-duplicated and resolved at the end of this store's __onDispatch function. + */ + protected __emitChange(): void; + + /** + * This method encapsulates all logic for invoking __onDispatch. It should + * be used for things like catching changes and emitting them after the + * subclass has handled a payload. + */ + protected __invokeOnDispatch(payload: TPayload): void; + + /** + * Subclasses must override this method. + * This is how the store receives actions from the dispatcher. + * All state mutation logic must be done during this method. + */ + protected __onDispatch(payload: TPayload): void; + + protected __changed: boolean; + + protected __changeEvent: string; + + protected __className: string; + + protected __dispatcher: Dispatcher; + + protected __emitter: fbEmitter.EventEmitter; +} + +export = FluxStore; diff --git a/types/flux/test/Flux.ts b/types/flux/test/Flux.ts new file mode 100644 index 0000000000..6666b830aa --- /dev/null +++ b/types/flux/test/Flux.ts @@ -0,0 +1,71 @@ +import { Dispatcher } from "flux"; + +enum ActionSource { + Server, + View +} +enum ActionType { + Create, + Update, + Delete +} + +interface Action { + source: ActionSource; + type: ActionType; + data: {}; +} + +function dispatcherCallback(payload: Action) { + let source: ActionSource = payload.source; + let type: ActionType = payload.type; + let data: {} = payload.data; +} + +let dispatcherIsDispatching: boolean; +let dispatcherToken: string; + +const actionPayload = { + data: {}, + source: ActionSource.Server, + type: ActionType.Create +}; + +/** + * Basic dispatcher + */ +const basicDispatcher: Dispatcher = new Dispatcher(); +dispatcherToken = basicDispatcher.register(dispatcherCallback); +basicDispatcher.unregister(dispatcherToken); +basicDispatcher.waitFor([dispatcherToken]); +basicDispatcher.dispatch(actionPayload); +dispatcherIsDispatching = basicDispatcher.isDispatching(); + +/** + * Custom dispatcher + */ +class CustomDispatcher extends Dispatcher { + // Dispatch an action with server as source + handleServerAction(type: ActionType, data: {}) { + this.dispatch({ + source: ActionSource.Server, + type, + data + }); + } + // Dispatch an action with view as source + handleViewAction(type: ActionType, data: {}) { + this.dispatch({ + source: ActionSource.View, + type, + data + }); + } +} + +const customDispatcher: CustomDispatcher = new CustomDispatcher(); +dispatcherToken = customDispatcher.register(dispatcherCallback); +customDispatcher.unregister(dispatcherToken); +customDispatcher.dispatch(actionPayload); +customDispatcher.waitFor([dispatcherToken]); +dispatcherIsDispatching = customDispatcher.isDispatching(); diff --git a/types/flux/test/FluxUtils.tsx b/types/flux/test/FluxUtils.tsx new file mode 100644 index 0000000000..1cd3705fab --- /dev/null +++ b/types/flux/test/FluxUtils.tsx @@ -0,0 +1,80 @@ +import { Dispatcher } from "flux"; +import { ReduceStore, Container } from "flux/utils"; +import * as React from "react"; + +interface Payload { + type: string; +} + +const basicDispatcher = new Dispatcher(); + +// Sample Reduce Store +class CounterStore extends ReduceStore { + getInitialState(): number { + return 0; + } + + reduce(state: number, action: Payload): number { + switch (action.type) { + case 'increment': + return state + 1; + case 'square': + return state * state; + default: + return state; + } + } +} +const Store = new CounterStore(basicDispatcher); + +// Sample Flux container with Store +interface Props { + a: string; + b: boolean; +} + +interface State { + counter: number; +} + +class CounterContainer extends React.Component { + static getStores() { + return [Store]; + } + + static a: string = "asd"; + + static calculateState(prevState: State, props: Props): State { + return { + counter: Store.getState() - (props.b ? 0 : 1) + }; + } + + render() { + return
+ {this.state.counter} +
; + } +} + +const ContainerComponent1 = Container.create(CounterContainer, { withProps: true }); +; + +const ContainerComponent2 = Container.create(CounterContainer, { withProps: true }); +; + +const ContainerComponent3 = Container.create(CounterContainer, { withProps: true }); +; + +// Functional flux container with Store +const FunctionalContainerComponent = Container.createFunctional( + (props) => { + return
+ {props.a} {props.b} +
; + }, + () => [Store], + (prevState) => ({ counter: Store.getState() }) +); + +; diff --git a/types/flux/tsconfig.json b/types/flux/tsconfig.json index 45255c7d7a..540faa9d2b 100644 --- a/types/flux/tsconfig.json +++ b/types/flux/tsconfig.json @@ -5,20 +5,27 @@ "es6", "dom" ], - "noImplicitAny": true, - "noImplicitThis": true, - "strictNullChecks": false, "baseUrl": "../", "typeRoots": [ "../" ], + "jsx": "react", "types": [], - "noEmit": true, - "forceConsistentCasingInFileNames": true + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": false, + "forceConsistentCasingInFileNames": true, + "noEmit": true }, "files": [ "index.d.ts", - "utils/index.d.ts", - "flux-tests.ts" + "utils.d.ts", + "lib/Dispatcher.d.ts", + "lib/FluxContainer.d.ts", + "lib/FluxMixinLegacy.d.ts", + "lib/FluxReduceStore.d.ts", + "lib/FluxStore.d.ts", + "test/Flux.ts", + "test/FluxUtils.tsx" ] } \ No newline at end of file diff --git a/types/flux/tslint.json b/types/flux/tslint.json new file mode 100644 index 0000000000..e60c15844f --- /dev/null +++ b/types/flux/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": "dtslint/dt.json" +} \ No newline at end of file diff --git a/types/flux/utils.d.ts b/types/flux/utils.d.ts new file mode 100644 index 0000000000..4eb3d96e99 --- /dev/null +++ b/types/flux/utils.d.ts @@ -0,0 +1,11 @@ +import * as Container from "./lib/FluxContainer"; +import * as Mixin from "./lib/FluxMixinLegacy"; +import * as ReduceStore from "./lib/FluxReduceStore"; +import * as Store from "./lib/FluxStore"; + +export { + Container, + Mixin, + ReduceStore, + Store +}; diff --git a/types/flux/utils/index.d.ts b/types/flux/utils/index.d.ts deleted file mode 100644 index b223945181..0000000000 --- a/types/flux/utils/index.d.ts +++ /dev/null @@ -1,140 +0,0 @@ -// Type definitions for Flux Utils 3.0 -// Project: http://facebook.github.io/flux/ -// Definitions by: Steve Baker , Giedrius Grabauskas -// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped - -/// -/// -/// - -import * as React from 'react'; -import * as Flux from 'flux'; -import * as fbEmitter from 'fbemitter'; - -export = FluxUtils; - -declare namespace FluxUtils { - /** - * Default options to create a Container with - * - * @interface RealOptions - */ - interface RealOptions { - /** - * Default value: true - * - * @type {boolean} - */ - pure?: boolean; - /** - * Default value: false - * - * @type {boolean} - */ - withProps?: boolean; - /** - * Default value: false - * - * @type {boolean} - */ - withContext?: boolean; - } - - export class Container { - constructor(); - /** - * Create is used to transform a react class into a container - * that updates its state when relevant stores change. - * The provided base class must have static methods getStores() and calculateState(). - */ - static create(base: React.ComponentClass, options?: RealOptions): React.ComponentClass; - - - - /** - * This is a way to connect stores to a functional stateless view - */ - static createFunctional( - viewFn: (props: State) => React.ReactElement, - getStores: (props?: Props, context?: any) => FluxUtils.Store[], - calculateState: (prevState?: State, props?: Props, context?: any) => State, - options?: RealOptions - ): React.ComponentClass; - } - - export class ReduceStore extends Store { - /** - * Getter that exposes the entire state of this store. - * If your state is not immutable you should override this and not expose state directly. - */ - getState(): T; - - /** - * Constructs the initial state for this store. - * This is called once during construction of the store. - */ - getInitialState(): T; - - /** - * Reduces the current state, and an action to the new state of this store. - * All subclasses must implement this method. - * This method should be pure and have no side-effects. - */ - reduce(state: T, action: TPayload): T; - - /** - * Checks if two versions of state are the same. - * You do not need to override this if your state is immutable. - */ - areEqual(one: T, two: T): boolean; - - } - - export class Store { - - /** - * Constructs and registers an instance of this store with the given dispatcher. - */ - constructor(dispatcher: Flux.Dispatcher); - - /** - * Adds a listener to the store, when the store changes the given callback will be called. - * A token is returned that can be used to remove the listener. - * Calling the remove() function on the returned token will remove the listener. - */ - addListener(callback: Function): fbEmitter.EventSubscription; - - /** - * Returns the dispatcher this store is registered with. - */ - getDispatcher(): Flux.Dispatcher; - - /** - * Returns the dispatch token that the dispatcher recognizes this store by. - * Can be used to waitFor() this store. - */ - getDispatchToken(): string; - - /** - * Ask if a store has changed during the current dispatch. - * Can only be invoked while dispatching. - * This can be used for constructing derived stores that depend on data from other stores. - */ - hasChanged(): boolean; - - /** - *Emit an event notifying all listeners that this store has changed. - * This can only be invoked when dispatching. - * Changes are de-duplicated and resolved at the end of this store's __onDispatch function. - */ - __emitChange(): void; - - /** - * Subclasses must override this method. - * This is how the store receives actions from the dispatcher. - * All state mutation logic must be done during this method. - */ - __onDispatch(payload: TPayload): void; - } -} -