Introduction
AppRun is a JavaScript library for developing high-performance and reliable web applications using the elm inspired architecture, events, and components. You can write JavaScript code and use AppRun without strong typing. Strong typing is optional when using AppRun.
However, if you choose to use strong typing, AppRun ships with a type definition file, apprun.d.ts since the first release. You can use the type definition file for strong typing with TypeScript. Strong typing gives you many benefits such as compile-time validation of your code, which you will see below. This post is for those want to opted-in TypeScript and strong typing.
Let’s start with exploring where strong typing is applicable in AppRun applications.
AppRun Architecture
AppRun is an event system, a state management system, and a virtual DOM rendering system that runs the applications. We divide our application logic into three declarative parts and let AppRun manages and runs the applications.
State View and Update
All AppRun applications have three parts: state, view, and update. (Figure 1)
- State (a.k.a. Model) — the state of your application
- View — a function to display the state
- Update — a collection of event handlers to update the state
AppRun Event Life Cycle
We pass the three parts to the app.start function. AppRun starts to wait for AppRun events. When AppRun events are published, AppRun manages the event life cycle. (Figure 2)
The AppRun event life cycle has the following steps:
- AppRun dispatches the events to the event handlers defined in the update along with the current state.
- The event handlers create a new state based on the current state.
- AppRun passes the new state to the view function.
- The view function creates HTML or a Virtual DOM.
- AppRun renders the HTML/Virtual DOM to the screen
- AppRun calls the optional rendered function to complete the AppRun event life cycle.
Web programming is also event-driven. We can subscribe to the DOM events and then publish the AppRun events. Often it likes converting DOM events to AppRun events.
You can try the above application in the AppRun Playground to feel the AppRun event life-cycle.
Needs for Strong Typing
From the AppRun architecture and AppRun event life cycle, we can see there are two places require strong type: state and event names.
- From the initial state, the state passed between the view function and event handlers requires to be strongly typed
- The event names for publishing and subscribing Apprun events require to be strong typed
Strong Typing for State
Typed View
Throughout the AppRun event life cycle, we can make the state strongly typed. First, we import the View type from AppRun and apply it to the view function.
const view: View<number> => state => <div>...</div>
The View type imported from AppRun is a generic type. It makes the view function into a generic function, which let us define the type of its parameters. E.g., we define the view function to be View<number>, TypeScript recognizes the state parameter of the view function is number. (Figure 3)
Typed Update
Next, let’s import the Update type from AppRun and apply it to the update object.
const update: Update<number> = { ... }
The Update type imported from AppRun is also a generic type. It makes the event handler functions in the update object into a generic function. TypeScript recognizes the state parameter of the event handler functions are number. (Figure 4)
Typed Application
Finally, let’s make the application strongly type. The app.start is generic function already. If we give the app.start function a type, E.g., number, it means the state of the application is number, the view of the application is View<number> and the update of the application is Update<number>.
app.start<number>( ... )
If the type of state does not match the type assigned to the app.start function, TypeScript catches the type error. (Figure 5)
If the type of the view function does not match the type assigned to the app.start function, TypeScript catches the type error. (Figure 6)
If the type of the update object does not match the type assigned to the app.start function, TypeScript catches the type error. (Figure 7)
You have seen we have made the strongly typed state to the view function, the event handlers in the update object, and the application. Next, we will move to make strongly typed events.
Strong Typing for Events
AppRun update object is a named collection or dictionary of the event handlers. The event names are strings, such as +1 and -1. They are not typed, not checked by the compiler, and not IDE friendly. We can use TypeScript enum to define the event names.
Enum for Event Names
We define the Events enum and use it in the view functions and the update object. (Figure 8).
enum Events { inc = '+1', dec = '-1' }
You can see when we type ‘Events.’, Visual Studio code provides the intelliSense to help us typing. However, you can also see that the string +0 is still allowed in the update object. This is because the object index can only be number or string. Or as long as number or string is used, the compiler is happy and has no more type checking.
Update Tuple
Starting 1.19.0, AppRun introduced update as an array of tuples for defining AppRun event handlers. (Figure 9)
The event handler tuple has two fields: the event name and the event handler function.
With the tuple, we can use two advanced TypeScript types: union type and string literal type to create so call discriminated union. For more information about union type, string literal type, and discriminated union please refer to the TypeScript handbook.
Discriminated Union Typed Event Names
First, we define a union type called Events, which is made out of two string literal types, -1 and +1.
type Events = '-1' | '+1';
Then, we use Events type as the second generic of the Update type imported from AppRun and apply to update tuple.
const update: Update<number, Events> = [ ... ]
The event names are strongly typed. TypeScript can even provide code auto-completion (IntelliSense) to hint the event names. (Figure 10)
If the event name is not one of the strings defined in Events type, TypeScript catch the error. (Figure 11)
Typed Event Names in View
To make the event names in the view function strongly type, we need to make the event names of the application strongly type first. We can add the Events type to the app.start generic function.
app.start<number, Events>(...)
It makes the application can only trigger the events that are defined in the Events type. (Figure 12)
Because we have made the event names strongly typed TypeScript provides intelliSense to the event names the application. (Figure 12) TypeScript also catches the event name errors in the view function. (Figure 13)
So far, we have accomplished strong typing for AppRun global application. Next, we will implement strong typing for AppRun Components.
Strong Typing for Component
AppRun Component is like a scoped AppRun application. It has also three parts: state, view, and update, but the three parts are scoped inside the component. AppRun Component can be used as application building blocks like in many other frameworks and libraries.
AppRun Component is a generic class. We create our components by extending the AppRun Component class. We can add the state type and events type to the Component class, as well as to the view function and update tuple to make the component strongly typed so that TypeScript can provide IntelliSense and type validation while we are coding. (Figure 14)
Typed Event Decorator
In addition to the update object and update tuple, we can the on decorator to mark a function field to be an AppRun event handler. The on decorator is also generic. When we add the event type to the on decorator, TypeScript recognizes and enforces the event names to be the event type. (Figure 15)
Typed $on Directive
Finally, starting AppRun 1.18.0 AppRun introduced the Directive, a syntax sugar for JSX. E.g., the $on directive is useful for converting DOM events to AppRun events. In AppRun 1.19.0 the $on directive links the DOM events to class functions and triggers the AppRun event life cycle without explicit event names. And it is strongly typed. (Figure 16)
Conclusion
Thanks to TypeScript, we can have strongly typed state and event names in the AppRun application by using enums, generic types, and discriminated types. Starting AppRun 1.19.0, we also have an alternative approach, which is to use the $on directive to hide the event names so that we only need to focus on the state type.
Happy coding!