refactor: transform connect

This commit is contained in:
EnixCoda 2019-02-17 00:17:40 +08:00
parent 91bbb64a83
commit 29ac1d88b9
No known key found for this signature in database
GPG key ID: 0C1A07377913A1DD
2 changed files with 118 additions and 71 deletions

View file

@ -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 <ComponentClass {...this.props} {...this.boundCore} {...this.state} />
}
}
}
}

118
src/driver/connect.ts Normal file
View file

@ -0,0 +1,118 @@
import * as React from 'react'
export type ParametersOfReturnedFunction<Func> = Func extends (<Args extends []>(
...args: any[]
) => (...args2: Args) => any)
? Args
: never
export type Method = (...args: Args) => void | Promise<void>
type Args = any[] // This had to be any
export type Middleware = <M extends Method, MM extends Method>(
method: M,
args: Parameters<M>
) => [MM | M, Parameters<MM | M>]
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<M extends Method>([method, args]: [M, Parameters<M>]) {
method.apply(null, args)
}
export type DispatchState<Props> = React.Component<Props, void>['setState']
export type PreDispatch<Props, State> = (
dispatchCallback: (props: Props, state: State) => Props | Promise<void> | void,
callback?: () => void
) => void
export type TriggerOtherMethod = <MC extends MethodCreator>(
methodCreator: MC,
...args: ParametersOfReturnedFunction<MC>
) => void
export type Dispatch<Props, State> = {
state: DispatchState<Props>
prepare: PreDispatch<Props, State>
for: TriggerOtherMethod
}
export type MethodCreator<Props = {}, State = any> = (dispatch: Dispatch<Props, State>) => Method
type Sources = {
[key: string]: MethodCreator
}
type WrappedMethods = {
[key: string]: Method
}
function link<P, S>(instance: React.Component<P, S>, sources: Sources): WrappedMethods {
const wrappedMethods: WrappedMethods = {
/* [keyof sources] -> wrappedMethods.method */
}
const map = new Map<MethodCreator, Method>(/* sources.creator -> wrappedMethods.method */)
const dispatchState: DispatchState<P> = (updater, callback) => {
instance.setState(updater, callback)
}
const prepareState: PreDispatch<P, S> = updater => {
updater(instance.props, instance.state)
}
const dispatch = {
for(createMethod: MethodCreator, ...otherArgs: ParametersOfReturnedFunction<MethodCreator>) {
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<BaseP, ExtraP>(mapping: Sources) {
return function linkComponent<S>(
ComponentClass: React.ComponentClass<BaseP & ExtraP, S>
): React.ComponentClass<BaseP, ExtraP> {
return class AwesomeApp extends React.PureComponent<BaseP, ExtraP> {
static displayName = `Connected(${ComponentClass.name})`
static defaultProps = ComponentClass.defaultProps
state = {} as ExtraP
boundCore = link<BaseP, ExtraP>(this, mapping) as WrappedMethods
render() {
const props = Object.assign({}, this.props, this.boundCore, this.state)
return React.createElement(ComponentClass, props)
}
}
}
}