<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: TJF</title>
    <description>The latest articles on DEV Community by TJF (@tjfroll).</description>
    <link>https://dev.to/tjfroll</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F473603%2F519c91a8-5e86-4afe-8fd1-c84b604dcd74.jpg</url>
      <title>DEV Community: TJF</title>
      <link>https://dev.to/tjfroll</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tjfroll"/>
    <language>en</language>
    <item>
      <title>Class-based enums in Typescript: Are they worth the trouble?</title>
      <dc:creator>TJF</dc:creator>
      <pubDate>Mon, 05 Oct 2020 16:33:31 +0000</pubDate>
      <link>https://dev.to/tjfroll/class-based-enums-in-typescript-are-they-worth-the-trouble-3616</link>
      <guid>https://dev.to/tjfroll/class-based-enums-in-typescript-are-they-worth-the-trouble-3616</guid>
      <description>&lt;p&gt;One of Javascript’s most glaring omissions is first-class support for enums. Anyone who’s spent time in other languages knows the value of these simple structures, so it’s no surprise that one of Typescript’s few language additions is &lt;a href="https://www.typescriptlang.org/docs/handbook/enums.html"&gt;the enum&lt;/a&gt;. But Typescript’s implementation is quite basic — under the hood, they’re just objects, and this presents two significant pain points.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem 1: Iteration over Typescript enums requires converting to Array&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This might seem like a nitpick since ES6 gave us &lt;code&gt;Object.values&lt;/code&gt; — but if we consider the most common use cases for enums, there’s a constant need for iteration. Converting every time we need to populate a list or dropdown is a nuisance, but there’s a hidden cost: the resulting type is no longer an enum, but a string. This quickly leads to situations where string values taken directly from an enum won’t be accepted anywhere we expect an enum.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;enum&lt;/span&gt; &lt;span class="nx"&gt;Priority&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;HIGH&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MEDIUM&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;LOW&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Priority&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;priorities&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Priority&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Error: Type 'string' is not assignable to type 'Priority'&lt;/span&gt;
&lt;span class="nx"&gt;logError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;priorities&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even if we try to annotate upstream, the problem persists.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Error: Type 'string' is not assignable to type 'Priority'&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;priorities&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Priority&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Priority&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;priorities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Priority&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Priority&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our only option is to cast or assert, which defeats the purpose of working with strong types and creates unhelpful noise in our code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem 2: Typescript enums can’t be extended&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Enums in &lt;a href="https://docs.python.org/3/library/enum.html"&gt;Python&lt;/a&gt; or &lt;a href="https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html"&gt;Java&lt;/a&gt; are classes, which allows for custom attributes and methods directly on the enum. Some code philosophers argue this goes against the ethos of enums, which are meant to be static lists and nothing more. In my experience, however, enums don’t live in isolation from changes in the application, and they’re rarely static. Consider a few common requirements that any application might present:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Define a static sort order for iteration/display&lt;/li&gt;
&lt;li&gt;Custom &lt;code&gt;toString&lt;/code&gt; for localization or business logic&lt;/li&gt;
&lt;li&gt;Deprecating values without deleting&lt;/li&gt;
&lt;li&gt;Static subsets of values&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Class-based enums make it possible colocate these features with the enum itself. Classes may have fallen out of vogue in the shift to functional-reactive styles over the last few years, but this is a situation where classes could offer the more declarative approach. How might we accomplish this in Typescript?&lt;/p&gt;

&lt;h3&gt;
  
  
  Writing a class-based enum in Typescript
&lt;/h3&gt;

