一大波优化
parent
e122462b1e
commit
85d4dbd6a2
157
src/constants.js
157
src/constants.js
|
@ -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) {
|
||||
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
|
||||
case Object:
|
||||
case Array:
|
||||
try {
|
||||
fromValue = JSON.parse(value)
|
||||
} catch (e) {
|
||||
fromValue = null
|
||||
}
|
||||
break
|
||||
}
|
||||
return fromValue
|
||||
function getDefaultValue(type) {
|
||||
switch (type) {
|
||||
case Number:
|
||||
return 0
|
||||
|
||||
case Boolean:
|
||||
return false
|
||||
|
||||
case Object:
|
||||
return {}
|
||||
|
||||
case Array:
|
||||
return []
|
||||
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
export function notEqual(value, old) {
|
||||
return old !== value && (old === old || value === value)
|
||||
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 const DEFAULT_PROPERTY_DECLARATION = {
|
||||
attribute: true,
|
||||
type: String,
|
||||
formater: DEFAULT_CONVERTER,
|
||||
// converter: DEFAULT_CONVERTER,
|
||||
reflect: false,
|
||||
hasChanged: notEqual,
|
||||
default: ''
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
16
src/html.js
16
src/html.js
|
@ -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 : {}
|
||||
)
|
||||
|
|
291
src/index.js
291
src/index.js
|
@ -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__]
|
||||
try {
|
||||
this[__update__](changedProperties)
|
||||
this._$didUpdate(changedProperties)
|
||||
} catch (e) {
|
||||
this.__markUpdated()
|
||||
throw e
|
||||
/**
|
||||
* 处理需要显式渲染到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
|
||||
}
|
||||
}
|
||||
_$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__]) {
|
||||
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)
|
||||
|
|
Loading…
Reference in New Issue