在非 onionified Cycle.js 应用中使用 cycle-onionify
Using cycle-onionify within a non-onionified Cycle.js app
我可能在尝试一些愚蠢的事情,但我有一个足够大的 non-onionified Cycle.js 应用程序,我正在尝试学习 onionify 有效,所以我想将一个 onionified 组件嵌入到我原来的非 onion 应用程序中。
所以我有一个简单的洋葱就绪组件,increment/decrement example, and I have a simple non-onion Cycle app, the “Hello Last Name” 示例——我如何将两者混合在一起,以便我在同一网页中一个接一个地拥有增量器组件和 Hello 组件?
Counter.ts
,洋葱就绪组件
import xs from 'xstream';
import run from '@cycle/run';
import { div, button, p, makeDOMDriver } from '@cycle/dom';
export default function Counter(sources) {
const action$ = xs.merge(
sources.DOM.select('.decrement').events('click').map(ev => -1),
sources.DOM.select('.increment').events('click').map(ev => +1)
);
const state$ = sources.onion.state$;
const vdom$ = state$.map(state =>
div([
button('.decrement', 'Decrement'),
button('.increment', 'Increment'),
p('Counter: ' + state.count)
])
);
const initReducer$ = xs.of(function initReducer() {
return { count: 0 };
});
const updateReducer$ = action$.map(num => function updateReducer(prevState) {
return { count: prevState.count + num };
});
const reducer$ = xs.merge(initReducer$, updateReducer$);
return {
DOM: vdom$,
onion: reducer$,
};
}
index.ts
,非洋葱主应用
import xs, { Stream } from 'xstream';
import { run } from '@cycle/run';
import { div, input, h2, button, p, makeDOMDriver, VNode, DOMSource } from '@cycle/dom';
import Counter from "./Counter";
import onionify from 'cycle-onionify';
const counts = onionify(Counter);
interface Sources {
DOM: DOMSource;
}
interface Sinks {
DOM: Stream<VNode>;
}
function main(sources: Sources): Sinks {
const firstName$ = sources.DOM
.select('.first')
.events('input')
.map(ev => (ev.target as HTMLInputElement).value)
.startWith('');
const lastName$ = sources.DOM
.select('.last')
.events('input')
.map(ev => (ev.target as HTMLInputElement).value)
.map(ln => ln.toUpperCase())
.startWith('');
const rawFullName$ = xs.combine(firstName$, lastName$)
.remember();
const validName$ = rawFullName$
.filter(([fn, ln]) => fn.length > 0 && ln.length >= 3)
.map(([fn, ln]) => `${ln.toUpperCase()}, ${fn}`);
const invalidName$ = rawFullName$
.filter(([fn, ln]) => fn.length === 0 || ln.length < 3)
.mapTo('');
const name$ = xs.merge(validName$, invalidName$);
const vdom$ = name$.map(name =>
div([
p([
'First name',
input('.first', { attrs: { type: 'text' } }),
]),
p([
'Last name',
input('.last', { attrs: { type: 'text' } }),
]),
h2('Hello ' + name),
]),
);
return {
DOM: vdom$,
};
}
run(main, {
DOM: makeDOMDriver('#main-container'),
});
尝试次数
如果我将 run(main, ...)
替换为 run(counts, ...)
,正如 cycle-onionify 文档建议的完全 onionified 应用程序,我只会看到预期的计数器。
但是 counts
,作为 onionify(Counter)
的输出,是一个函数,所以我不认为我可以在我的 `main() 中“实例化”它。
同样,我认为我无法通过在 main 中调用 Counter()
来创建计数器组件,因为该函数需要 sources.onion
输入,而且我不确定如何创建 .onion
字段,其类型为 StateSource
.
问题
我如何在我的非洋葱化 main
中使用这个 Counter
onion-ready 组件?
完整示例
完整示例可在 https://gist.github.com/fasiha/939ddc22d5af32bd5a00f7d9946ceb39 获得 — 克隆此,npm install
以获得必要的包,然后 make
(运行 tsc
和 browserify
转换 TypeScript→JavaScript→浏览器化 JS)。
其实很简单。正如你所说,你可以在你的 main 中调用 Counter()
,但是它没有 StateSource
.
解决办法是把Counter()
换成onionify(Counter)()
:
function main(sources: Sources): Sinks {
//other main stuff
const vdom$ = //implementation here
const counterSinks = onionify(Counter)(sources);
const combinedVdom$ = xs.combine(vdom$, counterSinks.DOM)
.map(div). //Enclose with div
return {
DOM: combinedVdom$,
};
}
请注意,这只会使用 Counter
中的 DOM 接收器,因此如果您想使用计数器中的其他接收器,您还必须指定它们:
const counterSinks = onionify(Counter)(sources);
const combinedVdom$ = xs.combine(vdom$, counterSinks.DOM)
.map(div). //Enclose with div
return {
DOM: combinedVdom$,
HTTP: xs.merge(myOwnRequest$, counterSinks.HTTP)
//...
};
因为对每个水槽都这样做有点乏味,我在包 cyclejs-utils
:
中创建了一个助手
const counterSinks = onionify(Counter)(sources);
const combinedVdom$ = xs.combine(vdom$, counterSinks.DOM)
.map(div). //Enclose with div
const ownSinks = { DOM: vdom$, /* ... */ };
return {
...mergeSinks(ownSinks, counterSinks)
DOM: combinedVdom$
};
我指定额外的 DOM 的原因是 mergeSinks 在每个接收器上调用合并,但是对于 DOM,您想使用 combine + map 来组合 DOM 到一个新的 parent DOM 正是你想如何使用它。
我可能在尝试一些愚蠢的事情,但我有一个足够大的 non-onionified Cycle.js 应用程序,我正在尝试学习 onionify 有效,所以我想将一个 onionified 组件嵌入到我原来的非 onion 应用程序中。
所以我有一个简单的洋葱就绪组件,increment/decrement example, and I have a simple non-onion Cycle app, the “Hello Last Name” 示例——我如何将两者混合在一起,以便我在同一网页中一个接一个地拥有增量器组件和 Hello 组件?
Counter.ts
,洋葱就绪组件
import xs from 'xstream';
import run from '@cycle/run';
import { div, button, p, makeDOMDriver } from '@cycle/dom';
export default function Counter(sources) {
const action$ = xs.merge(
sources.DOM.select('.decrement').events('click').map(ev => -1),
sources.DOM.select('.increment').events('click').map(ev => +1)
);
const state$ = sources.onion.state$;
const vdom$ = state$.map(state =>
div([
button('.decrement', 'Decrement'),
button('.increment', 'Increment'),
p('Counter: ' + state.count)
])
);
const initReducer$ = xs.of(function initReducer() {
return { count: 0 };
});
const updateReducer$ = action$.map(num => function updateReducer(prevState) {
return { count: prevState.count + num };
});
const reducer$ = xs.merge(initReducer$, updateReducer$);
return {
DOM: vdom$,
onion: reducer$,
};
}
index.ts
,非洋葱主应用
import xs, { Stream } from 'xstream';
import { run } from '@cycle/run';
import { div, input, h2, button, p, makeDOMDriver, VNode, DOMSource } from '@cycle/dom';
import Counter from "./Counter";
import onionify from 'cycle-onionify';
const counts = onionify(Counter);
interface Sources {
DOM: DOMSource;
}
interface Sinks {
DOM: Stream<VNode>;
}
function main(sources: Sources): Sinks {
const firstName$ = sources.DOM
.select('.first')
.events('input')
.map(ev => (ev.target as HTMLInputElement).value)
.startWith('');
const lastName$ = sources.DOM
.select('.last')
.events('input')
.map(ev => (ev.target as HTMLInputElement).value)
.map(ln => ln.toUpperCase())
.startWith('');
const rawFullName$ = xs.combine(firstName$, lastName$)
.remember();
const validName$ = rawFullName$
.filter(([fn, ln]) => fn.length > 0 && ln.length >= 3)
.map(([fn, ln]) => `${ln.toUpperCase()}, ${fn}`);
const invalidName$ = rawFullName$
.filter(([fn, ln]) => fn.length === 0 || ln.length < 3)
.mapTo('');
const name$ = xs.merge(validName$, invalidName$);
const vdom$ = name$.map(name =>
div([
p([
'First name',
input('.first', { attrs: { type: 'text' } }),
]),
p([
'Last name',
input('.last', { attrs: { type: 'text' } }),
]),
h2('Hello ' + name),
]),
);
return {
DOM: vdom$,
};
}
run(main, {
DOM: makeDOMDriver('#main-container'),
});
尝试次数
如果我将 run(main, ...)
替换为 run(counts, ...)
,正如 cycle-onionify 文档建议的完全 onionified 应用程序,我只会看到预期的计数器。
但是 counts
,作为 onionify(Counter)
的输出,是一个函数,所以我不认为我可以在我的 `main() 中“实例化”它。
同样,我认为我无法通过在 main 中调用 Counter()
来创建计数器组件,因为该函数需要 sources.onion
输入,而且我不确定如何创建 .onion
字段,其类型为 StateSource
.
问题
我如何在我的非洋葱化 main
中使用这个 Counter
onion-ready 组件?
完整示例
完整示例可在 https://gist.github.com/fasiha/939ddc22d5af32bd5a00f7d9946ceb39 获得 — 克隆此,npm install
以获得必要的包,然后 make
(运行 tsc
和 browserify
转换 TypeScript→JavaScript→浏览器化 JS)。
其实很简单。正如你所说,你可以在你的 main 中调用 Counter()
,但是它没有 StateSource
.
解决办法是把Counter()
换成onionify(Counter)()
:
function main(sources: Sources): Sinks {
//other main stuff
const vdom$ = //implementation here
const counterSinks = onionify(Counter)(sources);
const combinedVdom$ = xs.combine(vdom$, counterSinks.DOM)
.map(div). //Enclose with div
return {
DOM: combinedVdom$,
};
}
请注意,这只会使用 Counter
中的 DOM 接收器,因此如果您想使用计数器中的其他接收器,您还必须指定它们:
const counterSinks = onionify(Counter)(sources);
const combinedVdom$ = xs.combine(vdom$, counterSinks.DOM)
.map(div). //Enclose with div
return {
DOM: combinedVdom$,
HTTP: xs.merge(myOwnRequest$, counterSinks.HTTP)
//...
};
因为对每个水槽都这样做有点乏味,我在包 cyclejs-utils
:
const counterSinks = onionify(Counter)(sources);
const combinedVdom$ = xs.combine(vdom$, counterSinks.DOM)
.map(div). //Enclose with div
const ownSinks = { DOM: vdom$, /* ... */ };
return {
...mergeSinks(ownSinks, counterSinks)
DOM: combinedVdom$
};
我指定额外的 DOM 的原因是 mergeSinks 在每个接收器上调用合并,但是对于 DOM,您想使用 combine + map 来组合 DOM 到一个新的 parent DOM 正是你想如何使用它。