数据状态管理

状态管理通常是一个应用最复杂的部分,React 原生提供了 setState, Context 等方式来实现本地与全局状态管理,对于更复杂的业务场景,原生方案也不能完全满足需求,因此社区中产生了大量状态管理框架来解决这个问题,比较有名的诸如 Redux, Mobx,但是这些框架引入了很多概念,有不小的学习成本,而且滥用框架也会带来一定性能上的问题,因此飞冰团队从易用性与性能上出发,基于 React Hooks 设计了一款面向 React 的轻量级的状态管理方案 icestore,本文介绍 icestore 的用法与最佳实践,关于 icestore 更详尽的文档请参考 这里

安装

  1. $ npm i @ice/store --save

目录结构

对于大多数的中小型项目,推荐将项目所有 store 集中管理在全局的 src/stores/ 目录下:

  1. ├── src/
  2. ├── components/
  3. └── NotFound/
  4. ├── pages/
  5. └── Home
  6. ├── stores/
  7. ├── storeA.js
  8. ├── storeB.js
  9. ├── storeC.js
  10. └── index.js

如果项目比较庞大或者更倾向于 store 跟随页面维护,那么可以在每个 page 目录都声明一个 store 实例,但是这种情况建议尽量避免跨页面的 store 调用。

定义 store

  • 首先新建一个 js 文件,在其中定义以对象形式定义 store 对象。
  • 对于函数类型的属性归类为 action(动作),非函数类型归类为 state(状态)。
  • 在 action 中可以通过 this 访问并修改 state。
  1. // src/stores/todos.js,不同 store 对应不同的 js 文件
  2. export default {
  3. dataSource: [],
  4. async fetchData() {
  5. // 模拟异步请求
  6. const data = await new Promise(resolve =>
  7. setTimeout(() => {
  8. resolve([
  9. { name: 'react' },
  10. { name: 'vue', done: true},
  11. { name: 'angular' },
  12. ]);
  13. }, 1000)
  14. );
  15. this.dataSource = data;
  16. },
  17. add(todo) {
  18. this.dataSource.push(todo);
  19. },
  20. };

注册 store

  • 初始化 store 实例,然后将定义好的 store 对象注册到 icestore 实例上。
  • store 实例上允许注册多个 store。
  1. // src/stores/index.js,所有的 store 都在这里注册
  2. import todos from './todos';
  3. import Store from '@ice/store';
  4. const storeManager = new Store();
  5. const stores = storeManager.registerStores({
  6. todos
  7. });
  8. export default stores;

视图绑定

  • 在函数式组件中,通过 store 实例上的 useStore hook 访问定义的 state 与 action。
  • action 调用后会触发 useStore 的组件重新渲染。
  1. // src/index.js
  2. import React, { useEffect } from 'react';
  3. import ReactDOM from 'react-dom';
  4. import stores from './stores';
  5. function Todo() {
  6. const todos = stores.useStore('todos');
  7. const { dataSource, fetchData, add, } = todos;
  8. useEffect(() => {
  9. fetchData();
  10. }, []);
  11. function handleAdd(name) {
  12. add({ name });
  13. }
  14. if (fetchData.loading) {
  15. return <span>loading...</span>;
  16. } else {
  17. return (
  18. <div>
  19. <ul>
  20. {dataSource.map(({ name, done }, index) => (
  21. <li key={index}>
  22. <label>
  23. <input
  24. type="checkbox"
  25. checked={done}
  26. onClick={() => onCheck(index)}
  27. />
  28. {done ? <s>{name}</s> : <span>{name}</span>}
  29. </label>
  30. <button onClick={() => onRemove(index)}>-</button>
  31. </li>
  32. ))}
  33. </ul>
  34. <div>
  35. <input
  36. onKeyDown={event => {
  37. if (event.keyCode === 13) {
  38. handleAdd(event.target.value);
  39. event.target.value = '';
  40. }
  41. }}
  42. placeholder="Press Enter"
  43. />
  44. </div>
  45. </div>
  46. )
  47. }
  48. }
  49. ReactDOM.render(<Todo />, document.getElementById('root'));

完整示例请参考线上 codesandbox