{"id":28403871,"url":"https://github.com/kunduin/observable-context","last_synced_at":"2025-06-27T08:31:22.180Z","repository":{"id":57313080,"uuid":"386806283","full_name":"Kunduin/observable-context","owner":"Kunduin","description":"Rxjs + React Context + useSelector = Small Redux !!","archived":false,"fork":false,"pushed_at":"2021-08-03T08:31:50.000Z","size":43,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-02T04:18:04.541Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Kunduin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-07-17T01:27:30.000Z","updated_at":"2021-08-03T08:31:53.000Z","dependencies_parsed_at":"2022-09-20T23:10:33.369Z","dependency_job_id":null,"html_url":"https://github.com/Kunduin/observable-context","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kunduin%2Fobservable-context","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kunduin%2Fobservable-context/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kunduin%2Fobservable-context/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kunduin%2Fobservable-context/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Kunduin","download_url":"https://codeload.github.com/Kunduin/observable-context/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kunduin%2Fobservable-context/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":258749048,"owners_count":22751391,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-06-01T19:11:06.138Z","updated_at":"2025-06-27T08:31:22.168Z","avatar_url":"https://github.com/Kunduin.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Observable Context\n\n这是 rxjs 爱好者制作的一个 react 状态管理器。本项目使用 rxjs 作为响应式的内核，除了提供与 redux 类似的状态监听方法以外，还提供了 rxjs 独有的 pipe 支持。\n\n## 1. 安装\n\n```shell\nnpm i observable-context rxjs\n\nyarn add observable-context rxjs\n```\n\n## 2. 核心概念\n\n### 2.1 响应式，rxjs，redux\n\n[reactiveX](http://reactivex.io/intro.html) 是一套事件驱动的响应式编程库，其中 [rxjs](https://rxjs.dev/) 是其中 JavaScript 的一套实现。这个响应式编程其实也不是什么黑魔法，本质就是观察者模式的一个扩展，只是在其中使用了很多函数式的思想导致其有一定的“反直觉”。\n\n观察者模式的本质就是监听与广播，这个感兴趣可以略做了解。事实上 redux 的本质也只是一个可监听的容器，redux 容器的状态改变会通知所有监听的组件，每个 useSelector 或者 connect 的本质就是让这个组件监听 redux 的变化然后根据 redux 的变化决定是否重新渲染。这是 redux 能够做到只在某个节点进行高性能更新的本质。\n\n从观察者模式的思想出发，redux 和 rxjs 本质是一样的，既然如此作为 rxjs 的爱好者，重铸 rxjs 荣光我辈义不容辞呀。所以就实现了一套 rxjs 为核心的状态管理器，作为一个抛砖引玉，希望有更多优秀的同好可以加入到重铸 rxjs 荣光的道路上来，将 rxjs 优秀的响应式能力结合到 react 的生态中来，改进大家的开发体验。\n\n### 2.2 Subject\n\n下文提到的所有 subject，指的都是 rxjs 中的 [subject](https://rxjs.dev/guide/subject). subject 有广播的能力，使用这个能力\n\n### 2.3 本项目的容器\n\n这里也没有黑魔法。本项目使用的容器就是 react 的 context，只是 context 的 value 永远是不变的 rxjs subject，因此不会因为 context 的值更新导致所有使用 context 的组件重新渲染。了解这个概念之后，下面的很多内容都不过是上面的补充。\n\n## 3. 核心能力\n\n### 3.1 创建容器\n\n创建容器共有两个工厂，这两个工厂选择一个使用就可以了，本项目允许使用自己定制的核心 subject，因此除了提供一个默认的以 behavior subject 为核心的工厂 `createObservableContext` 以外，提供了一个使用自己 subject 的工厂 `createObservableContextBySubject`\n\n**示例**\n\n```ts\nexport const {\n  ObservableProvider, // provider 和你想象的那个 provider 用处一样\n  ObservableContext, // context 则是值为 subject 的 context\n  subject$, // subject 就是生产的 subject\n  useObservable, // useObservable 返回的就是 subject\n  useObservableSelector, // useObservableSelector 本意和 redux 一样\n  useObservableNext, // useObservableNext 返回 subject 的更新函数，扩展了一定的能力\n  useObservableOperator, // useObservableOperator 可以使用 pipe\n} = createObservableContext\u003cRoot\u003e({\n  todoList: [],\n  filter: VisibilityFilters.SHOW_ALL,\n});\n```\n\n#### 3.1.1 `createObservableContext (initialValue)`\n\n`initialValue` 是状态管理器的初始状态，返回值见下文。该方法使用 `BehaviorSubject` 作为 context 的核心\n\n#### 3.1.2 `createObservableContextBySubject (subject$)`\n\n`subject$` 是自己定制的 subject，这个 subject 需要和 behavior subject 一样拥有一个 `getValue` 方法，同时满足 subject 的所有能力。\n\n### 3.2 创建容器时返回的所有工具\n\n#### 3.2.1 `ObservableProvider`\n\n和你想象的那个 provider 用处一样，标准的 Context.Provider, 提前填好了初值和 subject，这两者都可以覆盖方便测试。\n\n#### 3.2.2 `ObservableContext`\n\n值为 subject 的 context，可以拿出来与后文提到的独立工具使用\n\n#### 3.2.3 `subject$`\n\n创建好的 subject，用于组件之外的场合\n\n#### 3.2.4 `useObservableSelector ([selector [,equalityFn]])`\n\n监听 store 中被 selector 选择的对象是否改变\n\n**类型定义**\n\n```ts\n/**\n * generic params\n * {InputType}: root state type\n * {OutputType}: selected type\n */\nfunction useObservableSelector\u003cOutputType = InputType\u003e(\n  selector?: Selector\u003cInputType, OutputType\u003e,\n  equalityFn?: EqualityFn\n): OutputType;\n\ntype Selector\u003cInputType, OutputType = InputType\u003e = (\n  inputs: InputType\n) =\u003e OutputType;\n\n/** determine whether a is equal with b */\nexport type EqualityFn = \u003cT\u003e(a: T, b: T) =\u003e boolean;\n```\n\n**参数定义**\n\n| 参数       | 类型                | 是否必须     | 描述                                 |\n| ---------- | ------------------- | ------------ | ------------------------------------ |\n| selector   | `(a) =\u003e b`          | not required | 选择store 中的某部分，不填就是全部值 |\n| equalityFn | `(a, b) =\u003e boolean` | not required | 比较值更新的函数，这里默认值为 ===   |\n\n**示例**\n\n```ts\nconst currentFilter = useObservableSelector(root =\u003e root.filter)\n```\n\n#### 3.2.5 `useObservableNext ()`\n\n获得 subject 的 next 方法，与原本的 next 方法不同的是，这里允许传入和 setState 一样的更新函数，可以简化流程\n\n```ts\n/**\n * generic params\n * {InputType}: root state type\n */\nfunction useObservableNext\u003cInputType = unknown\u003e(\n): (param: InputType | UpdateFunction\u003cInputType\u003e) =\u003e void\n\ntype UpdateFunction\u003cInputType\u003e = ((pre: InputType) =\u003e InputType)\n```\n\n**示例**\n\n```ts\nconst next = useObservableNext()\n\nconst addTodo = useCallback((text: string) =\u003e {\n  next(pre =\u003e ({\n    ...pre,\n    todoList: [...pre.todoList, {\n      id: getId(),\n      text,\n      completed: false\n    }]\n  }))\n}, [next])\n```\n\n#### 3.2.6 `useObservableOperator (withOperator [[, defaultValue], equalityFn])`\n\n这里是与 redux 不同的地方，使用 withOperator 可以使用自定义的 pipe 等逻辑，充分利用 rxjs 的各种能力。\n\n```ts\n/**\n * generic params\n * {InputType}: root state type\n * {OutputType}: with operator result type\n */\nfunction useObservableOperator\u003cOutputType = unknown\u003e (\n  withOperator: (events$: Observable\u003cInputType\u003e) =\u003e Observable\u003cOutputType\u003e,\n): OutputType | void;\nfunction useObservableOperator\u003cOutputType = unknown\u003e (\n  withOperator: (events$: Observable\u003cInputType\u003e) =\u003e Observable\u003cOutputType\u003e,\n  defaultValue: OutputType,\n  equalityFn?: EqualityFn\n): OutputType;\n\n/** determine whether a is equal with b */\nexport type EqualityFn = \u003cT\u003e(a: T, b: T) =\u003e boolean;\n```\n\n\n**参数定义**\n\n| 参数         | 类型                                                         | 是否必须     | 描述                                                                                                    |\n| ------------ | ------------------------------------------------------------ | ------------ | ------------------------------------------------------------------------------------------------------- |\n| withOperator | `(events$: Observable\u003cInputType\u003e) =\u003e Observable\u003cOutputType\u003e` | required     | 添加 pipe 逻辑的地方                                                                                    |\n| defaultValue | `OutputType`                                                 | not required | 如果 subject 没有触发更新，或者使用异步调度器，或操作符影响不一定有初值，这里的默认值可以保证返回值非空 |\n| equalityFn   | `(a, b) =\u003e boolean`                                          | not required | 比较值更新的函数，这里默认值为 ===                                                                      |\n\n**示例**\n\n```tsx\nconst operator = (observable$:Observable\u003cRoot\u003e) =\u003e observable$.pipe(\n  map(item =\u003e item.filter),\n  bufferCount(2)\n)\n\nconst Addon:FC = () =\u003e {\n  const histories = useObservableOperator(operator, [])\n  return \u003cdiv\u003e\n    last 2 histories buffer (rendered after 2 filter histories collected)\n    \u003cdiv\u003e\n      {histories.map(item =\u003e (\u003cdiv\u003e{item}\u003c/div\u003e))}\n    \u003c/div\u003e\n  \u003c/div\u003e\n}\n```\n\n#### 3.2.7 `useObservable ()`\n\n获得 subject 实例，如果希望自己进行更优雅的处理可以使用这个 hook\n\n```ts\n/**\n * generic params\n * {InputType}: root state type\n */\nfunction useObservable\u003cInputType = any\u003e (): ObservableContext\u003cInputType\u003e;\n```\n\n\n### 3.3 允许与任意容器结合的上述工具方法\n\n除了上面几个通过工厂构造的返回工具以外，每个勾子都有一个可以主动使用 context 的版本，比如`useObservableSelector(a =\u003e a.b)` 还可以写成 `useObservableSelector(ObservableContext, a =\u003e a.b)`，第二种函数是不依赖某个具体的 Context 的，需要从本项目引入。3.2 介绍的是 `createObservableContext` 的返回值，而这里提到的这是本项目直接对外暴露的函数，工厂做的不过是帮你提前把 context 处理好了，如果你觉得命名混乱，不如使用本节提到的办法解决。\n\n## 4. 感谢\n\n本项目大量参考了 redux 和 observable hook 的实现，希望任何看到本项目的人都去了解一下那两个伟大的项目。\n\n感谢 rxjs 团队制作了这么棒的工具。\n\n## 5. License\n\nMIT kunduin","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkunduin%2Fobservable-context","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkunduin%2Fobservable-context","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkunduin%2Fobservable-context/lists"}