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

推荐订阅源

N
News and Events Feed by Topic
GbyAI
GbyAI
博客园 - Franky
宝玉的分享
宝玉的分享
Blog — PlanetScale
Blog — PlanetScale
Google DeepMind News
Google DeepMind News
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
人人都是产品经理
人人都是产品经理
Microsoft Azure Blog
Microsoft Azure Blog
The Register - Security
The Register - Security
腾讯CDC
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
I
InfoQ
The Cloudflare Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Engineering at Meta
Engineering at Meta
MongoDB | Blog
MongoDB | Blog
有赞技术团队
有赞技术团队
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
F
Full Disclosure
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Vercel News
Vercel News
博客园 - 【当耐特】
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
Schneier on Security
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Project Zero
Project Zero
量子位
M
MIT News - Artificial intelligence
Stack Overflow Blog
Stack Overflow Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
美团技术团队
Attack and Defense Labs
Attack and Defense Labs
C
Cybersecurity and Infrastructure Security Agency CISA
T
The Blog of Author Tim Ferriss
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
T
Troy Hunt's Blog
Google Online Security Blog
Google Online Security Blog
罗磊的独立博客
P
Proofpoint News Feed
Schneier on Security
Schneier on Security
Spread Privacy
Spread Privacy
S
SegmentFault 最新的问题
L
LINUX DO - 最新话题
Simon Willison's Weblog
Simon Willison's Weblog
爱范儿
爱范儿
博客园 - 聂微东
A
About on SuperTechFans
PCI Perspectives
PCI Perspectives
D
Docker

oida.dev | TypeScript, Rust

TypeScript's `erasableSyntaxOnly` Flag Unsafe for work Tokio: Macros Tokio: Channels Tokio: Getting Started Network Applications on the Tokio Stack Remake, Remodel, Reduce. The `never` type and error handling in TypeScript 5 Inconvenient Truths about TypeScript Refactoring in Rust: Introducing Traits Refactoring in Rust: Abstraction with the Newtype Pattern Announcing the TypeScript Cookbook TypeScript: Iterating over objects The road to universal JavaScript 10 years of oida.dev Rust: Tiny little traits The TypeScript converging point How not to learn TypeScript Getting started with Rust Introducing Slides and Coverage TypeScript: The humble function overload TypeScript + React: Children types are broken TypeScript: In defense of any Rust: Enums to wrap multiple errors Dissecting Deno Error handling in Rust TypeScript: Unexpected intersections Upgrading Node.js dependencies after a yarn audit TypeScript: Array.includes on narrow types TypeScript + React: Typing Generic forwardRefs shared, util, core: Schroedinger's module names Learning Rust and Go TypeScript: Narrow types in catch clauses TypeScript: Low maintenance types Tidy TypeScript: Name your generics Tidy TypeScript: Prefer type aliases over interfaces Tidy TypeScript: Prefer union types over enums My new book: TypeScript in 50 Lessons Go Preact! ❤️ this in JavaScript and TypeScript TypeScript and ECMAScript Modules TypeScript + React: Why I don't use React.FC TypeScript + React: Component patterns TypeScript: Augmenting global and lib.dom.d.ts Vite with Preact and TypeScript TypeScript: Union to intersection type 11ty: Generate Twitter cards automatically Are large node module dependencies an issue? TypeScript: Variadic Tuple Types Preview TypeScript: Improving Object.keys Remake, Remodel. Part 4. TypeScript + React: Typing custom hooks with tuple types TypeScript: Assertion signatures and Object.defineProperty TypeScript: Check for object properties and narrow down type Boolean in JavaScript and TypeScript void in JavaScript and TypeScript Symbols in JavaScript and TypeScript Why I use TypeScript TypeScript + React: Extending JSX Elements TypeScript: Validate mapped types and const context TypeScript: Match the exact object shape TypeScript: The constructor interface pattern Streaming your Meetup - Part 4: Directing and Streaming with OBS Streaming your Meetup - Part 3: Speaker audio Streaming your Meetup - Part 2: Speaker video Streaming your Meetup - Part 1: Basics and Projector TypeScript and React Guide: Added a new styles chapter TypeScript and React Guide: Added a new render props chapter TypeScript and React: Styles and CSS TypeScript and React TypeScript and React Guide: Added a new prop types chapter TypeScript without TypeScript -- JSDoc superpowers TypeScript: Mapped types for type maps JAMStack vs serverless web apps The Unsung Benefits of JAMStack Sites TypeScript: Ambient modules for Webpack loaders My most favourite talks in 2018 TypeScript and React Guide: Added a new context chapter TypeScript: Built-in generic types TypeScript: Type predicates JSX is syntactic sugar TypeScript and React Guide: Added a new hooks chapter Getting your CfP application right FAQ on our Angular Connect Talk: Automating UI development TypeScript and Substitutability Debugging Node.js apps in TypeScript with Visual Studio Code From Medium: Deconfusing Pre- and Post-processing From Medium: PostCSS misconceptions Saving and scraping a website with Puppeteer Cutting the mustard - 2018 edition Wordpress as CMS for your JAMStack sites My most favourite podcast episodes in 2017 My most favourite talks in 2017 My most favourite books in 2017 The Best Request Is No Request, Revisited Not so hidden figures - Organizing ScriptConf My podcast journey to ScriptCast Grid layout, grid layout everywhere! #scriptconf and #devone Object streams in Node.js
Tidy TypeScript: Avoid traditional OOP patterns
2020-11-24 · via oida.dev | TypeScript, Rust

