一大波优化

pull/1/head
yutent 2023-03-13 18:28:03 +08:00
parent e122462b1e
commit 85d4dbd6a2
3 changed files with 244 additions and 220 deletions

View File

@ -38,60 +38,129 @@ 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 __finalized__ = Symbol('finalized')
export const __update__ = Symbol('update') export const __render__ = Symbol('render')
export const __init__ = Symbol('init') export const __init__ = Symbol('init')
export const __props__ = Symbol('props') export const __props__ = Symbol('props')
export const __changed_props__ = Symbol('changed_props') export const __changed_props__ = Symbol('changed_props')
export const __mounted__ = Symbol('mounted') export const __mounted__ = Symbol('mounted')
export const __feedback__ = Symbol('feedback')
export const __pending__ = Symbol('pending')
export const __prop2attr__ = Symbol('prop2attr')
export const __attr2prop__ = Symbol('attr2prop')
export const __clear_update__ = Symbol('clearupdate')
export const __children__ = Symbol('children')
export const __updated__ = Symbol('updated')
export const RESET_CSS_STYLE = `* {box-sizing: border-box;margin: 0;padding: 0;}::before,::after {box-sizing: border-box;}` export const RESET_CSS_STYLE = `* {box-sizing: border-box;margin: 0;padding: 0;}::before,::after {box-sizing: border-box;}`
export const DEFAULT_CONVERTER = { function getDefaultValue(type) {
toAttribute(value, type) { switch (type) {
switch (type) { case Number:
case Boolean: return 0
// console.log(this, '>>>', value)
value = value ? '' : null case Boolean:
break return false
case Object:
case Array: case Object:
value = value == null ? value : JSON.stringify(value) return {}
break
} case Array:
return value return []
},
fromAttribute(value, type) { default:
let fromValue = value return ''
switch (type) {
case Boolean:
fromValue = value !== null
break
case Number:
fromValue = value === null ? null : Number(value)
break
case Object:
case Array:
try {
fromValue = JSON.parse(value)
} catch (e) {
fromValue = null
}
break
}
return fromValue
} }
} }
export function notEqual(value, old) { function getType(v) {
return old !== value && (old === old || value === value) switch (typeof v) {
case 'number':
return { type: Number, default: v }
case 'boolean':
return { type: Boolean, default: v }
case 'object':
return Array.isArray(v)
? { type: Array, default: v }
: { type: Object, default: v }
default:
return { type: String, default: v + '' }
}
} }
export const DEFAULT_PROPERTY_DECLARATION = { export function parsePropsDeclaration(options) {
attribute: true, if (options && typeof options === 'object') {
type: String, if (options.hasOwnProperty('type')) {
formater: DEFAULT_CONVERTER, return Object.assign(
// converter: DEFAULT_CONVERTER, { attribute: true, default: getDefaultValue(options.type) },
reflect: false, options
hasChanged: notEqual, )
default: '' }
}
switch (options) {
case Number:
options = { type: Number }
break
case Boolean:
options = { type: Boolean }
break
case Object:
options = { type: Object }
break
case Array:
options = { type: Array }
break
default:
options = getType(options)
break
}
options.default = options.default || getDefaultValue(options.type)
options.attribute = true
return options
}
export function fixedValue(value, options) {
switch (options.type) {
case Number:
return +value || 0
break
case Boolean:
return value === false ? false : value !== null
break
case Object:
if (typeof value === 'object') {
return value
} else {
try {
return JSON.parse(value)
} catch (err) {
return {}
}
}
break
case Array:
if (typeof value === 'object') {
return value
} else {
try {
return JSON.parse(value)
} catch (err) {
return []
}
}
break
default:
return value + ''
break
}
} }

View File

