fix: remove observable component when componentWillUnmount

This commit is contained in:
dntzhang 2019-10-02 16:18:08 +08:00
parent 556e31bcb5
commit d4387d6392
6 changed files with 528 additions and 0 deletions

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

71
packages/omis/vue/src/omis/react.js vendored Normal file
View File

@ -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)
}
}
}

View File

@ -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)
}
}