RxJS和React:多个点击元素形成单个数据数组

8
我刚开始尝试学习rxjs,并决定在我目前正在使用React的UI上实现它(我有时间这样做,所以我就去做了)。然而,我仍然很难理解它的工作原理...不仅是“基础”知识,例如何时实际使用Subject和何时使用Observable,或者何时只需使用React的本地状态,还包括如何链接方法等。虽然这些都太广泛了,但这里是我遇到的具体问题。
假设我有一个UI,其中有一系列可单击的筛选器(按钮)。每次单击其中一个时,首先要确保后续操作会进行防抖处理(以避免过早过频地发出网络请求),然后要确保如果被单击(处于活动状态),则将其推入数组中,如果再次单击,则将其从数组中删除。现在,该数组应包括当前已单击或选择的所有按钮(筛选器)。
然后,在防抖时间结束时,我希望能够使用该数组并通过Ajax将其发送到我的服务器并执行一些操作。
import React, { Component } from 'react';
import * as Rx from 'rx';

export default class CategoryFilter extends Component {
 constructor(props) {
    super(props);

    this.state = {
        arr: []
    }

    this.click = new Rx.Subject();
    this.click
    .debounce(1000)
    // .do(x => this.setState({
    //  arr: this.state.arr.push(x)
    // }))
    .subscribe(
       click => this.search(click),
       e => console.log(`error ---> ${e}`),
       () => console.log('completed')
    );
 }

search(id) {
    console.log('search --> ', id);
    // this.props.onSearch({ search });
}

clickHandler(e) {
    this.click.onNext(e.target.dataset.id);
}

render() {
    return (
        <section>
            <ul>
                {this.props.categoriesChildren.map(category => {
                    return (
                        <li
                            key={category._id}
                            data-id={category._id}
                            onClick={this.clickHandler.bind(this)}
                        >
                            {category.nombre}
                        </li>
                    );
                })}
            </ul>
        </section>
    );
 }
}

我本可以不使用RxJS来检测数组并使用小的防抖等方法,但我选择了这种方式,因为我想试图理解它,并能够在更大的场景中使用它。然而,我必须承认,我对最佳方法已经迷失了方向。涉及到很多方法和不同的事情(模式和库),我被困在这里。
无论如何,欢迎任何和所有的帮助(以及关于如何改进此代码的一般评论)。提前致谢!
---------------------------------更新内容---------------------------------
我已经在我的代码中实现了Mark建议的部分,但仍存在两个问题:
1. 我仍然不确定如何过滤结果,使得数组只保持所点击按钮的ID(活动)。因此,换句话说,这将是以下操作:
单击按钮一次 -> 将其ID放入数组中
再次单击同一按钮(可能是在第一个点击后立即进行,也可能是在任何其他时间) -> 从数组中删除其ID。
这必须有效,以便通过ajax实际发送带有正确过滤器的数组。现在,我甚至不确定这是否是RxJS可以实现的操作,但人们可以梦想... (而且,我愿意打赌这是可以的)。
2. 也许这是一个更大的问题:我如何在这个视图上实际维护这个数组。我猜我可以在这个任务中使用React的本地状态,只是不知道如何用RxJS实现它。因为目前情况下,缓冲区仅在防抖时间结束之前单击的按钮返回,这意味着它每次都会“创建”一个新数组。这显然不是正确的行为。它应该总是指向现有的数组并对其进行过滤和处理。
以下是当前的代码:
import React, { Component } from 'react';
import * as Rx from 'rx';

export default class CategoryFilter extends Component {
 constructor(props) {
    super(props);

    this.state = {
        arr: []
    }

    this.click = new Rx.Subject();
    this.click
    .buffer(this.click.debounce(2000))
    .subscribe(
        click => console.log('click', click),
        e => console.log(`error ---> ${e}`),
        () => console.log('completed')
    );
 }

search(id) {
    console.log('search --> ', id);
    // this.props.onSearch({ search });
}

clickHandler(e) {
    this.click.onNext(e.target.dataset.id);
}

render() {
    return (
        <section>
            <ul>
                {this.props.categoriesChildren.map(category => {
                    return (
                        <li
                            key={category._id}
                            data-id={category._id}
                            onClick={this.clickHandler.bind(this)}
                        >
                            {category.nombre}
                        </li>
                    );
                })}
            </ul>
        </section>
    );
 }
}

