Hi!
I will tell you about my experience of creating a front-end framework. The goal was to create a framework that can be learned in 5 minutes by a front-end developer who has experience with React, Vue, or Angular.
How to create a component
In React, a component is a function; in Vue, it is a file; and in Angular, a component consists of several files. Personally, I prefer the possibility to create a helper component in a component file. Because of this, I decided that a component will be a function declared in the following way:
export const MyComponent = component(() => {
// component logic here
});
Reactive states
To save states, there are variables in Javascript, so we will it.
The rules are simple:
- If the variable name starts with
$than it will be reactive. - If the variable name does not start with
$than it will be just a variable.
If a derived/computed state is needed, then a constant is declared and initialized with the required value. When let is used, the linter can switch automatically to const, so constant is canonical.
Example of code:
export const MyComponent = component(() => {
let $a = 2;
let $b = 3;
const $sum = $a + $b;
const $sum2 = sum($a, $b);
});
Side effects
The component function is executed once, so the following side effects are present in the next functions:
-
watchexecutes a function each time when a reactive value from the body is updated. -
beforeMountexecutes a function after data initialization and before DOM nodes mount. -
afterMountexecutes a function after the DOM nodes mount. -
beforeDestroyexecutes a function before the DOM nodes removing.
The next code
export const MyComponent = component(() => {
let $state = 'init';
watch(() => { console.log($state) });
beforeMount(() => { $state = "before" });
afterMount(() => { $state = "after" });
});
Will print in console:
init
before
after
DOM
To declare DOM nodes, HTML code is used; it is written directly to the component function body, and it works via JSX. The event handlers like onclick/onpress accept functions as value.
Nodes declarations
Example of button with counter:
export const MyComponent = component(() => {
let $count = 'init';
function inc() {
$count++;
}
<button class="btn" onclick={inc}>
You clicked {$count} times
</button>;
});
The class attribute accepts a string array as a value; the style one accepts objects as a value.
Callbacks
To connect third-party libraries or to access DOM nodes directly, the callbacks are used. A callback is a function that is called when the element and all its children are mounted in DOM.
Example of callback usage:
export const MyComponent = component(() => {
function sideEffect(input: HTMLInputElement) {
input.showPicker();
}
<input type="date" callback={sideEffect}/>;
});
Share data between compoents
The data can be shared between components in the next ways:
- from parent to children via properties;
- from children to parent via callbacks;
- from children to parent and from parent to children via slots;
Sharing data via properties
Properties are an object; the field names can match the same rules as variable names, so if the field name starts with $ then the field is reactive; otherwise, it is not reactive.
Example of sharing data via properties:
interface Props {
userId: string;
$userName: string;
}
const Child = component(({userId, $userName}: Props) => {
<div>{userId} is named {$userName}</div>;
});
const Parent = component(() => {
const id = 1;
let $name = "First";
// When the name is updated here
// it will be automatically updated in the child component
<Child userId={id} $userName={$name}/>;
});
Sharing data via callbacks
The component function can return data; the returned data can be accessed in the parent component via callback.
Example of using callbacks as an alternative for forwardRef from React:
const Child = component(() => {
let input: HTMLInputElement|null = null;
<input callback={element => input = element}/>;
return input;
});
const Parent = component(() => {
<Child callback={input => { console.log(input) }}/>;
});
Sharing data via slots
The slots are sending data to the parent component data via properties, and the parent is sharing a DOM description with the child component:
interface Props {
$title: string;
slot?(props: { $name: string }): void;
}
const Child = component(({$title, slot}: Props) => {
<div>
<Slot model={slot} $name={`${$title} is amazing`}/>
</div>;
});
const Parent = component(() => {
let $title = "MyApp";
<Child $title={$title} slot=(($name) => {
<span>{$name}</span>;
})/>;
});
When the child is not sharing data with the parent, the DOM description can be included in the child tag: <Child><span>Text</span></Child>.
Also, inside the Slot tag can be present a DOM description, which will be used if the parent does not fill the slot.
The slot is not just a function; it is a component. The reactive states, derived/computed value, and watch, beforeMount, afterMount and beforeDestroy can be used in a slot.
Logic and loops
There are building components that operate the logic and loops: If, Else ElseIf and For.
Example of conditional text:
const MyComponent = component(() => {
let $count = 0;
<If $codition={$count > 2}>
Count is too big!
</If>;
});
Example of loop;
const MyComponent = component(() => {
const arr = [1, 2, 3];
<For model={arr} slot={number => {
<div>Number is {number}</div>
}}/>;
});
The property name rules are applied to built-in components. The If will react to updates of $condition, but For will not react to updates of the model; the model must be updated via push, pull and similar methods.
Conclusions
There is a minimal set of functions to create an SPA. All this is already working; the component styling library is already ready, and scripts for building component libraries and applications are ready. The last updates added the SSG compiling option and a React-like condition for conditional tags as an alternative to If/Else; it fixes some TypeScript errors.
The project is open-source. For volunteers who want to help, we have a Google Form for Customer Development Interview: https://docs.google.com/forms/d/e/1FAIpQLScvoUvCRlbONheW1lu_ZnerXLz1pcGxhKV_q3EKs3d40csXTw/viewform?usp=header
Thanks for your attention!
Top comments (0)