This is the third article in a series of articles where I want to highlight ways on how to keep your TypeScript code neat and tidy. This series is heavily opinionated and you might find out things you don’t like. Don’t take it personally, it’s just an opinion.

This time we look at POOP, as in “Patterns of Object-Oriented Programming”. With traditional OOP I mostly mean class-based OOP, which I assume the vast majority of developers think of when talking OOP. If you come from Java or C#, you might see a lot of familiar constructs in TypeScript, which might end up as false friends in the end.

Avoid static classes #

One thing I see a lot from people who worked a lot with Java is their urge to wrap everything inside a class. In Java, you don’t have any other options as classes are the only way to structure code. In JavaScript (and thus: TypeScript) there are plenty of other possibilities that do what you want without any extra steps. One of those things is static classes or classes with static methods, a true Java pattern.

// Environment.ts

export default class Environment {
private static variableList: string[] = []
static variables(): string[] { /* ... */ }
static setVariable(key: string, value: any): void { /* ... */ }
static getValue(key: string): unknown { /* ... */ }
}

// Usage in another file
import * as Environment from "./Environment";

console.log(Environment.variables());

While this works and is even – sans type annotations – valid JavaScript, it’s way too much ceremony for something that can easily be just plain, boring functions:

// Environment.ts
const variableList: string = []

export function variables(): string[] { /* ... */ }
export function setVariable(key: string, value: any): void { /* ... */ }
export function getValue(key: string): unknown { /* ... */ }

// Usage in another file
import * as Environment from "./Environment";

console.log(Environment.variables());

The interface for your users is exactly the same. You can access module scope variables just the way you would access static properties in a class, but you have them module-scoped automatically. You decide what to export and what to make visible, not some TypeScript field modifiers. Also, you don’t end up creating an Environment instance that doesn’t do anything.

Even the implementation becomes easier. Check out the class version of variables():

export default class Environment {
private static variableList: string[] = []
static variables(): string[] {
return this.variableList;
}
}

As opposed to the module version:

const variableList: string = []

export function variables(): string[] {
return variableList;
}

No this means less to think about. As an added benefit, your bundlers have an easier time doing tree-shaking, so you end up only with the things you actually use:

// Only the variables function and variablesList 
// end up in the bundle
import { variables } from "./Environment";

console.log(variables());

That’s why a proper module is always preferred to a class with static fields and methods. That’s just an added boilerplate with no extra benefit.

Avoid namespaces #

