惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Cloudbric
Cloudbric
T
Threat Research - Cisco Blogs
Simon Willison's Weblog
Simon Willison's Weblog
AWS News Blog
AWS News Blog
P
Privacy & Cybersecurity Law Blog
H
Help Net Security
云风的 BLOG
云风的 BLOG
G
GRAHAM CLULEY
Spread Privacy
Spread Privacy
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
A
Arctic Wolf
Project Zero
Project Zero
Engineering at Meta
Engineering at Meta
P
Privacy International News Feed
Blog — PlanetScale
Blog — PlanetScale
Stack Overflow Blog
Stack Overflow Blog
M
MIT News - Artificial intelligence
The Register - Security
The Register - Security
Recorded Future
Recorded Future
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
C
Cisco Blogs
PCI Perspectives
PCI Perspectives
Recent Announcements
Recent Announcements
Martin Fowler
Martin Fowler
A
About on SuperTechFans
W
WeLiveSecurity
GbyAI
GbyAI
V
Vulnerabilities – Threatpost
The GitHub Blog
The GitHub Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
Check Point Blog
Y
Y Combinator Blog
月光博客
月光博客
Scott Helme
Scott Helme
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Google DeepMind News
Google DeepMind News
F
Fortinet All Blogs
U
Unit 42
G
Google Developers Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Threatpost
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Google Online Security Blog
Google Online Security Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Cisco Talos Blog
Cisco Talos Blog
博客园 - 三生石上(FineUI控件)
Hugging Face - Blog
Hugging Face - Blog
MongoDB | Blog
MongoDB | Blog
博客园 - 司徒正美

Minko Gechev's blog

skillgrade Unit Tests for AI Agent Skills You Should Care About AI Generative Development LLM-first Web Framework Reactive framework in ~200 lines of JavaScript Managing Angular Are LLMs going to replace us? Prefetching Heuristics Design Patterns in Open Source Projects - Part II Design Patterns in Open Source Projects - Part I What I learned doing 125 public talks - Part I Dynamic imports solve all the problems, right? 5 Angular CLI Features You Didn't Know About Angular quicklink Preloading Strategy Introducing Bazel Schematics for Angular CLI Building TypeScript Projects with Bazel Joining Google Playing Mortal Kombat with TensorFlow.js. Transfer learning and data augmentation Fast, extensible, configurable, and beautiful linter for Go Introducing Guess.js - a toolkit for enabling data-driven user-experiences on the Web Machine Learning-Driven Bundling. The Future of JavaScript Tooling. JavaScript Decorators for Declarative and Readable Code Follow Your Dream Career with Open Source. Personal Story. Redux Anti-Patterns - Part 1. State Management. Faster Angular Applications - Understanding Differs. Developing a Custom IterableDiffer Faster Angular Applications - Part 2. Pure Pipes, Pure Functions and Memoization Faster Angular Applications - Part 1. On Push Change Detection and Immutability Understanding Dynamic Scoping and TemplateRef Implementing a Simple Compiler on 25 Lines of JavaScript Developing Statically Typed Programming Language WebVR for a Gamified IDE 7 Angular Tools That You Should Consider Announcing ngrev - Reverse Engineering Tool for Angular Implementing Angular's Dependency Injection in React. Understanding Element Injectors. Distributing an Angular Library - The Brief Guide Angular in Production Ahead-of-Time Compilation in Angular 2.5X Smaller Angular 2 Applications with Google Closure Compiler Using Stripe with Angular (Deprecated) Building an Angular Application for Production Implementing the Missing "resolve" Feature of the Angular 2 Router Scalable Single-Page Application Architecture Managing ambient type definitions and dealing with the "Duplicate identifier" TypeScript error Static Code Analysis of Angular 2 and TypeScript Projects Enforcing Best Practices with Static Code Analysis of Angular 2 Projects ViewChildren and ContentChildren in Angular Dynamically Configuring the Angular's Router Angular 2 Hot Loader Lazy Loading of Route Components in Angular 2 Aspect-Oriented Programming in JavaScript Flux in Depth. Store and Network Communication. Using JSX with TypeScript Flux in Depth. Overview and Components. Even Faster AngularJS Data Structures Boost the Performance of an AngularJS Application Using Immutable Data - Part 2 Angular2 - First Impressions Build Your own Simplified AngularJS in 200 Lines of JavaScript Persistent State of ReactJS Component Boost the Performance of an AngularJS Application Using Immutable Data Processing Binary Protocols with Client-Side JavaScript Stream your Desktop to HTML5 Video Element Multi-User Video Conference with WebRTC Asynchronous calls with ES6 generators Binary Tree iterator with ES6 generators WebRTC chat with React.js AngularJS in Patterns (Part 3) AngularJS in Patterns (Part 2). Services. Using GitHub Pages with Jekyll! AngularJS in Patterns (Part 1). Overview of AngularJS Singleton in JavaScript Express over HTTPS What I get from the JavaScript MV* frameworks Remote Desktop Client with AngularJS and Yeoman The magic of $resource (or simply a client-side Active Record) AngularJS Inheritance Patterns AngularAOP v0.1.0 Advanced JavaScript at Sofia University AngularJS style guide Lazy prefetching of AngularJS partials VNC client on 200 lines of JavaScript Aspect-Oriented Programming with AngularJS CSS3 flipping effect Practical programming with JavaScript Why I should use publish/subscribe in JavaScript JavaScript, the weird parts Functional programming with JavaScript plainvm Looking for performance? Probably you should NOT use [].sort (V8) JavaScript image scaling ELang Caching CSS with localStorage Self-invoking functions in JavaScript (or Immediately Invoked Function Expressions) Asus N56VZ + Ubuntu 12.04 (en) Asus N56VZ + Ubuntu 12.04 Debian Squeeze + LXDE on Google Nexus S (or having some fun while suffering) HTML5 image editor Курсови проекти – ФМИ Carousel Gallery SofiaJS...
3 Tricks For Using Redux and Immutable.js with TypeScript
2018-01-18 · via Minko Gechev's blog