@ -1,9 +1,6 @@
import { WC_PART, NO_CHANGE, NOTHING } from './constants.js' import { WC_PART, NO_CHANGE, NOTHING } from './constants.js'
var ENABLE_EXTRA_SECURITY_HOOKS = true var ENABLE_EXTRA_SECURITY_HOOKS = true
var global3 = window
var debugLogRenderId = 0
var issueWarning2
var identityFunction = value => value var identityFunction = value => value
var noopSanitizer = (_node, _name, _type) => identityFunction var noopSanitizer = (_node, _name, _type) => identityFunction
@ -726,17 +723,12 @@ class ElementPart {
} }
export function render(value, container, options) { export function render(value, container, options) {
const renderId = 0 let part = container[WC_PART]
const partOwnerNode = options?.renderBefore || container
let part = partOwnerNode[WC_PART]
if (part === void 0) { if (part === void 0) {
const endNode = options?.renderBefore || null container[WC_PART] = part = new ChildPart(
container.insertBefore(createMarker(), null),
partOwnerNode[WC_PART] = part = new ChildPart( null,
container.insertBefore(createMarker(), endNode),
endNode,
void 0, void 0,
options !== null && options !== void 0 ? options : {} options !== null && options !== void 0 ? options : {}
) )

View File

@ -4,29 +4,28 @@
* @date 2023/03/07 18:10:43 * @date 2023/03/07 18:10:43
*/ */
import { import {
DEFAULT_CONVERTER, fixedValue,
DEFAULT_PROPERTY_DECLARATION, parsePropsDeclaration,
notEqual,
boolMap, boolMap,
__finalized__, __finalized__,
__update__, __render__,
__init__, __init__,
__props__, __props__,
__changed_props__, __changed_props__,
__mounted__ __mounted__,
__feedback__,
__pending__,
__prop2attr__,
__attr2prop__,
__clear_update__,
__children__,
__updated__
} 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'
import { fire, bind, unbind } from './utils.js' import { nextTick, fire, bind, unbind } from './utils.js'
export { export { $, $$, offset, outsideClick, clearOutsideClick } from './utils.js'
$, export { html, css, svg, bind, unbind, nextTick }
$$,
nextTick,
offset,
outsideClick,
clearOutsideClick
} from './utils.js'
export { html, css, svg, bind, unbind }
export class Component extends HTMLElement { export class Component extends HTMLElement {
/** /**
@ -39,21 +38,12 @@ export class Component extends HTMLElement {
this.finalize() this.finalize()
this[__props__].forEach((options, prop) => { this[__props__].forEach((options, prop) => {
if (options) { options.attribute && list.push(prop.toLowerCase())
options.watch && list.push(k.toLowerCase())
} else {
list.push(k.toLowerCase())
}
}) })
return list return list
} }
static createProperty(name, options = DEFAULT_PROPERTY_DECLARATION) {
if (options.state) {
options.attribute = false
}
this[__props__].set(name, options)
static createProperty(name, options) {
let key = Symbol(name) let key = Symbol(name)
let descriptor = { let descriptor = {
get() { get() {
@ -61,15 +51,18 @@ export class Component extends HTMLElement {
}, },
set(value) { set(value) {
let oldValue = this[key] let oldValue = this[key]
value = fixedValue(value, options)
if (oldValue === value) {
return
}
this[key] = value this[key] = value
this.requestUpdate(name, oldValue, options) this.requestUpdate(name, oldValue)
}, },
enumerable: false enumerable: false
} }
this[__props__].set(name, options)
Object.defineProperty(this.prototype, name, descriptor) Object.defineProperty(this.prototype, name, descriptor)
// this.prototype[name] = options.default
} }
// 处理静态声明 // 处理静态声明
@ -83,187 +76,157 @@ export class Component extends HTMLElement {
if (this.hasOwnProperty('props')) { if (this.hasOwnProperty('props')) {
for (let k in this.props) { for (let k in this.props) {
let options = parsePropsDeclaration(this.props[k])
if (boolMap[k] && k !== boolMap[k]) { if (boolMap[k] && k !== boolMap[k]) {
this.props[boolMap[k]] = this.props[k]
delete this.props[k]
k = boolMap[k] k = boolMap[k]
} }
this.createProperty(k, this.props[k]) this.createProperty(k, options)
} }
} }
delete this.props delete this.props
return true
}
static __attributeNameForProperty(name, options) {
const attribute = options.attribute
return attribute === false
? void 0
: typeof attribute === 'string'
? attribute
: typeof name === 'string'
? name.toLowerCase()
: void 0
} }
constructor() { constructor() {
super() super()
this[__pending__] = false
this.isUpdatePending = false
this.__reflectingProperty = null
this[__mounted__] = false this[__mounted__] = false
this[__init__]() this[__init__]()
this.created && this.created() this.created()
} }
[__init__]() { [__init__]() {
this.__updatePromise = new Promise(res => (this.enableUpdating = res)) this.root = this.shadowRoot || this.attachShadow({ mode: 'open' })
this[__changed_props__] = new Map() // 记录本次变化的属性 this[__changed_props__] = new Map() // 记录本次变化的属性
// 初始化 props // 初始化 props
this.constructor[__props__].forEach((options, prop) => { this.constructor[__props__].forEach((options, prop) => {
this[prop] = options.default this[prop] = options.default
}) })
this.requestUpdate() // 若无定义props时, 手动执行一次渲染
if (this[__pending__] === false) {
this[__pending__] = true
this.performUpdate()
}
}
#getPropOptions(name) {
return this.constructor[__props__].get(name)
} }
connectedCallback() { connectedCallback() {
this.root = this.shadowRoot || this.attachShadow({ mode: 'open' })
adoptStyles(this.root, this.constructor.styles) adoptStyles(this.root, this.constructor.styles)
this.enableUpdating(true) this[__children__]?.setConnected(true)
this.__childPart?.setConnected(true)
} }
disconnectedCallback() { disconnectedCallback() {
this.__childPart?.setConnected(false) this[__children__]?.setConnected(false)
} }
attributeChangedCallback(name, _old, value) { // 监听属性变化
this._$attributeToProperty(name, value) attributeChangedCallback(name, old, val) {
} if (old === val) {
__propertyToAttribute(name, value, options = DEFAULT_PROPERTY_DECLARATION) {
const attr = this.constructor.__attributeNameForProperty(name, options)
if (attr !== void 0 && options.reflect === true) {
const converter = options.converter?.toAttribute
? options.converter
: DEFAULT_CONVERTER
const attrValue = converter.toAttribute(value, options.type)
this.__reflectingProperty = name
if (attrValue == null) {
this.removeAttribute(attr)
} else {
this.setAttribute(attr, attrValue)
}
this.__reflectingProperty = null
}
}
_$attributeToProperty(name, value) {
const ctor = this.constructor
const propName = ctor.__attributeToPropertyMap.get(name)
if (propName !== void 0 && this.__reflectingProperty !== propName) {
const options = ctor.getPropertyOptions(propName)
const converter =
typeof options.converter === 'function'
? { fromAttribute: options.converter }
: options.converter?.fromAttribute
? options.converter
: DEFAULT_CONVERTER
this.__reflectingProperty = propName
this[propName] = converter.fromAttribute(value, options.type)
this.__reflectingProperty = null
}
}
requestUpdate(name, oldValue, options) {
let shouldRequestUpdate = true
if (name !== void 0) {
options = options || this.constructor[__props__][name]
const hasChanged = options.hasChanged || notEqual
if (hasChanged(this[name], oldValue)) {
if (!this[__changed_props__].has(name)) {
this[__changed_props__].set(name, oldValue)
}
if (options.reflect === true && this.__reflectingProperty !== name) {
if (this.__reflectingProperties === void 0) {
this.__reflectingProperties = new Map()
}
this.__reflectingProperties.set(name, options)
}
} else {
shouldRequestUpdate = false
}
}
if (!this.isUpdatePending && shouldRequestUpdate) {
this.__updatePromise = this.__enqueueUpdate()
}
}
async __enqueueUpdate() {
this.isUpdatePending = true
try {
await this.__updatePromise
} catch (e) {
Promise.reject(e)
}
const result = this.scheduleUpdate()
if (result != null) {
await result
}
return !this.isUpdatePending
}
scheduleUpdate() {
return this.performUpdate()
}
performUpdate() {
if (!this.isUpdatePending) {
return return
} }
this[__attr2prop__](name, val)
}
const changedProperties = this[__changed_props__] /**
try { * 处理需要显式渲染到html标签上的属性
this[__update__](changedProperties) * 复杂类型永不显式渲染
this._$didUpdate(changedProperties) * @param name<String>
} catch (e) { * @param value<String|Boolean|Number>
this.__markUpdated() */
throw e [__prop2attr__](name, value) {
let options = this.#getPropOptions(name)
switch (options.type) {
case Number:
case String:
if (value === null) {
this.removeAttribute(name)
} else {
this.setAttribute(name, value)
}
break
case Boolean:
if (value === null || value === false) {
this.removeAttribute(name)
} else {
this.setAttribute(name, '')
}
break
} }
} }
_$didUpdate(changedProperties) {
/**
* 通过setAttribute设置的值, 需要转成props
* @param name<String>
* @param value<String|Boolean|Number>
*/
[__attr2prop__](name, value) {
let options = this.#getPropOptions(name)
this[name] = fixedValue(value, options)
}
// 请求更新
requestUpdate(name, oldValue) {
let shouldUpdate = true
this[__changed_props__].set(name, this[name])
this[__prop2attr__](name, this[name])
if (this[__pending__] === false) {
this[__pending__] = true
nextTick(_ => this[__updated__]())
}
}
// 确认更新到视图
[__updated__]() {
if (this[__pending__]) {
try {
let props = this[__changed_props__]
this[__render__]()
this[__feedback__](props)
} catch (err) {
console.error(err)
}
this[__clear_update__]()
}
}
// 更新回调反馈
[__feedback__](props) {
// 初始化时不触发updated回调
if (!this[__mounted__]) { if (!this[__mounted__]) {
this[__mounted__] = true this[__mounted__] = true
this.mounted && this.mounted() this.mounted(props)
} else {
this.updated(props)
} }
this.updated(changedProperties)
}
__markUpdated() {
this[__changed_props__] = new Map()
this.isUpdatePending = false
}
get updateComplete() {
return this.getUpdateComplete()
}
getUpdateComplete() {
return this.__updatePromise
} }
[__update__](_changedProperties) { [__clear_update__]() {
this[__changed_props__] = new Map()
this[__pending__] = false
}
// 渲染视图
[__render__]() {
let htmlText = this.render() let htmlText = this.render()
if (this.__reflectingProperties !== void 0) { this[__children__] = render(htmlText, this.root, {
this.__reflectingProperties.forEach((v, k) =>
this.__propertyToAttribute(k, this[k], v)
)
this.__reflectingProperties = void 0
}
this.__markUpdated()
this.__childPart = render(htmlText, this.root, {
host: this, host: this,
isConnected: !this[__mounted__] && this.isConnected isConnected: !this[__mounted__] && this.isConnected
}) })
} }
updated(_changedProperties) {} // 几个生命周期回调
created() {}
mounted() {}
updated() {}
$on(type, callback) { $on(type, callback) {
return bind(this, type, callback) return bind(this, type, callback)