这是我对@maxime1992的challenge的看法
首先,我选择使用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将相应地致电fromEvent
,next()
或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