&lt;p&gt;Let’s start with the code, and then walk through its features.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Priority&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;asArray&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Priority&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

  &lt;span class="c1"&gt;// Values&lt;/span&gt;
  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;CRITICAL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Priority&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CRITICAL&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;HIGH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Priority&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;HIGH&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;MODERATE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Priority&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MODERATE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;MEDIUM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Priority&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MEDIUM&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;LOW&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Priority&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;LOW&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Subsets&lt;/span&gt;
  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;GENERATES_WARNINGS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;Priority&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CRITICAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Priority&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HIGH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;ACTIVE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Priority&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asArray&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;deprecated&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;deprecated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;deprecated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Priority&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asArray&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;valueOf&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;someLocalizationFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;valueOf&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Priority&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asArray&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we define the static collection &lt;code&gt;asArray&lt;/code&gt;, as this needs to be instantiated before any values can be added. Next, we create our Priority enums. Take note that &lt;code&gt;MEDIUM&lt;/code&gt; uses a second argument of &lt;code&gt;false&lt;/code&gt; to designate itself as &lt;code&gt;deprecated&lt;/code&gt;. If we look ahead to the constructor, we see that &lt;code&gt;deprecated&lt;/code&gt; is defaulted to false for other enums, and each new Priority is getting added to the static &lt;code&gt;asArray&lt;/code&gt; collection. After the individual values are created, we can create arbitrary subsets of values manually or by using other properties of the enum.&lt;/p&gt;

&lt;p&gt;Lastly, we have our accessors. Using &lt;code&gt;valueOf()&lt;/code&gt; and &lt;code&gt;toString()&lt;/code&gt; provides a consistent interface with ECMAScript’s objects and strings. For our order getter, we’re able to rely on the definition order of the values themselves (represented in &lt;code&gt;asArray&lt;/code&gt;), which provides a simple mechanism to define sort order.&lt;/p&gt;

&lt;p&gt;This gives us everything we need to start using our new enum class just like we would a Typescript enum:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Priority&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logCritical&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;logError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Priority&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CRITICAL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logAll&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Priority&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asArray&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logWarnings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Priority&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GENERATES_WARNINGS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks great! We’ve solved for many common use cases and preserved type safety. But has this been worth all the effort?&lt;/p&gt;

&lt;h3&gt;
  
  
  Class-based enums have significant drawbacks
&lt;/h3&gt;

&lt;p&gt;There are some issues with our implementation.&lt;/p&gt;

&lt;p&gt;As soon we start creating more enums, we’ll find ourselves attempting to factor out the common operations — but this proves to be challenging. We could make a base Enum class and move some functions like &lt;code&gt;toString()&lt;/code&gt; and &lt;code&gt;valueOf()&lt;/code&gt;. However, all of our static members are specific to each enum, and can’t be abstracted away. Type definitions also can’t moved to the base class, as we would need to use generics — but generics cannot be applied to static members. The end result is that even with some clever abstraction, there will still be a lot of duplicated code with each new enum.&lt;/p&gt;

&lt;p&gt;Another problem is that these enums require instantiation. If we’re ingesting raw data from an external source — say, some JSON with a property we want to annotate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;PrioritizedError&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Priority&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errorData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PrioritizedError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CRITICAL&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;// Invalid type&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can’t annotate &lt;code&gt;errorData&lt;/code&gt; with our &lt;code&gt;PrioritizedError&lt;/code&gt; interface as-is. We would first have to transform this data to ensure that &lt;code&gt;error.priority&lt;/code&gt; gets instantiated with our Priority enum.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Data we received from an API&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;originalData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CRITICAL&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// The transformed data now carries our enum&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transformedData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Priority&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;originalData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a gap between the original data and the data used by the application. We face the reverse issue anywhere we might be sending data to an external source, requiring another transformation back into string format. This introduces additional layers in a pipeline that might otherwise have been seamless. &lt;strong&gt;Every time we touch data is another opportunity for bugs and corruption&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This transformation issue isn’t just isolated to file read/writes or API requests. Third-party libraries won’t accept our enums, so we may have to transform back-and-forth within individual components. It’s these handoffs that are particularly dangerous, as external dependencies might not warn us when we’ve failed to provide data in the expected format.&lt;/p&gt;




&lt;p&gt;So, are class-based enums worth the effort? As with most things, I think the answer is a rock-solid “it depends”.&lt;/p&gt;

