Subscription.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. import { getBatch } from './batch'; // encapsulates the subscription logic for connecting a component to the redux store, as
  2. // well as nesting subscriptions of descendant components, so that we can ensure the
  3. // ancestor components re-render before descendants
  4. function createListenerCollection() {
  5. const batch = getBatch();
  6. let first = null;
  7. let last = null;
  8. return {
  9. clear() {
  10. first = null;
  11. last = null;
  12. },
  13. notify() {
  14. batch(() => {
  15. let listener = first;
  16. while (listener) {
  17. listener.callback();
  18. listener = listener.next;
  19. }
  20. });
  21. },
  22. get() {
  23. let listeners = [];
  24. let listener = first;
  25. while (listener) {
  26. listeners.push(listener);
  27. listener = listener.next;
  28. }
  29. return listeners;
  30. },
  31. subscribe(callback) {
  32. let isSubscribed = true;
  33. let listener = last = {
  34. callback,
  35. next: null,
  36. prev: last
  37. };
  38. if (listener.prev) {
  39. listener.prev.next = listener;
  40. } else {
  41. first = listener;
  42. }
  43. return function unsubscribe() {
  44. if (!isSubscribed || first === null) return;
  45. isSubscribed = false;
  46. if (listener.next) {
  47. listener.next.prev = listener.prev;
  48. } else {
  49. last = listener.prev;
  50. }
  51. if (listener.prev) {
  52. listener.prev.next = listener.next;
  53. } else {
  54. first = listener.next;
  55. }
  56. };
  57. }
  58. };
  59. }
  60. const nullListeners = {
  61. notify() {},
  62. get: () => []
  63. };
  64. export function createSubscription(store, parentSub) {
  65. let unsubscribe;
  66. let listeners = nullListeners; // Reasons to keep the subscription active
  67. let subscriptionsAmount = 0; // Is this specific subscription subscribed (or only nested ones?)
  68. let selfSubscribed = false;
  69. function addNestedSub(listener) {
  70. trySubscribe();
  71. const cleanupListener = listeners.subscribe(listener); // cleanup nested sub
  72. let removed = false;
  73. return () => {
  74. if (!removed) {
  75. removed = true;
  76. cleanupListener();
  77. tryUnsubscribe();
  78. }
  79. };
  80. }
  81. function notifyNestedSubs() {
  82. listeners.notify();
  83. }
  84. function handleChangeWrapper() {
  85. if (subscription.onStateChange) {
  86. subscription.onStateChange();
  87. }
  88. }
  89. function isSubscribed() {
  90. return selfSubscribed;
  91. }
  92. function trySubscribe() {
  93. subscriptionsAmount++;
  94. if (!unsubscribe) {
  95. unsubscribe = parentSub ? parentSub.addNestedSub(handleChangeWrapper) : store.subscribe(handleChangeWrapper);
  96. listeners = createListenerCollection();
  97. }
  98. }
  99. function tryUnsubscribe() {
  100. subscriptionsAmount--;
  101. if (unsubscribe && subscriptionsAmount === 0) {
  102. unsubscribe();
  103. unsubscribe = undefined;
  104. listeners.clear();
  105. listeners = nullListeners;
  106. }
  107. }
  108. function trySubscribeSelf() {
  109. if (!selfSubscribed) {
  110. selfSubscribed = true;
  111. trySubscribe();
  112. }
  113. }
  114. function tryUnsubscribeSelf() {
  115. if (selfSubscribed) {
  116. selfSubscribed = false;
  117. tryUnsubscribe();
  118. }
  119. }
  120. const subscription = {
  121. addNestedSub,
  122. notifyNestedSubs,
  123. handleChangeWrapper,
  124. isSubscribed,
  125. trySubscribe: trySubscribeSelf,
  126. tryUnsubscribe: tryUnsubscribeSelf,
  127. getListeners: () => listeners
  128. };
  129. return subscription;
  130. }