0

我正在玩cyclejs,我试图弄清楚处理许多来源/意图的惯用方式应该是什么。我在 TypeScript 下面有一个简单的 cyclejs 程序,其中包含对最相关部分的注释。

您是否应该像在 Elm 或 Redux 中那样将源/意图建模为谨慎的事件,还是应该通过流操作做一些更聪明的事情?我很难看到当应用程序很大时如何避免这种事件模式。

如果这是正确的方法,它最终会不会成为 Elm 的 JS 版本,并增加了流管理的复杂性?

import { div, DOMSource, h1, makeDOMDriver, VNode, input } from '@cycle/dom';
import { run } from '@cycle/xstream-run';
import xs, { Stream } from 'xstream';

import SearchBox, { SearchBoxProps } from './SearchBox';


export interface Sources {
    DOM: DOMSource;
}

export interface Sinks {
    DOM: Stream<VNode>
}

interface Model {
    search: string
    searchPending: {
        [s: string]: boolean
    }
}

interface SearchForUser {
    type: 'SearchForUser'
}

interface SearchBoxUpdated {
    type: 'SearchBoxUpdated',
    value: string
}


type Actions = SearchForUser | SearchBoxUpdated;

/**
 * Should I be mapping these into discreet events like this?
 */
function intent(domSource: DOMSource): Stream<Actions> {
    return xs.merge(
        domSource.select('.search-box')
            .events('input')
            .map((event: Event) => ({
                type: 'SearchBoxUpdated',
                value: ((event.target as any).value as string)
            } as SearchBoxUpdated)),

        domSource.select('.search-box')
            .events('keypress')
            .map(event => event.keyCode === 13)
            .filter(result => result === true)
            .map(e => ({ type: 'SearchForUser' } as SearchForUser))
    )
}

function model(action$: Stream<Actions>): Stream<Model> {
    const initialModel: Model = {
        search: '',
        searchPending: {}
    };

    /*
     * Should I be attempting to handle events like this?
     */
    return action$.fold((model, action) => {
        switch (action.type) {
            case 'SearchForUser':
                return model;

            case 'SearchBoxUpdated':
                return Object.assign({}, model, { search: action.value })
        }
    }, initialModel)
}



function view(model$: Stream<Model>): Stream<VNode> {
    return model$.map(model => {
        return div([
            h1('Github user search'),
            input('.search-box', { value: model.search })
        ])
    })
}

function main(sources: Sources): Sinks {

    const action$ = intent(sources.DOM);
    const state$ = model(action$);

    return {
        DOM: view(state$)
    };
}

run(main, {
    DOM: makeDOMDriver('#main-container')
});
4

1 回答 1

2

在我看来,你不应该像你一样多路复用意图流(将所有意图合并到一个流中)。

相反,您可以尝试在您的intent函数中返回多个流。

就像是:

function intent(domSource: DOMSource): SearchBoxIntents {
  const input = domSource.select("...");
  const updateSearchBox$: Stream<string> = input
    .events("input")
    .map(/*...*/)

  const searchForUser$: Stream<boolean> = input
    .events("keypress")
    .filter(isEnterKey)
    .mapTo(true)

  return { updateSearchBox$, searchForUser$ };
}

然后,您可以将这些操作映射到model函数中的减速器,合并这些减速器,最后合并fold它们

function model({ updateSearchBox$, searchForUser$ }: SearchBoxIntents): Stream<Model> {
  const updateSearchBoxReducer$ = updateSearchBox$
    .map((value: string) => model => ({ ...model, search: value }))

  // v for the moment this stream doesn't update the model, so you can ignore it
  const searchForUserReducer$ = searchForUser$
    .mapTo(model => model);

  return xs.merge(updateSearchBoxReducer$, searchForUserReducer$)
    .fold((model, reducer) => reducer(model), initialModel);
}

此解决方案的多个优点:

  • 您可以键入函数的参数并检查是否传递了正确的流;
  • switch如果动作数量增加,您不需要巨大的;
  • 您不需要操作标识符。

在我看来,当两个组件之间存在父/子关系时,多路复用/多路分解流是好的。这样,父母只能消费events它需要的东西(这更像是一种直觉而不是一般规则,它需要更多的思考:))

于 2018-02-27T09:32:35.157 回答