als-component
Version:
lightweight JavaScript library for creating reactive, server-rendered, and browser-based components.
309 lines (222 loc) • 7.64 kB
Markdown
# als-component
`als-component` is a lightweight JavaScript library for creating reactive, server-rendered, and browser-based components. It allows you to build dynamic components that work both on the server (Node.js) and in the browser, providing seamless reactivity, lifecycle hooks, and event handling.
## Installation
Install via NPM:
```bash
npm install als-component
```
## Quick Start
### Example for Node.js (Server)
```js
const Component = require('als-component');
class HelloComponent extends Component {
constructor(props) { super(props) }
render({ name }) {
return `<div>Hello, ${name}!</div>`;
}
}
const instance = new HelloComponent({ name: 'World' });
console.log(instance.call()); // Outputs: <div component="HelloComponent">Hello, World!</div>
```
### Example for Browser
```html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>als-component Example</title>
<script src="/node_modules/als-component/component.js"></script>
<!-- Or minified version -->
<script src="/node_modules/als-component/component.min.js"></script>
</head>
<body>
<div component="App"></div>
<script>
class App extends Component {
constructor(props) { super(props) }
render({ count = 0 }) {
return `
<div>
<button click="${this.action('click', () => this.update({ count: count - 1 }))}">-</button>
<span>${count}</span>
<button click="${this.action('click', () => this.update({ count: count + 1 }))}">+</button>
</div>
`;
}
}
const app = new App({ count: 0 });
app.update();
</script>
</body>
</html>
```
## API Reference
### `constructor(props = {}, inner)`
Initializes a new component.
- `props` *(Object)*: The initial properties of the component.
- `inner` *(String)*: The inner content of the component.
### `call()`
Renders the component and returns the HTML string.
**Example:**
```js
const html = instance.call();
console.log(html); // <div component="ComponentName">Rendered content</div>
```
### `update(props, inner)`
Updates the component with new properties and re-renders it.
**Example:**
```js
instance.update({ count: 5 });
```
### `action(event, fn)`
Creates an event action and binds it to the element.
- `event` *(String)*: The event type (e.g., 'click').
- `fn` *(Function)*: The event handler function.
- **Returns**: *(String)*: A unique action identifier.
**Example:**
```js
const clickId = instance.action('click', () => alert('Button clicked'));
```
### `on(event, fn)`
Registers a lifecycle hook.
- `event` *(String)*: The lifecycle event ('mount' or 'unmount').
- `fn` *(Function)*: The callback function.
**Example:**
```js
instance.on('mount', () => console.log('Component mounted'));
instance.on('unmount', () => console.log('Component unmounted'));
```
### `runActions()`
Binds all stored actions to the corresponding elements and triggers `mount` hooks.
## Asynchronous Rendering
The `render` method can be asynchronous. If it is an `async` function, the component handles it automatically.
**Example:**
```js
class AsyncComponent extends Component {
async render() {
const data = await fetchData();
return `<div>${data}</div>`;
}
}
const asyncInstance = new AsyncComponent();
asyncInstance.update();
```
### `async buildAsync(updateElement = true)` and `buildSync(updateElement = true)`
You can call for `buildAsync` (return promise) or `buildSync` with `updateElement` flag to update or not to update the element.
### `set elementOuter`
You can manualy set outerHTML for component's element. If element is not exists, it simply will do nothing.
## Lifecycle Hooks
### `mount`
Called when the component is added to the DOM.
### `unmount`
Called when the component is removed from the DOM.
**Example:**
```js
instance.on('mount', () => console.log('Mounted'));
instance.on('unmount', () => console.log('Unmounted'));
```
## Event Handling
Use `action()` to bind events to elements within the component.
**Example:**
```js
class ClickComponent extends Component {
render() {
return `<button click="${this.action('click', () => alert('Button clicked'))}">Click Me</button>`;
}
}
```
## Nested Components
You can nest components within each other. The parent component can include a child component using `call()`.
**Example:**
```js
class ChildComponent extends Component {
render() {
return `<span>Child Component</span>`;
}
}
class ParentComponent extends Component {
render() {
return `<div>Parent: ${new ChildComponent().call()}</div>`;
}
}
const parentInstance = new ParentComponent();
parentInstance.update();
```
## Memory Management
The `als-component` library uses `WeakMap` to store component instances, ensuring that they are garbage collected when removed from the DOM.
**Example:**
```js
const instance = new Component();
document.body.appendChild(instance.element);
instance.update();
instance.element.remove();
instance.runActions();
```
The component is removed from `WeakMap` automatically when it is no longer referenced.
## Optimization Tips
- Use `update()` sparingly to avoid excessive DOM manipulations.
- Prefer using asynchronous `render` for components that require data fetching.
- Use unique keys for components to avoid collisions.
## Error Handling
When using asynchronous methods or complex nested components, ensure you handle potential errors gracefully.
**Example:**
```js
try {
await instance.update();
} catch (error) {
console.error('Error updating component:', error);
}
```
## Example
```html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="/component.js"></script>
<title>Users</title>
</head>
<body>
<div component="Users"></div>
</body>
<script>
class User extends Component {
constructor(props) { super(props) }
remove() {
const {user,usersComponent} = this.props
// const usersComponent = this.Component.components.Users // Another way to get component
usersComponent.update({users:usersComponent.props.users.filter(({id}) => id !== user.id)})
}
render({user}) {
const {id,email,age,gender,username,firstName} = user
return /*html*/`<div>
${firstName}
<button click="${this.action('click',() => this.remove())}">delete</button>
</div>`
}
}
class Users extends Component {
constructor(props={},inner) {
super(props,inner)
}
async render({users}) {
if(users === undefined) {
fetch('https://dummyjson.com/users')
.then(res => res.json())
.then(data => {
this.update({users:data.users})
});
return /*html*/`<div>Fething users</div>`
}
return /*html*/`<div>
${users.map(user => new User({user,key:user.id,usersComponent:this}).call()).join('')}
</div>`
}
}
const usersComponent = new Users()
usersComponent.update()
</script>
</html>
```