diff --git a/src/driver/connect.js b/src/driver/connect.js deleted file mode 100644 index f0f9f03..0000000 --- a/src/driver/connect.js +++ /dev/null @@ -1,71 +0,0 @@ -import React from 'react' - -const middlewares = [] - -export function addMiddleware(middleware) { - if (typeof middleware !== 'function') return null - const m = middleware.bind(null) - middlewares.push(m) - return function removeMiddleware() { - const index = middlewares.indexOf(m) - if (index === -1) return - middlewares.splice(index, 1) - } -} - -function applyMiddlewares(method, args) { - for(const middleware of middlewares) { - [method = method, args = args] = middleware(method, args) || [] - } - return [method, args] -} - -function run([method, args]) { - method.apply(null, args) -} - -function link(instance, sources) { - const wrappedMethods = {/* [keyof sources] -> wrappedMethods.method */} - const map = new Map(/* sources.creator -> wrappedMethods.method */) - - Object.entries(sources).forEach(([key, createMethod]) => { - const method = createMethod(dispatch) - wrappedMethods[key] = method - map.set(createMethod, method) - }) - - const sourcesValues = Object.values(sources) - function dispatch(...args) { - const isFromSource = sourcesValues.includes(args[0]) - if (isFromSource) { - const [createMethod, ...otherArgs] = args - const method = map.get(createMethod) - run(applyMiddlewares(method, otherArgs)) - } else { - // by doing so, no async updater is available anymore - // luckily I don't need them :) - let [updater, callback] = args - if (typeof updater === 'function') { - updater = updater(instance.state, instance.props) - } - instance.setState(updater, callback) - } - } - - return wrappedMethods -} - -export default function connect(mapping) { - return function linkComponent(ComponentClass) { - return class AwesomeApp extends React.PureComponent { - static displayName = `Driven${ComponentClass.name}` - - state = {} - boundCore = link(this, mapping) - - render() { - return - } - } - } -} diff --git a/src/driver/connect.ts b/src/driver/connect.ts new file mode 100644 index 0000000..4ae5e30 --- /dev/null +++ b/src/driver/connect.ts @@ -0,0 +1,118 @@ +import * as React from 'react' + +export type ParametersOfReturnedFunction = Func extends (( + ...args: any[] +) => (...args2: Args) => any) + ? Args + : never + +export type Method = (...args: Args) => void | Promise +type Args = any[] // This had to be any +export type Middleware = ( + method: M, + args: Parameters +) => [MM | M, Parameters] + +const middlewares: Middleware[] = [] + +export function addMiddleware(middleware: Middleware) { + if (typeof middleware !== 'function') return null + const m = middleware.bind(null) + middlewares.push(m) + return function removeMiddleware() { + const index = middlewares.indexOf(m) + if (index === -1) return + middlewares.splice(index, 1) + } +} + +const applyMiddlewares: Middleware = function applyMiddlewares(method, args) { + for (const middleware of middlewares) { + ;[method = method, args = args] = middleware(method, args) + } + return [method, args] +} + +function run([method, args]: [M, Parameters]) { + method.apply(null, args) +} + +export type DispatchState = React.Component['setState'] +export type PreDispatch = ( + dispatchCallback: (props: Props, state: State) => Props | Promise | void, + callback?: () => void +) => void +export type TriggerOtherMethod = ( + methodCreator: MC, + ...args: ParametersOfReturnedFunction +) => void + +export type Dispatch = { + state: DispatchState + prepare: PreDispatch + for: TriggerOtherMethod +} + +export type MethodCreator = (dispatch: Dispatch) => Method + +type Sources = { + [key: string]: MethodCreator +} +type WrappedMethods = { + [key: string]: Method +} + +function link(instance: React.Component, sources: Sources): WrappedMethods { + const wrappedMethods: WrappedMethods = { + /* [keyof sources] -> wrappedMethods.method */ + } + const map = new Map(/* sources.creator -> wrappedMethods.method */) + + const dispatchState: DispatchState

= (updater, callback) => { + instance.setState(updater, callback) + } + const prepareState: PreDispatch = updater => { + updater(instance.props, instance.state) + } + const dispatch = { + for(createMethod: MethodCreator, ...otherArgs: ParametersOfReturnedFunction) { + const isFromSource = sourcesValues.includes(createMethod) + if (isFromSource) { + const method = map.get(createMethod) + const runnable = applyMiddlewares(method, otherArgs) + run(runnable) + } + }, + prepare: prepareState, + state: dispatchState, + } + + Object.entries(sources).forEach(([key, createMethod]) => { + const method = createMethod(dispatch) + wrappedMethods[key] = method + map.set(createMethod, method) + }) + + const sourcesValues = Object.values(sources) + + return wrappedMethods +} + +export default function connect(mapping: Sources) { + return function linkComponent( + ComponentClass: React.ComponentClass + ): React.ComponentClass { + return class AwesomeApp extends React.PureComponent { + static displayName = `Connected(${ComponentClass.name})` + static defaultProps = ComponentClass.defaultProps + + state = {} as ExtraP + boundCore = link(this, mapping) as WrappedMethods + + render() { + const props = Object.assign({}, this.props, this.boundCore, this.state) + return React.createElement(ComponentClass, props) + } + } + } +}