In this post, I’ll show you a few tricks which can make your life using Immutable.js and Redux with TypeScript easier. All the practices are inspired by my recent work on the project Rhyme.com.

Immutable Statically Typed Records

Let’s start with the definition of our store. For the purpose I prefer to use Immutable records because of two main reasons:

  • Immutable stores make the mutation more explicit and well organized.
  • Records allow convenient property access.

Now let’s suppose we have the domain object VirtualMachine. We can define a well-typed record using the following definition:

export interface IVirtualMachine {
  id: string;
  state: VMState;
  connection: Connection;
  screenshot: string;
}

const virtualMachine = RecordFactory<IVirtualMachine>({
  id: '',
  state: VMState.Pending,
  connection: new Connection({}),
  screenshot: '',
});

export class VirtualMachine extends virtualMachine implements IVirtualMachine {
  id: string;
  state: VMState;
  connection: Connection;
  screenshot: string;
  constructor(config: Partial<IVirtualMachine>) {
    super(config);
  }
}

Notice a few things:

  • We define the interface IVirtualMachine.
  • We define a virtualMachine record invoking RecordFactory with its type parameter set to IVirtualMachine.
  • We define a class called VirtualMachine which extends the record and accepts a single property of type Partial<IVirtualMachine> in its constructor.

Notice that we export IVirtualMachine and VirtualMachine but keep the virtualMachine record encapsulated inside the current module.

This way we can already instantiate a new VirtualMachine object and configure it using an object literal:

const vm = new VirtualMachine({ id: '123', state: VMState.Running });

Since the config parameter of the constructor is of type Partial<IVirtualMachine> we do not have to set all the properties of the IVirtualMachine interface. Of course, the drawback of this approach is that by mistake we can skip a configuration property and get the default value instead, which might not be the intended behavior. In this specific case, I prefer convenience over security.

In order to get a better idea of why the definition above is safer than a regular record, let’s take a look at the RecordFactory function:

interface Constructable<T> {
  new (...args: any[]): T;
}

