【问题标题】:Cycle.js: making a multiselect component. Combined stream of option component states doesn't output changed statesCycle.js:制作多选组件。选项组件状态的组合流不输出更改的状态
【发布时间】:2017-02-27 23:12:59
【问题描述】:

可运行示例: https://esnextb.in/?gist=978799bf48a7f914cbbd39df0213d672

我正在尝试为 cycle.js 创建一个多选组件,以查看我是否想用循环应用程序替换应用程序的当前 UI。

我有一个组件用于多选中的选项。每个选项都以 {selected: Bool, label: String, value: Int}

的形式传递道具

以下是代码概览:

Option 组件侦听自身的点击,并通过组合通过在传递给 props$ 源中的选项的初始布尔值上折叠逻辑“非”函数生成的布尔值流来切换“选定”布尔值。

Option 组件然后输出它的状态流,以便 Multiselect 组件可以引用它,以便返回选定值的流等。

Multiselect 组件接收一个 props$ 源,其中包含一个带有选项 [{selected, label, value}...] 的流。

从这些选项构造一个选项组件的数组流。

Option 组件流然后被展平为虚拟 dom 树数组流(每个选项一个)。

Option 组件流也被扁平化为状态数组流(每个选项一个)。

问题从这里开始:

如果我对它们调用 .observe(console.log) ,上述两者都只会记录一次。

为了显示多选,我将选项的虚拟 dom 树数组流映射以生成多选的虚拟 dom 树。

我还将这个 vtree 流与状态流结合起来,这样我就可以同时使用 console.log(因为 observe 只记录一次)。

当我记录它们时,vtree 数组确实发生了变化,但所有选项的状态始终与其初始状态相同。

只有在选项被隔离时才会发生这种情况。如果它们不是被隔离的,它们都会响应对其中任何一个的点击,但它们的状态会被记录为已更改。

这里发生了什么?为什么隔离很重要?因为所有组合流不会每次都触发?我正在做的事情完全不是惯用的吗?

http://pastebin.com/RNWvL4nf 这里也是一个 pastebin。我想把它放在 webpackbin 上,但它不会下载我需要的包。

import * as most from 'most';
import {run} from '@cycle/most-run';
import {div, input, p, makeDOMDriver, li, ul, span, h2} from '@cycle/dom';
import fp from 'lodash/fp'
import isolate from '@cycle/isolate'

// props : { selected : Bool, label : String, value: Int}
function Option({DOM, props$}) {
    const click$ = DOM.select('.option').events('click') 
    // a stream of toggle functions. one for each click
    const toggle$ = click$
                       .map(() => bool => !bool) 
    /// stream of toggle functions folded upon the inital value so that clicking the option checks if off and on  
    const selected$ = props$.map(prop => toggle$.scan((b, f) => f(b), prop.selected)).join()
    // a stream of states which has the same structure as props, but toggled 'selected' field according to selected$ stream 
    const state$ = most.combineArray((props, selected) => ({...props, selected}), [props$, selected$])
    // the state mapped to a representation of the option
    const vtree$ = state$.map(state => { 
        return li('.option', {class: {selected: state.selected}}, [
            input({attrs: {type: 'checkbox', checked: state.selected}}),
            p(state.label),
        ]) 
    })
    // returns the stream of state$ so that multiselect can output a stream of selected values
    return {
        DOM: vtree$,
        state$,
    }
}

function Multiselect({DOM, props$}) {
    // a stream of arrays of isolated Option components
    const options$ = props$.map(
        options => 
            options.map(it => 
                isolate(Option)({DOM, props$: most.of(it)}))) 
                // Option({DOM, props$: most.of(it)}))) // comment above line and uncomment this one. Without isolation the component doesn't work correctly, but the states are updated 

    // a stream of arrays of virtual tree representations of child Option components
    const optionsVtree$ = options$.map(options => fp.map(fp.get('DOM'), options))
                                  .map(arrayOfVtree$ => most.combineArray(Array, arrayOfVtree$))
                                  .join() 
    // a stream of arrays of states of child Option components 
    const optionStates$ = options$.map(options => fp.map(fp.get('state$'), options))
                                  .map(arrayOfState$ => most.combineArray(Array, arrayOfState$))
                                  .join() 
                                //   .map(fp.filter(fp.get('selected')))  

    // here the virtual trees of options are combined with the state stream so that I can log the state. I only use the virtual dom trees   
    const vtree$ = optionsVtree$.combine(Array, optionStates$).map(([vtrees, states]) => {
        console.log(states.map(state => state.selected)); // this always prints the initial states
        // console.log(vtrees); // but the vtrees do change
        return div('.multiselect', [
            ul('.options', vtrees)
        ])
    }) 

    const sinks = {
        DOM: vtree$,
        optionStates$
    };
    return sinks;
}

run(Multiselect, {
  DOM: makeDOMDriver('#app'),
  props$: () => most.of([
      {value: 0, label: 'Option 1', selected: false},
      {value: 1, label: 'Option 2', selected: false},
      {value: 2, label: 'Option 3', selected: false},
      {value: 3, label: 'Option 4', selected: false},
    ])
});

编辑: franciscotln 好心地做了一个工作示例,它揭示了我的一些东西。 https://gist.github.com/franciscotln/e1d9b270ca1051fece868738d854d4e9 如果 props 是一个没有包裹在 observable 中的简单数组,它就可以工作。也许是因为如果它是一个可观察的数组,它需要一个可观察的组件数组,而不是一个简单的子组件数组。

但是两者并不等价。使用我的原始版本(其中选项是数组流), props$ 可以不仅仅是 .of 可观察的。它的值可以在程序过程中改变,而第二个我被原始数组卡住了。

隔离组件的数组流是否存在问题?

const props = [{value: 0, label: 'Option 1', selected: false},
                 {value: 1, label: 'Option 2', selected: false},
                 {value: 2, label: 'Option 3', selected: false},
                 {value: 3, label: 'Option 4', selected: false}]
// a stream of arrays of isolated Option components
const options = props.map(prop =>
    isolate(Option)({
        DOM,
        props$: most.of(prop)
    }) 
);

【问题讨论】:

    标签: javascript observable cyclejs xstream-js


    【解决方案1】:

    @tusharmath 在循环 gitter 聊天中发现我做错了什么。

    显然,我所要做的就是在选项组件的数组流上调用 .multicast():

    const options$ = props$.map(
            options => 
                options.map(it => 
                    isolate(Option)({DOM, props$: most.of(it)}))).multicast()
    

    与冷热可观察对象有关。我猜是被冰冷的可观察物烧伤了。

    这是他的解释。

    我会尽力解释我从专家那里了解到的 这里。隔离将范围参数附加到虚拟 dom 节点,即 创建的。现在在您的情况下,您正在创建虚拟 dom 两次

    因为这里有两个订阅——DOM: https://esnextb.in/?gist=978799bf48a7f914cbbd39df0213d672state$: https://esnextb.in/?gist=978799bf48a7f914cbbd39df0213d672两人 VDom 具有不同的范围,事件侦听器有效地使用 不同的作用域和 dom 元素有不同的作用域。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-02-15
      • 2021-05-11
      • 1970-01-01
      • 1970-01-01
      • 2018-11-03
      • 2014-02-05
      • 2016-03-08
      • 2017-05-18
      相关资源
      最近更新 更多