2017-07-27 55 views
5

Mam projektu TypeScript, który używa React i Redux i próbuję dodać niektóre funkcje middleware. I zaczęło poprzez realizację jednego z próbek Redux jest tak:Jak utworzyć silnie typowane oprogramowanie pośrednie redux w języku TypeScript z definicji typów Redux?

// ---- middleware.ts ---- 
export type MiddlewareFunction = (store: any) => (next: any) => (action: any) => any; 

export class MyMiddleWare { 
    public static Logger: MiddlewareFunction = store => next => action => { 
     // Do stuff 
     return next(action); 
    } 
} 

// ---- main.ts ---- 
import * as MyMiddleware from "./middleware"; 

const createStoreWithMiddleware = Redux.applyMiddleware(MyMiddleWare.Logger)(Redux.createStore); 

Powyższe działa dobrze, ale ponieważ jest to maszynopis Chciałbym, aby to silnie wpisane, najlepiej przy użyciu typów zdefiniowanych przez Redux więc don Muszę wymyślić i utrzymać własne. Więc tutaj są odpowiednie fragmenty z mojego pliku index.d.ts dla Redux:

// ---- index.d.ts from Redux ---- 
export interface Action { 
    type: any; 
} 

export interface Dispatch<S> { 
    <A extends Action>(action: A): A; 
} 

export interface MiddlewareAPI<S> { 
    dispatch: Dispatch<S>; 
    getState(): S; 
} 

export interface Middleware { 
    <S>(api: MiddlewareAPI<S>): (next: Dispatch<S>) => Dispatch<S>; 
} 

Próbuję dowiedzieć się, jak doprowadzić te typy do mojego sposobu Logger ale nie mam dużo szczęścia. Wydaje mi się, że coś takiego powinno działać:

interface MyStore { 
    thing: string; 
    item: number; 
} 

interface MyAction extends Action { 
    note: string; 
} 

export class MyMiddleWare { 
    public static Logger: Middleware = (api: MiddlewareAPI<MyStore>) => (next: Dispatch<MyStore>) => (action: MyAction) => { 
     const currentState: MyStore = api.getState(); 
     const newNote: string = action.note; 
     // Do stuff 
     return next(action); 
    }; 
} 

ale zamiast ja dostać ten błąd:

błąd TS2322: Type „(API: MiddlewareAPI) => (dalej: Dispatch) => (action: Action) => Action 'nie można przypisać' Middleware '.
Typy parametrów "api" i "api" są niekompatybilne.
Typ 'MiddlewareAPI' nie można przypisać "MiddlewareAPI".
Typ "S" nie może być przypisany do "MyStore".

widzę <S> generic zadeklarowane w definicji typu, ale próbowałem wiele różnych kombinacji i nie mogę wydawać się, aby dowiedzieć się, jak określić go jako MyStore tak, że jest uznawany za rodzajowy w pozostałych deklaracjach. Na przykład, zgodnie z deklaracją api.getState() powinien zwrócić obiekt MyStore. To samo myślenie dotyczy oczywiście typu działania: <A>.

Odpowiedz

4

MyStore nie jest wymagany.

export const Logger: Middleware = 
    (api: MiddlewareAPI<void>) => 
    (next: Dispatch<void>) => 
    <A extends Action>(action: A) => { 
    // Do stuff 
    return next(action); 
    }; 

lub

export const Logger: Middleware = api => next => action => { 
    // Do stuff 
    return next(action); 
}; 

Miłego Dev

+0

Żadne z nich nie odpowiada na pytanie (które zaktualizuję w celu wyjaśnienia), ponieważ wywołania api.getState() nie zwracają silnie wpisanego obiektu. Chodzi o to, aby kompilator stosował się do deklaracji typu, ponieważ są one zdefiniowane, więc nie musisz wykonywać żadnych dodatkowych rzutów. Najpierw musisz to zrobić: const currentState: MyStore = api.getState() jak każdy jako MyStore; Po drugie trzeba jeszcze to zrobić: const currentState: MyStore = api.getState() jako MyStore; –

+0

@BernardHymmen czy kiedykolwiek to rozgryzłeś? – NSjonas

+0