As with static classes, I see people with a Java or C# background clinging on to namespaces. Namespaces are a feature that TypeScript introduced to organize code long before ECMAScript modules were standardized. They allowed you to split things across files, merging them again with reference markers.

// file users/models.ts
namespace Users {
export interface Person {
name: string;
age: number;
}
}

// file users/controller.ts

/// <reference path="./models.ts" />
namespace Users {
export function updateUser(p: Person) {
// do the rest
}
}

Back then, TypeScript even had a bundling feature. It should still work to this day. But as said, this was before ECMAScript introduced modules. Now with modules, we have a way to organize and structure code that is compatible with the rest of the JavaScript ecosystem. So that’s a plus.

So what do we need namespaces for?

Extending declarations #

Namespaces are still valid if you want to extend definitions from a third party dependency, e.g. that lives inside node modules. Some of my articles use that heavily. For example if you want to extend the global JSX namespace and make sure img elements feature alt texts:

declare namespace JSX {
interface IntrinsicElements {
"img": HTMLAttributes & {
alt: string,
src: string,
loading?: 'lazy' | 'eager' | 'auto';
}
}
}

Or if you want to write elaborate type definitions in ambient modules. But other than that? There is not much use for it anymore.

Needless namespaces #

Namespaces wrap your definitions into an Object. Writing something like this:

export namespace Users {
type User = {
name: string;
age: number;
}

export function createUser(name: string, age: number): User {
return { name, age }
}
}

emits something very elaborate:

export var Users;
(function (Users) {
function createUser(name, age) {
return {
name, age
};
}
Users.createUser = createUser;
})(Users || (Users = {}));

This not only adds cruft but also keeps your bundlers from tree-shaking properly! Also using them becomes a bit wordier:

import * as Users from "./users";

Users.Users.createUser("Stefan", "39");

Dropping them makes things a lot easier. Stick to what JavaScript offers you. Not using namespaces outside of declaration files makes your code clear, simple, and tidy.

Avoid abstract classes #

Abstract classes are a way to structure a more complex class hierarchy where you pre-define some behavior, but leave the actual implementation of some features to classes that extend from your abstract class.

abstract class Lifeform {
age: number;
constructor(age: number) {
this.age = age;
}

abstract move(): string;
}

class Human extends Lifeform {
move() {
return "Walking, mostly..."
}
}

It’s for all sub-classes of Lifeform to implement move. This is a concept that exists in basically every class-based programming language. The problem is, JavaScript isn’t traditionally class-based. For example, an abstract class like below generates a valid JavaScript class, but is not allowed to be instantiated in TypeScript:

abstract class Lifeform {
age: number;
constructor(age: number) {
this.age = age;
}
}

const lifeform = new Lifeform(20);
// ^ 💥 Cannot create an instance of an abstract class.(2511)

This can lead to some unwanted situations if you’re writing regular JavaScript but rely on TypeScript to provide you the information in form of implicit documentation. E.g. if a function definition looks like this:

declare function moveLifeform(lifeform: Lifeform);
  • You or your users might read this as an invitation to pass a Lifeform object to moveLifeform. Internally, it calls lifeform.move().
  • Lifeform can be instantiated in JavaScript, as it is a valid class
  • The method move does not exist in Lifeform, thus breaking your application!

This is due to a false sense of security. What you actually want is to put some pre-defined implementation in the prototype chain, and have a contract that definitely tells you what to expect:

interface Lifeform {
move(): string
}

class BasicLifeForm {
age: number;
constructor(age: number) {
this.age = age
}
}

class Human extends BasicLifeForm implements Lifeform {
move() {
return "Walking"
}
}

The moment you look up Lifeform, you can see the interface and everything it expects, but you hardly run into a situation where you instantiate the wrong class by accident.

Bottom line #

TypeScript included bespoke mechanisms in the early years of the language, where there was a severe lack of structuring in JavaScript. Now that JavaScript reached a different language of maturity, it gives you enough means to structure your code. So it’s a really good idea to make use of what’s native and idiomatic: Modules, objects, and functions. Occasional classes.

Related Articles