&lt;p&gt;These implementations are certainly not optimal — I’m sure there’s plenty that could be improved on, leveraging some more advanced features in Typescript. Some of these improvements might properly address scalability / DRY issues. Still, the decision mostly comes down to your application’s needs.&lt;/p&gt;

&lt;p&gt;If you find that some of your enums tend to come with tightly-coupled business logic or you need a structure that flexibly supports additional properties and metadata, this may be a useful pattern. But if you just want easy iteration and don’t need any custom methods, class enums are probably overkill. I would exercise particular caution in situations where it becomes necessary to add new transformations.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>A short &amp; terrible history of CSS: Does it ever get better?</title>
      <dc:creator>TJF</dc:creator>
      <pubDate>Tue, 29 Sep 2020 15:42:52 +0000</pubDate>
      <link>https://dev.to/tjfroll/a-short-terrible-history-of-css-does-it-ever-get-better-41mj</link>
      <guid>https://dev.to/tjfroll/a-short-terrible-history-of-css-does-it-ever-get-better-41mj</guid>
      <description>&lt;p&gt;If you fell into a coma in 2005 and woke up today (yikes), what would you need to learn to catch up with modern CSS?&lt;/p&gt;

&lt;p&gt;The first stop would have to be &lt;a href="https://css-tricks.com/snippets/css/a-guide-to-flexbox/"&gt;flexbox&lt;/a&gt; and &lt;a href="https://css-tricks.com/snippets/css/complete-guide-grid/"&gt;grid&lt;/a&gt; — arguably the most important new features of the language. Well-implemented with a strong conceptual backbone and a consistent vocabulary, they (mostly) take the guesswork out of layout. We still can’t do proper &lt;a href="https://css-tricks.com/piecing-together-approaches-for-a-css-masonry-layout/"&gt;masonry layouts&lt;/a&gt; and it’s hard to remember the difference between &lt;code&gt;align-items&lt;/code&gt; and &lt;code&gt;align-content&lt;/code&gt;, but at least we’ve left &lt;code&gt;float&lt;/code&gt; behind.&lt;/p&gt;

&lt;p&gt;HTML5 arrived with semantic elements, promising improved accessibility and machine readability. But more questions than answers resulted. Vague element names rooted in the world of print media were added: &lt;code&gt;aside&lt;/code&gt;, &lt;code&gt;section&lt;/code&gt;, and &lt;code&gt;details&lt;/code&gt; appeared without a clear purpose in the application development landscape. Many of these elements brought a host of new default browser styling, completely undocumented. &lt;a href="https://meyerweb.com/eric/tools/css/reset/"&gt;CSS resets&lt;/a&gt; emerged to cleanse the palate and bring consistency.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://css-tricks.com/css-media-queries/"&gt;Media queries&lt;/a&gt; and &lt;a href="https://css-tricks.com/the-lengths-of-css/"&gt;relative units&lt;/a&gt; deserve mention for enabling responsive design amidst the switch to mobile, but the honor is dubious. Breakpoints and font-based scaling are crude tools in the fight to support a vast range of devices that come in every shape, size, and pixel density. Browsers have stepped in to help with device simulators, while frameworks like &lt;a href="https://getbootstrap.com/"&gt;Bootstrap&lt;/a&gt; give folks a reasonable starting point — at the cost of injecting huge stylesheets that are difficult to customize or override. Collectively, humanity has probably lost centuries in the time spent struggling to make minor tweaks to Bootstrap forms.&lt;/p&gt;

&lt;p&gt;On the build side, &lt;a href="https://sass-lang.com/"&gt;SASS&lt;/a&gt; and &lt;a href="http://lesscss.org/"&gt;LESS&lt;/a&gt; have made it possible for CSS to support larger websites and applications, pretty much entirely by the virtues of nested selectors and reusable variables. Combined with hot reloading from tools like webpack and in-place editing with autocomplete through browser dev tools, the development flow for debugging and experimentation requires far less context-switching than it once did.&lt;/p&gt;