@NSjonas - Przepraszamy za brak odpowiedzi wcześniej. Mój kolega zwrócił uwagę, że definicje typów uniemożliwiły strukturalnie stworzenie ściśle wpisanego oprogramowania pośredniego w sposób bezpośredni. Najlepsze, co możesz zrobić, to zaimplementować jakąś pracę, jak to sugerował Martin Backschat. W zasadzie to właśnie robiłem, chociaż nie wiedziałem o tworzeniu strażników typowych za pomocą "jest" w tym czasie, więc moje rozwiązanie nie było tak ładne jak u Martina. FWIW: Wygląda na to, że ludzie pracowali nad tym problemem (https://github.com/reactjs/redux/pull/2563), ale nie próbowałem jeszcze tych zmian. –

0

Mam rozwiązanie, które idzie tak:

export type StateType = { thing: string, item: number }; 

export type ActionType = 
    { type: "MY_ACTION", note: string } | 
    { type: "PUSH_ACTIVITIY", activity: string }; 

// Force cast of generic S to my StateType 
// tslint:disable-next-line:no-any 
function isApi<M>(m: any): m is MiddlewareAPI<StateType> { 
    return true; 
} 

export type MiddlewareFunction = 
    (api: MiddlewareAPI<StateType>, next: (action: ActionType) => ActionType, action: ActionType) => ActionType; 

export function handleAction(f: MiddlewareFunction): Middleware { 
    return <S>(api: MiddlewareAPI<S>) => next => action => { 
     if (isApi(api)) { 
      // Force cast of generic A to my ActionType 
      const _action = (<ActionType>action); 
      const _next: (action: ActionType) => ActionType = a => { 
       // Force cast my ActionType to generic A 
       // tslint:disable-next-line:no-any 
       return next(<any>a); 
      }; 
      // Force cast my ActionType to generic A 
      // tslint:disable-next-line:no-any 
      return f(api, _next, _action) as any; 
     } else { 
      return next(action); 
     } 
    }; 
} 

Dzięki funkcji handeAction teraz mogę określić middleware:

// Log actions and state.thing before and after action dispatching 
export function loggingMiddleware(): Middleware { 
    return handleAction((api, next, action) => { 
     console.log(" \nBEGIN ACTION DISPATCHING:"); 
     console.log(`----- Action: ${JSON.stringify(action)}\n`); 
     const oldState = api.getState(); 

     const retVal = next(action); 

     console.log(` \n----- Old thing: ${oldState.thing}`); 
     console.log(`----- New thing: ${api.getState().thing)}\n`); 
     console.log("END ACTION DISPATCHING\n"); 

     return retVal; 
    }); 
} 

// Another middleware... 
export interface DataHub = { ... }: 
export function dataHandlingMiddleware(datahub: DataHub): Middleware { 
    return handleAction((api, next, action) => { 
     switch (action.type) { 
      case "PUSH_ACTIVITY": { 
       handlePushActivities(action.activity, api, /* outer parameter */ datahub); 
       break; 
      } 
      default: 
     } 
     return next(action); 
    }); 
} 

Należy pamiętać, że middleware może również wymagać dodatkowych parametrów, takich jak usługi itp. (Tutaj: DataHub), które są przekazywane podczas konfiguracji. Konfiguracja sklep wygląda następująco:

import { 
    Store, applyMiddleware, StoreCreator, StoreEnhancer, 
    createStore, combineReducers, Middleware, MiddlewareAPI 
} from "redux"; 

const middlewares = [ 
    dataHandlingMiddleware(datahub), 
    loggingMiddleware()]; 

const rootReducer = combineReducers<StateType>({ ... }); 
const initialState: StateType = {}; 

// Trick to enable Redux DevTools with TS: see https://www.npmjs.com/package/redux-ts 
const devTool = (f: StoreCreator) => { 
    // tslint:disable-next-line:no-any 
    return ((window as any).__REDUX_DEVTOOLS_EXTENSION__) ? (window as any).__REDUX_DEVTOOLS_EXTENSION__ : f; 
}; 
const middleware: StoreEnhancer<StateType> = applyMiddleware(...middlewares); 
const store: Store<StateType> = middleware(devTool(createStore))(rootReducer, initialState); 

Nadzieja to pomaga.

0

Właśnie przeszedłem przez ten sam problem co ty!

rozwiązać go poprzez umieszczenie ostatniej funkcji między nawiasie, a następnie zmusza to wpisać się Dispatch<EffectAction>

interface EffectAction extends Action { 
    effect<T> (action: T): void 
} 

const effects: Middleware = (api: MiddlewareAPI<any>) => (next: Dispatch<EffectAction>) => ((action: EffectAction) => { 
    if (action.effect instanceof Function) action.effect(action) 
    return next(action) 
}) as Dispatch<EffectAction> 
0

Oto moje rozwiązanie:

Pierwszy jest twórcą middleware, który przyjmuje funkcję todo jako dane wejściowe, które jest uruchamiany jako logika rdzenia dla oprogramowania pośredniego. Funkcja todo akceptuje obiekt, który hermetyzuje, jak również wszystkie inne parametry użytkownika. Należy pamiętać, że używam as Middleware, aby zmusić twórcę oprogramowania pośredniego do zwrócenia oprogramowania pośredniego. To jest magia, której używam, aby pozbyć się kłopotów.

import { MiddlewareAPI, Dispatch, Middleware } from 'redux'; 
import { Action } from 'redux-actions'; 

export interface MiddlewareTodoParams<S> { 
    store: MiddlewareAPI<S>; 
    next: Dispatch<S>; 
    action: Action<S>; 
    [otherProperty: string]: {}; 
} 

export interface MiddlewareTodo<S> { 
    (params: MiddlewareTodoParams<S>): Action<S>; 
} 

// <S>(api: MiddlewareAPI<S>): (next: Dispatch<S>) => Dispatch<S>; 
export const createMiddleware = <S>(
    todo: MiddlewareTodo<S>, 
    ...args: {}[] 
): Middleware => { 
    return ((store: MiddlewareAPI<S>) => { 
    return (next: Dispatch<S>) => { 
     return action => { 
     console.log(store.getState(), action.type); 
     return todo({ store, next, action, ...args }); 
     }; 
    }; 
    // Use as Middleware to force the result to be Middleware 
    }) as Middleware; 
}; 

Druga część to definicja mojej funkcji todo. W tym przykładzie zapisuję jakiś token w cookie. To tylko POC dla Middleware, więc w ogóle nie przejmuję się ryzykiem XSS w moich kodach.

export type OAUTH2Token = { 
    header: { 
    alg: string; 
    typ: string; 
    }; 
    payload?: { 
    sub: string; 
    name: string; 
    admin: boolean; 
    }; 
}; 


export const saveToken2Cookie: MiddlewareTodo<OAUTH2Token> = params => { 
    const { action, next } = params; 
    if (action.type === AUTH_UPDATE_COOKIE && action.payload !== undefined) { 
    cookie_set('token', JSON.stringify(action.payload)); 
    } 
    return next(action); 
}; 

Oto, jak wygląda konfiguracja mojego sklepu.

const store: Store<{}> = createStore(
    rootReducer, 
    // applyMiddleware(thunk, oauth2TokenMiddleware(fetch)) 
    applyMiddleware(thunk, createMiddleware<OAUTH2Token>(saveToken2Cookie)) 
);