1

我可能正在尝试一些愚蠢的事情,但是我有一个足够大的洋葱化 Cycle.js 应用程序,并且我正在尝试了解onionify 的工作原理,所以我想将一个洋葱化组件嵌入到我原来的非洋葱应用程序中。

所以我有一个简单的 onion-ready 组件,增量/减量示例,还有一个简单的非洋葱循环应用程序,“Hello Last Name”</a> 示例——我如何将两者结合在一起,这样我就有了在同一个网页中一个接一个的增量组件和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 文档建议的完全洋葱化的应用程序一样,我只会看到预期的计数器。

但是counts,由于 , 的输出onionify(Counter)是一个函数,所以我认为我不能在我的 main() 中“实例化”它。

同样,我认为我不能通过Counter()在 main 内部调用来创建计数器组件,因为该函数需要sources.onion输入,而且我不确定如何创建.onion具有 type 的字段StateSource

问题

我如何Counter在我的 non-onionified 中使用这个 onion-ready 组件main

完整示例

完整示例可在https://gist.github.com/fasiha/939ddc22d5af32bd5a00f7d9946ceb39获得——克隆它,npm install以获取必要的包,然后make(运行tscbrowserify转换 TypeScript→JavaScript→browserified JS)。

4

1 回答 1

2

这其实很简单。正如你所说,你可以调用Counter()你的 main ,但它没有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$,
    };
}

请注意,这只会使用 DOM 接收器,Counter因此如果您想在计数器中使用其他接收器,您还必须指定它们:

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 在每个接收器上调用 merge,但是对于 DOM,你想使用 combine + map,将 DOM 组合到一个新的父 DOM,这正是你想要使用它的方式。

于 2017-08-09T09:21:59.580 回答