
本文适用于具有React和RxJS经验的人。 我只是分享我认为对创建这样的UI有用的模板。
这是我们的工作:

没有类,则使用生命周期或setState 。
准备工作
您只需要在GitHub上的我的存储库中即可。
git clone https://github.com/yazeedb/recompose-github-ui cd recompose-github-ui yarn install
在master分支中,有一个完成的项目。 如果要逐步进行,请切换到start分支。
git checkout start
并运行该项目。
npm start
该应用程序应从localhost:3000开始,这是我们的初始UI。

启动您喜欢的编辑器,然后打开src/index.js 。

重新组合
如果您不熟悉Recompose ,那么这是一个很棒的库,它允许您以功能样式创建React组件。 它包含大量功能。 这是我最喜欢的。
就像Lodash / Ramda,仅是React。
我也很高兴它支持Observer模式。 引用文档 :
事实证明,大多数React Component API都可以通过Observer模式来表达。
今天我们将实践这个概念!
内联组件
到目前为止,我们拥有App最常见的React组件。 使用Recompose库中的componentFromStream函数,我们可以通过一个可观察对象来获取它。
componentFromStream函数从我们的observable开始对每个新值进行渲染。 如果尚无值,则 null 。
构型
Recompose中的流遵循ECMAScript Observable Proposal文档。 它描述了可观察对象在现代浏览器中实现时应如何工作。
同时,我们将使用RxJS,xstream,most,Flyd等库。
Recompose不知道我们使用哪个库,因此它提供了setObservableConfig函数。 有了它,您可以将我们需要的所有内容转换为ES Observable。
在src文件夹中创建一个新文件,并将其命名为observableConfig.js 。
要将RxJS 6连接到Recompose,请在其中编写以下代码:
import { from } from 'rxjs'; import { setObservableConfig } from 'recompose'; setObservableConfig({ fromESObservable: from });
将此文件导入index.js :
import './observableConfig';
仅此而已!
重组+ RxJS
将componentFromStream导入添加到index.js :
import { componentFromStream } from 'recompose';
让我们开始覆盖App组件:
const App = componentFromStream(prop$ => { ... });
请注意, componentFromStream将带有prop$参数的函数作为参数,这是props的可观察版本。 这个想法是使用map将常规props变成React组件。
如果您使用RxJS,则应该熟悉map运算符。
地图
顾名思义, map将Observable(something)变为Observable(somethingElse) 。 在我们的情况下Observable(component) Observable(props) Observable(component) 。
导入map运算符:
import { map } from 'rxjs/operators';
补充我们的App组件:
const App = componentFromStream(prop$ => { return prop$.pipe( map(() => ( <div> <input placeholder="GitHub username" /> </div> )) ) });
对于RxJS 5,我们使用pipe而不是语句链。
保存文件并检查结果。 一切都没有改变!

