优化组件创建逻辑;改用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 | ||||||
|  |     this.__reflectingProperty = null | ||||||
|  |     this[__mounted__] = false | ||||||
|  |     this[__init__]() | ||||||
|  |     this.created && this.created() | ||||||
|   } |   } | ||||||
|   removeController(controller) { | 
 | ||||||
|     this.__controllers?.splice(this.__controllers.indexOf(controller) >>> 0, 1) |   [__init__]() { | ||||||
|   } |     this.__updatePromise = new Promise(res => (this.enableUpdating = res)) | ||||||
|   __saveInstanceProperties() { |     this[__changed_props__] = new Map() // 记录本次变化的属性
 | ||||||
|     this.constructor.elementProperties.forEach((_v, p) => { |     // 初始化 props
 | ||||||
|       if (this.hasOwnProperty(p)) { |     this.constructor[__props__].forEach((options, prop) => { | ||||||
|         this.__instanceProperties.set(p, this[p]) |       this[prop] = options.default | ||||||
|         delete this[p] |  | ||||||
|       } |  | ||||||
|     }) |     }) | ||||||
|  |     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