# Tags VS Class API ## TL;DR * Developer Experience * Fewer concepts to learn and less boilerplate * Encourages safer and more correct patterns * Optimized for colocation, refactoring and componentization * User Experience * Only interactive code is bundled for the browser * Server work is not re-executed on the browser * Performant fine-grained updates instead of vdom/diffing ## Examples ### Sync input.title to document.title <div class="comparison"> ```jsx /* tags */ <const/{ title } = input/> <effect() { document.title = title; }/> ``` ```jsx /* class */ class { onMount() { this.setTitle(); } onUpdate() { this.setTitle(); } setTitle() { if (this.previousTitle !== this.input.title) { document.title = this.previousTitle = this.input.title; } } } ``` </div> The tags api here is less code, but also less likely to introduce a bug. It's easy for developers to forget to guard updates in the class api, leading to unecessary work or bugs. ### Sync input.title to document.title (with cleanup) <div class="comparison"> ```jsx /* tags */ <const/{ title } = input/> <effect() { const originalTitle = document.title; document.title = title; $signal.onabort = () => { document.title = originalTitle; } }/> ``` ```jsx /* class */ class { onMount() { this.originalTitle = document.title; this.syncTitle(); } onUpdate() { this.syncTitle(); } onDestroy() { document.title = this.originalTitle; } syncTitle() { const { title } = this.input; if (title !== this.previousTitle) { document.title = this.previousTitle = title; } } } ``` </div> It's also easier in the tags api to colocate the cleanup functionality (when a component is destroyed) with the rest of the feature code. ### Use input to open and close a native dialog <div class="comparison"> ```jsx /* tags */ <const/{ open } = input/> <effect() { if (open) { el().showModal(); } else { el().close(); } }/> <dialog/el> <p>Greetings, one and all!</p> </dialog> ``` ```jsx /* class */ class { onMount() { this.syncOpen(); } onUpdate() { this.syncOpen(); } syncOpen() { const { open } = this.input; if (open !== this.previousOpen) { this.previousOpen = open; if (open) { this.getEl("el").showModal(); } else { this.getEl("el").close(); } } } } <dialog key="el"> <p>Greetings, one and all!</p> </dialog> ``` </div> In the class api with this example we spread things across multiple lifecycle events which can lead to problems when other code is in those lifecycle events. So now lets create a component that merges this dialog logic _and_ the title logic from earlier. ### Combined Examples <div class="comparison"> ```jsx /* tags */ // Title functionality <const/{ title } = input/> <effect() { const originalTitle = document.title; document.title = title; $signal.onabort = () => { document.title = originalTitle; } }/> // Dialog functionality <const/{ open } = input/> <effect() { if (open) { el().showModal(); } else { el().close(); } }/> <dialog/el> <p>Greetings, one and all!</p> </dialog> ``` ```jsx /* class */ class { onMount() { // Title functionality this.originalTitle = document.title; this.syncTitle(); // Dialog functionality this.syncOpen(); } onUpdate() { // Title functionality this.syncTitle(); // Dialog functionality this.syncOpen(); } onDestroy() { // Title functionality document.title = this.originalTitle; } // Dialog functionality syncOpen() { const { open } = this.input; if (open !== this.previousOpen) { this.previousOpen = open; if (open) { this.getEl("el").showModal(); } else { this.getEl("el").close(); } } } // Title functionality syncTitle() { if (this.previousTitle !== this.input.title) { document.title = this.previousTitle = this.input.title; } } } // Dialog functionality <dialog key="el"> <p>Greetings, one and all!</p> </dialog> ``` </div> The dialog and title related features are interspersed in the class api lifecycle methods. This makes it hard to tell where the code for either feature is and a challenge to refactor out those features later. With the tags API you can cut and paste either feature into a separate Marko file without having to move around any code. ## Take Away The tags api may look different from the class api, but it's mostly about moving "state" into variables exposed from tags. It allows colocating state/lifecycle logic with your markup which makes code easier to write, read, reason-about and refactor. ...And it means the compiler can better understand your code and deliver optimized experiences to the user. <style> .comparison { display: grid; grid-template-columns: repeat(auto-fit, minmax(25rem, 1fr)); gap: 1rem; pre { margin: 0; } } </style>