优化组件创建逻辑;改用Symbol解决原型方法被覆盖的风险
parent
08d8eb3b74
commit
a1f4823537
|
@ -4,11 +4,45 @@
|
||||||
* @date 2023/03/06 12:08:35
|
* @date 2023/03/06 12:08:35
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const FINALIZED = Symbol('finalized')
|
const boolMap = {}
|
||||||
export const UPDATE = Symbol('update')
|
|
||||||
|
;[
|
||||||
|
'autofocus',
|
||||||
|
'autoplay',
|
||||||
|
'async',
|
||||||
|
'allowTransparency',
|
||||||
|
'checked',
|
||||||
|
'controls',
|
||||||
|
'declare',
|
||||||
|
'disabled',
|
||||||
|
'defer',
|
||||||
|
'defaultChecked',
|
||||||
|
'defaultSelected',
|
||||||
|
'contentEditable',
|
||||||
|
'isMap',
|
||||||
|
'loop',
|
||||||
|
'multiple',
|
||||||
|
'noHref',
|
||||||
|
'noResize',
|
||||||
|
'noShade',
|
||||||
|
'open',
|
||||||
|
'readOnly',
|
||||||
|
'selected'
|
||||||
|
].forEach(function (name) {
|
||||||
|
boolMap[name.toLowerCase()] = name
|
||||||
|
})
|
||||||
|
|
||||||
|
export { boolMap }
|
||||||
|
|
||||||
export const WC_PART = Symbol('wc_path')
|
export const WC_PART = Symbol('wc_path')
|
||||||
export const NO_CHANGE = Symbol('wc-noChange')
|
export const NO_CHANGE = Symbol('wc-noChange')
|
||||||
export const NOTHING = Symbol('wc-nothing')
|
export const NOTHING = Symbol('wc-nothing')
|
||||||
|
export const __finalized__ = Symbol('finalized')
|
||||||
|
export const __update__ = Symbol('update')
|
||||||
|
export const __init__ = Symbol('init')
|
||||||
|
export const __props__ = Symbol('props')
|
||||||
|
export const __changed_props__ = Symbol('changed_props')
|
||||||
|
export const __mounted__ = Symbol('mounted')
|
||||||
|
|
||||||
export const RESET_CSS_STYLE = `
|
export const RESET_CSS_STYLE = `
|
||||||
* {box-sizing: border-box;margin: 0;padding: 0;}
|
* {box-sizing: border-box;margin: 0;padding: 0;}
|
||||||
|
@ -19,6 +53,7 @@ export const DEFAULT_CONVERTER = {
|
||||||
toAttribute(value, type) {
|
toAttribute(value, type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case Boolean:
|
case Boolean:
|
||||||
|
// console.log(this, '>>>', value)
|
||||||
value = value ? '' : null
|
value = value ? '' : null
|
||||||
break
|
break
|
||||||
case Object:
|
case Object:
|
||||||
|
@ -57,7 +92,9 @@ export function notEqual(value, old) {
|
||||||
export const DEFAULT_PROPERTY_DECLARATION = {
|
export const DEFAULT_PROPERTY_DECLARATION = {
|
||||||
attribute: true,
|
attribute: true,
|
||||||
type: String,
|
type: String,
|
||||||
converter: DEFAULT_CONVERTER,
|
formater: DEFAULT_CONVERTER,
|
||||||
|
// converter: DEFAULT_CONVERTER,
|
||||||
reflect: false,
|
reflect: false,
|
||||||
hasChanged: notEqual
|
hasChanged: notEqual,
|
||||||
|
default: ''
|
||||||
}
|
}
|
||||||
|
|
168
src/index.js
168
src/index.js
|
@ -4,11 +4,16 @@
|
||||||
* @date 2023/03/07 18:10:43
|
* @date 2023/03/07 18:10:43
|
||||||
*/
|
*/
|
||||||
import {
|
import {
|
||||||
FINALIZED,
|
|
||||||
UPDATE,
|
|
||||||
DEFAULT_CONVERTER,
|
DEFAULT_CONVERTER,
|
||||||
DEFAULT_PROPERTY_DECLARATION,
|
DEFAULT_PROPERTY_DECLARATION,
|
||||||
notEqual
|
notEqual,
|
||||||
|
boolMap,
|
||||||
|
__finalized__,
|
||||||
|
__update__,
|
||||||
|
__init__,
|
||||||
|
__props__,
|
||||||
|
__changed_props__,
|
||||||
|
__mounted__
|
||||||
} from './constants.js'
|
} from './constants.js'
|
||||||
import { css, adoptStyles } from './css.js'
|
import { css, adoptStyles } from './css.js'
|
||||||
import { render, html, svg } from './html.js'
|
import { render, html, svg } from './html.js'
|
||||||
|
@ -24,78 +29,71 @@ export {
|
||||||
export { html, css, svg, bind, unbind }
|
export { html, css, svg, bind, unbind }
|
||||||
|
|
||||||
export class Component extends HTMLElement {
|
export class Component extends HTMLElement {
|
||||||
constructor() {
|
/**
|
||||||
super()
|
* 声明可监听变化的属性列表
|
||||||
|
* @return list<Array>
|
||||||
this.__instanceProperties = new Map()
|
*/
|
||||||
this.isUpdatePending = false
|
|
||||||
this.hasUpdated = false
|
|
||||||
this.__reflectingProperty = null
|
|
||||||
this._initialize()
|
|
||||||
this.created && this.created()
|
|
||||||
}
|
|
||||||
static addInitializer(initializer) {
|
|
||||||
this.finalize()
|
|
||||||
if (!this._initializers) {
|
|
||||||
this._initializers = []
|
|
||||||
}
|
|
||||||
this._initializers.push(initializer)
|
|
||||||
}
|
|
||||||
static get observedAttributes() {
|
static get observedAttributes() {
|
||||||
|
let list = []
|
||||||
|
|
||||||
this.finalize()
|
this.finalize()
|
||||||
const attributes = []
|
|
||||||
this.elementProperties.forEach((v, p) => {
|
this[__props__].forEach((options, prop) => {
|
||||||
const attr = this.__attributeNameForProperty(p, v)
|
if (options) {
|
||||||
if (attr !== void 0) {
|
options.watch && list.push(k.toLowerCase())
|
||||||
this.__attributeToPropertyMap.set(attr, p)
|
} else {
|
||||||
attributes.push(attr)
|
list.push(k.toLowerCase())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return attributes
|
return list
|
||||||
}
|
}
|
||||||
static createProperty(name, options = DEFAULT_PROPERTY_DECLARATION) {
|
static createProperty(name, options = DEFAULT_PROPERTY_DECLARATION) {
|
||||||
if (options.state) {
|
if (options.state) {
|
||||||
options.attribute = false
|
options.attribute = false
|
||||||
}
|
}
|
||||||
|
|
||||||
this.elementProperties.set(name, options)
|
this[__props__].set(name, options)
|
||||||
|
|
||||||
let key = Symbol(name)
|
let key = Symbol(name)
|
||||||
let descriptor = this.getPropertyDescriptor(name, key, options)
|
let descriptor = {
|
||||||
|
|
||||||
this.prototype[key] = options.default
|
|
||||||
Object.defineProperty(this.prototype, name, descriptor)
|
|
||||||
}
|
|
||||||
static getPropertyDescriptor(name, key, options) {
|
|
||||||
return {
|
|
||||||
get() {
|
get() {
|
||||||
return this[key]
|
return this[key]
|
||||||
},
|
},
|
||||||
set(value) {
|
set(value) {
|
||||||
const oldValue = this[name]
|
let oldValue = this[key]
|
||||||
this[key] = value
|
this[key] = value
|
||||||
this.requestUpdate(name, oldValue, options)
|
this.requestUpdate(name, oldValue, options)
|
||||||
},
|
},
|
||||||
configurable: true
|
enumerable: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Object.defineProperty(this.prototype, name, descriptor)
|
||||||
|
|
||||||
|
// this.prototype[name] = options.default
|
||||||
}
|
}
|
||||||
static getPropertyOptions(name) {
|
|
||||||
return this.elementProperties.get(name) || DEFAULT_PROPERTY_DECLARATION
|
// 处理静态声明
|
||||||
}
|
|
||||||
static finalize() {
|
static finalize() {
|
||||||
if (this[FINALIZED]) {
|
if (this[__finalized__]) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
this[FINALIZED] = true
|
this[__finalized__] = true
|
||||||
|
|
||||||
|
this[__props__] = new Map()
|
||||||
|
|
||||||
this.elementProperties = new Map()
|
|
||||||
this.__attributeToPropertyMap = new Map()
|
|
||||||
if (this.hasOwnProperty('props')) {
|
if (this.hasOwnProperty('props')) {
|
||||||
for (let k in this.props) {
|
for (let k in this.props) {
|
||||||
|
if (boolMap[k] && k !== boolMap[k]) {
|
||||||
|
this.props[boolMap[k]] = this.props[k]
|
||||||
|
delete this.props[k]
|
||||||
|
k = boolMap[k]
|
||||||
|
}
|
||||||
this.createProperty(k, this.props[k])
|
this.createProperty(k, this.props[k])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete this.props
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,33 +107,25 @@ export class Component extends HTMLElement {
|
||||||
? name.toLowerCase()
|
? name.toLowerCase()
|
||||||
: void 0
|
: void 0
|
||||||
}
|
}
|
||||||
_initialize() {
|
|
||||||
this.__updatePromise = new Promise(res => (this.enableUpdating = res))
|
|
||||||
this._$changedProperties = new Map()
|
|
||||||
this.__saveInstanceProperties()
|
|
||||||
this.requestUpdate()
|
|
||||||
this.constructor._initializers?.forEach(i => i(this))
|
|
||||||
}
|
|
||||||
addController(controller) {
|
|
||||||
if (!this.__controllers) {
|
|
||||||
this.__controllers = []
|
|
||||||
}
|
|
||||||
this.__controllers.push(controller)
|
|
||||||
|
|
||||||
if (this.root !== void 0 && this.isConnected) {
|
constructor() {
|
||||||
controller.hostConnected?.call(controller)
|
super()
|
||||||
}
|
|
||||||
}
|
this.isUpdatePending = false
|
||||||
removeController(controller) {
|
this.__reflectingProperty = null
|
||||||
this.__controllers?.splice(this.__controllers.indexOf(controller) >>> 0, 1)
|
this[__mounted__] = false
|
||||||
}
|
this[__init__]()
|
||||||
__saveInstanceProperties() {
|
this.created && this.created()
|
||||||
this.constructor.elementProperties.forEach((_v, p) => {
|
|
||||||
if (this.hasOwnProperty(p)) {
|
|
||||||
this.__instanceProperties.set(p, this[p])
|
|
||||||
delete this[p]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[__init__]() {
|
||||||
|
this.__updatePromise = new Promise(res => (this.enableUpdating = res))
|
||||||
|
this[__changed_props__] = new Map() // 记录本次变化的属性
|
||||||
|
// 初始化 props
|
||||||
|
this.constructor[__props__].forEach((options, prop) => {
|
||||||
|
this[prop] = options.default
|
||||||
})
|
})
|
||||||
|
this.requestUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
|
@ -145,14 +135,10 @@ export class Component extends HTMLElement {
|
||||||
|
|
||||||
this.enableUpdating(true)
|
this.enableUpdating(true)
|
||||||
|
|
||||||
this.__controllers?.forEach(it => it.hostConnected?.call(it))
|
|
||||||
this.__childPart?.setConnected(true)
|
this.__childPart?.setConnected(true)
|
||||||
|
|
||||||
this.mounted && this.mounted()
|
|
||||||
}
|
}
|
||||||
enableUpdating(_requestedUpdate) {}
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
this.__controllers?.forEach(it => it.hostDisconnected?.call(it))
|
|
||||||
this.__childPart?.setConnected(false)
|
this.__childPart?.setConnected(false)
|
||||||
}
|
}
|
||||||
attributeChangedCallback(name, _old, value) {
|
attributeChangedCallback(name, _old, value) {
|
||||||
|
@ -194,11 +180,11 @@ export class Component extends HTMLElement {
|
||||||
requestUpdate(name, oldValue, options) {
|
requestUpdate(name, oldValue, options) {
|
||||||
let shouldRequestUpdate = true
|
let shouldRequestUpdate = true
|
||||||
if (name !== void 0) {
|
if (name !== void 0) {
|
||||||
options = options || this.constructor.getPropertyOptions(name)
|
options = options || this.constructor[__props__][name]
|
||||||
const hasChanged = options.hasChanged || notEqual
|
const hasChanged = options.hasChanged || notEqual
|
||||||
if (hasChanged(this[name], oldValue)) {
|
if (hasChanged(this[name], oldValue)) {
|
||||||
if (!this._$changedProperties.has(name)) {
|
if (!this[__changed_props__].has(name)) {
|
||||||
this._$changedProperties.set(name, oldValue)
|
this[__changed_props__].set(name, oldValue)
|
||||||
}
|
}
|
||||||
if (options.reflect === true && this.__reflectingProperty !== name) {
|
if (options.reflect === true && this.__reflectingProperty !== name) {
|
||||||
if (this.__reflectingProperties === void 0) {
|
if (this.__reflectingProperties === void 0) {
|
||||||
|
@ -235,15 +221,9 @@ export class Component extends HTMLElement {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.__instanceProperties) {
|
const changedProperties = this[__changed_props__]
|
||||||
this.__instanceProperties.forEach((v, p) => (this[p] = v))
|
|
||||||
this.__instanceProperties = void 0
|
|
||||||
}
|
|
||||||
|
|
||||||
const changedProperties = this._$changedProperties
|
|
||||||
try {
|
try {
|
||||||
this.__controllers?.forEach(it => it.hostUpdate?.call(it))
|
this[__update__](changedProperties)
|
||||||
this[UPDATE](changedProperties)
|
|
||||||
this._$didUpdate(changedProperties)
|
this._$didUpdate(changedProperties)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.__markUpdated()
|
this.__markUpdated()
|
||||||
|
@ -251,16 +231,14 @@ export class Component extends HTMLElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_$didUpdate(changedProperties) {
|
_$didUpdate(changedProperties) {
|
||||||
this.__controllers?.forEach(it => it.hostUpdated?.call(it))
|
if (!this[__mounted__]) {
|
||||||
|
this[__mounted__] = true
|
||||||
if (!this.hasUpdated) {
|
this.mounted && this.mounted()
|
||||||
this.hasUpdated = true
|
|
||||||
this.firstUpdated(changedProperties)
|
|
||||||
}
|
}
|
||||||
this.updated(changedProperties)
|
this.updated(changedProperties)
|
||||||
}
|
}
|
||||||
__markUpdated() {
|
__markUpdated() {
|
||||||
this._$changedProperties = new Map()
|
this[__changed_props__] = new Map()
|
||||||
this.isUpdatePending = false
|
this.isUpdatePending = false
|
||||||
}
|
}
|
||||||
get updateComplete() {
|
get updateComplete() {
|
||||||
|
@ -270,8 +248,8 @@ export class Component extends HTMLElement {
|
||||||
return this.__updatePromise
|
return this.__updatePromise
|
||||||
}
|
}
|
||||||
|
|
||||||
[UPDATE](_changedProperties) {
|
[__update__](_changedProperties) {
|
||||||
let value = this.render()
|
let htmlText = this.render()
|
||||||
|
|
||||||
if (this.__reflectingProperties !== void 0) {
|
if (this.__reflectingProperties !== void 0) {
|
||||||
this.__reflectingProperties.forEach((v, k) =>
|
this.__reflectingProperties.forEach((v, k) =>
|
||||||
|
@ -280,14 +258,12 @@ export class Component extends HTMLElement {
|
||||||
this.__reflectingProperties = void 0
|
this.__reflectingProperties = void 0
|
||||||
}
|
}
|
||||||
this.__markUpdated()
|
this.__markUpdated()
|
||||||
|
this.__childPart = render(htmlText, this.root, {
|
||||||
this.__childPart = render(value, this.root, {
|
|
||||||
host: this,
|
host: this,
|
||||||
isConnected: !this.hasUpdated && this.isConnected
|
isConnected: !this[__mounted__] && this.isConnected
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
updated(_changedProperties) {}
|
updated(_changedProperties) {}
|
||||||
firstUpdated(_changedProperties) {}
|
|
||||||
|
|
||||||
$on(type, callback) {
|
$on(type, callback) {
|
||||||
return bind(this, type, callback)
|
return bind(this, type, callback)
|
||||||
|
|
Loading…
Reference in New Issue