Skip to content

Commit

Permalink
feat: urlSync supports inheritance! 💥
Browse files Browse the repository at this point in the history
  • Loading branch information
imcuttle committed Mar 9, 2018
1 parent db14cff commit 981f0c6
Show file tree
Hide file tree
Showing 3 changed files with 204 additions and 99 deletions.
229 changes: 142 additions & 87 deletions src/decorator/utils/getStateLifeDecorator.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,19 @@ import {
observable,
isObservableArray
} from 'mobx'
import logger from '../../utils/logger'

export const assignState = action(
function (self, property, val) {
const setVal = (value = val) => {
if (self[property] !== value) {
// console.log(name + ' set', property, value)
logger.debug(name + ' set', property, value)
self[property] = value
}
}

if (typeof val !== 'undefined') {
// console.log('before load url: `' + property + '`:', this[property]);
logger.debug('before load `' + property + '`:', self[property])
if (val == null) {
setVal()
}
Expand All @@ -31,7 +32,9 @@ export const assignState = action(
// remove overflow items if arrays
if (
Array.isArray(val) &&
(isObservableArray(self[property]) || Array.isArray(self[property]))
(
isObservableArray(self[property]) || Array.isArray(self[property])
)
&& val.length < self[property].length
) {
self[property].splice(val.length, self[property].length - val.length)
Expand All @@ -58,13 +61,34 @@ export const assignState = action(
setVal()
}

// console.log('after loaded url: `' + property + '`:', this[property]);
logger.debug('after load: `' + property + '`:', self[property])
}
}
)

// const g = {}
function extendsHideProps(target, propKey, value) {
const old = target[propKey] && target[propKey]
if (typeof old === 'object' && old !== null) {
// only saved on top parent
if (!Array.isArray(old)) {
Object.assign(old, value)
}
else {
old.push(value)
}
return
}
Object.defineProperty(target, propKey, {
value,
configurable: true,
enumerable: false
})
}

export default (config = {}, name = 'state-life') => {
config = config || {}
// const collection = g[name] = g[name] || {}

return (urlKey, options = {}, target, property, descriptor) => {
if (typeof urlKey !== 'string') {
Expand All @@ -73,112 +97,143 @@ export default (config = {}, name = 'state-life') => {
}
options = options || {}
const { initKey = 'init', exitKey = 'exit', updateKey } = options
const assignStateValue = function () {
return assignState.call(null, this, property, config.get(urlKey))
const assignStateValue = function (self, property, urlKey) {
return assignState(self, property, config.get(urlKey))
}

if ('value' in descriptor && typeof descriptor.value === 'function') {
throw new Error('`' + name + '` can NOT use in member method')
}

// maybe observable
// if ('get' in descriptor) {
// throw new Error('`' + name + '` can NOT use in getter')
// }
if ('initializer' in descriptor) {
console.warn('`' + property + '`' + 'is unobservable,', name, 'would may it to be observable.')
logger.warn('`' + property + '`' + 'is unobservable,', name, 'would make it to be observable.')
descriptor = observable(target, property, descriptor)
}
// https://github.com/mobxjs/mobx/issues/1382
let dispose
let syncUrlTimer
let syncUrlFn

// eslint-disable-next-line no-inner-declarations
function release() {
dispose && dispose()
dispose = null
if (syncUrlTimer) {
clearTimeout(syncUrlTimer)
syncUrlFn && syncUrlFn()
syncUrlTimer = void 0
syncUrlFn = void 0
const hidePropKey = `__[[${name}_origin_hooks]]__`
const hideArrPropKey = `__[[${name}_array]]__`
// Firstly!
// Supports inheritance
if (!target[hidePropKey] || !target[hidePropKey][property]) {
const hooks = {
init: target[initKey],
update: target[updateKey],
exit: target[exitKey]
}
extendsHideProps(target, hidePropKey, {
[property]: hooks
})
}

let originExit = target[exitKey]
target[exitKey] = function (...args) {
config.exit && config.exit(this, property, urlKey)
// console.log('dispose ' + name + ' `' + property + '`')
release()
return originExit && originExit.call(this, ...args)
if (!target[hideArrPropKey]) {
extendsHideProps(target, hideArrPropKey, [])
}

// eslint-disable-next-line no-use-before-define
target[initKey] = init(target[initKey], 'init')

if (updateKey) {
target[updateKey] = (
function (origin) {
return action(function (...args) {
assignStateValue.call(this)
return origin && origin.call(this, ...args)
})
}
)(target[updateKey])
let i = target[hideArrPropKey].findIndex(([p]) => p === property)
if (i >= 0) {
target[hideArrPropKey].splice(i, 1)
}
const func = (
function () {
let dispose
let syncUrlTimer
let syncUrlFn

// eslint-disable-next-line no-inner-declarations
function release() {
dispose && dispose()
dispose = null
if (syncUrlTimer) {
clearTimeout(syncUrlTimer)
syncUrlFn && syncUrlFn()
syncUrlTimer = void 0
syncUrlFn = void 0
}
}

// eslint-disable-next-line no-inner-declarations,no-unused-vars
function init(origin, actionType) {
return action(function (...args) {
config.init && config.init(this, property, urlKey)

// console.log(actionType + ' ' + name + ' `' + property + '`')
release()
assignStateValue.call(this)

let isFirst = true
dispose = autorun(
() => {
// 一段时间内的修改以最后一次为准
if (syncUrlTimer) {
clearTimeout(syncUrlTimer)
syncUrlTimer = void 0
}

let obj = { [urlKey]: this[property] }
// invoke the deep `getter` of this[property]
// noop op
try {
JSON.stringify(obj)
} catch (err) {
console.error('[Stringify]', obj, 'Error happened:', err)
}
return {
init: function () {
config.init && config.init(this, property, urlKey)
logger.debug('init ' + name + ' `' + property + '`')
release()
assignStateValue(this, property, urlKey)

let isFirst = true
dispose = autorun(() => {
// 一段时间内的修改以最后一次为准
if (syncUrlTimer) {
clearTimeout(syncUrlTimer)
syncUrlTimer = void 0
}

syncUrlFn = (isFirst = false) => {
let save = isFirst ? (
config.saveFirstTime || config.save
) : config.save
save.call(config, urlKey, this[property], config.fetch())
syncUrlTimer = void 0
syncUrlFn = void 0
}
let obj = { [urlKey]: this[property] }
// invoke the deep `getter` of this[property]
// noop op
try {
JSON.stringify(obj)
} catch (err) {
console.error('[Stringify]', obj, 'Error happened:', err)
}

if (isFirst) {
if (options.initialWrite) {
syncUrlFn(true)
syncUrlFn = (isFirst = false) => {
let save = isFirst ? (
config.saveFirstTime || config.save
) : config.save
// console.log('save', urlKey, property, this[property])
save.call(config, urlKey, this[property], config.fetch())
syncUrlTimer = void 0
syncUrlFn = void 0
}
isFirst = false
return
}

syncUrlTimer = setTimeout(syncUrlFn, 250)
if (isFirst) {
if (options.initialWrite) {
syncUrlFn(true)
}
isFirst = false
return
}
syncUrlTimer = setTimeout(syncUrlFn, 250)
})
},
update: function () {
assignStateValue(this, property, urlKey)
},
exit: function () {
config.exit && config.exit(this, property, urlKey)
logger.debug('dispose ' + name + ' `' + property + '`')
release()
}
)
}
}
)()
extendsHideProps(target, hideArrPropKey, [property, func])

const hooks = target[hidePropKey]
const arrays = target[hideArrPropKey]
const callHook = (self, hookName, args) => {
return typeof hooks[hookName] === 'function'
&& hooks[hookName].apply(self, args)
}

return origin && origin.call(this, ...args)
target[initKey] = function (...args) {
arrays.forEach(([, { init }]) => {
init.call(this)
})
return callHook(this, initKey, args)
}
if (updateKey) {
target[updateKey] = function (...args) {
arrays.forEach(([, { update }]) => {
update.call(this)
})
return callHook(this, updateKey, args)
}
}
target[exitKey] = function (...args) {
arrays.forEach(([, { init }]) => {
init.call(this)
})
return callHook(this, exitKey, args)
}

return descriptor && { ...descriptor, configurable: true }
}
}
12 changes: 4 additions & 8 deletions src/utils/logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,14 @@

export default {
debug: function (...args) {
if (process.env.NODE_ENV !== 'production') {
console.log(...args)
if (global && global.VM_DEBUG || process.env.NODE_ENV !== 'production') {
console.log('[react-mobx-vm] Debug:',...args)
}
},
warn: function (...args) {
if (process.env.NODE_ENV !== 'production') {
console.warn(...args)
}
console.warn('[react-mobx-vm] Warning:', ...args)
},
error: function (...args) {
if (process.env.NODE_ENV !== 'production') {
console.error(...args)
}
console.error('[react-mobx-vm] Error:',...args)
}
}
Loading

0 comments on commit 981f0c6

Please sign in to comment.