使用 RxJS 和 <$> 片段在 React 中获取数据

2025-06-10

使用 RxJS 和 <$> 片段在 React 中获取数据

我们经常需要在组件中获取数据。以下是使用 useState hook 和fetch API获取并显示数据的示例:



function App(){
  const [data, setData] = useState(null);

  // useEffect for fetching data on mount
  useEffect(() => {
    fetch('//...')
    .then(response => response.json())
    .then(data => setData(data));
  }, []);

  return <div>Data: { data }</div>
}


Enter fullscreen mode Exit fullscreen mode
response.ok为了简洁起见, 我将在这里和以后不再检查

看起来还好吗?

嗯,这种方法缺少一些重要特征:

  • 取消组件卸载时的获取(例如,如果用户离开当前页面)
  • 处理错误
  • 显示加载指示器

为了很好地处理所有这些问题,我们将使用 RxJS!

RxJS是一款非常强大的工具,用于管理和协调异步事件(例如获取和 UI 事件)。学习它会让你受益匪浅!

请不要惊慌,我会指导您如何添加和使用它🙂

tl;dr: 生成的应用程序游乐场<$> fragment 库


让我们开始更新我们的应用程序以使用 RxJS!

🔋 加电

首先,我们将切换到 RxJS 的fromFetch — 它是原生 fetch的包装器



function App(){
  const [data, setData] = useState(null);

  useEffect(() => {
    fromFetch('//...')
      .subscribe(response =>
        response.json().then(data => setData(data))
      );
  }, []);

  return <div>Data: { data }</div>
}


Enter fullscreen mode Exit fullscreen mode

.subscribe方法是 Promises 中的类似物.then——它将从 RxJS 流接收值更新(目前它只能处理一个更新,但会有更多)

.subscribe返回一个对象,我们可以用它来取消“订阅”。这将帮助我们解决第一个问题:在组件卸载时取消获取。



function App(){
  const [data, setData] = useState(null);

  useEffect(() => {
    const subscription = fromFetch('//...')
      .subscribe(response =>
        response.json().then(data => setData(data))
      );

    // this function will be called on component unmount
    // it will terminate the fetching
    return () => subscription.unsubscribe();
  }, []);

  return <div>Data: { data }</div>
}


Enter fullscreen mode Exit fullscreen mode

有关详细信息,请参阅 React 的useEffect#cleaning-up-an-effect文档部分

好哇:已完成 1 个,还剩 2 个!


在继续之前,我们先来做一点清理工作:

🔧 重构和 <$> 片段

如您所见,我们在函数response.json()内部使用异步操作subscribe——这是一种不好的做法,原因如下:如果我们已经处于response.json()解析阶段,则此流将不可重用,并且取消将不起作用。

我们将使用mergeMapRxJS 操作符来解决这个问题:



function App(){
  const [data, setData] = useState(null);

  useEffect(() => {
    const subscription = fromFetch('//...')
      .pipe(
        // mergeMap is an operator to do another async task
        mergeMap(response => response.json())
      )
      .subscribe(data => setData(data));

    return () => subscription.unsubscribe();
  }, []);

  return <div>Data: { data }</div>
}


Enter fullscreen mode Exit fullscreen mode

更新@benlesh提出了一个很好的观点,可以使用 RxJS 的ajax.getJSON来代替 fetch 包装器,并且跳过mergeMap。例如: 。出于教育和懒惰的原因,ajax.getJSON(url).subscribe(/* etc. */)我会保留这种fromFetch方法🙂

我们已经将response.json()操作与结果处理分离。现在我们的subscribe处理程序只负责显示数据——我们现在可以使用<$>fragment 了!

<$> — 是一个小型(1Kb)包,用于在我们的 React 组件中显示 RxJS 值。

它会为我们订阅提供的流并显示更新。它还会在组件卸载时取消订阅,所以我们也不用担心这个!



function App(){
  // we need useMemo to ensure stream$ persist
  // between App re-renders
  const stream$ = useMemo(() =>
    fromFetch('//...')
      .pipe(
        mergeMap(response => response.json())
      )
  , []);

  return <div>Data: <$>{ stream$ }</$></div>
}


Enter fullscreen mode Exit fullscreen mode

请注意,我们已经删除了useState并且.subscribe<$>完成了所有这些!


现在,我们准备添加更多运算符来继续解决我们的任务。让我们添加一个加载指示器!

⏳ 加载指示器



