create.js 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. import diff from './diff'
  2. let originData = null
  3. let globalStore = null
  4. let fnMapping = {}
  5. const ARRAYTYPE = '[object Array]'
  6. const OBJECTTYPE = '[object Object]'
  7. const FUNCTIONTYPE = '[object Function]'
  8. export default function create(store, option) {
  9. let updatePath = null
  10. if (arguments.length === 2) {
  11. if (!originData) {
  12. originData = JSON.parse(JSON.stringify(store.data))
  13. globalStore = store
  14. store.instances = {}
  15. store.update = update
  16. store.push = push
  17. store.pull = pull
  18. store.add = add
  19. store.remove = remove
  20. store.originData = originData
  21. store.env && initCloud(store.env)
  22. extendStoreMethod(store)
  23. }
  24. getApp().globalData && (getApp().globalData.store = store)
  25. //option.data = store.data
  26. const onLoad = option.onLoad
  27. walk(store.data)
  28. // 解决函数属性初始化不能显示的问题,要求必须在data中声明使用
  29. // 这段代码是同步store.data到option.data,只有经过walk方法后store.data中的函数才能变成属性,才能被小程序page方法渲染
  30. if (option.data && Object.keys(option.data).length > 0) {
  31. updatePath = getUpdatePath(option.data)
  32. syncValues(store.data, option.data)
  33. }
  34. option.onLoad = function (e) {
  35. this.store = store
  36. this._updatePath = updatePath
  37. rewriteUpdate(this)
  38. store.instances[this.route] = []
  39. store.instances[this.route].push(this)
  40. onLoad && onLoad.call(this, e)
  41. syncValues(store.data, this.data)
  42. this.setData(this.data)
  43. }
  44. // 解决执行navigateBack或reLaunch时清除store.instances对应页面的实例
  45. const onUnload = option.onUnload
  46. option.onUnload = function () {
  47. onUnload && onUnload.call(this)
  48. store.instances[this.route] = []
  49. }
  50. Page(option)
  51. } else {
  52. const ready = store.ready
  53. const pure = store.pure
  54. const componentUpdatePath = getUpdatePath(store.data)
  55. store.ready = function () {
  56. if (pure) {
  57. this.store = { data: store.data || {} }
  58. this.store.originData = store.data ? JSON.parse(JSON.stringify(store.data)) : {}
  59. walk(store.data || {})
  60. rewritePureUpdate(this)
  61. } else {
  62. this.page = getCurrentPages()[getCurrentPages().length - 1]
  63. this.store = this.page.store
  64. this._updatePath = componentUpdatePath
  65. syncValues(this.store.data, store.data)
  66. walk(store.data || {})
  67. this.setData.call(this, this.store.data)
  68. rewriteUpdate(this)
  69. this.store.instances[this.page.route].push(this)
  70. }
  71. ready && ready.call(this)
  72. }
  73. Component(store)
  74. }
  75. }
  76. function syncValues(from, to){
  77. Object.keys(to).forEach(key=>{
  78. if(from.hasOwnProperty(key)){
  79. to[key] = from[key]
  80. }
  81. })
  82. }
  83. function getUpdatePath(data) {
  84. const result = {}
  85. dataToPath(data, result)
  86. return result
  87. }
  88. function dataToPath(data, result) {
  89. Object.keys(data).forEach(key => {
  90. result[key] = true
  91. const type = Object.prototype.toString.call(data[key])
  92. if (type === OBJECTTYPE) {
  93. _objToPath(data[key], key, result)
  94. } else if (type === ARRAYTYPE) {
  95. _arrayToPath(data[key], key, result)
  96. }
  97. })
  98. }
  99. function _objToPath(data, path, result) {
  100. Object.keys(data).forEach(key => {
  101. result[path + '.' + key] = true
  102. delete result[path]
  103. const type = Object.prototype.toString.call(data[key])
  104. if (type === OBJECTTYPE) {
  105. _objToPath(data[key], path + '.' + key, result)
  106. } else if (type === ARRAYTYPE) {
  107. _arrayToPath(data[key], path + '.' + key, result)
  108. }
  109. })
  110. }
  111. function _arrayToPath(data, path, result) {
  112. data.forEach((item, index) => {
  113. result[path + '[' + index + ']'] = true
  114. delete result[path]
  115. const type = Object.prototype.toString.call(item)
  116. if (type === OBJECTTYPE) {
  117. _objToPath(item, path + '[' + index + ']', result)
  118. } else if (type === ARRAYTYPE) {
  119. _arrayToPath(item, path + '[' + index + ']', result)
  120. }
  121. })
  122. }
  123. function rewritePureUpdate(ctx) {
  124. ctx.update = function (patch) {
  125. const store = this.store
  126. const that = this
  127. return new Promise(resolve => {
  128. //defineFnProp(store.data)
  129. if (patch) {
  130. for (let key in patch) {
  131. updateByPath(store.data, key, patch[key])
  132. }
  133. }
  134. let diffResult = diff(store.data, store.originData)
  135. let array = []
  136. if (Object.keys(diffResult)[0] == '') {
  137. diffResult = diffResult['']
  138. }
  139. if (Object.keys(diffResult).length > 0) {
  140. array.push( new Promise( cb => that.setData(diffResult, cb) ) )
  141. store.onChange && store.onChange(diffResult)
  142. for (let key in diffResult) {
  143. updateByPath(store.originData, key, typeof diffResult[key] === 'object' ? JSON.parse(JSON.stringify(diffResult[key])) : diffResult[key])
  144. }
  145. }
  146. Promise.all(array).then( e => resolve(diffResult) )
  147. })
  148. }
  149. }
  150. function initCloud(env) {
  151. wx.cloud.init()
  152. globalStore.db = wx.cloud.database({
  153. env: env
  154. })
  155. }
  156. function push(patch) {
  157. return new Promise(function (resolve, reject) {
  158. _push(update(patch), resolve, reject)
  159. })
  160. }
  161. function _push(diffResult, resolve) {
  162. const objs = diffToPushObj(diffResult)
  163. Object.keys(objs).forEach((path) => {
  164. const arr = path.split('-')
  165. const id = globalStore.data[arr[0]][parseInt(arr[1])]._id
  166. const obj = objs[path]
  167. if (globalStore.methods && globalStore.methods[arr[0]]) {
  168. Object.keys(globalStore.methods[arr[0]]).forEach(key => {
  169. if (obj.hasOwnProperty(key)) {
  170. delete obj[key]
  171. }
  172. })
  173. }
  174. globalStore.db.collection(arr[0]).doc(id).update({
  175. data: obj
  176. }).then((res) => {
  177. resolve(res)
  178. })
  179. })
  180. }
  181. function update(patch) {
  182. return new Promise(resolve => {
  183. //defineFnProp(globalStore.data)
  184. if (patch) {
  185. for (let key in patch) {
  186. updateByPath(globalStore.data, key, patch[key])
  187. }
  188. }
  189. let diffResult = diff(globalStore.data, originData)
  190. if (Object.keys(diffResult)[0] == '') {
  191. diffResult = diffResult['']
  192. }
  193. const updateAll = matchGlobalData(diffResult)
  194. let array = []
  195. if (Object.keys(diffResult).length > 0) {
  196. for (let key in globalStore.instances) {
  197. globalStore.instances[key].forEach(ins => {
  198. if(updateAll || globalStore.updateAll || ins._updatePath){
  199. // 获取需要更新的字段
  200. const needUpdatePathList = getNeedUpdatePathList(diffResult, ins._updatePath)
  201. if (needUpdatePathList.length) {
  202. const _diffResult = {}
  203. for (let _path in diffResult) {
  204. if (needUpdatePathList.includes(_path)) {
  205. _diffResult[_path] = diffResult[_path]
  206. }
  207. }
  208. array.push( new Promise(cb => {
  209. ins.setData.call(ins, _diffResult, cb)
  210. }) )
  211. }
  212. }
  213. })
  214. }
  215. globalStore.onChange && globalStore.onChange(diffResult)
  216. for (let key in diffResult) {
  217. updateByPath(originData, key, typeof diffResult[key] === 'object' ? JSON.parse(JSON.stringify(diffResult[key])) : diffResult[key])
  218. }
  219. }
  220. Promise.all(array).then(e=>{
  221. resolve(diffResult)
  222. })
  223. })
  224. }
  225. function matchGlobalData(diffResult) {
  226. if(!globalStore.globalData) return false
  227. for (let keyA in diffResult) {
  228. if (globalStore.globalData.indexOf(keyA) > -1) {
  229. return true
  230. }
  231. for (let i = 0, len = globalStore.globalData.length; i < len; i++) {
  232. if (includePath(keyA, globalStore.globalData[i])) {
  233. return true
  234. }
  235. }
  236. }
  237. return false
  238. }
  239. function getNeedUpdatePathList(diffResult, updatePath){
  240. const paths = []
  241. for(let keyA in diffResult){
  242. if(updatePath[keyA]){
  243. paths.push(keyA)
  244. }
  245. for(let keyB in updatePath){
  246. if(includePath(keyA, keyB)){
  247. paths.push(keyA)
  248. }
  249. }
  250. }
  251. return paths
  252. }
  253. function includePath(pathA, pathB){
  254. if(pathA.indexOf(pathB)===0){
  255. const next = pathA.substr(pathB.length, 1)
  256. if(next === '['||next === '.'){
  257. return true
  258. }
  259. }
  260. return false
  261. }
  262. function rewriteUpdate(ctx) {
  263. ctx.update = update
  264. }
  265. function updateByPath(origin, path, value) {
  266. const arr = path.replace(/]/g, '').replace(/\[/g, '.').split('.')
  267. let current = origin
  268. for (let i = 0, len = arr.length; i < len; i++) {
  269. if (i === len - 1) {
  270. current[arr[i]] = value
  271. } else {
  272. current = current[arr[i]]
  273. }
  274. }
  275. }
  276. function pull(cn, where) {
  277. return new Promise(function (resolve) {
  278. globalStore.db.collection(cn).where(where || {}).get().then(res => {
  279. extend(res, cn)
  280. resolve(res)
  281. })
  282. })
  283. }
  284. function extend(res, cn) {
  285. res.data.forEach(item => {
  286. const mds = globalStore.methods[cn]
  287. mds && Object.keys(mds).forEach(key => {
  288. Object.defineProperty(item, key, {
  289. enumerable: true,
  290. get: () => {
  291. return mds[key].call(item)
  292. },
  293. set: () => {
  294. //方法不能改写
  295. }
  296. })
  297. })
  298. })
  299. }
  300. function add(cn, data) {
  301. return globalStore.db.collection(cn).add({ data })
  302. }
  303. function remove(cn, id) {
  304. return globalStore.db.collection(cn).doc(id).remove()
  305. }
  306. function diffToPushObj(diffResult) {
  307. const result = {}
  308. Object.keys(diffResult).forEach(key => {
  309. diffItemToObj(key, diffResult[key], result)
  310. })
  311. return result
  312. }
  313. function diffItemToObj(path, value, result) {
  314. const arr = path.replace(/]/g, '').replace(/\[/g, '.').split('.')
  315. const obj = {}
  316. let current = null
  317. const len = arr.length
  318. for (let i = 2; i < len; i++) {
  319. if (len === 3) {
  320. obj[arr[i]] = value
  321. } else {
  322. if (i === len - 1) {
  323. current[arr[i]] = value
  324. } else {
  325. const pre = current
  326. current = {}
  327. if (i === 2) {
  328. obj[arr[i]] = current
  329. } else {
  330. pre[arr[i]] = current
  331. }
  332. }
  333. }
  334. }
  335. const key = arr[0] + '-' + arr[1]
  336. result[key] = Object.assign(result[key] || {}, obj)
  337. }
  338. function extendStoreMethod() {
  339. globalStore.method = function (path, fn) {
  340. fnMapping[path] = fn
  341. let ok = getObjByPath(path)
  342. Object.defineProperty(ok.obj, ok.key, {
  343. enumerable: true,
  344. get: () => {
  345. return fnMapping[path].call(globalStore.data)
  346. },
  347. set: () => {
  348. console.warn('Please using store.method to set method prop of data!')
  349. }
  350. })
  351. }
  352. }
  353. function getObjByPath(path) {
  354. const arr = path.replace(/]/g, '').replace(/\[/g, '.').split('.')
  355. const len = arr.length
  356. if (len > 1) {
  357. let current = globalStore.data[arr[0]]
  358. for (let i = 1; i < len - 1; i++) {
  359. current = current[arr[i]]
  360. }
  361. return { obj: current, key: arr[len - 1] }
  362. } else {
  363. return { obj: globalStore.data, key: arr[0] }
  364. }
  365. }
  366. function walk(data) {
  367. Object.keys(data).forEach(key => {
  368. const obj = data[key]
  369. const tp = type(obj)
  370. if (tp == FUNCTIONTYPE) {
  371. setProp(key, obj)
  372. } else if (tp == OBJECTTYPE) {
  373. Object.keys(obj).forEach(subKey => {
  374. _walk(obj[subKey], key + '.' + subKey)
  375. })
  376. } else if (tp == ARRAYTYPE) {
  377. obj.forEach((item, index) => {
  378. _walk(item, key + '[' + index + ']')
  379. })
  380. }
  381. })
  382. }
  383. function _walk(obj, path) {
  384. const tp = type(obj)
  385. if (tp == FUNCTIONTYPE) {
  386. setProp(path, obj)
  387. } else if (tp == OBJECTTYPE) {
  388. Object.keys(obj).forEach(subKey => {
  389. _walk(obj[subKey], path + '.' + subKey)
  390. })
  391. } else if (tp == ARRAYTYPE) {
  392. obj.forEach((item, index) => {
  393. _walk(item, path + '[' + index + ']')
  394. })
  395. }
  396. }
  397. function setProp(path, fn) {
  398. const ok = getObjByPath(path)
  399. fnMapping[path] = fn
  400. Object.defineProperty(ok.obj, ok.key, {
  401. enumerable: true,
  402. get: () => {
  403. return fnMapping[path].call(globalStore.data)
  404. },
  405. set: () => {
  406. console.warn('Please using store.method to set method prop of data!')
  407. }
  408. })
  409. }
  410. function type(obj) {
  411. return Object.prototype.toString.call(obj)
  412. }