store.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  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. this._dependents
  54. .filter(dependent => {
  55. const componentState = {};
  56. let dirty = false;
  57. for (let j = 0; j < dependent.props.length; j += 1) {
  58. const prop = dependent.props[j];
  59. if (prop in changed) {
  60. componentState['$' + prop] = this._state[prop];
  61. dirty = true;
  62. }
  63. }
  64. if (dirty) {
  65. dependent.component._stage(componentState);
  66. return true;
  67. }
  68. })
  69. .forEach(dependent => {
  70. dependent.component.set({});
  71. });
  72. this.fire('update', {
  73. changed,
  74. previous,
  75. current: this._state
  76. });
  77. },
  78. _sortComputedProperties() {
  79. const computed = this._computed;
  80. const sorted = this._sortedComputedProperties = [];
  81. const visited = blankObject();
  82. let currentKey;
  83. function visit(key) {
  84. const c = computed[key];
  85. if (c) {
  86. c.deps.forEach(dep => {
  87. if (dep === currentKey) {
  88. throw new Error(`Cyclical dependency detected between ${dep} <-> ${key}`);
  89. }
  90. visit(dep);
  91. });
  92. if (!visited[key]) {
  93. visited[key] = true;
  94. sorted.push(c);
  95. }
  96. }
  97. }
  98. for (const key in this._computed) {
  99. visit(currentKey = key);
  100. }
  101. },
  102. compute(key, deps, fn) {
  103. let value;
  104. const c = {
  105. deps,
  106. update: (state, changed, dirty) => {
  107. const values = deps.map(dep => {
  108. if (dep in changed) dirty = true;
  109. return state[dep];
  110. });
  111. if (dirty) {
  112. const newValue = fn.apply(null, values);
  113. if (this._differs(newValue, value)) {
  114. value = newValue;
  115. changed[key] = true;
  116. state[key] = value;
  117. }
  118. }
  119. }
  120. };
  121. this._computed[key] = c;
  122. this._sortComputedProperties();
  123. const state = assign({}, this._state);
  124. const changed = {};
  125. c.update(state, changed, true);
  126. this._set(state, changed);
  127. },
  128. fire,
  129. get,
  130. on,
  131. set(newState) {
  132. const oldState = this._state;
  133. const changed = this._changed = {};
  134. let dirty = false;
  135. for (const key in newState) {
  136. if (this._computed[key]) throw new Error(`'${key}' is a read-only computed property`);
  137. if (this._differs(newState[key], oldState[key])) changed[key] = dirty = true;
  138. }
  139. if (!dirty) return;
  140. this._set(newState, changed);
  141. }
  142. });
  143. export { Store };