diff --git a/packages/core/src/core/mergeOptions.js b/packages/core/src/core/mergeOptions.js index 517c7c41cb..8b7eaebb3a 100644 --- a/packages/core/src/core/mergeOptions.js +++ b/packages/core/src/core/mergeOptions.js @@ -122,9 +122,10 @@ function extractObservers (options) { Object.keys(props).forEach(key => { const prop = props[key] if (prop && prop.observer) { + let callback = prop.observer + delete prop.observer mergeWatch(key, { handler (...rest) { - let callback = prop.observer if (typeof callback === 'string') { callback = this[callback] } diff --git a/packages/core/src/core/proxy.js b/packages/core/src/core/proxy.js index 54d4603f55..15ff6719c6 100644 --- a/packages/core/src/core/proxy.js +++ b/packages/core/src/core/proxy.js @@ -1,4 +1,4 @@ -import { reactive } from '../observer/reactive' +import { reactive, defineReactive } from '../observer/reactive' import { ReactiveEffect, pauseTracking, resetTracking } from '../observer/effect' import { effectScope } from '../platform/export/index' import { watch } from '../observer/watch' @@ -111,7 +111,7 @@ export default class MpxProxy { this.uid = uid++ this.name = options.name || '' this.options = options - this.ignoreReactivePattern = this.options.options?.ignoreReactivePattern + this.shallowReactivePattern = this.options.options?.shallowReactivePattern // beforeCreate -> created -> mounted -> unmounted this.state = BEFORECREATE this.ignoreProxyMap = makeMap(Mpx.config.ignoreProxyWhiteList) @@ -145,10 +145,12 @@ export default class MpxProxy { this.initApi() } - processIgnoreReactive (obj) { - if (this.ignoreReactivePattern && isObject(obj)) { + processShallowReactive (obj) { + if (this.shallowReactivePattern && isObject(obj)) { Object.keys(obj).forEach((key) => { - if (this.ignoreReactivePattern.test(key)) { + if (this.shallowReactivePattern.test(key)) { + // 命中shallowReactivePattern的属性将其设置为 shallowReactive + defineReactive(obj, key, obj[key], true) Object.defineProperty(obj, key, { enumerable: true, // set configurable to false to skip defineReactive @@ -290,10 +292,10 @@ export default class MpxProxy { if (isReact) { // react模式下props内部对象透传无需深clone,依赖对象深层的数据响应触发子组件更新 this.props = this.target.__getProps() - reactive(this.processIgnoreReactive(this.props)) + reactive(this.processShallowReactive(this.props)) } else { this.props = diffAndCloneA(this.target.__getProps(this.options)).clone - reactive(this.processIgnoreReactive(this.props)) + reactive(this.processShallowReactive(this.props)) } proxy(this.target, this.props, undefined, false, this.createProxyConflictHandler('props')) } @@ -333,7 +335,7 @@ export default class MpxProxy { if (isFunction(dataFn)) { Object.assign(this.data, callWithErrorHandling(dataFn.bind(this.target), this, 'data function')) } - reactive(this.processIgnoreReactive(this.data)) + reactive(this.processShallowReactive(this.data)) proxy(this.target, this.data, undefined, false, this.createProxyConflictHandler('data')) this.collectLocalKeys(this.data) } @@ -514,7 +516,7 @@ export default class MpxProxy { if (hasOwn(renderData, key)) { const data = renderData[key] const firstKey = getFirstKey(key) - if (!this.localKeysMap[firstKey] || (this.ignoreReactivePattern && this.ignoreReactivePattern.test(firstKey))) { + if (!this.localKeysMap[firstKey]) { continue } // 外部clone,用于只需要clone的场景 diff --git a/packages/core/src/platform/builtInMixins/proxyEventMixin.web.js b/packages/core/src/platform/builtInMixins/proxyEventMixin.web.js index 019c74b158..1938d8e349 100644 --- a/packages/core/src/platform/builtInMixins/proxyEventMixin.web.js +++ b/packages/core/src/platform/builtInMixins/proxyEventMixin.web.js @@ -1,4 +1,5 @@ -import { setByPath } from '@mpxjs/utils' +import { setByPath, error, parseDataset } from '@mpxjs/utils' +import Mpx from '../../index' export default function proxyEventMixin () { return { @@ -19,11 +20,58 @@ export default function proxyEventMixin () { const value = filterMethod ? (innerFilter[filterMethod] ? innerFilter[filterMethod](originValue) : typeof this[filterMethod] === 'function' && this[filterMethod]) : originValue setByPath(this, expr, value) }, - __invokeHandler (eventName, $event) { - const handler = this[eventName] - if (handler && typeof handler === 'function') { - handler.call(this, $event) + __invoke (rawEvent, eventConfig = []) { + if (typeof Mpx.config.proxyEventHandler === 'function') { + try { + Mpx.config.proxyEventHandler(rawEvent) + } catch (e) {} } + const location = this.__mpxProxy.options.mpxFileResource + + if (rawEvent.target && !rawEvent.target._datasetProcessed) { + const originalDataset = rawEvent.target.dataset + Object.defineProperty(rawEvent.target, 'dataset', { + get: () => parseDataset(originalDataset), + configurable: true, + enumerable: true + }) + rawEvent.target._datasetProcessed = true + } + if (rawEvent.currentTarget && !rawEvent.currentTarget._datasetProcessed) { + const originalDataset = rawEvent.currentTarget.dataset + Object.defineProperty(rawEvent.currentTarget, 'dataset', { + get: () => parseDataset(originalDataset), + configurable: true, + enumerable: true + }) + rawEvent.currentTarget._datasetProcessed = true + } + + let returnedValue + eventConfig.forEach((item) => { + const callbackName = item[0] + if (callbackName) { + const params = + item.length > 1 + ? item.slice(1).map((item) => { + if (item === '__mpx_event__') { + return rawEvent + } else { + return item + } + }) + : [rawEvent] + if (typeof this[callbackName] === 'function') { + returnedValue = this[callbackName].apply(this, params) + } else { + error( + `Instance property [${callbackName}] is not function, please check.`, + location + ) + } + } + }) + return returnedValue } } } diff --git a/packages/core/src/platform/env/event.js b/packages/core/src/platform/env/event.js index 82cb7bf77a..d6fdb09b66 100644 --- a/packages/core/src/platform/env/event.js +++ b/packages/core/src/platform/env/event.js @@ -11,57 +11,60 @@ function extendEvent (e, extendObj = {}) { }) } -function MpxEvent (layer) { - this.targetElement = null - this.touches = [] - this.touchStartX = 0 - this.touchStartY = 0 - this.startTimer = null - this.needTap = true - this.isTouchDevice = document && ('ontouchstart' in document.documentElement) +function createMpxEvent (layer) { + let startTimer = null + let needTap = true + let touchStartX = 0 + let touchStartY = 0 + let targetElement = null + const isTouchDevice = document && 'ontouchstart' in document.documentElement - this.onTouchStart = (event) => { + const onTouchStart = (event) => { if (event.targetTouches?.length > 1) { return true } - this.touches = event.targetTouches - this.targetElement = event.target - this.needTap = true - this.startTimer = null - this.touchStartX = this.touches[0].pageX - this.touchStartY = this.touches[0].pageY - this.startTimer = setTimeout(() => { - this.needTap = false - this.sendEvent(this.targetElement, 'longpress', event) - this.sendEvent(this.targetElement, 'longtap', event) + const touches = event.targetTouches + targetElement = event.target + needTap = true + startTimer = null + touchStartX = touches[0].pageX + touchStartY = touches[0].pageY + startTimer = setTimeout(() => { + needTap = false + sendEvent(targetElement, 'longpress', event) + sendEvent(targetElement, 'longtap', event) }, 350) } - this.onTouchMove = (event) => { + const onTouchMove = (event) => { const touch = event.changedTouches[0] - if (Math.abs(touch.pageX - this.touchStartX) > 1 || Math.abs(touch.pageY - this.touchStartY) > 1) { - this.needTap = false - this.startTimer && clearTimeout(this.startTimer) - this.startTimer = null + if ( + Math.abs(touch.pageX - touchStartX) > 1 || + Math.abs(touch.pageY - touchStartY) > 1 + ) { + needTap = false + startTimer && clearTimeout(startTimer) + startTimer = null } } - this.onTouchEnd = (event) => { + const onTouchEnd = (event) => { if (event.targetTouches?.length > 1) { return true } - this.startTimer && clearTimeout(this.startTimer) - this.startTimer = null - if (this.needTap) { - this.sendEvent(this.targetElement, 'tap', event) + startTimer && clearTimeout(startTimer) + startTimer = null + if (needTap) { + sendEvent(targetElement, 'tap', event) } } - this.onClick = (event) => { - this.targetElement = event.target - this.sendEvent(this.targetElement, 'tap', event) + const onClick = (event) => { + targetElement = event.target + sendEvent(targetElement, 'tap', event) } - this.sendEvent = (targetElement, type, event) => { + + const sendEvent = (targetElement, type, event) => { const touchEvent = new CustomEvent(type, { bubbles: true, cancelable: true @@ -72,7 +75,6 @@ function MpxEvent (layer) { changedTouches, touches: changedTouches, detail: { - // pc端点击事件可能没有changedTouches,所以直接从 event中取 x: changedTouches[0]?.pageX || event.pageX || 0, y: changedTouches[0]?.pageY || event.pageY || 0 } @@ -80,28 +82,23 @@ function MpxEvent (layer) { targetElement && targetElement.dispatchEvent(touchEvent) } - this.addListener = () => { - if (this.isTouchDevice) { - layer.addEventListener('touchstart', this.onTouchStart, true) - layer.addEventListener('touchmove', this.onTouchMove, true) - layer.addEventListener('touchend', this.onTouchEnd, true) - } else { - layer.addEventListener('click', this.onClick, true) - } + if (isTouchDevice) { + layer.addEventListener('touchstart', onTouchStart, true) + layer.addEventListener('touchmove', onTouchMove, true) + layer.addEventListener('touchend', onTouchEnd, true) + } else { + layer.addEventListener('click', onClick, true) } - this.addListener() } export function initEvent () { if (isBrowser && !global.__mpxCreatedEvent) { global.__mpxCreatedEvent = true if (document.readyState === 'complete' || document.readyState === 'interactive') { - // eslint-disable-next-line no-new - new MpxEvent(document.body) + createMpxEvent(document.body) } else { document.addEventListener('DOMContentLoaded', function () { - // eslint-disable-next-line no-new - new MpxEvent(document.body) + createMpxEvent(document.body) }, false) } } diff --git a/packages/core/src/platform/patch/getDefaultOptions.js b/packages/core/src/platform/patch/getDefaultOptions.js index 5701775f15..38b39e99d4 100644 --- a/packages/core/src/platform/patch/getDefaultOptions.js +++ b/packages/core/src/platform/patch/getDefaultOptions.js @@ -23,13 +23,11 @@ function transformProperties (properties) { } else { newFiled = Object.assign({}, rawFiled) } - const rawObserver = rawFiled?.observer newFiled.observer = function (value, oldValue) { if (this.__mpxProxy) { this[key] = value this.__mpxProxy.propsUpdated() } - rawObserver && rawObserver.call(this, value, oldValue) } newProps[key] = newFiled }) diff --git a/packages/webpack-plugin/lib/platform/template/wx/index.js b/packages/webpack-plugin/lib/platform/template/wx/index.js index cc6335c642..1da86356b1 100644 --- a/packages/webpack-plugin/lib/platform/template/wx/index.js +++ b/packages/webpack-plugin/lib/platform/template/wx/index.js @@ -384,10 +384,6 @@ module.exports = function getSpec ({ warn, error }) { } }, web ({ name, value }, { eventRules, el, usingComponents }) { - const parsed = parseMustacheWithContext(value) - if (parsed.hasBinding) { - value = '__invokeHandler(' + parsed.result + ', $event)' - } const match = this.test.exec(name) const prefix = match[1] const eventName = match[2] diff --git a/packages/webpack-plugin/lib/template-compiler/compiler.js b/packages/webpack-plugin/lib/template-compiler/compiler.js index 9445aad991..2781eee4be 100644 --- a/packages/webpack-plugin/lib/template-compiler/compiler.js +++ b/packages/webpack-plugin/lib/template-compiler/compiler.js @@ -1157,6 +1157,49 @@ function getModelConfig (el, match) { } } +function processEventWeb (el) { + const eventConfigMap = {} + el.attrsList.forEach(function ({ name, value }) { + if (/^@[a-zA-Z]+$/.test(name)) { + const parsedFunc = parseFuncStr(value) + if (parsedFunc) { + if (!eventConfigMap[name]) { + eventConfigMap[name] = { + configs: [] + } + } + eventConfigMap[name].configs.push( + Object.assign({ name, value }, parsedFunc) + ) + } + } + }) + + // let wrapper + for (const name in eventConfigMap) { + const { configs } = eventConfigMap[name] + if (!configs.length) continue + configs.forEach(({ name }) => { + if (name) { + // 清空原始事件绑定 + let has + do { + has = getAndRemoveAttr(el, name).has + } while (has) + } + }) + const value = `(e)=>__invoke(e, [${configs.map( + (item) => item.expStr + )}])` + addAttrs(el, [ + { + name, + value + } + ]) + } +} + function processEventReact (el) { const eventConfigMap = {} el.attrsList.forEach(function ({ name, value }) { @@ -2642,6 +2685,7 @@ function processElement (el, root, options, meta) { // 预处理代码维度条件编译 processIfWeb(el) processScoped(el) + processEventWeb(el) // processWebExternalClassesHack(el, options) processExternalClasses(el, options) processComponentGenericsWeb(el, options, meta) diff --git a/packages/webpack-plugin/test/platform/common/mode.spec.js b/packages/webpack-plugin/test/platform/common/mode.spec.js index 7aa4e81e5b..7df73a9d2b 100644 --- a/packages/webpack-plugin/test/platform/common/mode.spec.js +++ b/packages/webpack-plugin/test/platform/common/mode.spec.js @@ -76,7 +76,7 @@ describe('template should transform correct', function () { it('should work correct with web mode', function () { const input = '' const output = compileTemplate(input, { mode: 'web' }) - expect(output).toBe('获取用户信息') + expect(output).toBe("获取用户信息") }) it('should work normal if no attr in tag', function () {