You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React.js + RxJS:为何仅在Store中订阅Observable才能让生命游戏应用正常运行?

问题

我正在尝试用React.js结合RxJS实现带“炸弹”功能的简易生命游戏。但遇到个奇怪的问题:只在React组件里订阅状态Observable时,应用完全没反应;但在数据Store里额外加个订阅后,整个应用就正常运行了。只要添加注释“Just add this and it starts to work!”下面的订阅代码就能生效。

组件代码

import styles from './GOLGrid.module.css';
import { GOLState, useGOLContext } from '../../../../../../store/GOLStore';
import { useObservable } from 'rxjs-hooks';
import { useEffect, useState } from 'react';

export function GOLGrid() {
  const context = useGOLContext()
  const state = useObservable(() => context.GOLState$);
  // const [state, setState] = useState<GOLState>();

  // useEffect(() => {
  //   const subscription = context.GOLState$.subscribe({
  //     next: (state) => setState(state),
  //     error: (err) => console.error('Error:', err),
  //     complete: () => console.log('Complete'),
  // });
  //   return () => subscription.unsubscribe();
  // },[]);

  const buildElements = () => {
    return state?.alife.map(
      (alife, i) => <div key={i} className={`${styles.golElement} ${alife ? styles.golElementLiving : ''}`}/>);
  }

  return <div 
  className={styles.GOLGrid} 
  style={{
    gridTemplateColumns: `repeat(${state?.columns ?? 0},auto)`, 
    gridTemplateRows: `repeat(${state?.rows ?? 0},auto)`}}>
    {buildElements()}
  </div>;
}

Store代码

import { createContext } from 'react';
import React from 'react';
import { BehaviorSubject, Observable, combineLatest, filter, interval, map, merge, switchMap, tap, } from 'rxjs';
import { bomb, createStartConfig, newGeneration } from '../services/GOLService';

export type GOLState = {
  rows: number,
  columns: number,
  alife: Array<boolean>,
}

const numberOfRows$ = new BehaviorSubject(8);
const numberOfColumns$ = new BehaviorSubject(8);
const delay$ = new BehaviorSubject(1000);

const bombs$ = new BehaviorSubject<number | null>(null)
const GOLStateLatest$ = new BehaviorSubject<GOLState>({
        rows: numberOfRows$.value,
        columns: numberOfColumns$.value,
        alife: createStartConfig(numberOfRows$.value, numberOfColumns$.value),
    });

const bombed$ = bombs$.pipe(
    filter((e) => e !== null),
    map((e) => bomb(GOLStateLatest$.value!, e!)),
);

const newGeneration$ = delay$.pipe(
    switchMap((e) => interval(e).pipe(
        map(() => newGeneration(GOLStateLatest$.value)),
    ))
)

const GOLState$ = merge(bombed$, newGeneration$).pipe(
    tap((e) => GOLStateLatest$.next(e)),
);

// Just add this and it starts to work!
GOLState$.subscribe({
    next: (state) => null,
    error: (err) => console.error('Error:', err),
    complete: () => console.log('Complete'),
  });
  

type GOLContent = {
    GOLState$: Observable<GOLState>,
}

const GOLContext = createContext<GOLContent>({
    GOLState$: GOLState$,
})

export const useGOLContext = () => React.useContext(GOLContext);

type Props = {
    children?: React.ReactNode
};
export const GOLProvider: React.FC<Props> = ({children}) => (
    <GOLContext.Provider
    value={{
        GOLState$: GOLStateLatest$,
    }}
    >
        {children}
    </GOLContext.Provider>
)
原因分析

这本质是RxJS中冷Observable的特性导致的,拆解核心逻辑:

  1. 冷Observable的“惰性”:你代码里的newGeneration$基于interval创建,而interval属于冷Observable——这类Observable不会主动生成数据,必须等到有至少一个订阅者存在时,才会启动并开始发射数据流。没有订阅的话,它就一直处于“休眠”状态。

  2. GOLState$是状态更新的核心管道:GOLState$合并了bombed$和newGeneration$,并且通过tap操作符把计算出的新状态推给GOLStateLatest$(也就是组件实际订阅的BehaviorSubject)。但如果GOLState$没有被订阅,整个管道都不会执行:interval不会启动定时任务,newGeneration不会计算新状态,tap里的GOLStateLatest$.next(e)也不会触发,GOLStateLatest$自然不会有新值输出,组件也就看不到任何变化。

  3. 组件订阅的是GOLStateLatest$而非GOLState$:你的GOLProvider给组件提供的是GOLStateLatest$,但这个BehaviorSubject的更新完全依赖GOLState$管道的执行。当你在Store里给GOLState$加了那个空订阅后,GOLState$的管道被激活:interval开始定时发射值,新状态不断计算并通过tap推给GOLStateLatest$,组件订阅的GOLStateLatest$就能拿到更新,应用也就正常运行了。

如果不想保留这个“空订阅”,可以给GOLState$加上share()操作符把它转成热Observable,这样只要有一个订阅者(比如组件的订阅)就能激活整个管道,避免额外的订阅。


内容的提问来源于stack exchange,提问作者Thom

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 01:12:35