45
loading...
This website collects cookies to deliver better user experience
Fish
out of an array of multiple animals, we have a filter function isFish
that can create the subset.const zoo: (Fish | Bird)[] = [...];
const fishes: Fish[] = zoo.filter(isFish) as Fish[];
as Fish[]
at the end? That's what we had to add to get type safety on the filtered array of fish. This explicit cast of type is necessary if the implementation of the isFish
function returns only a boolean. But there's a way to tell TypeScript that we'll only return true from isFish
for objects which are of type Fish
. The way we do that is with a "type predicate", or "user-defined typeguard" (docs). Here's how it looks with a type guard:function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
const fishes: Fish[] = zoo.filter(isFish)
isFish
is pet is Fish
, we get type-safety on the array of fish - and anywhere else we'd use this predicate! This type of implicit typing is better for reading and maintaining the code, and many functions exported by libraries will do this for you..trigger
and listen
rather than next
and subscribe
. One excellent use of an Event Bus is to replace Redux middleware, like Redux Saga or Redux-Observable. A bus may carry any type, but for now let's assume it's duck-typing Redux, carrying only Flux Standard Actions. Action<SearchRequest>
, and the listener will trigger actions of type Action<SearchResult>
back onto the bus. Our search may return many values - one SearchRequest
potentially triggering many SearchResult
actions. With explicit typing:interface SearchRequest { query: string }
interface SearchResult { result: string }
const bus = new Omnibus<Action<any>>();
bus.listen<Action<SearchRequest>, Action<SearchResult>>(
(action) => (action.type === 'search/request'),
({ payload }) => { /* TODO use payload.query */
Action<any>
- so that it can test any bus item. The listener is explicitly typed so it knows it has a SearchRequest
SearchResult
from a query string
.import { from } from 'rxjs';
const searchEndpoint : Observable<SearchResult> = (query: string) => from([
{result: 'abba'},
{result: 'apple'}
]);
typescript-fsa
library, so let's use action creators for those types:const searchAction = actionCreatorFactory('search');
const searchRequestCreator = searchAction<SearchRequest>('request');
const resultCreator = searchAction<SearchResult>('result');
query
property will be its event's payload. In other words, we'd like the type of the handler's argument to be narrowed by the predicate. What must the signature of listen
be to allow this? public listen<SubType extends TBusItem>(
predicate: (item: TBusItem) => boolean,
handler: (item: SubType) => { ... }
isFish
function? One little change, and the answer is yes. Here's the type-guarded version:public listen<TMatchType extends TBusItem = TBusItem>(
predicate: (i: TBusItem) => i is TMatchType,
handler: (i: TMatchType) => { /* ... */ }
TMatchType
to the supertype of all bus items. Yet, if a predicate narrows the type, our handler will act as if its argument of that narrowed type..match
function on searchRequestCreator
provides a type predicate, not just a boolean return value! So when we plug it in, all the stars align:bus.listen(
searchRequestCreator.match,
(({ payload: { query } }) => // we have .query!
searchEndpoint(query).pipe(
tap(result => {
bus.trigger(resultCreator(result))
)
)
)
Action<any>
, so we could even return action of many types. But if we wanted to we could specify listen<SearchResult>
just to ensure we don't introduce an action we weren't expecting..no-inferrable-types
(link), and further take extra syntax/noise out of your way!