fix: remove observable component when componentWillUnmount
This commit is contained in:
parent
556e31bcb5
commit
d4387d6392
|
@ -64,6 +64,22 @@ export function $(options) {
|
|||
return !isSelf
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
for (let i = 0, len = components.length; i < len; i++) {
|
||||
if (components[i] === this) {
|
||||
components.splice(i, 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0, len = updateSelfComponents.length; i < len; i++) {
|
||||
if (updateSelfComponents[i] === this) {
|
||||
updateSelfComponents.splice(i, 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return options.render.apply(this, arguments)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,232 @@
|
|||
export function obaa(target, arr, callback) {
|
||||
let eventPropArr = []
|
||||
if (isArray(target)) {
|
||||
if (target.length === 0) {
|
||||
target.__o_ = {
|
||||
__r_: target,
|
||||
__p_: '#'
|
||||
}
|
||||
}
|
||||
mock(target, target)
|
||||
}
|
||||
for (let prop in target) {
|
||||
if (target.hasOwnProperty(prop)) {
|
||||
if (callback) {
|
||||
if (isArray(arr) && isInArray(arr, prop)) {
|
||||
eventPropArr.push(prop)
|
||||
watch(target, prop, null, target)
|
||||
} else if (isString(arr) && prop === arr) {
|
||||
eventPropArr.push(prop)
|
||||
watch(target, prop, null, target)
|
||||
}
|
||||
} else {
|
||||
eventPropArr.push(prop)
|
||||
watch(target, prop, null, target)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!target.__c_) {
|
||||
target.__c_ = []
|
||||
}
|
||||
let propChanged = callback ? callback : arr
|
||||
target.__c_.push({
|
||||
all: !callback,
|
||||
propChanged,
|
||||
eventPropArr
|
||||
})
|
||||
}
|
||||
|
||||
let triggerStr = [
|
||||
'concat',
|
||||
'copyWithin',
|
||||
'fill',
|
||||
'pop',
|
||||
'push',
|
||||
'reverse',
|
||||
'shift',
|
||||
'sort',
|
||||
'splice',
|
||||
'unshift',
|
||||
'size'
|
||||
].join(',')
|
||||
|
||||
let methods = [
|
||||
'concat',
|
||||
'copyWithin',
|
||||
'entries',
|
||||
'every',
|
||||
'fill',
|
||||
'filter',
|
||||
'find',
|
||||
'findIndex',
|
||||
'forEach',
|
||||
'includes',
|
||||
'indexOf',
|
||||
'join',
|
||||
'keys',
|
||||
'lastIndexOf',
|
||||
'map',
|
||||
'pop',
|
||||
'push',
|
||||
'reduce',
|
||||
'reduceRight',
|
||||
'reverse',
|
||||
'shift',
|
||||
'slice',
|
||||
'some',
|
||||
'sort',
|
||||
'splice',
|
||||
'toLocaleString',
|
||||
'toString',
|
||||
'unshift',
|
||||
'values',
|
||||
'size'
|
||||
]
|
||||
|
||||
function mock(target, root) {
|
||||
methods.forEach(item => {
|
||||
target[item] = function() {
|
||||
let old = Array.prototype.slice.call(this, 0)
|
||||
let result = Array.prototype[item].apply(
|
||||
this,
|
||||
Array.prototype.slice.call(arguments)
|
||||
)
|
||||
if (new RegExp('\\b' + item + '\\b').test(triggerStr)) {
|
||||
for (let cprop in this) {
|
||||
if (this.hasOwnProperty(cprop) && !isFunction(this[cprop])) {
|
||||
watch(this, cprop, this.__o_.__p_, root)
|
||||
}
|
||||
}
|
||||
//todo
|
||||
onPropertyChanged(
|
||||
'Array-' + item,
|
||||
this,
|
||||
old,
|
||||
this,
|
||||
this.__o_.__p_,
|
||||
root
|
||||
)
|
||||
}
|
||||
return result
|
||||
}
|
||||
target[
|
||||
'pure' + item.substring(0, 1).toUpperCase() + item.substring(1)
|
||||
] = function() {
|
||||
return Array.prototype[item].apply(
|
||||
this,
|
||||
Array.prototype.slice.call(arguments)
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function watch(target, prop, path, root) {
|
||||
if (prop === '__o_') return
|
||||
if (isFunction(target[prop])) return
|
||||
if (!target.__o_)
|
||||
target.__o_ = {
|
||||
__r_: root
|
||||
}
|
||||
if (path !== undefined && path !== null) {
|
||||
target.__o_.__p_ = path
|
||||
} else {
|
||||
target.__o_.__p_ = '#'
|
||||
}
|
||||
|
||||
let currentValue = (target.__o_[prop] = target[prop])
|
||||
Object.defineProperty(target, prop, {
|
||||
get() {
|
||||
return this.__o_[prop]
|
||||
},
|
||||
set(value) {
|
||||
let old = this.__o_[prop]
|
||||
this.__o_[prop] = value
|
||||
onPropertyChanged(prop, value, old, this, target.__o_.__p_, root)
|
||||
},
|
||||
configurable: true,
|
||||
enumerable: true
|
||||
})
|
||||
if (typeof currentValue === 'object') {
|
||||
if (isArray(currentValue)) {
|
||||
mock(currentValue, root)
|
||||
if (currentValue.length === 0) {
|
||||
if (!currentValue.__o_) currentValue.__o_ = {}
|
||||
if (path !== undefined && path !== null) {
|
||||
currentValue.__o_.__p_ = path + '-' + prop
|
||||
} else {
|
||||
currentValue.__o_.__p_ = '#-' + prop
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let cprop in currentValue) {
|
||||
if (currentValue.hasOwnProperty(cprop)) {
|
||||
watch(currentValue, cprop, target.__o_.__p_ + '-' + prop, root)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onPropertyChanged(prop, value, oldValue, target, path, root) {
|
||||
if (value !== oldValue && !(nan(value) && nan(oldValue)) && root.__c_) {
|
||||
let rootName = getRootName(prop, path)
|
||||
for (let i = 0, len = root.__c_.length; i < len; i++) {
|
||||
let handler = root.__c_[i]
|
||||
if (
|
||||
handler.all ||
|
||||
isInArray(handler.eventPropArr, rootName) ||
|
||||
rootName.indexOf('Array-') === 0
|
||||
) {
|
||||
handler.propChanged.call(target, prop, value, oldValue, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (prop.indexOf('Array-') !== 0 && typeof value === 'object') {
|
||||
watch(target, prop, target.__o_.__p_, root)
|
||||
}
|
||||
}
|
||||
|
||||
function isFunction(obj) {
|
||||
return Object.prototype.toString.call(obj) === '[object Function]'
|
||||
}
|
||||
|
||||
function nan(value) {
|
||||
return typeof value === 'number' && isNaN(value)
|
||||
}
|
||||
|
||||
function isArray(obj) {
|
||||
return Object.prototype.toString.call(obj) === '[object Array]'
|
||||
}
|
||||
|
||||
function isString(obj) {
|
||||
return typeof obj === 'string'
|
||||
}
|
||||
|
||||
function isInArray(arr, item) {
|
||||
for (let i = arr.length; --i > -1; ) {
|
||||
if (item === arr[i]) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function getRootName(prop, path) {
|
||||
if (path === '#') {
|
||||
return prop
|
||||
}
|
||||
return path.split('-')[1]
|
||||
}
|
||||
|
||||
obaa.add = function(obj, prop) {
|
||||
watch(obj, prop, obj.__o_.__p_, obj.__o_.__r_)
|
||||
}
|
||||
|
||||
obaa.set = function(obj, prop, value) {
|
||||
watch(obj, prop, obj.__o_.__p_, obj.__o_.__r_)
|
||||
obj[prop] = value
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
/* eslint-disable */
|
||||
Array.prototype.size = function (length) {
|
||||
this.length = length
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
import { $ } from './react'
|
||||
import { $v } from './vue'
|
||||
|
||||
const root = getGlobal()
|
||||
|
||||
const omis = {
|
||||
$,
|
||||
$v
|
||||
}
|
||||
|
||||
root.Omis = omis
|
||||
root.omis = omis
|
||||
root.Omis.version = '2.0.0'
|
||||
|
||||
export default omis
|
||||
|
||||
export { $, $v }
|
||||
|
||||
function getGlobal() {
|
||||
if (
|
||||
typeof global !== 'object' ||
|
||||
!global ||
|
||||
global.Math !== Math ||
|
||||
global.Array !== Array
|
||||
) {
|
||||
return (
|
||||
self ||
|
||||
window ||
|
||||
global ||
|
||||
(function() {
|
||||
return this
|
||||
})()
|
||||
)
|
||||
}
|
||||
return global
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
const OBJECTTYPE = '[object Object]'
|
||||
const ARRAYTYPE = '[object Array]'
|
||||
|
||||
export function getUsing(data, paths) {
|
||||
const obj = []
|
||||
paths.forEach((path, index) => {
|
||||
const isPath = typeof path === 'string'
|
||||
if (isPath) {
|
||||
obj[index] = getTargetByPath(data, path)
|
||||
} else {
|
||||
const key = Object.keys(path)[0]
|
||||
const value = path[key]
|
||||
if (typeof value === 'string') {
|
||||
obj[index] = getTargetByPath(data, value)
|
||||
} else {
|
||||
const tempPath = value[0]
|
||||
if (typeof tempPath === 'string') {
|
||||
const tempVal = getTargetByPath(data, tempPath)
|
||||
obj[index] = value[1] ? value[1](tempVal) : tempVal
|
||||
} else {
|
||||
const args = []
|
||||
tempPath.forEach(path => {
|
||||
args.push(getTargetByPath(data, path))
|
||||
})
|
||||
obj[index] = value[1].apply(null, args)
|
||||
}
|
||||
}
|
||||
obj[key] = obj[index]
|
||||
}
|
||||
})
|
||||
return obj
|
||||
}
|
||||
|
||||
export function getTargetByPath(origin, path) {
|
||||
const arr = path
|
||||
.replace(/]/g, '')
|
||||
.replace(/\[/g, '.')
|
||||
.split('.')
|
||||
let current = origin
|
||||
for (let i = 0, len = arr.length; i < len; i++) {
|
||||
current = current[arr[i]]
|
||||
}
|
||||
return current
|
||||
}
|
||||
|
||||
export function getPath(obj) {
|
||||
if (Object.prototype.toString.call(obj) === '[object Array]') {
|
||||
const result = {}
|
||||
obj.forEach(item => {
|
||||
if (typeof item === 'string') {
|
||||
result[item] = true
|
||||
} else {
|
||||
const tempPath = item[Object.keys(item)[0]]
|
||||
if (typeof tempPath === 'string') {
|
||||
result[tempPath] = true
|
||||
} else if (typeof tempPath[0] === 'string') {
|
||||
result[tempPath[0]] = true
|
||||
} else {
|
||||
tempPath[0].forEach(path => (result[path] = true))
|
||||
}
|
||||
}
|
||||
})
|
||||
return result
|
||||
}
|
||||
return getUpdatePath(obj)
|
||||
}
|
||||
|
||||
export function getUpdatePath(data) {
|
||||
const result = {}
|
||||
dataToPath(data, result)
|
||||
return result
|
||||
}
|
||||
|
||||
function dataToPath(data, result) {
|
||||
Object.keys(data).forEach(key => {
|
||||
result[key] = true
|
||||
const type = Object.prototype.toString.call(data[key])
|
||||
if (type === OBJECTTYPE) {
|
||||
_objToPath(data[key], key, result)
|
||||
} else if (type === ARRAYTYPE) {
|
||||
_arrayToPath(data[key], key, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function _objToPath(data, path, result) {
|
||||
Object.keys(data).forEach(key => {
|
||||
result[path + '.' + key] = true
|
||||
delete result[path]
|
||||
const type = Object.prototype.toString.call(data[key])
|
||||
if (type === OBJECTTYPE) {
|
||||
_objToPath(data[key], path + '.' + key, result)
|
||||
} else if (type === ARRAYTYPE) {
|
||||
_arrayToPath(data[key], path + '.' + key, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function _arrayToPath(data, path, result) {
|
||||
data.forEach((item, index) => {
|
||||
result[path + '[' + index + ']'] = true
|
||||
delete result[path]
|
||||
const type = Object.prototype.toString.call(item)
|
||||
if (type === OBJECTTYPE) {
|
||||
_objToPath(item, path + '[' + index + ']', result)
|
||||
} else if (type === ARRAYTYPE) {
|
||||
_arrayToPath(item, path + '[' + index + ']', result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function needUpdate(diffResult, updatePath) {
|
||||
for (let keyA in diffResult) {
|
||||
if (updatePath[keyA]) {
|
||||
return true
|
||||
}
|
||||
for (let keyB in updatePath) {
|
||||
if (includePath(keyA, keyB)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function includePath(pathA, pathB) {
|
||||
if (pathA.indexOf(pathB) === 0) {
|
||||
const next = pathA.substr(pathB.length, 1)
|
||||
if (next === '[' || next === '.') {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export function fixPath(path) {
|
||||
let mpPath = ''
|
||||
const arr = path.replace('#-', '').split('-')
|
||||
arr.forEach((item, index) => {
|
||||
if (index) {
|
||||
if (isNaN(Number(item))) {
|
||||
mpPath += '.' + item
|
||||
} else {
|
||||
mpPath += '[' + item + ']'
|
||||
}
|
||||
} else {
|
||||
mpPath += item
|
||||
}
|
||||
})
|
||||
return mpPath
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
import React from 'react'
|
||||
import { obaa } from './obaa'
|
||||
import { getPath, needUpdate, fixPath } from './path'
|
||||
|
||||
const components = []
|
||||
const updateSelfComponents = []
|
||||
|
||||
let isSelf = false
|
||||
let currentComponent = null
|
||||
|
||||
export function $(options) {
|
||||
if (options.store) {
|
||||
$.store = options.store
|
||||
obaa($.store.data, (prop, val, old, path) => {
|
||||
const patch = {}
|
||||
|
||||
patch[fixPath(path + '-' + prop)] = true
|
||||
components.forEach(component => {
|
||||
if (
|
||||
component.__$updatePath_ &&
|
||||
needUpdate(patch, component.__$updatePath_)
|
||||
) {
|
||||
component.setState({ __$id_: component.state.__$id_++ })
|
||||
|
||||
isSelf = false
|
||||
}
|
||||
})
|
||||
|
||||
updateSelfComponents.forEach(component => {
|
||||
if (
|
||||
component.__$updateSelfPath_ &&
|
||||
needUpdate(patch, component.__$updateSelfPath_)
|
||||
) {
|
||||
component.setState({ __$id_: component.state.__$id_++ })
|
||||
isSelf = true
|
||||
currentComponent = component
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const updatePath = options.use && getPath(options.use)
|
||||
const updateSelfPath = options.useSelf && getPath(options.useSelf)
|
||||
|
||||
return class extends React.Component {
|
||||
state = {
|
||||
__$id_: 0
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
if (updatePath) {
|
||||
components.push(this)
|
||||
this.__$updatePath_ = updatePath
|
||||
}
|
||||
if (updateSelfPath) {
|
||||
updateSelfComponents.push(this)
|
||||
this.__$updateSelfPath_ = updateSelfPath
|
||||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate() {
|
||||
if (currentComponent === this) return true
|
||||
return !isSelf
|
||||
}
|
||||
|
||||
render() {
|
||||
return options.render.apply(this, arguments)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
const components = []
|
||||
|
||||
export function $v(options) {
|
||||
|
||||
const beforeCreate = options.beforeCreate
|
||||
const destroyed = options.destroyed
|
||||
options.beforeCreate = function () {
|
||||
components.push(this)
|
||||
beforeCreate && beforeCreate.apply(this, arguments)
|
||||
}
|
||||
options.destroyed = function () {
|
||||
for (let i = 0, len = components.length; i < len; i++) {
|
||||
if (components[i] === this) {
|
||||
components.splice(i, 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
destroyed && destroyed.apply(this, arguments)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue