watch.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. /**
  2. * 包含功能:
  3. * wx.$get 获取值 wx.$watch 监听值 wx.$emit 提交值
  4. *
  5. * 使用方法:
  6. * 普通页面使用方法 在页面onLoad或者onShow里面设置监听 推荐在onLoad监听 onShow会增加额外的变更回调函数开销
  7. * 组件中使用方法 在onready中设置监听
  8. *
  9. * 注意事项:
  10. * 1、在组件中使用时需手动引入 wx.$behavior 该方法会在组件卸载时自动卸载监听函数 在页面中使用时会在页面卸载时自动移除监听函数 防止抓住页面不放造成小程序卡顿
  11. * 2、如仅仅是需要储存值获取值 不需要实时对在栈中的页面进行操作 推荐使用app.globalData
  12. * 3、不建议在基础组件中使用 例如列表组件的子组件 会大量重复渲染推荐使用状态控制 这个方法并没有做优化
  13. */
  14. class Watch {
  15. constructor(that, watchData) {
  16. // watch函数回调集合
  17. that.$_watchCallBack = {}
  18. // 绑定到指定对象
  19. that.$on = this.$watch;
  20. that.$emit = this.$emit;
  21. that.$get = this.$get;
  22. that.$behavior = this.$behavior();
  23. that.$_watch = this.$_watch;
  24. that.$_remove = this.$_remove;
  25. that.$_autoRemove = this.$_autoRemove;
  26. // 设置监听对象
  27. this._setWatchData(that, watchData);
  28. }
  29. /**
  30. * 设置被监听的对象
  31. * @that {Object} this对象
  32. * @watchData {object} 被监听的对象
  33. */
  34. _setWatchData(that, watchData) {
  35. if (that && watchData) {
  36. that.$watchData = Object.assign({}, watchData)
  37. } else {
  38. throw new Error('请输入正确的初始值')
  39. }
  40. }
  41. /**
  42. * 设置监听
  43. * @obj {Object} 将要监听的对象
  44. * @callbackArray {Array} 回调函数集合
  45. *
  46. */
  47. $_watch(key, value, callback) {
  48. const obj = this.$watchData;
  49. let _oldValue = obj[key];
  50. obj[key] = value;
  51. // 确认回调函数存在 循环执行回调数组的方法集合
  52. if (typeof _oldValue === 'object' && typeof value === 'object') {
  53. this.$_watchCallBack[key] && this.$_watchCallBack[key].forEach(val => val && val.cb && val.cb.call(val.page, value, _oldValue))
  54. } else {
  55. // 两次相同的值不执行
  56. _oldValue !== value && this.$_watchCallBack[key] && this.$_watchCallBack[key].forEach(val => val && val.cb && val.cb.call(val.page, value, _oldValue))
  57. }
  58. callback && callback()
  59. }
  60. /**
  61. * 提交值
  62. * @key {String} 提交的键值
  63. * @value {any} 提交的值
  64. * @callback {function} 执行之后完成回调
  65. */
  66. $emit(key, value, callback) {
  67. // 不提交未设置字段 减少消耗
  68. if (!this.$watchData.hasOwnProperty(key)) {
  69. throw new Error('未设置此字段')
  70. }
  71. // 不可传值为function、null、undefined以及NaN
  72. if (typeof value === 'function' || typeof value === 'undefined' || value === null || Number.isNaN(value)) {
  73. throw new Error(`${key}-${value}为非法参数传值`)
  74. }
  75. this.$_watch(key, value, callback);
  76. }
  77. /**
  78. * 获取值
  79. * @key {String} 获取的键值
  80. * @callback {function} 获取之后执行回调函数
  81. */
  82. $get(key, callback) {
  83. callback && callback(this.$watchData[key]);
  84. return this.$watchData[key]
  85. }
  86. /**
  87. * 添加watch方法
  88. * @key {String} 要观察的键值
  89. * @page {Object} 需监听的页面
  90. * @callback {function} 需要执行的回调函数
  91. *
  92. */
  93. $watch(key, page, callback) {
  94. // 首先检测 $_watchCallBack 中对应的值是否存在 如果存在就推入方法
  95. this.$_watchCallBack = Object.assign({}, this.$_watchCallBack, {
  96. [key]: this.$_watchCallBack[key] || []
  97. });
  98. // 判断页面是否存入过该方法 若存入过则更改回调方法 否则推入函数
  99. let _index = this.$_watchCallBack[key].findIndex(val => val.name === page.$$PageName);
  100. if (_index === -1) {
  101. console.info(`${page.is} watch=>`, key)
  102. // 添加自动移除的方法
  103. page = this.$_autoRemove(key, page)
  104. page.$$PageName = Symbol("PageName");
  105. // 推入新的监听事件
  106. this.$_watchCallBack[key].push({
  107. name: page.$$PageName,
  108. page,
  109. cb: callback
  110. })
  111. } else {
  112. // 存在则改写回调函数
  113. this.$_watchCallBack[key][_index].cb = callback;
  114. }
  115. console.log(Symbol("wangxin"))
  116. console.log(page)
  117. }
  118. /**
  119. * 移除监听事件
  120. * @key {String} 需要移除监听的键值
  121. * @page {Object} 需移除监听的页面
  122. * @callback {function} 移除监听完成的回调函数
  123. */
  124. $_remove(key, page, callback) {
  125. // 获取索引
  126. let _index = this.$_watchCallBack[key].findIndex(val => val.name === page.$$PageName);
  127. if (_index !== -1) {
  128. // 移除监听
  129. let _item = this.$_watchCallBack[key].splice(_index, 1);
  130. if (_item) {
  131. // console.log('remove', key, page)
  132. // 确认函数存在 执行回调函数
  133. callback && callback.call(page)
  134. }
  135. }
  136. }
  137. /**
  138. * 自动移除监听
  139. * @key {String} 移除的键值
  140. * @page {Object} 移除的页面
  141. */
  142. $_autoRemove(key, page) {
  143. // 如果是页面则重载onUnload 函数
  144. if (page.route) {
  145. let _onUnload = page.onUnload;
  146. page.onUnload = () => {
  147. this.$_remove(key, page, () => {
  148. console.info(`${page.is} remove=>`, key)
  149. _onUnload()
  150. })
  151. };
  152. return page;
  153. } else {
  154. // 自定义组件使用behaviors移除 将监听字段赋值给$$_watch
  155. page.$$_watch = key;
  156. return page;
  157. }
  158. }
  159. /**
  160. * 组件Behavior方法 需自行引入组件的behaviors
  161. */
  162. $behavior() {
  163. return Behavior({
  164. detached() {
  165. let key = this.$$_watch;
  166. wx.$_remove(this.$$_watch, this, () => {
  167. console.info(`${this.is} remove=>`, key)
  168. })
  169. },
  170. })
  171. }
  172. }
  173. module.exports = {
  174. Watch: Watch
  175. }