{"id":13780746,"url":"https://github.com/yisbug/iostore","last_synced_at":"2026-01-14T23:08:25.846Z","repository":{"id":85218609,"uuid":"181748849","full_name":"yisbug/iostore","owner":"yisbug","description":"极简的全局数据管理方案，基于 React Hooks API","archived":false,"fork":false,"pushed_at":"2023-08-17T09:47:44.000Z","size":38,"stargazers_count":120,"open_issues_count":10,"forks_count":20,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-06-02T18:02:50.714Z","etag":null,"topics":["hooks","iostore","react","redux","state","store"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","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/yisbug.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,"governance":null,"roadmap":null,"authors":null}},"created_at":"2019-04-16T18:55:37.000Z","updated_at":"2025-05-28T12:19:09.000Z","dependencies_parsed_at":"2024-01-15T19:45:07.490Z","dependency_job_id":"94ebbeac-1324-41ff-876e-9ff6eb043f3d","html_url":"https://github.com/yisbug/iostore","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/yisbug/iostore","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yisbug%2Fiostore","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yisbug%2Fiostore/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yisbug%2Fiostore/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yisbug%2Fiostore/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yisbug","download_url":"https://codeload.github.com/yisbug/iostore/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yisbug%2Fiostore/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28437928,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T22:37:52.437Z","status":"ssl_error","status_checked_at":"2026-01-14T22:37:31.496Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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","iostore","react","redux","state","store"],"created_at":"2024-08-03T18:01:19.291Z","updated_at":"2026-01-14T23:08:25.813Z","avatar_url":"https://github.com/yisbug.png","language":"JavaScript","funding_links":[],"categories":["目录","List"],"sub_categories":[],"readme":"### iostore\n\n[![NPM version](https://img.shields.io/npm/v/iostore.svg?style=flat)](https://npmjs.org/package/iostore)\n[![build status](https://img.shields.io/travis/yisbug/iostore.svg?style=flat-square)](https://travis-ci.org/yisbug/iostore)\n[![Test coverage](https://img.shields.io/codecov/c/github/yisbug/iostore.svg?style=flat-square)](https://codecov.io/gh/yisbug/iostore)\n[![Known Vulnerabilities](https://snyk.io/test/npm/iostore/badge.svg)](https://snyk.io/test/npm/iostore)\n[![David deps](https://img.shields.io/david/yisbug/iostore.svg?style=flat-square)](https://david-dm.org/yisbug/iostore)\n\n[背景介绍](docs/about.md)\n\n由原 `react-hooks-model` 更名为 `iostore`。\n\n极简的全局数据管理方案，忘掉 `redux`、`state`、`reducer`、`action`、`observer` 这些复杂的概念吧。\n\n### 特性\n\n- 总计只有 100 多行代码。\n- 只需要学会两个 `API` 即可使用，非常简单：`createStore()`、`useStore()`。\n- 像普通的 `js` 对象一样定义 `store`。\n- 像普通的 `js` 对象一样使用数据和方法。\n- `store` 定义的方法内部可任意修改数据，可直接返回数据，支持同步、异步方法。\n- 当数据发生变化时，自动触发组件渲染。基于`React Hooks API`，实现了完整的单向数据流。\n- 集成异步方法的执行状态管理，目前最优雅的`loading`状态解决方案之一。\n- `store` 内部方法可以使用`this.stores.TodoStore`访问其他的 `store` 示例，实现更复杂的数据交互。\n\n和之前的方案相比：\n\n- 不再区分 `state`, `reducer`, `helper`，去掉了这些概念，更简单。\n- 定义 `store` 就像定义一个普通的 `js object` 一样，只需要传入一个 `namespace` 用于区分不同的 `store`。\n- 基于 `Proxy` 重新设计，数据变化，则自动通知组件，重新渲染。\n\n### TODO\n\n- [ ] TypeScript 支持\n- [ ] 支持 Vuejs\n- [ ] 更多的测试用例\n\n### 如何使用\n\n安装：\n\n```shell\nnpm install iostore\n// or\nyarn add iostore\n```\n\n### API\n\n引入\n\n```js\nimport { createStore, useStore } from 'iostore';\n```\n\n#### createStore(params)\n\n定义一个 store。参数：\n\n普通的 js 对象，必须指定一个`namespace`。\n\n```js\n// TodoStore.js\nimport { createStore } from 'iostore';\ncreateStore({\n  namespace: 'TodoStore',\n  todos: [],\n  getTodoCount() {\n    return this.todos.length;\n  },\n  getNs() {\n    return this.namespace;\n  },\n  ...rest, // 其余自定义的数据和方法\n});\n\n// UserStore.js\nimport { createStore } from 'iostore';\ncreateStore({\n  namespace: 'UserStore',\n  // 访问其他 store 的方法。\n  getTodoCount() {\n    return this.stores.TodoStore.getTodoCount();\n  },\n  ...rest, // 其余自定义的数据和方法\n});\n```\n\n#### useStore()\n\n在 `React` 函数式组件中引入所需 `store`。 无参数。\n得益于 ES6 中的解构赋值语法，我们从该方法的返回值中，单独声明所需的 store。\n\n\u003e 框架会在 `store` 中注入 `stores` 对象，用来访问其他 `store` 的数据。\n\u003e 一般来说，只推荐访问其他 `store` 的计算数据，不要访问其他 `store` 中可能导致修改数据的方法。\n\u003e 如果需要修改其他 `store` 的数据，请在逻辑层/组件内处理。\n\n如下：\n\n```js\nconst Todo = () =\u003e {\n  const { TodoStore } = useStore();\n  // 之后便可以自由的使用 TodoStore 中定义的方法了。\n  const ns = TodoStore.getNs();\n  return \u003cdiv\u003e{ns}\u003c/div\u003e;\n};\n```\n\n#### 关于 loading\n\n在对交互要求较高的场景下，获取异步方法的执行状态是非常必要的。\n\n例如显示一个 `loading` 页面告诉用户正在加载数据，按钮上显示一个`loading`样式提示用户该按钮已经被点击。\n\n当你使用`iostore`时，这一切变得非常简单。\n\n我们可以非常容易的获取到每一个异步方法的`loading`状态，甚至可以获取到一个`store`下有没有异步方法正在执行。\n\n- 获取`store`中有没有异步方法正在执行：`Store.loading`，返回 `true/false`\n- 获取`store`中某个异步方法的 loading 状态：`Store.asyncFunction.loading`，返回 `true/false`\n\n示例如下：\n\n```js\n// 定义 store\ncreateStore({\n  namespace: 'TodoStore',\n  id: 0,\n  async inc() {\n    await sleep(1000 * 5);\n    this.id++;\n  },\n});\n\n// 获取 loading 状态\nconst Todo = () =\u003e {\n  const { TodoStore } = useStore();\n  const handleClick = () =\u003e TodoStore.inc();\n  // TodoStore.loading  store 级别的 loading 状态\n  // TodoStore.inc.loading 某个异步方法的 loading 状态\n  return (\n    \u003cbutton loading={TodoStore.inc.loading} onClick={handleClick}\u003e\n      submit\n    \u003c/button\u003e\n  );\n};\n```\n\n### 完整的 Todo 示例\n\n```js\n// TodoStore.js\nimport store, { createStore, useStore } from 'iostore';\nexport default createStore({\n  namespace: 'TodoStore', // store 命名空间\n  id: 0,\n  todos: [\n    {\n      id: 0,\n      content: 'first',\n      status: 'DOING',\n    },\n  ],\n  addTodo(content) {\n    this.id++;\n    const todo = {\n      id: this.id,\n      content,\n      status: 'DOING',\n    };\n    this.todos.push(todo);\n  },\n  getTodoById(id) {\n    return this.todos.filter(item =\u003e item.id === id)[0];\n  },\n  updateTodo(id, status) {\n    const todo = this.getTodoById(id);\n    if (!todo) return;\n    todo.status = status;\n  },\n  // test async function\n  incId: 0,\n  async delayIncId() {\n    await sleep(1000 * 3);\n    this.incId++;\n  },\n});\n\n// Todos.js\nimport React, { useRef } from 'react';\nimport store, { createStore, useStore } from '../src/index';\nimport todoStore from './TodoStore';\n\nexport default () =\u003e {\n  /**\n   * 获取 TodoStore 的几种方式：\n   * const { TodoStore } = useStore(); // 更符合 React Hooks 的理念\n   * const { TodoStore } = store;\n   * const TodoStore = todoStore.useStore();\n   */\n  const { TodoStore } = useStore();\n  const inputEl = useRef(null);\n  const handleClick = item =\u003e {\n    if (item.status === 'DOING') {\n      TodoStore.updateTodo(item.id, 'COMPLETED');\n    } else if (item.status === 'COMPLETED') {\n      TodoStore.updateTodo(item.id, 'DOING');\n    }\n  };\n  const handleAddTodo = () =\u003e {\n    console.warn('set data within component, should be got console.error : ');\n    TodoStore.todos[0].id = 1000;\n    const text = inputEl.current.value;\n    if (text) {\n      TodoStore.addTodo(text);\n    }\n  };\n  console.log('render', 'totos.length:' + TodoStore.todos.length);\n  return (\n    \u003cdiv\u003e\n      \u003cdiv data-testid=\"incid\"\u003e{TodoStore.incId}\u003c/div\u003e\n      {!TodoStore.delayIncId.loading ? \u003cdiv data-testid=\"incidfinish\" /\u003e : ''}\n\n      \u003cdiv data-testid=\"incidloading\"\u003e{TodoStore.delayIncId.loading ? 'loading' : 'completed'}\u003c/div\u003e\n      \u003cdiv data-testid=\"todocount\"\u003e{TodoStore.todos.length}\u003c/div\u003e\n      \u003cul data-testid=\"todolist\"\u003e\n        {TodoStore.todos.map(item =\u003e {\n          return (\n            \u003cli onClick={() =\u003e handleClick(item)} key={item.id}\u003e\n              {item.content}\n              \u003cspan\u003e{item.status}\u003c/span\u003e\n            \u003c/li\u003e\n          );\n        })}\n      \u003c/ul\u003e\n      \u003cinput ref={inputEl} data-testid=\"todoinput\" type=\"text\" /\u003e\n      \u003cbutton data-testid=\"todobtn\" onClick={() =\u003e handleAddTodo()}\u003e\n        add todo\n      \u003c/button\u003e\n\n      \u003cbutton data-testid=\"incbtn\" onClick={() =\u003e TodoStore.delayIncId()}\u003e\n        delay inc id\n      \u003c/button\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\n### License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyisbug%2Fiostore","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyisbug%2Fiostore","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyisbug%2Fiostore/lists"}