Currently, I've been developing a TypeScript framework for creating single-page applications (SPAs).
I decided to do it because I want to improve my knowledge of TypeScript language, create a nice open-source project, and learn how to apply some concepts that I've found in other frameworks, like Angular and React.
This challenge is helping me learn how to use a lot of things that I always wanted to understand better: decorators, template and object observers, some design patterns, command-line applications with JavaScript, and much more.
And I'm very excited to share with you some features that I'm already using in my project after learning more about them.
Permalink1) Template observers
Sometimes, we need to know when something in DOM changes to run a proper script after that action. MutationObserver
comes to help us do that.
PermalinkUsage
First, we define the target element:
const target = document.getElementById('root');
Now, we create a configuration object for the things that will be observed:
const config = {
attributes: true, // eg. "src", "class"
childList: true, // target chidren
characterData: true // text content
};
Finally, we define a callback function for changes, initialize MutationObserver
, and start observing the target element:
// mutations handler
const callback = (mutationsList: MutationRecord[], observer: MutationObserver) => {
// handler
}
// observer (still not observing)
const rootObserver = new MutationObserver(callback);
// start observing the target
rootObserver.observe();
It's possible to stop observing changes when the observer is no longer needed:
rootObserver.disconnect();
PermalinkHow am I using it in my framework?
When an asynchronous operation inserts new components in the current view, the observer callback injects the necessary template and style code inside them.
Permalink2) Proxy objects
Proxy is a mediator between the program and a target object.
PermalinkUsage
See an example:
const blog = { name: "Blog do Lipe ๐๐ป", username: "luizfilipezs" };
When we ask for the property name
, our program will get it directly from the object blog
, as it follows:
blog.name; // "Blog do Lipe ๐๐ป"
We could change that behavior by using a Proxy
:
const blogProxy = new Proxy(blog, {
get: (target, name) => {
console.log(target, name);
return name in target ? target[name] : null;
}
set: (target, key, value) => {
console.log(key, value);
return value;
}
});
The second parameter of the Proxy
constructor
is an object that handles the operations we want to control. In the example above, we changed the way properties are read and set in blog
.
Now, this is what happens when we use blogProxy
:
blogProxy.name; // Prints "Blog do Lipe ๐๐ป"
blogProxy.username = "lipe"; // Prints "username" and "lipe"
PermalinkHow am I using it in my framework?
I currently use this approach to control object changes and update templates in real-time.
Permalink3) Decorators
Here I'll talk about decorators for types, but they also can be used with basic methods and even function parameters. See all documentation about decorators here.
Using decorators allows us to handle a given type before it gets its instances created.
PermalinkUsage
Take a look at the example below:
@Component({
selector: "home-component",
template: "<p>Home component works!</p>"
})
export class HomeComponent {
// Component logic goes here...
foo: string = "bar";
bar: string = "baz";
}
@Component
decorator is a function. So, behind the scenes, when the code above runs, Component
is called and handles the class we passed. That decorator could be written like this:
// Represents a class
type Type<T> = { new (...args: any[]): T };
// Things a component must have
interface ComponentProps {
foo: string;
bar: string;
}
// Object we pass inside the decorator function
interface ComponentConfig {
selector?: string;
template: string;
}
// Component decorator
export const Component = (config: ComponentConfig) =>
<T extends Type<ComponentProps>>(type: T): T => {
doSomething(type, config);
return type;
};
As you can see, a decorator is a function that returns another function, which expects a type (class) - that is automatically given - and returns it. In our example, type
parameter must extend (or implement) ComponentProps
. This way, we'll be sure that type
contains the properties that will be read in doSomething()
.
Note that a decorator function doesn't run every time we initialize an object using the class we declared after it. It only runs when the code is read for the first time.
In simples words, we can say that TypeScript decorators are good to prepare the way for instances of specific object types, or just handling the types you declare.
PermalinkHow am I using it in my framework?
I use decorators to store information about the components and views that will be rendered in the application. Thus I'm able to separate DOM information from component logic.
PermalinkConclusion
In my article 5 tips to become a better developer, one of my tips was challenge yourself. That's what I'm doing now, and I already learned a lot of cool things that are improving the quality of my applications and my ability on drawing ideas.
In future posts, I hope to share more content about things I'm learning from this and other challenges I'm delegating to myself.
Soon I'll be sharing, in this blog, the repository of my framework (currently, it's private) and you'll be able to use it, notify issues, and contribute to its development. ๐