使用RXJS和Rimmel.js构建一个反应性拆分显示屏
#javascript #网络开发人员 #rxjs #rimmel

这是我对@maxime1992challenge的看法

首先,我选择使用Rimmel.js,这是一个牢记流的创建的模板引擎(披露:由我),您可以只喂食承诺和可观察到的东西,它们将无缝地订阅并沉没到DOM,DOM,DOM,下帮助您摆脱许多RX应用程序中仍然存在的最新命令代码。

接下来,@maxime1992我要祝贺您的代码。我真的很喜欢它的优雅,我首先了解了switchScan操作员。

对于那些尚未阅读挑战以及Maxime的实施的人,我热情建议在继续之前进行查看。

挑战的重点是RXJS,所以我也将重点放在这方面,围绕下面的解决方案使用Rimmel来源和下沉。

异步发电机

为了使事情变得有趣,我还创建了一个新的getLettersFromTo()作为异步发电机函数,因此它可以保持懒惰并以我们想要的速度发出。

export async function* getLettersFromTo(from, to) {
  const a = LETTERS.indexOf(from) +1;
  const b = LETTERS.indexOf(to);
  const l = LETTERS.length;
  const carry = b < a ? l : 0;

  for (var i = a; i <= b +carry; i++) {
    yield LETTERS[i % l];
    await delay(DELAY_AMOUNT);
  }
}

实际上,我并不是只是为了好玩,而是要解决另一个问题,然后我意识到它使我能够通过一些功能简化utils.js,所以我保留了它,尽管我可能需要如果它不符合挑战的FRP要求,则退休。
但是,重点不是这个功能。

将模型一起推

rxjs可以很好地将异步发电机变成可观察到的物品,从而为我们提供了某种懒惰的基于Push模型的流。

import { from as ObservableFrom } from 'rxjs';

const sequence = ([prev, next]) =>
  ObservableFrom(getLettersFromTo(prev, next))

个人注:我不能以这种方式使自己像from()一样。它曾经被称为Observable.from(),这对我的耳朵很有意义,我很想念它,因此将其导入它,因为ObservableFrom有助于提醒良好的旧RX5天。 :)

因此,上面的函数本质上返回一个可观察到的序列,这些字母的拆分范围显示需要循环。

主流

以下是input$$,一个Subject我们将馈送我们希望板从UI显示的数据。

const input$$ = new Subject().pipe(
  debounceTime(300),
  map(e => inputToBoardLetters(e.target.value)),
  share(),
  startWith(BASE),
);

到目前为止非常简单,它将仅采用输入字符串,将其转换为大写字母,然后使流“可共享”以减少处理。

每封信

然后,我们在板上的每个字母都有一个流。是的,每个人都可以观察到,我们稍后再回到。

const nextLetter = index => input$$.pipe(
  map(str => str[index]),
  distinctUntilChanged(),
  pairwise(),
  switchMap(sequence),
);

这个一个字符串以显示整个字符串,选择了我们指定的index指定字母,然后通过switchMap返回要显示的字符序列,该字符将由上面的sequence函数提供。

我们使用的是pairwise,它每次都会返回上一个和下一个值。这有助于根据板上的当前字母和下一个显示的字母来生成序列。

SwitchScan vs switchmap

我认为这与Maxime的解决方案是最有趣的区别。他使用了switchScan,我使用了switchMap

switchScan背后的想法是,您可以逐步更改已经进行中已经进行的转换。就像您开始以某种方式“减少”流一样,然后发生了一些事情(例如用户给渲染的新输入),然后您可以相应地适应该变化,而整个板也从当前状态开始,但是没有完全开始。

这无疑是解决问题的最迷人的方法,但是我认为我仍然尝试另一种方法,并且意识到swtichMap也可以解决问题。

UI

UI由两个Rimmel组件制成。它们与React的函数组件非常相似,除了您可以使用标记的模板,并且只是无缝将可观察到的物品分配为源或汇到HTML属性。
Rimmel将相应地致电fromEventnext()subscribe()

字母组件

const board = initial => Object.keys(initial).map(i => render`
  <span class="letter">${nextLetter(i)}</span>
`).join('')

此组件通过每个字母循环并呈现一个span标签,其中我们下沉了由nextLetter创建的可观察到的。

表现?

回到为什么我在板上每个字母创建一个可观察到的序列的原因。那是为了保持功能反应性的样式,并避免必须创建一个命令式水槽才能更新每个水槽。
以命令式的方式,您循环循环并将其设置在DOM中:

for(i=0;i<letters.length;i++) {
  some.dom.element[i].innerHTML = letters[i];
}

以更具功能反应的样式,DOM中的每个字母组件可以单独声明为相应流的水槽。

  <span class="letter">${nextLetter(i)}</span>

当然,在这种情况下将有绩效考虑。如果原始速度受到威胁,命令风格将击败其他任何东西,尽管如果我们有一个(n个极其)巨大的董事会以非常高的频率更新,那将打破帧速度预算,因此我宁愿利用调度程序来使用调度程序来保持FRP风格,所有好处和高性能。

主模板

最后,我们有了我们的主模板,其中包含一个文本框输入$$将从中获取数据。

const page = () => render`
  Input String: <input oninput="${input$$}" onmount="${input$$}" value="${INITIAL_STRING}">

  <div class="letters">
    ${board(BASE)}
  </div>

  <div>Bye</div>
`;

document.body.innerHTML = page();

本质上,每当oninput或Custom onmount Events Fire时,他们都会在幕后致电input$$.next(event)

结论

@maxime1992我喜欢这个挑战,感谢您的乐趣,希望其他人也会加入!

完整的代码可在此处提供:https://stackblitz.com/edit/rxjs-xubnby