function App(){
  const stream$ = useMemo(() =>
    fromFetch('//...')
      .pipe(
        mergeMap(response => response.json()),
        // immediately show a loading text
        startWith('loading...')
      )
  , []);

  return <div>Data: <$>{ stream$ }</$></div>
}


Enter fullscreen mode Exit fullscreen mode

startWith将在异步数据流前面添加提供的值。在我们的例子中,它看起来有点像这样:



start -o---------------------------o- end

       ^ show 'loading'            ^ receive and display
       | immediately               | response later


Enter fullscreen mode Exit fullscreen mode

太棒了:已完成 2 个,还剩 1 个!


接下来我们将处理错误:

⚠️ 错误处理

另一个操作符catchError可以让我们处理获取时的错误:



function App(){
  const stream$ = useMemo(() =>
    fromFetch('//...')
      .pipe(
        mergeMap(response => response.json()),
        catchError(() => of('ERROR')),
        startWith('loading...')
      )
  , []);

  return <div>Data: <$>{ stream$ }</$></div>
}


Enter fullscreen mode Exit fullscreen mode

现在,如果获取失败 - 我们将显示“错误”文本。

如果您想深入了解,我写了一篇关于管理错误的详细文章:“RxJS 中的错误处理或如何使用 Observables 避免失败” ——抑制、战略性回退、简单重试和指数延迟——一切都在那里。

已完成 3 个,剩余 0 个!


让我们最后移动一些divs :

🖼 更好的用户界面

我们很可能希望正确显示高亮的错误,并设置样式(甚至动画)的加载指示。为此,我们只需将 JSX 代码直接移到流中即可:



function App(){
  const stream$ = useMemo(() =>
    fromFetch('//...')
      .pipe(
        mergeMap(response => response.json()),
        // now we'll map not only to text
        // but to JSX
        map(data => <div className="data">Data: { data }</div>),
        catchError(() => of(<div className="err">ERROR</div>)),
        startWith(<div className="loading">loading...</div>)
      )
  , []);

  return <$>{ stream$ }</$>
}


Enter fullscreen mode Exit fullscreen mode

请注意,现在我们可以完全自定义每个状态的视图!

🍰 额外功能:防闪烁

有时,如果响应太快,我们会看到加载指示器闪烁一瞬间。这通常是不受欢迎的,因为我们花了很长时间来制作加载指示器动画,并且希望确保用户能够看到它🙂

为了解决这个问题,我们将分离出 Observable 的创建和加入延迟 500ms 的获取:



function App(){
  const stream$ = useMemo(() =>
    customFetch('//...').pipe(
        map(data => <div className="data">Data: { data }</div>),
        catchError(() => of(<div className="err">ERROR</div>)),
        startWith(<div className="loading">loading...</div>)
      )
  , []);

  return <$>{ stream$ }</$>
}

function customFetch(URL) {
  // wait for both fetch and a 500ms timer to finish
  return zip(
    fromFetch(URL).pipe( mergeMap(r => r.json()) ),
    timer(500) // set a timer for 500ms
  ).pipe(
    // then take only the first value (fetch result)
    map(([data]) => data)
  )
}


Enter fullscreen mode Exit fullscreen mode

现在我们亲爱的用户将看到至少 500 毫秒的加载动画!

完成 4 个,还剩🍰!


最后说几句:

🎉 结尾

如果您想试用一下,这就是我们最终的应用程序。

要在 React 组件中开始使用 RxJS,只需执行以下操作:



npm i rxjs react-rxjs-elements


Enter fullscreen mode Exit fullscreen mode

然后将流放入其中<$>



import { timer } from 'rxjs';
import { $ } from 'react-rxjs-elements';

function App() {
  return <$>{ timer(0, 1000) } ms</$>
}


Enter fullscreen mode Exit fullscreen mode

就是这样,我希望你学到了一些新东西!

感谢您阅读本文!保持关注,祝您拥有美好的一天🙂

如果您喜欢阅读 — — 请用 ❤️ 🦄 📘 按钮表示

在 Twitter 上关注我,了解更多 React、RxJS 和 JS 帖子:

结束

感谢@niklas_wortmann@sharlatta的审核!

鏂囩珷鏉ユ簮锛�https://dev.to/rxjs/fetching-data-in-react-with-rxjs-and-lt-gt-fragment-54h7
PREV
使用 Composer 时的 PHP 部署最佳实践
NEXT
关于 switchMap 及其相关