&lt;p&gt;But the &lt;a href="http://lesscss.org/functions/#functions-overview"&gt;advanced features&lt;/a&gt; in preprocessors enable fragile anti-patterns that are difficult to debug and even harder to read. There are no established frameworks for managing stylesheets in large applications — inconsistent spaghetti is inevitable. &lt;a href="https://stylelint.io/"&gt;Linters&lt;/a&gt; can offset some of these maintainability issues, but the &lt;a href="https://css-tricks.com/regarding-css-global-scope/"&gt;global scope&lt;/a&gt; is inexorable. One typo in one rule can mangle any application’s display, and no test will be the wiser.&lt;/p&gt;

&lt;p&gt;Thus, we find ourselves at the end of the timeline with &lt;a href="https://css-tricks.com/css-modules-part-1-need/"&gt;CSS Modules&lt;/a&gt; and &lt;a href="https://styled-components.com/"&gt;CSS-in-JS&lt;/a&gt;. These solve the scoping problem — a huge, necessary scalability win — but introduce new tribulations for reusability. Legibility in the DOM suffers as generated class names obfuscate intent. Performance drops as two decades of browser optimizations for rendering cascading stylesheets goes out the window. Separation of concerns dissolves as each component file now hosts a hundred lines of &lt;code&gt;border-blah&lt;/code&gt; styles before the code even begins.&lt;/p&gt;