interface StaticallyTypedRecord<T> extends Constructable<T> {
  get<K extends keyof T>(key: K): T[K];
  set<K extends keyof T, V extends T[K]>(key: K, value: V);
  withMutations(cb: (r: StaticallyTypedRecord<T>) => StaticallyTypedRecord<T>);
  setIn<K1 extends keyof T, V extends T[K1]>(keys: [K1], val: V);
  setIn<K1 extends keyof T, K2 extends keyof T[K1], V extends T[K1][K2]>(keys: [K1, K2], val: V);
  setIn<K1 extends keyof T, K2 extends keyof T[K1], K3 extends keyof T[K1][K2], V extends T[K1][K2][K3]>(
    keys: [K1, K2, K3],
    val: V
  );
  toJS(): T;
}

export const RecordFactory = <T>(seed: T): StaticallyTypedRecord<T> => {
  return (Record(seed) as any) as StaticallyTypedRecord<T>;
};

Here we have three definitions:

  • Constructable<T> is a generic interface which defines a type for a family of objects which can be instantiated with the operator new.
  • StaticallyTypedRecord<T> is another generic interface which extends the Constructable<T> interface and declares a bunch of methods. These are all methods in the prototype of the Immutable.Record abstraction.
  • RecordFactory is a generic function which accepts an object called seed of type T, passes it to Immutable.Record(seed) and ascribes the result to the type StaticallyTypedRecord<T>.

What’s the win here?

Let’s suppose we have our VM:

const vm = new VirtualMachine({ id: '123', screenshot: '...' });

Now if we want to access the VM properties we can:

vm.id;
vm.get('id');

// [ts] Argument of type '"ID"' is not assignable to
// parameter of type '"id" | "state" | "connection" | "screenshot" | "templateId"'.
vm.get('ID');

// [ts] Property 'ID' does not exist on type 'VirtualMachine'. Did you mean 'id'?
vm.ID;

Notice how we got a compile-time error when we tried to access the id property with vm.get('ID') since such key doesn’t exist in the record.

We are also going to get compile-time errors when trying to set a missing property:

// [ts] Argument of type '"scrnshot"' is not assignable to
// parameter of type '"id" | "state" | "connection" | "screenshot" | "templateId"'.
vm.set('scrnshot');

This way we’re able to catch mistakes caused by a misspelling of a property name much sooner - at compile-time!

Even further, now let’s suppose the Connection abstraction has an ipAddress field of type string. If we want to set it using the setIn method of the VirtualMachine record and we misspell it we’re going to get a compile-time error again!

// [ts]
// Argument of type '["connection", "ipaddress"]' is not assignable to parameter of type '["connection", "id" | "connectionId" | "type" | "get" | "set" | "withMu...'.
//   Types of property '1' are incompatible.
//     Type '"ipaddress"' is not assignable to type '"id" | "connectionId" | "ipAddress" | "get" | "set" | "withMutations" | "set...'.
vm.setIn(['connection', 'ipaddress'], '192.168.0.102');

Notice that currently, we support no more than three levels of nesting with setIn, this is due to the explicit definition above. A random level of nesting will be possible once the variadic types proposal gets introduced into TypeScript.

Action Creators

Alright, we now have a solid foundation for our store. Now, we can define the actions in the application. First, we can define an enum which contains all the action types:

enum ActionType {
  AddVirtualMachine = 'AddVM',
  RemoveVirtualMachine = 'RemoveVM'
}

As next step, we can define an interface for each action:

interface AddVirtualMachine {
  type: ActionType.AddVirtualMachine;
  vm: VirtualMachine;
}

interface RemoveVirtualMachine {
  type: ActionType.RemoveVirtualMachine;
  id: string;
}

type Action = AddVirtualMachine | RemoveVirtualMachine;

Notice that in the end we also define the type Action as the intersection between the AddVirtualMachine and RemoveVirtualMachine. This allows us to have very convenient support from the type checker in our reducers:

const virtualMachinesReducer = (action: Action, state: List<VirtualMachine> = initialState) {
  switch (a.type) {
    case ActionType.AddVirtualMachine:
    return state.push(action.vm);
    case ActionType.RemoveVirtualMachine:
    return state.delete(state.findIndex((v: VirtualMachine) => v.id === action.id));
    break;
  }
}

In this case, the type checker will know exactly what the type of the action argument will be in any of the branches of the switch statement, so we will get a great type checking support and auto-completion.

Interface vs Factory

A typical pattern in redux is to use factories for creating the action objects. Another typical pattern in the Angular community (more specifically, this part of it using ngrx) is to use classes, for example:

export class AddVirtualMachine {
  readonly type = ActionType.AddVirtualMachine;

  constructor(public vm: VirtualMachine) {}
}

export class RemoveVirtualMachine {
  readonly type = ActionType.RemoveVirtualMachine;

  constructor(public id: string) {}
}

When using classes we get few benefits:

  • Read-only action type because of the readonly modified in the property declaration.
  • Convenient static typing when passing the arguments to the constructor.

Let’s compare two syntaxes for dispatching actions - one using classes and another just object literals:

dispatch(new RemoveVirtualMachine(vm.id))
dispatch({ type: ActionType.RemoveVirtualMachine, id: vm.id })

We can clearly see that the second option is a bit longer and also couples the component with the action type. On the other hand, new need to introduce an additional middleware in redux because by default it accepts only “plain” objects:

const isPlainObject = require('lodash.isplainobject');

// Allows us have the privilege to create actions
// with classes instead of action creators.
export const plainObjectMiddleware = store => next => (a: any) => {
  if (!isPlainObject(a)) {
    const result: any = {};
    // We don't need hasOwnProperty and we know what we are doing.
    // tslint:disable
    for (let prop in a) {
      result[prop] = a[prop];
    }
    // tslint:enable
    a = result;
  }
  return next(a);
};

Improved Middleware Declaration

By default the TypeScript’s type definitions for redux come with the following middleware interface:

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

This way when we want to declare a middleware, let’s say:

import { Middleware } from 'redux';

export const sampleMiddleware = (): Middleware => {
  return ({ getState }) => next => action => {
    const state = getState();
    return next(a);
  };
};

This snippet has few problems:

  • getState will have type S which in this case is completely meaningless to us.
  • next has type Dispatch<S>, completely meaningless as well.
  • action has type any, which doesn’t bring any extra semantics.

To have a well-typed middleware we can apply the following definition:

export const sampleMiddleware = (): Middleware => {
  return ({ getState }: MiddlewareAPI<IStore>) => next => <A extends Action>(action: A) => {
    const state = getState();
    console.log(state);
    return next(a);
  };
};

This brings several improvements:

  • getState is well-typed because of MiddlewareAPI<IStore> type.
  • next has now type Dispatch<IStore>, thanks to the MiddlewareAPI<IStore> annotation.
  • action now brings some extra semantics, for instance, we can access its type property and in case of a conditional statement, we can use the provided by TypeScript control flow type inference.

Note that, depending on the version of the redux type definitions you use, you may need to take advantage of the open interfaces of TypeScript and improve the Middleware declaration:

import { MiddlewareAPI } from 'redux';

declare module 'redux' {
  export interface Middleware<T = any> {
    <U>(api: MiddlewareAPI<T>): (next: Dispatch<T>) => Dispatch<T>;
  }
}

Otherwise, you may get the error:

[ts]
Type '<S>({ getState }: MiddlewareAPI<IStore>) => (next: Dispatch<S>) => <A extends Action>(action: A) ...' is not assignable to type 'Middleware'.
  Types of parameters '__0' and 'api' are incompatible.
    Type 'MiddlewareAPI<S>' is not assignable to type 'MiddlewareAPI<IStore>'.
      Type 'S' is not assignable to type 'IStore'.

Conclusion

Redux provides a lovely architectural pattern which allows us to isolate the side-effects and keep most of our codebase pure. This way, the state management of our application gets much more predictable.

On top of this, using a statically typed language which fails early when the compiler finds a defect in our program brings significant improvements in our development process.

Combining Redux and TypeScript is a great middle ground between purity and practicality.