许多人已经知道效应器状态管理器,不仅有人观看了它,还在产品中使用了它。从秋天末开始,它的作者一直在积极地为效果器开发devtools,在此工作过程中,他设法编写了一个非常有趣的库来渲染应用程序-dom-dom。
我们将了解此渲染-在本教程中,我们将创建一个简单的Todo应用程序。

为了使用逻辑,我们将使用effector来渲染应用程序-effector-dom。
对于视觉部分,让我们以现成的todomvc-app-template模板为模板,其模板以astejs为基础的todomvc-app-css样式。
1.项目准备
我假设您已经熟悉webpack和npm,因此我们跳过安装npm,使用webpack创建项目并启动应用程序的步骤(如果我们不熟悉,请使用google webpack样板)。
使用撰写本文时使用的版本安装必要的软件包:
npm install effector@20.11.5 effector-dom@0.0.10 todomvc-app-css
2.开始吧
首先,让我们确定应用程序的结构。
, , , - best practices .
, .
:
srs/
view/
app.js
title.js
footer.js
header.js
main.js
todoItem.js
model.js
index.js
3.
, : , , .
( undefined) Store, — Event.
combine, .
import {createStore, createEvent, combine} from 'effector';
export const $todos = createStore([]);
export const $activeFilter = createStore(null);
export const $filteredTodos = combine(
$todos,
$activeFilter,
(todos, filter) => filter === null
? todos
: todos.filter(todo => todo.completed === filter)
);
export const appended = createEvent();
export const toggled = createEvent();
export const removed = createEvent();
export const allCompleted = createEvent();
export const completedRemoved = createEvent();
export const filtered = createEvent();
.
, store.on
...
$todos
.on(appended, (state, title) => [...state, {title, completed: false}])
.on(removed, (state, title) => state.filter(item => item.title !== title))
.on(toggled, (state, title) => state.map(item => item.title === title
? ({...item, completed: !item.completed})
: item))
.on(allCompleted, state => state.map(item => item.completed
? item
: ({...item, completed: true})))
.on(completedRemoved, state => state.filter(item => !item.completed));
$activeFilter
.on(filtered, (_, filter) => filter);
. .
4. view
view effector-dom. , SwiftUI .
, , . dom- .
effector-dom dom , . using, dom- , :
import {using} from 'effector-dom';
import {App} from './view/app';
using(document.body, () => {
App();
});
h, dom- , .
dom- spec, :
import {h, spec} from 'effector-dom';
import classes from 'todomvc-app-css/index.css';
import {Header} from './header';
import {Main} from './main';
import {Footer} from './footer';
export const App = () => {
h('section', () => {
spec({attr: {class: classes.todoapp}});
Header();
Main();
Footer();
});
};
.
5.
h1 .
dom- , , . spec , :
import {h} from 'effector-dom';
export const Title = () => {
h('h1', {text: 'todos'});
};
6.
, effector-dom effector , , ..
, header. dom- spec ( ), handler.
, input , $value input.
sample . api sample — , — , : event = sample($store, triggerEvent).
import {h, spec} from 'effector-dom';
import {createEvent, createStore, forward, sample} from 'effector';
import classes from 'todomvc-app-css/index.css';
import {Title} from './title';
import {appended} from '../model';
export const Header = () => {
h('header', () => {
Title();
h('input', () => {
const keypress = createEvent();
const input = createEvent();
const submit = keypress.filter({fn: e => e.key === 'Enter'});
const $value = createStore('')
.on(input, (_, e) => e.target.value)
.reset(appended);
forward({
from: sample($value, submit).filter({fn: Boolean}),
to: appended,
});
spec({
attr: {
class: classes["new-todo"],
placeholder: 'What needs to be done?',
value: $value
},
handler: {keypress, input},
})
});
});
};
7.
, effector-dom list.
— , , . list($store, itemCallback) .
.
, todomvc-app-css - , . .
import {h, spec, list} from 'effector-dom';
import classes from 'todomvc-app-css/index.css';
import {TodoItem} from './todoItem';
import {$filteredTodos, allCompleted} from '../model';
export const Main = () => {
h('section', () => {
spec({attr: {class: classes.main}});
h('input', {
attr: {id: 'toggle-all', class: classes['toggle-all'], type: 'checkbox'}
});
h('label', {attr: {for: 'toggle-all'}, handler: {click: allCompleted}});
h('ul', () => {
spec({attr: {class: classes["todo-list"]}});
list({
source: $filteredTodos,
key: 'title',
fields: ['title', 'completed']
}, ({fields: [title, completed], key}) => TodoItem({title, completed, key}));
});
});
};
8.
, effector-dom - , ..
, toggled removed - — .
effector — event.prepend.
store.map
import {h, spec} from 'effector-dom';
import classes from 'todomvc-app-css/index.css';
import {toggled, removed} from '../model';
export const TodoItem = ({title, completed, key}) => {
h('li', () => {
spec({attr: {class: completed.map(flag => flag ? classes.completed : false)}});
h('div', () => {
spec({attr: {class: classes.view}});
h('input', {
attr: {class: classes.toggle, type: 'checkbox', checked: completed},
handler: {click: toggled.prepend(() => key)},
});
h('label', {text: title});
h('button', {
attr: {class: classes.destroy},
handler: {click: removed.prepend(() => key)},
});
});
});
};
9.
,
import {h, spec} from 'effector-dom';
import classes from 'todomvc-app-css/index.css';
import {$todos, $activeFilter, filtered, completedRemoved} from '../model';
export const Footer = () => {
h('footer', () => {
spec({attr: {class: classes['footer']}});
h('span', () => {
spec({attr: {class: classes['todo-count']}});
const $activeCount = $todos.map(
todos => todos.filter(todo => !todo.completed).length
);
h('strong', {text: $activeCount});
h('span', {text: $activeCount.map(count => count === 1
? ' item left'
: ' items left'
)});
});
h('ul', () => {
spec({attr: {class: classes.filters}});
h('li', () => {
h('a', {
attr: {class: $activeFilter.map(active => active === null
? classes.selected
: false
)},
text: 'All',
handler: {click: filtered.prepend(() => null)},
});
});
h('li', () => {
h('a', {
attr: {class: $activeFilter.map(completed => completed === false
? classes.selected
: false
)},
text: 'Active',
handler: {click: filtered.prepend(() => false)},
});
});
h('li', () => {
h('a', {
attr: {class: $activeFilter.map(completed => completed === true
? classes.selected
: false
)},
text: 'Completed',
handler: {click: filtered.prepend(() => true)},
});
});
});
h('button', {
attr: {class: classes['clear-completed']},
text: 'Clear completed',
handler: {click: completedRemoved},
});
});
};
10.
事实证明,这样一个简单的应用程序是经过特别简化的,尽可能减少了分离成单独实体的可能性。
当然,如果需要,可以将相同的过滤器按钮取出到一个单独的实体中,该实体可以用于传达过滤器的类型和名称。
const FilterButton = ({filter, text}) => {
h('li', () => {
h('a', {
attr: {class: $activeFilter.map(completed => completed === filter
? classes.selected
: false
)},
text: text,
handler: {click: filtered.prepend(() => filter)},
});
});
};
h('ul', () => {
spec({attr: {class: classes.filters}});
FilterButton({filter: null, text: 'All'});
FilterButton({filter: false, text: 'Active'});
FilterButton({filter: true, text: 'Completed'});
});
同样,由于其基于堆栈的工作,effector-dom使您不仅可以自由渲染单个元素,还可以自由渲染常规行为。
呈现的代码将专门应用于必要的元素,例如:
const WithFocus = () => {
const focus = createEvent();
focus.watch(() => console.log('focused'));
spec({handler: {focus}});
};
h('input', () => {
...
WithFocus();
...
});
11.总计
使用新渲染的简单性给我个人留下了深刻的印象,特别是因为我已经在大型应用程序中使用过效果器,因此我对此印象深刻。
我期待一个稳定的版本,某种形式的普及,以便有机会在产品中使用渲染。