&lt;p&gt;Congratulations! We’re all caught up on fifteen years of CSS. In total, we got two excellent specs for layout and a handful of tools that all fight each other. This a bit hyperbolic, of course (who could forget the indispensable &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@page/marks"&gt;page marks&lt;/a&gt; spec?), but we’ve mostly traded one set of problems for another. Just as we left behind the eternal struggle with Internet Explorer, mobile devices came in to detonate the possibility space.&lt;/p&gt;

&lt;p&gt;The language simply hasn’t kept up — but why? CSS renders the entire web! Millions of programmers, designers, and unfortunate interns work with CSS every day. It’s worth our time to make this experience better; our creative capacity is limited by the quality of our tools.&lt;/p&gt;

&lt;p&gt;Enough complaining, though. We know CSS is a bad experience — water is blue, the sky is wet. No one is coming to save us. What could we do to change this? Let’s think big. Full-throttle, pie-in-the-sky stuff.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Syntactic sugar.&lt;/strong&gt; Nested and &lt;a href="http://lesscss.org/features/#parent-selectors-feature"&gt;parent selectors&lt;/a&gt; supported in native CSS would be a great start; anything to lessen the need for preprocessors just to get the most basic quality of life improvements.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First-class IDE support.&lt;/strong&gt; There’s plugins for &lt;a href="https://marketplace.visualstudio.com/items?itemName=Zignd.html-css-class-completion"&gt;intellisense and code peeking&lt;/a&gt; when writing classes and styles in HTML and JSX, but they’re stopped cold by any number of tools and dependencies that abstract styles away from markup. There’s autocomplete for writing directly in stylesheets, but these don’t provide context or definitions like a fully-featured language might offer. Fluency with CSS shouldn’t demand memorization of its entire vocabulary and logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better specificity resolution.&lt;/strong&gt; A relentless gotcha through the CSS experience is that collisions in &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity"&gt;specificity&lt;/a&gt; are resolved by using the last defined rule in the stylesheet. There’s nothing wrong with this on its own, except that a) it’s &lt;em&gt;so easy&lt;/em&gt; to write selectors with equal specificity and b) order is not easily deterministic. With file concatenation and imported stylesheets from dependencies, we can’t rely on definition order. &lt;code&gt;!important&lt;/code&gt; provides an escape hatch, but it’s an excessively nuclear option that breaks the most fundamental concept in CSS. Instead, a decorator applied at the selector or individual property level to give priority at its existing specificity could address this gap. More radically, we could define our own rules for precedence based on the needs of the application.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customize the cascade.&lt;/strong&gt; The rules of &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Cascade_and_inheritance"&gt;inheritance&lt;/a&gt; are countless, obtuse, and the single greatest source of confusion. Why does &lt;code&gt;text-emphasis&lt;/code&gt; inherit but &lt;code&gt;text-decoration&lt;/code&gt; does not? I’m sure there’s a valid reason somewhere in the story behind these specs — but as a consumer of this API, I don’t care. I need consistency and predictability. What if we could eliminate the guesswork and dictate inheritance per-property or per-selector?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validation between styles and components.&lt;/strong&gt; In React Native, you can get an immediate warning if you try to add text style properties to a non-text component — saving countless hours of fruitless tinkering. Currently, this kind of validation isn’t supported in HTML because any tag can (theoretically) support any property. If we could create and specify a style type for any component, this could prevent us from using invalid properties or values.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It turns out that someone else has already thought of this future: enter the &lt;a href="http://houdini.glitch.me/"&gt;CSS Houdini&lt;/a&gt; specifications, which offer direct access to the rendering engine. It enables custom parsing of selectors — a path towards syntactic sugar. It supports &lt;a href="http://houdini.glitch.me/custom-properties"&gt;custom properties&lt;/a&gt; — a mechanism for controlling inheritance and default values. It provides &lt;a href="http://houdini.glitch.me/typed-om"&gt;typed object models&lt;/a&gt; for properties and values — style validation becomes possible!&lt;/p&gt;

&lt;p&gt;When I started writing this piece, I had no idea this spec was under development— and I suspect this is true for many others. This &lt;a href="https://www.youtube.com/watch?v=ICbNc7ax1ME"&gt;great walkthrough by Tab Atkins&lt;/a&gt; currently has 32 views. &lt;a href="https://www.youtube.com/watch?v=FYWZrLpN5Po"&gt;This talk by Sam Richards&lt;/a&gt; has 900. Sure, &lt;a href="https://twitter.com/search?q=houdini%20css&amp;amp;src=typed_query"&gt;some people are aware and excited&lt;/a&gt; — but the overwhelming perception I see among developers is that CSS as a whole is just legacy baggage, an atrophied limb of web development waiting to fall off.&lt;/p&gt;

&lt;p&gt;I know better than to believe an incomplete spec — one that’s still in proposal and working draft phase after several years — is going to provide relief any time soon. Done properly, however, CSS Houdini could elevate the work of front-end design and UX development into something that is both more predictable and more expressive. After decades of spaghetti specifications and unreliable tooling, this is something worth being excited about in the ecosystem of web design.&lt;/p&gt;

</description>
      <category>css</category>
      <category>webdev</category>
      <category>design</category>
    </item>
    <item>
      <title>Migrating to Typescript: Keeping it smooth &amp; steady</title>
      <dc:creator>TJF</dc:creator>
      <pubDate>Tue, 22 Sep 2020 22:05:12 +0000</pubDate>
      <link>https://dev.to/tjfroll/migrating-to-typescript-keeping-it-smooth-steady-202m</link>
      <guid>https://dev.to/tjfroll/migrating-to-typescript-keeping-it-smooth-steady-202m</guid>
      <description>&lt;p&gt;In the process of transitioning two mature frontend codebases (totalling ~5k files / 300k lines) from Javascript to Typescript, I learned a bit about the process that might be helpful to anyone considering this change in their stack. Much of this advice is not really specific to Typescript, and could possibly be generalized to any language or framework migration — but I’ll stick to what I know.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Write all new code in Typescript — sometimes
&lt;/h3&gt;

&lt;p&gt;Don’t create extra work for your team down the road by pushing more code to be migrated later — as soon as Typescript has been introduced into your stack, every pull request going forward should be written in TS. But how strict should this be? It’s easy enough to write new files and components in TS, but what about changes to existing files? If a pull request changes just one line in a file, should the whole file be converted?&lt;/p&gt;

&lt;p&gt;Requiring devs to migrate every changed file can be a morale and productivity killer. Even the smallest bug fixes become chores, and PRs for new features are impossible to review as the diff often interprets migrated files as being new. On the other hand, if the migration isn’t required, the work might never get done. This is particularly true for older files that aren’t edited often. Find the balance that makes sense for your team and keeps the migration moving forward.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Convert common files and generic components first
&lt;/h3&gt;

&lt;p&gt;You’ll get the most immediate benefit from Typescript’s features by targeting the files most likely to be imported in any new feature work. If these shared components aren’t converted, you’re building up tech debt in all of your new TS files. Get ahead of this and enjoy the autocomplete and instant errors on all your new files.&lt;/p&gt;

&lt;p&gt;Use the most accurate type available for all the properties of the API on these core components. It can be challenging to find the exact type for functions, callbacks, and events (especially for React events, DOM properties, or third-party dependencies), but it will save you trouble downstream in your consumers. Going slowly to get the core components right will save you time overall.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Communicate with your team about upcoming migrations
&lt;/h3&gt;

&lt;p&gt;Migrations can sometimes create huge diffs that lead to nightmarish merge conflicts if multiple devs are working in the same file. Don’t set yourself up for pointless hours of frustrating and buggy conflict resolutions. Check in with your team before starting a migration. If there’s significant activity in that area of the code, consider postponing the work or basing your branch from theirs.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Resist the urge to refactor
&lt;/h3&gt;

&lt;p&gt;When migrating any large codebase, you’ll inevitably find yourself opening ancient files with terrible debt. &lt;em&gt;Gross, look at all these deprecated patterns and inefficiencies. Oh, you could totally write this in a third of the lines. No one’s using this function any more, right? Snip-snip.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It’s hubris. Don’t do it. You’re going to create regressions. Be good to yourself and your team. Don’t stress QA out.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Avoid maintaining parallel versions of the same component
&lt;/h3&gt;

&lt;p&gt;Sometimes, migrating a complex or highly-used component is just too fraught to risk an in-place conversion — in such situations, the only choice might be to duplicate, convert, and gradually swap out the old for the new throughout your codebase. But as long as both versions exist, there will be confusion about which one to use; the old one will get imported by habit or copy-paste; bug fixes and enhancements will need to be applied to both; behavior may even drift over time.&lt;/p&gt;

&lt;p&gt;Minimize the amount of time spent in this situation — when adding a duplicate TS component, prioritize this area of migration. Name your files and components clearly to avoid confusion when importing.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Account for migrations in estimations and planning
&lt;/h3&gt;

&lt;p&gt;When providing time or point estimates for future work, add another 20% if you plan on migrating the code first. Plan out your migrations; if you know that major work is upcoming in one area, get the migration done early. Don’t leave it as an invisible or unexpected cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Leave a comment whenever you use &lt;code&gt;ts-ignore&lt;/code&gt; or &lt;code&gt;any&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Some of your third-party dependencies are going to give you incorrect type definitions that leave you scratching your head for days. An obscure corner case with generic tuples will send you down a Stack Overflow wormhole for 5 hours. The greatest good is to keep moving forward and leave good comments whenever you’re forced into a hack.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Don’t let it linger
&lt;/h3&gt;

&lt;p&gt;One of the possible migration routes for Typescript is to progressively increase its strictness; turning off these rules can be helpful while getting started, but delaying this for long will hold your team back from the full benefits of the language. It can be a high cost initially as you get the necessary dependencies migrated so even a single component can be fully typed, but it’s better than facing a massive diff on the horizon once you do enable it.&lt;/p&gt;

&lt;p&gt;There will be boom and bust periods in the speed of change, but mid-migration tech debt is exhausting to deal with. You can never remember which component has been converted already or not. You still can’t trust the IDE to catch some of the biggest mistakes in your code. Why did we even start this stupid transition in the first place?&lt;/p&gt;

&lt;p&gt;But the virtues of a strongly typed language grow exponentially as more of the codebase is converted. The first time you refactor a core component and the compiler immediately tells you down to the line and column where the fixes are needed, it’s all worth it. Most other languages have had this experience for decades, of course, but not Javascript.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good luck! It’s a lot of work, but it will eventually pay off.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>refactorit</category>
      <category>codequality</category>
    </item>
  </channel>
</rss>