添加事件处理程序
现在,我们将使输入字段变得有些反应。
添加createEventHandler导入:
import { componentFromStream, createEventHandler } from 'recompose';
我们将像这样使用它:
const App = componentFromStream(prop$ => { const { handler, stream } = createEventHandler(); return prop$.pipe( map(() => ( <div> <input onChange={handler} placeholder="GitHub username" /> </div> )) ) });
createEventHandler创建的对象有两个有趣的字段: handler和stream 。
在后台, handler是一个事件发射器,它将值传输到stream 。 并且, stream是将值传递给订阅者的可观察对象。
我们将链接stream和prop$以获取输入字段的当前值。
在我们的情况下,一个不错的选择是使用combineLatest函数。
鸡蛋和鸡肉问题
要使用combineLatest , stream和prop$必须产生值。 但是, stream不会释放任何内容,除非某些值释放了prop$ ,反之亦然。
您可以通过将stream设置stream初始值来解决此问题。
从RxJS导入startWith语句:
import { map, startWith } from 'rxjs/operators';
创建一个新变量以从更新的stream获取值:
// App component const { handler, stream } = createEventHandler(); const value$ = stream.pipe( map(e => e.target.value) startWith('') );
我们知道,当输入字段更改时, stream将引发事件,因此让我们立即将其转换为文本。
并且由于输入字段的默认值是一个空字符串,因此请使用该value$初始化value$对象value$
编织在一起
现在我们准备连接两个流。 导入combineLatest作为创建Observable对象的方法, 而不是作为operator 。
import { combineLatest } from 'rxjs';
您也可以导入tap语句以检查输入值。
import { map, startWith, tap } from 'rxjs/operators';
像这样使用它:
const App = componentFromStream(prop$ => { const { handler, stream } = createEventHandler(); const value$ = stream.pipe( map(e => e.target.value), startWith('') ); return combineLatest(prop$, value$).pipe( tap(console.warn),
现在,如果您开始在我们的输入字段中输入内容,则值[props, value]将出现在控制台中。

用户组件
该组件将负责显示我们将其姓名转移给他的用户。 它将从App组件接收value并将其转换为AJAX请求。
jsx / css
所有这些都基于出色的GitHub Cards项目。 大多数代码(尤其是样式)均已复制或改编。
创建src/User文件夹。 在其中创建一个User.css文件,并将此代码复制到其中。
并将此代码复制到src/User/Component.js文件。
该组件仅使用来自GitHub API调用的数据填充模板。
货柜
现在这个组件是“哑巴”,我们不在路上,让我们制作一个“智能”组件。
这是src/User/index.js
import React from 'react'; import { componentFromStream } from 'recompose'; import { debounceTime, filter, map, pluck } from 'rxjs/operators'; import Component from './Component'; import './User.css'; const User = componentFromStream(prop$ => { const getUser$ = prop$.pipe( debounceTime(1000), pluck('user'), filter(user => user && user.length), map(user => ( <h3>{user}</h3> )) ); return getUser$; }); export default User;
我们将User定义为componentFromStream ,它返回一个Observable prop$对象,该对象将传入的属性转换为<h3> 。
去抖时间
每次在键盘上按下一个键,我们的User都会收到新值,但是我们不需要这种行为。
当用户开始键入时, debounceTime(1000)将跳过所有持续时间少于一秒的事件。
拔
我们希望将user对象作为props.user传递。 pluck运算符从对象中获取指定的字段并返回其值。
过滤器
在这里,我们确保user通过,并且不是空字符串。
地图
我们从user制作<h3>标签。
连接
返回src/index.js并导入User组件:
import User from './User';
我们将值value作为user参数传递:
return combineLatest(prop$, value$).pipe( tap(console.warn), map(([props, value]) => ( <div> <input onChange={handler} placeholder="GitHub username" /> <User user={value} /> </div> )) );
现在,我们的值将延迟一秒钟显示。

还不错,现在我们需要获取有关用户的信息。
资料要求
GitHub提供了一个用于获取用户信息的API: https : //api.github.com/users/${ user } 。 我们可以轻松地编写一个辅助函数:
const formatUrl = user => `https://api.github.com/users/${user}`;
现在我们可以在filter之后添加map(formatUrl) :
const getUser$ = prop$.pipe( debounceTime(1000), pluck('user'), filter(user => user && user.length), map(formatUrl),
现在,URL代替用户名显示在屏幕上。
我们需要提出要求! switchMap和ajax可以switchMap 。
switchMap
该运算符是在多个可观察值之间切换的理想选择。
假设用户键入了名称,我们将在switchMap进行请求。
如果用户在API的响应到达之前输入了内容,将会发生什么? 我们应该担心以前的要求吗?
不行
switchMap将取消旧请求并切换到新请求。
阿贾克斯
RxJS提供了自己的ajax实现,可与switchMap一起switchMap !
试一下
我们导入两个运算符。 我的代码如下所示:
import { ajax } from 'rxjs/ajax'; import { debounceTime, filter, map, pluck, switchMap } from 'rxjs/operators';
并像这样使用它们:
const User = componentFromStream(prop$ => { const getUser$ = prop$.pipe( debounceTime(1000), pluck('user'), filter(user => user && user.length), map(formatUrl), switchMap(url => ajax(url).pipe( pluck('response'), map(Component) ) ) ); return getUser$; });
switchMap从我们的输入字段切换到AJAX请求。 当答案到达时,它将答案传递给我们的哑组件。
结果就是这里!

错误处理
尝试输入不存在的用户名。

我们的应用程序已损坏。
catchError
使用catchError运算符catchError我们可以显示理智的响应,而不是安静地catchError 。
我们导入:
import { catchError, debounceTime, filter, map, pluck, switchMap } from 'rxjs/operators';
并将其插入我们的AJAX请求的末尾:
switchMap(url => ajax(url).pipe( pluck('response'), map(Component), catchError(({ response }) => alert(response.message)) ) )

已经不错,但是您当然可以做得更好。
组件错误
使用内容创建src/Error/index.js :
import React from 'react'; const Error = ({ response, status }) => ( <div className="error"> <h2>Oops!</h2> <b> {status}: {response.message} </b> <p>Please try searching again.</p> </div> ); export default Error;
它将很好地显示我们的AJAX请求的response和status 。
我们将其导入User/index.js ,同时将其从RxJS导入operator:
import Error from '../Error'; import { of } from 'rxjs';
请记住,传递给componentFromStream的函数应返回observable。 我们可以使用of运算符来实现:
ajax(url).pipe( pluck('response'), map(Component), catchError(error => of(<Error {...error} />)) )
现在我们的用户界面看起来好多了:

加载指示器
现在该介绍状态管理了。 您还可以如何实现加载指标?
如果放置setState我们将使用BehaviorSubject怎么办?
重组文档建议以下内容:
代替setState(),组合多个线程
好的,您需要两个新的导入:
import { BehaviorSubject, merge, of } from 'rxjs';
BehaviorSubject将包含下载状态,并且merge会将其与组件相关联。
内部componentFromStream :
const User = componentFromStream(prop$ => { const loading$ = new BehaviorSubject(false); const getUser$ = ...
BehaviorSubject用初始值或“状态”初始化。 由于在用户开始输入文本之前我们不会做任何事情,因此请将其初始化为false 。
我们将使用tap运算符更改loading$状态:
import { catchError, debounceTime, filter, map, pluck, switchMap, tap
我们将像这样使用它:
const loading$ = new BehaviorSubject(false); const getUser$ = prop$.pipe( debounceTime(1000), pluck('user'), filter(user => user && user.length), map(formatUrl), tap(() => loading$.next(true)), // <--- switchMap(url => ajax(url).pipe( pluck('response'), map(Component), tap(() => loading$.next(false)), // <--- catchError(error => of(<Error {...error} />)) ) ) );
就在switchMap和AJAX请求之前,我们将true传递给loading$值,并将false传递给成功响应。
现在,我们只需连接loading$和getUser$ 。
return merge(loading$, getUser$).pipe( map(result => (result === true ? <h3>Loading...</h3> : result)) );
在开始工作之前,我们可以导入delay语句,以使过渡不会太快。
import { catchError, debounceTime, delay, filter, map, pluck, switchMap, tap } from 'rxjs/operators';
在map(Component)之前添加delay :
ajax(url).pipe( pluck('response'), delay(1500), map(Component), tap(() => loading$.next(false)), catchError(error => of(<Error {...error} />)) )
结果呢

全部:)