感谢大家再次的支持!
2个回答

1
使用Rx.Observable.fromevent(请参见https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/events.md#converting-a-dom-event-to-a-rxjs-observable-sequence),将筛选项设置为可观察的点击事件流 - 它理解用于处理单击的多元素选择器。
您希望保持接收点击事件,直到达到防抖动效果(用户已启用/禁用所有要使用的筛选器)。 您可以使用Bufferoperator,并使用需要在缓冲区关闭并发出缓冲值时发出值的closingSelector
但是,问题在于如何知道当前实际状态。 更新 似乎更容易使用.scan运算符创建您的filterState数组并对其进行防抖动。
const sources = document.querySelectorAll('input[type=checkbox]');
const clicksStream = Rx.Observable.fromEvent(sources, 'click')
  .map(evt => ({
        name:  evt.target.name,
        enabled: evt.target.checked
  }));

const filterStatesStream = clicksStream.scan((acc, curr) => {
  acc[curr.name] = curr.enabled;
  return acc
}, {})
.debounce(5 * 1000)

filterStatesStream.subscribe(currentFilterState => console.log('time to do something with the current filter state: ', currentFilterState);

(https://jsfiddle.net/crunchie84/n1x06016/6/)


谢谢,马克。这看起来是我开始的好地方。不过,我还有几个问题。首先,考虑到我正在使用React,Rx.Observable.fromEvent是否是最佳选择(因此document.querySelectorAll('input[type=button]');并不像最佳方法)?我也在考虑这对于将所有被点击的内容添加到数组中很好,但是如何从已经被点击的按钮中删除数组呢?所以:单击一次->进入数组;再次单击相同的按钮->从数组中删除。这样做可能吗? - deathandtaxes
实际上,我相信通过使用Subject(就像我最初的想法一样)以及缓冲区,我可以获得与您的解决方案相同的结果(除非在幕后发生了什么“错误”的事情。如果您知道,请告诉我,否则似乎我已经接近成功了。您选择那种方法是出于特定的原因--性能方面--还是纯粹为了演示目的?)这样,我只需要处理过滤器的问题...确保数组仅具有“活动”状态下的单个实例,没有重复项和未被“取消选中”的按钮。 - deathandtaxes
大多数情况下,主题并不是您所需要的东西。我已经更新了我的原始答案,并提供了一个完整的rx流,可以满足您的需求。 - Mark van Straten

0

实际上,你的问题与 React 本身无关,而是与 RxJS 有关。所以很容易解决。假设你有两个函数:

const removeTag = tagName =>
  tags => {
    const index = tags.indexOf(index)
    if (index !== -1)
      return tags
    else
      return tags.splice(index, 1, 0)
  }

const addTag = tagName =>
  tags => {
    const index = tags.indexOf(index)
    if (index !== -1)
      return tags.push(tagName)
    else
      return tags
  }

然后你可以使用scan

const modifyTags$ = new Subject()

modifyTags$.pipe(
  scan((tags, action) => action(tags), [])
).subscribe(tags => sendRequest(tags))

modifyTags$.next(addTag('a'))
modifyTags$.next(addTag('b'))
modifyTags$.next(removeTag('a'))

或者为 tags 创建一个单独的对象:

const tags$ = new BehaviorSubject([])

const modifyTags$ = new Subject()

tags$.pipe(
  switchMap(
    tags => modifyTags$.pipe(
      map(action => action(tags))
    )
  )
).subscribe(tags$)

tags$.subscribe(tags => sendRequest(tags))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接