Компонент маршрутизатора
Навігація
- Функція компонента
- Шаблон компонента
- Реквізити компонентів
- Контекст компонента
- Події сторінки компонента
- Обробка подій DOM
- Кореневий елемент компонента
-
Однофайловий компонентВикористання з Webpack і Vite; JSX
-
Віртуальний DOMКлючі в списках і компонентах автоініціалізації; innerHTML
- Основний компонент програми
-
Спеціальні компонентиРеєстрація компонентів; Локальні компоненти; Події; Слоти
-
Шаблон рецептівУмовний рендеринг; Відображення масиву на елементи
Компонент маршрутизатора
Функція компонента
const MyComponent = (props, context) => {
// some component logic
let value = 'foo';
// return render function
return () => context.$h`
<div class="page">
<p>Value is ${value}</p>
</div>
`;
}
Шаблон компонента
<div class="my-div"></div>
<!-- also valid as -->
<div class="my-div" />
Реквізити компонентів
{
path: '/blog/:id',
component: MyComponent
}
router.navigate('/blog/34/', {
props: {
foo: 'bar'
}
})
<my-component foo="bar" id="25" user=${{name: 'John'}} number=${30}></my-component>
{
foo: 'bar',
id: '25',
user: {
name: 'John'
},
number: 30
}
Контекст компонента
Властивість | Опис |
---|---|
$h | Спеціальний літерал шаблону з тегами, який потрібно використовувати для обгортання результату функції відтворення компонента та всіх записів HTML усередині: |
const MyComponent = (props, { $h }) => {
let list = ['item 1', 'item 2'];
return () => $h`
<div class="page">
<ul>
${list.map((item) => $h`
<li>${item}</li>
`)}
</ul>
</div>
`
}
Властивість | Опис |
---|---|
$el | Об’єкт, у якому властивість .value містить екземпляр Dom7 з елементом HTML компонента.
$el.value буде доступним лише після монтування компонента (або в будь-якій події сторінки, як-от pageInit). |
const MyComponent = (props, { $el, $onMounted }) => {
$onMounted(() => {
$el.value.find('p').addClass('red')
})
// ...
}
Властивість | Опис |
---|---|
$ | Dom7 бібліотека: |
const MyComponent = (props, { $, $onMounted }) => {
$onMounted(() => {
$('p').text('hello world')
})
// ...
}
Властивість | Опис |
---|---|
$t4 | Екземпляр програми Techno4 |
$t4.dialog.alert('Hello world!')
Властивість | Опис |
---|---|
$store | Зберігати екземпляр. Щоб отримати додаткові відомості та приклади, перегляньте документацію Store. |
$t4route | Поточний маршрут. Містить об'єкт із маршрутом query, hash, params, path і url |
Властивість | Опис |
---|---|
$t4router | Пов’язаний екземпляр маршрутизатора |
$t4router.back(); //navigate back
Властивість | Опис |
---|---|
$theme | Об’єкт із логічними властивостями md, ios і aurora, які вказують на поточну тему. Наприклад:
|
if ($theme.ios) { /* do something when iOS theme is active */ }
if ($theme.md) { /* do something when MD theme is active */ }
if ($theme.aurora) { /* do something when Aurora theme is active */ }
Властивість | Опис |
---|---|
$update(callback) | Цей метод повідомляє, що цей компонент і його дочірні компоненти потрібно повторно відобразити з оновленим станом
Немає гарантії, що зміни DOM будуть застосовані негайно, тому, якщо ви покладаєтеся на DOM (наприклад, потрібно отримати вміст HTML або значення атрибутів після зміни стану), тоді передайте функцію callback як аргумент. |
const MyComponent = (props, { $update, $h }) => {
// initial state
let value = 'foo';
const updateValue = () => {
// update local state
value = 'foo2';
// call $update method
$update();
}
return () => $h`
<div class="page">
<p>Value is ${value}</p>
<button @click=${updateValue}>Update Value</button>
</div>
`;
}
Властивість | Опис |
---|---|
$ref(initialValue) | Цей метод створює реактивну "змінну", яка після оновлення автоматично оновлює компонент без необхідності викликати метод $update().
Він повертає об’єкт із властивістю value, якому має бути присвоєно нове значення.
Немає гарантії, що зміни DOM будуть застосовані негайно, тому, якщо ви покладаєтеся на DOM (наприклад, потрібно отримати вміст HTML або значення атрибутів після зміни стану), тоді передайте функцію callback як аргумент. |
const MyComponent = (props, { $ref, $h }) => {
// create reactive object
const someVar = $ref('foo'); //-> { value: 'foo' }
const updateValue = () => {
// update "value" property of the reactive object
someVar.value = 'bar';
}
return () => $h`
<div class="page">
<p>Value is ${someVar.value}</p>
<button @click=${updateValue}>Update Value</button>
</div>
`;
}
Властивість | Опис |
---|---|
$tick(callback) | Ви також можете використовувати цей метод, якщо ви покладаєтеся на DOM і вам потрібно переконатися, що стан компонента та DOM оновлені після виклику методів $update().
Переданий callack буде виконано під час оновлення DOM.
Цей метод повертає Promise, який також буде вирішено під час оновлення DOM.
Тож ви можете використовувати це так: |
$update();
$tick(function () {
console.log('DOM and state updated');
});
// Or as Promise
$tick().then(() => {
console.log('DOM and state updated');
})
// Or in async function/method as:
await $tick();
console.log('DOM and state updated');
Властивість | Опис |
---|---|
$t4ready(callback) | Цей метод потрібно використовувати лише тоді, коли ви використовуєте основний компонент програми, щоб обов’язково викликати Techno4 API під час ініціалізації програми.
|
const AppComponent = (props, { $t4, $t4ready }) => {
$t4ready(() => {
// now it is safe to call Techno4 APIs
$t4.dialog.alert('Hello!');
});
// ...
Події
Властивість | Опис |
---|---|
$on | Функція для приєднання обробників подій DOM до кореневого елемента компонента
Такі обробники подій будуть автоматично відключені, коли компонент буде знищено |
const MyComponent = (props, { $on }) => {
// attach 'pageInit' event handler
$on('pageInit', (e, page) => {
console.log(page.name)
});
// ...
}
Властивість | Опис |
---|---|
$once |
Функція для приєднання обробників подій DOM до кореневого елемента компонента. Те саме, що $on, але такі обробники виконуватимуться лише один раз.
|
Властивість | Опис |
---|---|
$emit(event, data) | Функція запуску настроюваних подій DOM у багаторазово використовуваних настроюваних компонентах: |
const MyComponent = (props, { $emit }) => {
// emits custom event
$emit('myevent')
// ...
}
І в іншому батьківському компоненті:
<my-component @myevent=${doSomething} />
Хуки життєвого циклу
Властивість | Опис |
---|---|
$onBeforeMount | Викликається безпосередньо перед тим, як компонент буде додано до DOM |
Властивість | Опис |
---|---|
$onMounted | Викликається одразу після додавання компонента до DOM |
const MyComponent = (props, { $onMounted }) => {
// do something when component mounted
$onMounted(() => {
console.log('component mounted')
});
// ...
}
Властивість | Опис |
---|---|
$onBeforeUpdate | Викликається відразу після компонента перед тим, як VDOM буде виправлено/оновлено |
$onUpdated | Викликається одразу після виправлення/оновлення компонента VDOM |
$onBeforeUnmount | Викликається безпосередньо перед тим, як компонент буде демонтовано (від’єднано від DOM) |
$onUnmounted | Викликається, коли компонент демонтовано та знищено |
Отже, приклад маршруту з компонентом сторінки може виглядати так:
routes = [
// ...
{
path: '/some-page/',
// Component
component: (props, { $h, $t4, $on }) => {
const title = 'Component Page';
const names = ['John', 'Vladimir', 'Timo'];
const openAlert = () => {
$t4.dialog.alert('Hello world!');
}
$on('pageInit', (e, page) => {
// do something on page init
});
$on('pageAfterOut', (e, page) => {
// page has left the view
});
return () => $h`
<div class="page">
<div class="navbar">
<div class="navbar-bg"></div>
<div class="navbar-inner">
<div class="title">${title}</div>
</div>
</div>
<div class="page-content">
<a @click=${openAlert} class="red-link">Open Alert</a>
<div class="list simple-list">
<ul>
${names.map((name) => $h`
<li>${name}</li>
`)}
</ul>
</div>
</div>
</div>
`;
},
},
// ...
]
Події сторінки компонента
const MyComponent = (props, { $on }) => {
$on('pageMounted', (e, page) => {
console.log('page mounted');
});
$on('pageInit', (e, page) => {
console.log('page init');
});
$on('pageBeforeIn', (e, page) => {
console.log('page before in');
});
$on('pageAfterIn', (e, page) => {
console.log('page after in');
});
$on('pageBeforeOut', (e, page) => {
console.log('page before out');
});
$on('pageAfterOut', (e, page) => {
console.log('page after out');
});
$on('pageBeforeUnmount', (e, page) => {
console.log('page before unmount');
});
$on('pageBeforeRemove', (e, page) => {
console.log('page before remove');
});
}
Обробка подій DOM
const MyComponent = (props, { $h, $update }) => {
let value = 10;
const addValue = (number) => {
value += number;
$update();
}
const onClick = () => {
console.log('click');
}
return () => $h`
<div class="page">
<!-- pass function to attribute -->
<button @click=${onClick}>Button</button>
<!-- also work -->
<button @click=${() => onClick()}>Button</button>
<!-- will not work, attribute value "onClick" is just a string -->
<button @click="onClick">Button</button>
<!-- passing dynamic data will work as expected -->
<button @click=${() => addValue(15)}>Button</button>
</div>
`
}
const MyComponent = (props, { $h, $on }) => {
const onClick = () => {
console.log('click');
}
$on('pageInit', (e, page) => {
// this won't work
page.$el.append('<a @click="onClick">Link</a>');
});
return () => $h`
<div class="page">
</div>
`
}
Кореневий елемент компонента
Якщо ви завантажуєте сторінки як компонент маршрутизатора, компонент маршрутизатора повинен повертати елемент Page:
<template>
<div class="page">
...
</div>
</template>
<template>
<div class="popup">
...
</div>
</template>
<template>
<div class="panel panel-left panel-cover">
...
</div>
</template>
<template>
<div class="some-element">
...
</div>
</template>
Однофайловий компонент
routes = [
...
{
path: '/some-page/',
componentUrl: './some-page.t4',
},
..
];
<!-- component template, uses same tagged template literals -->
<template>
<div class="page">
<div class="navbar">
<div class="navbar-bg"></div>
<div class="navbar-inner">
<div class="title">${title}</div>
</div>
</div>
<div class="page-content">
<a @click=${openAlert}>Open Alert</a>
<div class="list simple-list">
<ul>
${names.map((name) => $h`
<li>${name}</li>
`)}
</ul>
</div>
</div>
</div>
</template>
<!-- component styles -->
<style>
.red-link {
color: red;
}
</style>
<!-- rest of component logic -->
<script>
// script must return/export component function
export default (props, { $t4, $on }) => {
const title = 'Component Page';
const names = ['John', 'Vladimir', 'Timo'];
const openAlert = () => {
$t4.dialog.alert('Hello world!');
}
$on('pageInit', () => {
// do something on page init
});
$on('pageAfterOut', () => {
// page has left the view
});
// component function must return render function
return $render;
}
</script>
Використання з Webpack і Vite
Для Webpack існує спеціальний плагін Techno4-loader, який дозволяє об’єднувати однофайлові компоненти в основний пакет і не використовувати XHR (наприклад, componentUrl) для кожного разу завантажувати та аналізувати файли компонентів.
Для Vite.js також існує спеціальний плагін rollup-plugin-Techno4 для об’єднання однофайлових компонентів.
Ці плагіни аналізують файл однофайлового компонента та перетворюють його на простий об’єкт JS під час процесу об’єднання. Отже, потенційно це може збільшити продуктивність програми, оскільки не буде синтаксичного аналізу та компіляції під час виконання.
Коли плагін налаштовано, нам потрібно зберігати однофайлові компоненти у файлах .t4 (або у .t4.html для Webpack) і використовувати export default для експорту компонентів:<template>
<div class="page">
...
</div>
</template>
<script>
export default () => {
let foo = 'bar';
const doThis = () => {
// ...
}
return $render;
}
</script>
Також можна імпортувати необхідні залежності та стилі:
<template>
<div class="page">
...
</div>
</template>
<script>
import './path/to/some-styles.css';
import utils from './path/to/utils.js';
export default () => {
let foo = 'bar';
let now = utils.now();
const doThis = () => {
// ...
}
return $render;
}
</script>
// routes.js
import NewsPage from './path/to/news.t4';
import ServicesPage from './path/to/services.t4';
export default [
{
path: '/news/',
component: NewsPage,
},
{
path: '/services/',
component: ServicesPage,
}
]
JSX
export default (props, { $update }) => {
let value = 10;
const items = ['Item 1', 'Item 2'];
const addValue = (number) => {
value += number;
$update();
}
//- render function should returns JSX
return () => (
<div class="page">
<p>The value is {value}</p>
<p>
{/* JSX doesn't support @ in attribute name so event handlers should start from "on" */}
<button onClick={() => addValue(10)}>Add Value</button>
</p>
<ul>
{items.map((item) => (
<li>{item}</li>
))}
</ul>
</div>
)
}
import NewsPage from './path/to/news.t4.jsx';
import ServicesPage from './path/to/services.t4.jsx';
export default [
{
path: '/news/',
component: NewsPage,
},
{
path: '/services/',
component: ServicesPage,
}
]
Віртуальний DOM
Віртуальний DOM (VDOM) — це концепція програмування, де ідеальне або «віртуальне» представлення інтерфейсу користувача зберігається в пам’яті та синхронізується з «реальним» DOM. Це дозволяє нам представити вигляд нашої програми як функцію її стану.
Бібліотека VDOM називається Snabbdom, оскільки вона надзвичайно легка, швидка та чудово підходить для середовища Techno4.
Отже, як працює VDOM-рендеринг компонента маршрутизатора Techno4? Шаблон компонента перетворюється на VDOM замість безпосереднього вставлення в DOM. Пізніше, коли стан компонента змінюється, він створює новий VDOM і порівнює його з попереднім VDOM. І на основі цієї різниці він виправляє реальний DOM шляхом зміни лише елементів і атрибутів, які потрібно змінити. І все це відбувається автоматично!
Давайте розглянемо приклад компонента профілю користувача, який автоматично оновлюватиме макет, коли ми запитуватимемо дані користувача:
<template>
<div class="page">
<div class="navbar">
<div class="navbar-bg"></div>
<div class="navbar-inner">
<div class="title">Profile</div>
</div>
</div>
<div class="page-content">
${user && $h`
<!-- Show user list when it is loaded -->
<div class="list simple-list">
<ul>
<li>First Name: ${user.firstName}</li>
<li>Last Name: ${user.lastName}</li>
<li>Age: ${user.age}</li>
</ul>
</div>
`}
${!user && $h`
<!-- Otherwise show preloader -->
<div class="block block-strong text-align-center">
<div class="preloader"></div>
</div>
`}
</div>
</div>
</template>
<script>
export default (props, { $on, $t4, $update }) => {
// empty initial user data
let user = null;
$on('pageInit', () => {
// request user data on page init
$t4.request.get('https://api.website.com/get-user-profile').then((res) => {
// update user with new data
user = res.data;
// trigger re-render
$update();
});
})
return $render;
}
</script>
Ключі в списках і компонентах автоініціалізації
Цей режим за замовчуванням є ефективним, але придатним лише тоді, коли вихідні дані візуалізації не залежать від стану дочірнього компонента чи тимчасового стану DOM (наприклад, вхідні значення форми).
Щоб дати VDOM підказку, щоб він міг відстежувати ідентичність кожного вузла, і таким чином повторно використовувати та змінювати порядок існуючих елементів, вам потрібно надати унікальний атрибут key для кожного елемента.
Під час відтворення списків ідеальним значенням для key буде унікальний ідентифікатор кожного елемента:<template>
...
<ul>
${items.map((item) => $h`
<li key=${item.id}>...</li>
`)}
</ul>
...
</template>
<script>
export default () => {
const items = [
{
id: 1,
title: 'Item A'
},
{
id: 2,
title: 'Item B'
},
];
return $render;
}
</script>
<template>
<div class="page">
...
<div class="page-content">
${gaugeVisible && $h`
<!-- must have unique key -->
<div key="gauge" class="gauge gauge-init" data-type="circle"
data-value="0.60"
data-value-text="60%"
data-value-text-color="#ff9800"
data-border-color="#ff9800"
></div>
`}
...
<a href="#" class="button" @click=${showGauge}>Show Gauge</a>
</div>
</div>
</template>
<script>
export default (props, { $update }) => {
let gaugeVisible = false;
const showGauge = () => {
gaugeVisible = true;
$update();
}
return $render;
}
</script>
Якщо атрибут key не вказано, а елемент має атрибут id, тоді атрибутid використовуватиметься як унікальний ключ віртуального вузла.
innerHTML
<template>
<div class="page">
...
<div class="block" innerHTML=${customHTML}></div>
</div>
</template>
<script>
export default (props) => {
const customHTML = '<p>Hello <b>World!</b></p>';
return $render;
}
</script>
Основний компонент програми
Зауважте, що через реалізацію VDOM настійно рекомендується додавати унікальний id або атрибут key до кожного автоматично ініціалізованого представлення (перегляд із view-init класом ):
Щоб увімкнути його, спочатку ми повинні залишити кореневий елемент додатка порожнім у index.html:<body>
<!-- empty app root element -->
<div id="app"></div>
</body>
<!-- app.t4 -->
<template>
<div id="app">
${loggedIn.value && $h`
<div class="panel panel-left panel-reveal panel-init">
<!-- every View has unique ID attribute -->
<div class="view view-init" id="view-panel" data-url="/panel/"></div>
</div>
<div class="view view-main view-init" id="view-main" data-url="/"></div>
`}
${!loggedIn.value && $h`
<div class="login-screen modal-in">
<div class="view view-init" id="view-auth" data-url="/auth/"></div>
</div>
`}
</div>
</template>
<script>
export default (props, { $store }) => {
const loggedIn = $store.getters.loggedIn;
return $render;
}
</script>
// import main app component
import App from './path/to/app.t4';
var app = new Techno4({
// specify main app component
component: App,
})
var app = new Techno4({
// load main app component
componentUrl: './path/to/app.t4',
})
<template>
<div id="app">
...
</div>
</template>
<script>
export default (props, { $t4ready, $t4 }) => {
$t4ready(() => {
// now it is safe to call Techno4 APIs
$t4.dialog.alert('Hello!');
})
}
</script>
Спеціальні компоненти
Реєстрація компонентів
Techno4.registerComponent(tagName, component)
- зареєструвати спеціальний компонент
tagName - string.
Назва тегу компонента, напр. my-component (буде використано як <my-component>).
Назва тегу спеціального компонента має містити символ дефіса/тире "-"
component - object or class.
Функція компонента
Зауважте, що на даний момент користувальницькі компоненти можна використовувати лише в компонентах маршрутизатора (компоненти, завантажені маршрутизатором).
Techno4.registerComponent(
// component name
'my-list-item',
// component function
(props, { $h }) => {
let foo = 'bar';
return () => $h`
<li class="item-content" id="${props.id}">...</li>
`
}
)
<div class="list">
<ul>
<my-list-item id="item-1"></my-list-item>
</ul>
</div>
Локальні компоненти
<template>
<ul>
<!-- use tag names as variables -->
<${ListItem} title="Item 1" />
<${ListItem} title="Item 2" />
<${ListItem} title="Item 3" />
</ul>
</template>
<script>
// create local component
const ListItem = (props, { $h }) => {
return () => $h`<li>${props.title}</li>`;
}
// export main component
export default () => {
return $render;
}
</script>
<template>
<ul>
<!-- use tag names as variables -->
<${ListItem} title="Item 1" />
<${ListItem} title="Item 2" />
<${ListItem} title="Item 3" />
</ul>
</template>
<script>
// import component
import ListItem from 'path/to/list-item.t4';
// export main component
export default () => {
return $render;
}
</script>
const ListItem = (props) => {
return (
<li>{props.title}</li>
)
}
/* or
import ListItem from 'path/to/list-item.t4.jsx'
*/
export default () => {
return () => (
<ul>
<ListItem title="Item 1" />
<ListItem title="Item 2" />
<ListItem title="Item 3" />
</ul>
)
}
export default () => {
const ListItem = (props) => {
return (
<li>{props.title}</li>
)
}
return () => (
<ul>
<ListItem title="Item 1" />
<ListItem title="Item 2" />
<ListItem title="Item 3" />
</ul>
)
}
Події
<template>
<div class="page">
...
<my-button @click="onClick">Click Me</my-button>
</div>
</template>
<script>
return {
// ...
methods: {
onClick: function(e) {
console.log('clicked');
}
},
// ...
}
</script>
Слоти
За допомогою тегу slot ми вказуємо, де слід розмістити дочірні компоненти. Наприклад, шаблон компонента my-button:
<a class="button button-fill">
<slot></slot>
</a>
<my-button>Click Me</my-button>
<a class="button button-fill">
<slot>Default Button Text</slot>
</a>
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
<my-container>
<h1 slot="header">Title</h1>
<p>Text for main content.</p>
<p>More text for main content.</p>
<p slot="footer">Footer content</p>
</my-container>
<div class="container">
<header>
<h1>Title</h1>
</header>
<main>
<p>Text for main content.</p>
<p>More text for main content.</p>
</main>
<footer>
<p>Footer content</p>
</footer>
</div>
Рецепти шаблонів
Умовний рендеринг
if
<template>
<div class="page">
${someVar && $h`
<p>Text will be visible when "someVar" is truthy</p>
`}
${someVar === 1 && $h`
<p>Text will be visible when "someVar" equals to 1</p>
`}
</div>
</template>
<script>
export default () => {
const someVar = 1;
return $render;
}
</script>
export default () => {
const someVar = 1;
return () => (
<div class="page">
{someVar && (
<p>Text will be visible when "someVar" is truthy</p>
)}
{someVar === 1 && (
<p>Text will be visible when "someVar" equals to 1</p>
)}
</div>
)
}
if-else
<template>
<div class="page">
${someVar ? $h`
<p>Text will be visible when "someVar" is truthy</p>
` : $h`
<p>Text will be visible when "someVar" is falsy</p>
`}
{someVar && (
<p>Text will be visible when "someVar" is truthy</p>
)}
{!someVar && (
<p>Text will be visible when "someVar" is falsy</p>
)}
</div>
</template>
<script>
export default () => {
const someVar = 1;
return $render;
}
</script>
export default () => {
const someVar = 1;
return () => (
<div class="page">
{someVar ? (
<p>Text will be visible when "someVar" is truthy</p>
) : (
<p>Text will be visible when "someVar" is falsy</p>
)}
{someVar && (
<p>Text will be visible when "someVar" is truthy</p>
)}
{!someVar && (
<p>Text will be visible when "someVar" is falsy</p>
)}
</div>
)
}
Відображення масиву на елементи
<template>
<div class="page">
<ul>
${items.map((item) => $h`
<li>${item}</li>
`)}
</ul>
</div>
</template>
<script>
export default () => {
const items = [
'item 1',
'item 2',
'item 3',
];
return $render;
}
</script>
export default () => {
const items = [
'item 1',
'item 2',
'item 3',
];
return () => (
<div class="page">
<ul>
{items.map((item) => (
<li>{item}</li>
))}
</ul>
</div>
)
}
Techno4 Framework
- Get Started
- Introduction
- Kitchen Sink
- Installation
- Package Structure
- App HTML Layout
- RTL Layout
- Initialize App
- Events
- Router Component
- Routes
- Store
- App/Core
- Accordion/Collapsible
- Action Sheet
- Area chart
- Autocomplete
- Badge
- Block/Content Block
- Breadcrumbs
- Button
- Calendar/Date Picker
- Cards
- Checkbox
- Chips
- Color Picker
- Contact List
- Data Table
- Dialog
- Floating action button
- Form
- Gauge
- Icons
- Inputs / Form Inputs
- Grid /Layout Grid
- Link
- List Button
- List View
- List Index
- Login Screen
- Menu List
- Messagebar
- Navbar
- Messages
- Notification
- Panel/Side Panels
- Photo Browser
- Picker
- Pie Chart
- Popover
- Popup
- Preloader
- Progressbar
- Pull to Refresh
- Radio
- Range Slider
- Searchbar