store.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. import {
  2. assign,
  3. blankObject,
  4. _differs,
  5. _differsImmutable,
  6. get,
  7. on,
  8. fire
  9. } from './shared.js';
  10. function Store(state, options) {
  11. this._handlers = {};
  12. this._dependents = [];
  13. this._computed = blankObject();
  14. this._sortedComputedProperties = [];
  15. this._state = assign({}, state);
  16. this._differs = options && options.immutable ? _differsImmutable : _differs;
  17. }
  18. assign(Store.prototype, {
  19. _add(component, props) {
  20. this._dependents.push({
  21. component: component,
  22. props: props
  23. });
  24. },
  25. _init(props) {
  26. const state = {};
  27. for (let i = 0; i < props.length; i += 1) {
  28. const prop = props[i];
  29. state['$' + prop] = this._state[prop];
  30. }
  31. return state;
  32. },
  33. _remove(component) {
  34. let i = this._dependents.length;
  35. while (i--) {
  36. if (this._dependents[i].component === component) {
  37. this._dependents.splice(i, 1);
  38. return;
  39. }
  40. }
  41. },
  42. _set(newState, changed) {
  43. const previous = this._state;
  44. this._state = assign(assign({}, previous), newState);
  45. for (let i = 0; i < this._sortedComputedProperties.length; i += 1) {
  46. this._sortedComputedProperties[i].update(this._state, changed);
  47. }
  48. this.fire('state', {
  49. changed,
  50. previous,
  51. current: this._state
  52. });
  53. const dependents = this._dependents.slice(); // guard against mutations
  54. for (let i = 0; i < dependents.length; i += 1) {
  55. const dependent = dependents[i];
  56. const componentState = {};
  57. let dirty = false;
  58. for (let j = 0; j < dependent.props.length; j += 1) {
  59. const prop = dependent.props[j];
  60. if (prop in changed) {
  61. componentState['$' + prop] = this._state[prop];
  62. dirty = true;
  63. }
  64. }
  65. if (dirty) dependent.component.set(componentState);
  66. }
  67. this.fire('update', {
  68. changed,
  69. previous,
  70. current: this._state
  71. });
  72. },
  73. _sortComputedProperties() {
  74. const computed = this._computed;
  75. const sorted = this._sortedComputedProperties = [];
  76. const visited = blankObject();
  77. let currentKey;
  78. function visit(key) {
  79. const c = computed[key];
  80. if (c) {
  81. c.deps.forEach(dep => {
  82. if (dep === currentKey) {
  83. throw new Error(`Cyclical dependency detected between ${dep} <-> ${key}`);
  84. }
  85. visit(dep);
  86. });
  87. if (!visited[key]) {
  88. visited[key] = true;
  89. sorted.push(c);
  90. }
  91. }
  92. }
  93. for (const key in this._computed) {
  94. visit(currentKey = key);
  95. }
  96. },
  97. compute(key, deps, fn) {
  98. let value;
  99. const c = {
  100. deps,
  101. update: (state, changed, dirty) => {
  102. const values = deps.map(dep => {
  103. if (dep in changed) dirty = true;
  104. return state[dep];
  105. });
  106. if (dirty) {
  107. const newValue = fn.apply(null, values);
  108. if (this._differs(newValue, value)) {
  109. value = newValue;
  110. changed[key] = true;
  111. state[key] = value;
  112. }
  113. }
  114. }
  115. };
  116. this._computed[key] = c;
  117. this._sortComputedProperties();
  118. const state = assign({}, this._state);
  119. const changed = {};
  120. c.update(state, changed, true);
  121. this._set(state, changed);
  122. },
  123. fire,
  124. get,
  125. on,
  126. set(newState) {
  127. const oldState = this._state;
  128. const changed = this._changed = {};
  129. let dirty = false;
  130. for (const key in newState) {
  131. if (this._computed[key]) throw new Error(`'${key}' is a read-only property`);
  132. if (this._differs(newState[key], oldState[key])) changed[key] = dirty = true;
  133. }
  134. if (!dirty) return;
  135. this._set(newState, changed);
  136. }
  137. });
  138. export { Store };