
本文适用于具有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} />)) )
结果呢

全部:)