一大波优化

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 NOTHING = Symbol('wc-nothing')
export const __finalized__ = Symbol('finalized')
export const __update__ = Symbol('update')
export const __render__ = Symbol('render')
export const __init__ = Symbol('init')
export const __props__ = Symbol('props')
export const __changed_props__ = Symbol('changed_props')
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 DEFAULT_CONVERTER = {
toAttribute(value, type) {
function getDefaultValue(type) {
switch (type) {
case Boolean:
// console.log(this, '>>>', value)
value = value ? '' : null
break
case Object:
case Array:
value = value == null ? value : JSON.stringify(value)
break
}
return value
},
fromAttribute(value, type) {
let fromValue = value
switch (type) {
case Boolean:
fromValue = value !== null
break
case Number:
fromValue = value === null ? null : Number(value)
break
return 0
case Boolean:
return false
case Object:
return {}
case Array:
try {
fromValue = JSON.parse(value)
} catch (e) {
fromValue = null
return []
default:
return ''
}
}
function getType(v) {
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 function parsePropsDeclaration(options) {
if (options && typeof options === 'object') {
if (options.hasOwnProperty('type')) {
return Object.assign(
{ attribute: true, default: getDefaultValue(options.type) },
options
)
}
}
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
}
return fromValue
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
}
}
export function notEqual(value, old) {
return old !== value && (old === old || value === value)
}
export const DEFAULT_PROPERTY_DECLARATION = {
attribute: true,
type: String,
formater: DEFAULT_CONVERTER,
// converter: DEFAULT_CONVERTER,
reflect: false,
hasChanged: notEqual,
default: ''
}

View File

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

View File

@ -4,29 +4,28 @@
* @date 2023/03/07 18:10:43
*/
import {
DEFAULT_CONVERTER,
DEFAULT_PROPERTY_DECLARATION,
notEqual,
fixedValue,
parsePropsDeclaration,
boolMap,
__finalized__,
__update__,
__render__,
__init__,
__props__,
__changed_props__,
__mounted__
__mounted__,
__feedback__,
__pending__,
__prop2attr__,
__attr2prop__,
__clear_update__,
__children__,
__updated__
} from './constants.js'
import { css, adoptStyles } from './css.js'
import { render, html, svg } from './html.js'
import { fire, bind, unbind } from './utils.js'
export {
$,
$$,
nextTick,
offset,
outsideClick,
clearOutsideClick
} from './utils.js'
export { html, css, svg, bind, unbind }
import { nextTick, fire, bind, unbind } from './utils.js'
export { $, $$, offset, outsideClick, clearOutsideClick } from './utils.js'
export { html, css, svg, bind, unbind, nextTick }
export class Component extends HTMLElement {
/**
@ -39,21 +38,12 @@ export class Component extends HTMLElement {
this.finalize()
this[__props__].forEach((options, prop) => {
if (options) {
options.watch && list.push(k.toLowerCase())
} else {
list.push(k.toLowerCase())
}
options.attribute && list.push(prop.toLowerCase())
})
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 descriptor = {
get() {
@ -61,15 +51,18 @@ export class Component extends HTMLElement {
},
set(value) {
let oldValue = this[key]
value = fixedValue(value, options)
if (oldValue === value) {
return
}
this[key] = value
this.requestUpdate(name, oldValue, options)
this.requestUpdate(name, oldValue)
},
enumerable: false
}
this[__props__].set(name, options)
Object.defineProperty(this.prototype, name, descriptor)
// this.prototype[name] = options.default
}
// 处理静态声明
@ -83,187 +76,157 @@ export class Component extends HTMLElement {
if (this.hasOwnProperty('props')) {
for (let k in this.props) {
let options = parsePropsDeclaration(this.props[k])
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, options)
}
}
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() {
super()
this.isUpdatePending = false
this.__reflectingProperty = null
this[__pending__] = false
this[__mounted__] = false
this[__init__]()
this.created && this.created()
this.created()
}
[__init__]() {
this.__updatePromise = new Promise(res => (this.enableUpdating = res))
this.root = this.shadowRoot || this.attachShadow({ mode: 'open' })
this[__changed_props__] = new Map() // 记录本次变化的属性
// 初始化 props
this.constructor[__props__].forEach((options, prop) => {
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() {
this.root = this.shadowRoot || this.attachShadow({ mode: 'open' })
adoptStyles(this.root, this.constructor.styles)
this.enableUpdating(true)
this.__childPart?.setConnected(true)
this[__children__]?.setConnected(true)
}
disconnectedCallback() {
this.__childPart?.setConnected(false)
this[__children__]?.setConnected(false)
}
attributeChangedCallback(name, _old, value) {
this._$attributeToProperty(name, value)
}
__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) {
// 监听属性变化
attributeChangedCallback(name, old, val) {
if (old === val) {
return
}
this[__attr2prop__](name, val)
}
const changedProperties = this[__changed_props__]
/**
* 处理需要显式渲染到html标签上的属性
* 复杂类型永不显式渲染
* @param name<String>
* @param value<String|Boolean|Number>
*/
[__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
}
}
/**
* 通过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 {
this[__update__](changedProperties)
this._$didUpdate(changedProperties)
} catch (e) {
this.__markUpdated()
throw e
let props = this[__changed_props__]
this[__render__]()
this[__feedback__](props)
} catch (err) {
console.error(err)
}
this[__clear_update__]()
}
}
_$didUpdate(changedProperties) {
// 更新回调反馈
[__feedback__](props) {
// 初始化时不触发updated回调
if (!this[__mounted__]) {
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()
if (this.__reflectingProperties !== void 0) {
this.__reflectingProperties.forEach((v, k) =>
this.__propertyToAttribute(k, this[k], v)
)
this.__reflectingProperties = void 0
}
this.__markUpdated()
this.__childPart = render(htmlText, this.root, {
this[__children__] = render(htmlText, this.root, {
host: this,
isConnected: !this[__mounted__] && this.isConnected
})
}
updated(_changedProperties) {}
// 几个生命周期回调
created() {}
mounted() {}
updated() {}
$on(type, callback) {
return bind(this, type, callback)