提交学习笔记专用
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

268 lines
6.3 KiB

  1. import { createReactiveSystem } from './system.mjs';
  2. let cycle = 0;
  3. let batchDepth = 0;
  4. let notifyIndex = 0;
  5. let queuedLength = 0;
  6. let activeSub;
  7. const queued = [];
  8. const { link, unlink, propagate, checkDirty, shallowPropagate, } = createReactiveSystem({
  9. update(node) {
  10. if (node.depsTail !== undefined) {
  11. return updateComputed(node);
  12. }
  13. else {
  14. return updateSignal(node);
  15. }
  16. },
  17. notify(effect) {
  18. let insertIndex = queuedLength;
  19. let firstInsertedIndex = insertIndex;
  20. do {
  21. effect.flags &= ~2;
  22. queued[insertIndex++] = effect;
  23. effect = effect.subs?.sub;
  24. if (effect === undefined || !(effect.flags & 2)) {
  25. break;
  26. }
  27. } while (true);
  28. queuedLength = insertIndex;
  29. while (firstInsertedIndex < --insertIndex) {
  30. const left = queued[firstInsertedIndex];
  31. queued[firstInsertedIndex++] = queued[insertIndex];
  32. queued[insertIndex] = left;
  33. }
  34. },
  35. unwatched(node) {
  36. if (!(node.flags & 1)) {
  37. effectScopeOper.call(node);
  38. }
  39. else if (node.depsTail !== undefined) {
  40. node.depsTail = undefined;
  41. node.flags = 17;
  42. purgeDeps(node);
  43. }
  44. },
  45. });
  46. export function getActiveSub() {
  47. return activeSub;
  48. }
  49. export function setActiveSub(sub) {
  50. const prevSub = activeSub;
  51. activeSub = sub;
  52. return prevSub;
  53. }
  54. export function getBatchDepth() {
  55. return batchDepth;
  56. }
  57. export function startBatch() {
  58. ++batchDepth;
  59. }
  60. export function endBatch() {
  61. if (!--batchDepth) {
  62. flush();
  63. }
  64. }
  65. export function isSignal(fn) {
  66. return fn.name === 'bound ' + signalOper.name;
  67. }
  68. export function isComputed(fn) {
  69. return fn.name === 'bound ' + computedOper.name;
  70. }
  71. export function isEffect(fn) {
  72. return fn.name === 'bound ' + effectOper.name;
  73. }
  74. export function isEffectScope(fn) {
  75. return fn.name === 'bound ' + effectScopeOper.name;
  76. }
  77. export function signal(initialValue) {
  78. return signalOper.bind({
  79. currentValue: initialValue,
  80. pendingValue: initialValue,
  81. subs: undefined,
  82. subsTail: undefined,
  83. flags: 1,
  84. });
  85. }
  86. export function computed(getter) {
  87. return computedOper.bind({
  88. value: undefined,
  89. subs: undefined,
  90. subsTail: undefined,
  91. deps: undefined,
  92. depsTail: undefined,
  93. flags: 0,
  94. getter: getter,
  95. });
  96. }
  97. export function effect(fn) {
  98. const e = {
  99. fn,
  100. subs: undefined,
  101. subsTail: undefined,
  102. deps: undefined,
  103. depsTail: undefined,
  104. flags: 2,
  105. };
  106. const prevSub = setActiveSub(e);
  107. if (prevSub !== undefined) {
  108. link(e, prevSub, 0);
  109. }
  110. try {
  111. e.fn();
  112. }
  113. finally {
  114. activeSub = prevSub;
  115. }
  116. return effectOper.bind(e);
  117. }
  118. export function effectScope(fn) {
  119. const e = {
  120. deps: undefined,
  121. depsTail: undefined,
  122. subs: undefined,
  123. subsTail: undefined,
  124. flags: 0,
  125. };
  126. const prevSub = setActiveSub(e);
  127. if (prevSub !== undefined) {
  128. link(e, prevSub, 0);
  129. }
  130. try {
  131. fn();
  132. }
  133. finally {
  134. activeSub = prevSub;
  135. }
  136. return effectScopeOper.bind(e);
  137. }
  138. function updateComputed(c) {
  139. ++cycle;
  140. c.depsTail = undefined;
  141. c.flags = 5;
  142. const prevSub = setActiveSub(c);
  143. try {
  144. const oldValue = c.value;
  145. return oldValue !== (c.value = c.getter(oldValue));
  146. }
  147. finally {
  148. activeSub = prevSub;
  149. c.flags &= ~4;
  150. purgeDeps(c);
  151. }
  152. }
  153. function updateSignal(s) {
  154. s.flags = 1;
  155. return s.currentValue !== (s.currentValue = s.pendingValue);
  156. }
  157. function run(e) {
  158. const flags = e.flags;
  159. if (flags & 16
  160. || (flags & 32
  161. && checkDirty(e.deps, e))) {
  162. ++cycle;
  163. e.depsTail = undefined;
  164. e.flags = 6;
  165. const prevSub = setActiveSub(e);
  166. try {
  167. e.fn();
  168. }
  169. finally {
  170. activeSub = prevSub;
  171. e.flags &= ~4;
  172. purgeDeps(e);
  173. }
  174. }
  175. else {
  176. e.flags = 2;
  177. }
  178. }
  179. function flush() {
  180. while (notifyIndex < queuedLength) {
  181. const effect = queued[notifyIndex];
  182. queued[notifyIndex++] = undefined;
  183. run(effect);
  184. }
  185. notifyIndex = 0;
  186. queuedLength = 0;
  187. }
  188. function computedOper() {
  189. const flags = this.flags;
  190. if (flags & 16
  191. || (flags & 32
  192. && (checkDirty(this.deps, this)
  193. || (this.flags = flags & ~32, false)))) {
  194. if (updateComputed(this)) {
  195. const subs = this.subs;
  196. if (subs !== undefined) {
  197. shallowPropagate(subs);
  198. }
  199. }
  200. }
  201. else if (!flags) {
  202. this.flags = 1;
  203. const prevSub = setActiveSub(this);
  204. try {
  205. this.value = this.getter();
  206. }
  207. finally {
  208. activeSub = prevSub;
  209. }
  210. }
  211. const sub = activeSub;
  212. if (sub !== undefined) {
  213. link(this, sub, cycle);
  214. }
  215. return this.value;
  216. }
  217. function signalOper(...value) {
  218. if (value.length) {
  219. if (this.pendingValue !== (this.pendingValue = value[0])) {
  220. this.flags = 17;
  221. const subs = this.subs;
  222. if (subs !== undefined) {
  223. propagate(subs);
  224. if (!batchDepth) {
  225. flush();
  226. }
  227. }
  228. }
  229. }
  230. else {
  231. if (this.flags & 16) {
  232. if (updateSignal(this)) {
  233. const subs = this.subs;
  234. if (subs !== undefined) {
  235. shallowPropagate(subs);
  236. }
  237. }
  238. }
  239. let sub = activeSub;
  240. while (sub !== undefined) {
  241. if (sub.flags & 3) {
  242. link(this, sub, cycle);
  243. break;
  244. }
  245. sub = sub.subs?.sub;
  246. }
  247. return this.currentValue;
  248. }
  249. }
  250. function effectOper() {
  251. effectScopeOper.call(this);
  252. }
  253. function effectScopeOper() {
  254. this.depsTail = undefined;
  255. this.flags = 0;
  256. purgeDeps(this);
  257. const sub = this.subs;
  258. if (sub !== undefined) {
  259. unlink(sub);
  260. }
  261. }
  262. function purgeDeps(sub) {
  263. const depsTail = sub.depsTail;
  264. let dep = depsTail !== undefined ? depsTail.nextDep : sub.deps;
  265. while (dep !== undefined) {
  266. dep = unlink(dep, sub);
  267. }
  268. }