{"id":17039811,"url":"https://github.com/ymzuiku/react-consumer","last_synced_at":"2025-04-12T14:12:25.729Z","repository":{"id":35127864,"uuid":"191317197","full_name":"ymzuiku/react-consumer","owner":"ymzuiku","description":"满足声明式的前提下, 控制重绘的颗粒度, Example:","archived":false,"fork":false,"pushed_at":"2023-01-04T11:39:31.000Z","size":5073,"stargazers_count":15,"open_issues_count":23,"forks_count":1,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-04-05T23:32:01.172Z","etag":null,"topics":["hooks","immer","react","react-native","react-redux","redux"],"latest_commit_sha":null,"homepage":"http://consumer.writeflowy.com","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ymzuiku.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-06-11T07:26:47.000Z","updated_at":"2021-06-11T22:54:23.000Z","dependencies_parsed_at":"2023-01-15T14:30:28.449Z","dependency_job_id":null,"html_url":"https://github.com/ymzuiku/react-consumer","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ymzuiku%2Freact-consumer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ymzuiku%2Freact-consumer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ymzuiku%2Freact-consumer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ymzuiku%2Freact-consumer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ymzuiku","download_url":"https://codeload.github.com/ymzuiku/react-consumer/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248578870,"owners_count":21127713,"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":["hooks","immer","react","react-native","react-redux","redux"],"created_at":"2024-10-14T09:07:33.653Z","updated_at":"2025-04-12T14:12:25.707Z","avatar_url":"https://github.com/ymzuiku.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 满足声明式、单一状态源的前提下，控制重绘的颗粒度\n\n**请使用作者另一个库：react-ob ，更简约**\n\n\u003e 旨在阐述清楚 声明式 的状态管理思路，以 React 作为例子，思路适用于所有 声明式 UI 的状态管理方案\n\n只监听不可变对象有差异时，对颗粒 React 节点进行更新的状态管理方案，使用 redux 一样的思路，它是声明式的、单一数据源的\n\n整个状态管理方案的思路请查阅 Keynote: [React 状态管理之温故知新.key](./React状态管理之温故知新.key)\n\n这个 Keynote 是内部演讲的产物，每一页均有注释，请激活 Keynote 的演讲注释搞即可查看。\n\n## 状态管理的配置\n\n### 1. 安装依赖\n\n```sh\nyarn add react-consumer\n```\n\n源码可以直接看此仓库的 `createStateManager.tsx` 文件，代码仅有几十行。\n\n### 2. 实例化 store, Consumer\n\n```js\nimport ReactConsumer from 'react-consumer';\nimport produce from 'immer';\n\n// 一个多层级的对象示例，以验证immutable\nconst initState = {\n  user: {\n    info: {\n      num: 0,\n    },\n  },\n};\n\n// 可选编写更新处理方法，这里默认如下：使用 immer 来使用不可变对象\nconst updater = (state, event) =\u003e {\n  return produce(state, draft =\u003e {\n    event(draft);\n  });\n};\n\nconst { store, Consumer } = ReactConsumer.createStateManager(initState, updater);\n\n// 以上代码相当于：\n// const { store, Consumer } = ReactConsumer.createStateManager(initState);\n\nexport { store, Consumer };\n```\n\n## 状态管理的使用\n\n### 1. 编写 action\n\n整个项目的状态管理代码，只有 action, 我们只需要要编写 action 即可。\n\n```js\nimport { store } from './store';\n\nexport function actionOfAddNum() {\n  // 在任何异步结束之后，处理状态更新\n  store.update(state =\u003e {\n    // 此处执行区域是 immer 的更新函数，所以直接赋值即可，不需要返回整个 state\n    state.user.info.num += 1;\n  });\n}\n```\n\n### 2. 在代码中使用状态和触发状态\n\nConsumer API\n\n| props     | 类型                                   | 描述                                                                              |\n| --------- | -------------------------------------- | --------------------------------------------------------------------------------- |\n| subscribe | `(state) =\u003e any[]`                     | 返回一个数组对象, 只有当数组对象变更了, 才会更新组件                              |\n| memo      | `any[]`                                | 组件内部拦截了更新派发，若有非 subscribe 之外的上下文依赖更新，需要声明在 memo 中 |\n| onMount   | `(...subscribeDatas) =\u003e void`          | 当组件 onMount 时的回调                                                           |\n| onUnmount | `(...subscribeDatas) =\u003e void`          | 当组件将要销毁之前的回调                                                          |\n| onUpdate  | `(...subscribeDatas) =\u003e void`          | 当组件将要更新之前的回调                                                          |\n| children  | `(...subscribeDatas) =\u003e React.Element` | Consumer 的子组件是一个函数(renderProps), 函数参数是 memo 对象和 state            |\n\n示例：\n\n```js\nimport React from 'react';\nimport * as actions from './actions';\nimport { Consumer } from './store';\n\nfunction Page() {\n  return (\n    \u003cdiv className=\"app\"\u003e\n      \u003cp\u003e最简单的例子\u003c/p\u003e\n      \u003cConsumer subscribe={state =\u003e [state.user.info.num]}\u003e{num =\u003e \u003ch2\u003e{num}\u003c/h2\u003e}\u003c/Consumer\u003e\n      \u003cbutton onClick={actions.actionOfAddNum}\u003e点击仅重绘number\u003c/button\u003e\n    \u003c/div\u003e\n  );\n}\n\nexport default Page;\n```\n\n## 扩展阅读\n\n我们都知道 redux 给我们带来一个可能就是`时间旅行`，`时间旅行`之所以可行是因为整个项目都被一个单一数据管理，我们只需要修改状态数据，整个应用就可以随时切换到相应的状态。\n\n我们或许不需要`时间旅行`，但是我们需要让整个应用满足单一数据源，由一个状态管理。\n\n我们如果要确保整个项目 UI 都由一个状态管理，相当于整个项目 UI 都抽象成无副作用的函数，那么还需要讲路由也纳入状态管理中。\n\n我们可以使用 react-router, 使用 action 为 history 封装一层，这样就可以很好的管理状态和路由。\n\n`react-consumer` 内置了一个路由扩展模块，它帮我们无缝实现了以上功能，它非常接近 react-route，但是有些许不一样。具体可以查看：\n\n[使用 react-consumer 的路由扩展模块](./README_Of_Route.md)\n\n## 单元测试\n\n单元测试我们只需要覆盖 actions 的测试即可, actions 仅是一个个函数，测试起来非常简单：\n\n```js\nimport { actionOfAddNum } from '../src/actions';\nimport { store } from '../src/store';\n\ntest('add card', async () =\u003e {\n  await actionOfAddNum(10);\n\n  // 当函 action 执行完成，我们检查一下 store 是否和我们预期的值一致即可\n  expect(store.getState().user.info.num).toBe(10);\n});\n```\n\n我们可以在项目中运行 yarn test 验证以上测试\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fymzuiku%2Freact-consumer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fymzuiku%2Freact-consumer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fymzuiku%2Freact-consumer/lists"}