项目作者: yisbug

项目描述 :
极简的全局数据管理方案,基于 React Hooks API
高级语言: JavaScript
项目地址: git://github.com/yisbug/iostore.git


iostore

NPM version
build status
Test coverage
Known Vulnerabilities
David deps

背景介绍

由原 react-hooks-model 更名为 iostore

极简的全局数据管理方案,忘掉 reduxstatereduceractionobserver 这些复杂的概念吧。

特性

  • 总计只有 100 多行代码。
  • 只需要学会两个 API 即可使用,非常简单:createStore()useStore()
  • 像普通的 js 对象一样定义 store
  • 像普通的 js 对象一样使用数据和方法。
  • store 定义的方法内部可任意修改数据,可直接返回数据,支持同步、异步方法。
  • 当数据发生变化时,自动触发组件渲染。基于React Hooks API,实现了完整的单向数据流。
  • 集成异步方法的执行状态管理,目前最优雅的loading状态解决方案之一。
  • store 内部方法可以使用this.stores.TodoStore访问其他的 store 示例,实现更复杂的数据交互。

和之前的方案相比:

  • 不再区分 state, reducer, helper,去掉了这些概念,更简单。
  • 定义 store 就像定义一个普通的 js object 一样,只需要传入一个 namespace 用于区分不同的 store
  • 基于 Proxy 重新设计,数据变化,则自动通知组件,重新渲染。

TODO

  • TypeScript 支持
  • 支持 Vuejs
  • 更多的测试用例

如何使用

安装:

  1. npm install iostore
  2. // or
  3. yarn add iostore

API

引入

  1. import { createStore, useStore } from 'iostore';

createStore(params)

定义一个 store。参数:

普通的 js 对象,必须指定一个namespace

  1. // TodoStore.js
  2. import { createStore } from 'iostore';
  3. createStore({
  4. namespace: 'TodoStore',
  5. todos: [],
  6. getTodoCount() {
  7. return this.todos.length;
  8. },
  9. getNs() {
  10. return this.namespace;
  11. },
  12. ...rest, // 其余自定义的数据和方法
  13. });
  14. // UserStore.js
  15. import { createStore } from 'iostore';
  16. createStore({
  17. namespace: 'UserStore',
  18. // 访问其他 store 的方法。
  19. getTodoCount() {
  20. return this.stores.TodoStore.getTodoCount();
  21. },
  22. ...rest, // 其余自定义的数据和方法
  23. });

useStore()

React 函数式组件中引入所需 store。 无参数。
得益于 ES6 中的解构赋值语法,我们从该方法的返回值中,单独声明所需的 store。

框架会在 store 中注入 stores 对象,用来访问其他 store 的数据。
一般来说,只推荐访问其他 store 的计算数据,不要访问其他 store 中可能导致修改数据的方法。
如果需要修改其他 store 的数据,请在逻辑层/组件内处理。

如下:

  1. const Todo = () => {
  2. const { TodoStore } = useStore();
  3. // 之后便可以自由的使用 TodoStore 中定义的方法了。
  4. const ns = TodoStore.getNs();
  5. return <div>{ns}</div>;
  6. };

关于 loading

在对交互要求较高的场景下,获取异步方法的执行状态是非常必要的。

例如显示一个 loading 页面告诉用户正在加载数据,按钮上显示一个loading样式提示用户该按钮已经被点击。

当你使用iostore时,这一切变得非常简单。

我们可以非常容易的获取到每一个异步方法的loading状态,甚至可以获取到一个store下有没有异步方法正在执行。

  • 获取store中有没有异步方法正在执行:Store.loading,返回 true/false
  • 获取store中某个异步方法的 loading 状态:Store.asyncFunction.loading,返回 true/false

示例如下:

  1. // 定义 store
  2. createStore({
  3. namespace: 'TodoStore',
  4. id: 0,
  5. async inc() {
  6. await sleep(1000 * 5);
  7. this.id++;
  8. },
  9. });
  10. // 获取 loading 状态
  11. const Todo = () => {
  12. const { TodoStore } = useStore();
  13. const handleClick = () => TodoStore.inc();
  14. // TodoStore.loading store 级别的 loading 状态
  15. // TodoStore.inc.loading 某个异步方法的 loading 状态
  16. return (
  17. <button loading={TodoStore.inc.loading} onClick={handleClick}>
  18. submit
  19. </button>
  20. );
  21. };

完整的 Todo 示例

  1. // TodoStore.js
  2. import store, { createStore, useStore } from 'iostore';
  3. export default createStore({
  4. namespace: 'TodoStore', // store 命名空间
  5. id: 0,
  6. todos: [
  7. {
  8. id: 0,
  9. content: 'first',
  10. status: 'DOING',
  11. },
  12. ],
  13. addTodo(content) {
  14. this.id++;
  15. const todo = {
  16. id: this.id,
  17. content,
  18. status: 'DOING',
  19. };
  20. this.todos.push(todo);
  21. },
  22. getTodoById(id) {
  23. return this.todos.filter(item => item.id === id)[0];
  24. },
  25. updateTodo(id, status) {
  26. const todo = this.getTodoById(id);
  27. if (!todo) return;
  28. todo.status = status;
  29. },
  30. // test async function
  31. incId: 0,
  32. async delayIncId() {
  33. await sleep(1000 * 3);
  34. this.incId++;
  35. },
  36. });
  37. // Todos.js
  38. import React, { useRef } from 'react';
  39. import store, { createStore, useStore } from '../src/index';
  40. import todoStore from './TodoStore';
  41. export default () => {
  42. /**
  43. * 获取 TodoStore 的几种方式:
  44. * const { TodoStore } = useStore(); // 更符合 React Hooks 的理念
  45. * const { TodoStore } = store;
  46. * const TodoStore = todoStore.useStore();
  47. */
  48. const { TodoStore } = useStore();
  49. const inputEl = useRef(null);
  50. const handleClick = item => {
  51. if (item.status === 'DOING') {
  52. TodoStore.updateTodo(item.id, 'COMPLETED');
  53. } else if (item.status === 'COMPLETED') {
  54. TodoStore.updateTodo(item.id, 'DOING');
  55. }
  56. };
  57. const handleAddTodo = () => {
  58. console.warn('set data within component, should be got console.error : ');
  59. TodoStore.todos[0].id = 1000;
  60. const text = inputEl.current.value;
  61. if (text) {
  62. TodoStore.addTodo(text);
  63. }
  64. };
  65. console.log('render', 'totos.length:' + TodoStore.todos.length);
  66. return (
  67. <div>
  68. <div data-testid="incid">{TodoStore.incId}</div>
  69. {!TodoStore.delayIncId.loading ? <div data-testid="incidfinish" ></div> : ''}
  70. <div data-testid="incidloading">{TodoStore.delayIncId.loading ? 'loading' : 'completed'}</div>
  71. <div data-testid="todocount">{TodoStore.todos.length}</div>
  72. <ul data-testid="todolist">
  73. {TodoStore.todos.map(item => {
  74. return (
  75. <li onClick={() => handleClick(item)} key={item.id}>
  76. {item.content}
  77. <span>{item.status}</span>
  78. </li>
  79. );
  80. })}
  81. </ul>
  82. <input ref={inputEl} data-testid="todoinput" type="text" />
  83. <button data-testid="todobtn" onClick={() => handleAddTodo()}>
  84. add todo
  85. </button>
  86. <button data-testid="incbtn" onClick={() => TodoStore.delayIncId()}>
  87. delay inc id
  88. </button>
  89. </div>
  90. );
  91. };

License

MIT