diff --git a/packages/taro-swan/package.json b/packages/taro-swan/package.json index ca9020608a66..e9590f9eb148 100644 --- a/packages/taro-swan/package.json +++ b/packages/taro-swan/package.json @@ -11,7 +11,8 @@ ], "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "build": "rollup -c rollup.config.js" + "build": "rollup -c rollup.config.js", + "watch": "rollup -c rollup.config.js -w" }, "repository": { "type": "git", diff --git a/packages/taro-swan/src/create-component.js b/packages/taro-swan/src/create-component.js index bcefb12bde5d..07d02d272010 100644 --- a/packages/taro-swan/src/create-component.js +++ b/packages/taro-swan/src/create-component.js @@ -3,36 +3,19 @@ import { getCurrentPageUrl } from '@tarojs/utils' import { isEmptyObject, noop } from './util' import { updateComponent } from './lifecycle' import { cacheDataGet, cacheDataHas } from './data-cache' +import propsManager from './propsManager' -const privatePropValName = 'privateTriggerObserer' const anonymousFnNamePreffix = 'funPrivate' -const componentFnReg = /^__fn_/ const PRELOAD_DATA_KEY = 'preload' const pageExtraFns = ['onPullDownRefresh', 'onReachBottom', 'onShareAppMessage', 'onPageScroll', 'onTabItemTap'] -function bindProperties (weappComponentConf, ComponentClass) { - weappComponentConf.properties = ComponentClass.properties || {} - const defaultProps = ComponentClass.defaultProps || {} - for (const key in defaultProps) { - if (defaultProps.hasOwnProperty(key)) { - weappComponentConf.properties[key] = { - type: null, - value: defaultProps[key] - } - } - } - // 拦截props的更新,插入生命周期 - // 调用小程序setData或会造成性能消耗 - weappComponentConf.properties[privatePropValName] = { - type: Boolean, - value: false, - observer: function () { - if (!this.$component || !this.$component.__isReady) return - const nextProps = filterProps(ComponentClass.properties, ComponentClass.defaultProps, this.$component.props, this.data) - this.$component.props = nextProps - this.$component._unsafeCallUpdate = true - updateComponent(this.$component) - this.$component._unsafeCallUpdate = false +function bindProperties (weappComponentConf, ComponentClass, isPage) { + weappComponentConf.properties = {} + weappComponentConf.properties.compid = { + type: null, + value: null, + observer () { + initComponent.apply(this, [ComponentClass, isPage]) } } } @@ -156,28 +139,12 @@ function bindEvents (weappComponentConf, events, isPage) { }) } -function filterProps (properties, defaultProps = {}, componentProps = {}, weappComponentData) { - let newProps = Object.assign({}, componentProps) - for (const propName in properties) { - if (propName === privatePropValName) { - continue - } - if (typeof componentProps[propName] === 'function') { - newProps[propName] = componentProps[propName] - } else if (propName in weappComponentData) { - newProps[propName] = weappComponentData[propName] - } - if (componentFnReg.test(propName)) { - if (weappComponentData[propName] === true) { - const fnName = propName.replace(componentFnReg, '') - newProps[fnName] = noop - } - delete newProps[propName] - } - } +export function filterProps (defaultProps = {}, propsFromPropsManager = {}, curAllProps = {}) { + let newProps = Object.assign({}, curAllProps, propsFromPropsManager) + if (!isEmptyObject(defaultProps)) { for (const propName in defaultProps) { - if (newProps[propName] === undefined || newProps[propName] === null) { + if (newProps[propName] === undefined) { newProps[propName] = defaultProps[propName] } } @@ -186,6 +153,11 @@ function filterProps (properties, defaultProps = {}, componentProps = {}, weappC } export function componentTrigger (component, key, args) { + if (key === 'componentWillUnmount') { + const compid = component.$scope.data.compid + if (compid) propsManager.delete(compid) + } + args = args || [] component[key] && typeof component[key] === 'function' && component[key](...args) if (key === 'componentWillUnmount') { @@ -208,7 +180,7 @@ export function componentTrigger (component, key, args) { let hasPageInited = false function initComponent (ComponentClass, isPage) { - if (this.$component.__isReady) return + if (!this.$component || this.$component.__isReady) return // ready之后才可以setData, // ready之前,小程序组件初始化时仍然会触发observer,__isReady为否的时候放弃处理observer this.$component.__isReady = true @@ -220,7 +192,12 @@ function initComponent (ComponentClass, isPage) { // 小程序组件ready,但是数据并没有ready,需要通过updateComponent来初始化数据,setData完成之后才是真正意义上的组件ready // 动态组件执行改造函数副本的时,在初始化数据前计算好props if (hasPageInited && !isPage) { - const nextProps = filterProps(ComponentClass.properties, ComponentClass.defaultProps, this.$component.props, this.data) + const compid = this.data.compid + propsManager.observers[compid] = { + component: this.$component, + ComponentClass + } + const nextProps = filterProps(ComponentClass.defaultProps, propsManager.map[compid], this.$component.props) this.$component.props = nextProps } if (hasPageInited || isPage) { @@ -229,10 +206,8 @@ function initComponent (ComponentClass, isPage) { } function createComponent (ComponentClass, isPage) { - let initData = { - _componentProps: 1 - } - const componentProps = filterProps({}, ComponentClass.defaultProps) + let initData = {} + const componentProps = filterProps(ComponentClass.defaultProps) const componentInstance = new ComponentClass(componentProps) componentInstance._constructor && componentInstance._constructor(componentProps) try { @@ -269,9 +244,6 @@ function createComponent (ComponentClass, isPage) { initComponent.apply(this, [ComponentClass, isPage]) }, ready () { - if (!isPage) { - initComponent.apply(this, [ComponentClass, isPage]) - } const component = this.$component if (component['$$refs'] && component['$$refs'].length > 0) { let refs = {} @@ -334,7 +306,7 @@ function createComponent (ComponentClass, isPage) { } } - bindProperties(weappComponentConf, ComponentClass) + bindProperties(weappComponentConf, ComponentClass, isPage) bindBehaviors(weappComponentConf, ComponentClass) bindStaticFns(weappComponentConf, ComponentClass) bindStaticOptions(weappComponentConf, ComponentClass) diff --git a/packages/taro-swan/src/index.js b/packages/taro-swan/src/index.js index 640935127a28..f46726e1fb5d 100644 --- a/packages/taro-swan/src/index.js +++ b/packages/taro-swan/src/index.js @@ -17,7 +17,8 @@ import PureComponent from './pure-component' import createApp from './create-app' import createComponent from './create-component' import initNativeApi from './native-api' -import { getElementById } from './util' +import propsManager from './propsManager' +import { getElementById, genCompid, genLoopCompid } from './util' export const Taro = { Component, @@ -35,7 +36,10 @@ export const Taro = { createComponent, internal_get_original, interceptors, - getElementById + getElementById, + propsManager, + genCompid, + genLoopCompid } export default Taro diff --git a/packages/taro-swan/src/lifecycle.js b/packages/taro-swan/src/lifecycle.js index 717c22e6ce7e..cc19c8c13e6a 100644 --- a/packages/taro-swan/src/lifecycle.js +++ b/packages/taro-swan/src/lifecycle.js @@ -9,7 +9,6 @@ import PropTypes from 'prop-types' const isDEV = typeof process === 'undefined' || !process.env || process.env.NODE_ENV !== 'production' -const privatePropKeyName = '_triggerObserer' export function updateComponent (component) { const { props, __propTypes } = component @@ -63,7 +62,6 @@ function doUpdate (component, prevProps, prevState) { const isRunLoopRef = !component.__mounted data = component._createData(state, props, isRunLoopRef) || data } - let privatePropKeyVal = component.$scope.data[privatePropKeyName] || 0 data = Object.assign({}, props, data) if (component.$usedState && component.$usedState.length) { @@ -85,8 +83,7 @@ function doUpdate (component, prevProps, prevState) { }) data = _data } - // 改变这个私有的props用来触发(observer)子组件的更新 - data[privatePropKeyName] = privatePropKeyVal + 1 + data['$taroCompReady'] = true const __mounted = component.__mounted // 每次 setData 都独立生成一个 callback 数组 @@ -96,7 +93,7 @@ function doUpdate (component, prevProps, prevState) { component._pendingCallbacks = [] } - component.$scope.setData(data, function () { + const cb = function () { if (__mounted) { if (component['$$refs'] && component['$$refs'].length > 0) { component['$$refs'].forEach(ref => { @@ -130,5 +127,10 @@ function doUpdate (component, prevProps, prevState) { typeof cbs[i] === 'function' && cbs[i].call(component) } } - }) + } + if (Object.keys(data).length === 0) { + cb() + } else { + component.$scope.setData(data, cb) + } } diff --git a/packages/taro-swan/src/propsManager.js b/packages/taro-swan/src/propsManager.js new file mode 100644 index 000000000000..d49be93bb069 --- /dev/null +++ b/packages/taro-swan/src/propsManager.js @@ -0,0 +1,43 @@ +import { updateComponent } from './lifecycle' +import { filterProps } from './create-component' + +class Manager { + map = {} + observers = {} + + set (props = {}, compid) { + if (!compid) return + + const { observers } = this + if (!this.map[compid]) { + Object.defineProperty(this.map, compid, { + configurable: true, + get () { + return this[`__${compid}`] + }, + set (props) { + this[`__${compid}`] = props + + const component = observers[compid] && observers[compid].component + const ComponentClass = observers[compid] && observers[compid].ComponentClass + if (!component || !ComponentClass || !component.__isReady) return + + const nextProps = filterProps(ComponentClass.defaultProps, props, component.props) + component.props = nextProps + component._unsafeCallUpdate = true + updateComponent(component) + component._unsafeCallUpdate = false + } + }) + } + this.map[compid] = props + } + + delete (compid) { + delete this.map[compid] + delete this.map[`__${compid}`] + delete this.observers[compid] + } +} + +export default new Manager() diff --git a/packages/taro-swan/src/util.js b/packages/taro-swan/src/util.js index 0c7dfff4499d..5ca6a97263b8 100644 --- a/packages/taro-swan/src/util.js +++ b/packages/taro-swan/src/util.js @@ -232,3 +232,27 @@ export function getElementById (component, id, type) { return null } + +let id = 0 +export function genCompid () { + return String(id++) +} + +export function genLoopCompid (scope, variableName, loops) { + if (scope && scope.data) { + let data = scope.data + for (let len = loops.length, i = 0; i < len; i++) { + const { indexId, name } = loops[i] + if (data[name] && data[name][indexId]) { + data = data[name][indexId] + } else { + return genCompid() + } + } + if (data[variableName]) { + return data[variableName] + } else { + return genCompid() + } + } +} diff --git a/packages/taro-transformer-wx/src/class.ts b/packages/taro-transformer-wx/src/class.ts index 3ce2087816eb..741ab23433aa 100644 --- a/packages/taro-transformer-wx/src/class.ts +++ b/packages/taro-transformer-wx/src/class.ts @@ -70,7 +70,7 @@ function processThisPropsFnMemberProperties ( ] ) ) - } else if (Adapters.weapp !== Adapter.type) { + } else if (Adapters.weapp !== Adapter.type && Adapters.swan !== Adapter.type) { path.replaceWith( t.callExpression( t.memberExpression(t.thisExpression(), t.identifier('__triggerPropsFn')), @@ -414,14 +414,14 @@ class Transformer { t.isIdentifier(expr.callee.property, { name: 'bind' }) ) { if ( - Adapter.type !== Adapters.weapp || + (Adapter.type !== Adapters.weapp && Adapter.type !== Adapters.swan) || (t.isJSXIdentifier(jsx.node.name) && DEFAULT_Component_SET.has(jsx.node.name.name)) ) { self.buildPropsAnonymousFunc(attr, expr, true) } } else if (t.isMemberExpression(expr)) { if ( - Adapter.type !== Adapters.weapp || + (Adapter.type !== Adapters.weapp && Adapter.type !== Adapters.swan) || (t.isJSXIdentifier(jsx.node.name) && DEFAULT_Component_SET.has(jsx.node.name.name)) ) { self.buildPropsAnonymousFunc(attr, expr as any, false) @@ -608,7 +608,7 @@ class Transformer { if (methodName.startsWith('on')) { this.componentProperies.add(`__fn_${methodName}`) } - const method = Adapters.weapp !== Adapter.type ? + const method = (Adapters.weapp !== Adapter.type && Adapters.swan !== Adapter.type) ? t.classMethod('method', t.identifier(funcName), [], t.blockStatement([ t.expressionStatement(t.callExpression( t.memberExpression(t.thisExpression(), t.identifier('__triggerPropsFn')), diff --git a/packages/taro-transformer-wx/src/jsx.ts b/packages/taro-transformer-wx/src/jsx.ts index eafa2411daa7..6dc823dd3c39 100644 --- a/packages/taro-transformer-wx/src/jsx.ts +++ b/packages/taro-transformer-wx/src/jsx.ts @@ -184,7 +184,7 @@ export function parseJSXElement (element: t.JSXElement): string { if (attributes.length) { attributesTrans = attributes.reduce((obj, attr) => { if (t.isJSXSpreadAttribute(attr)) { - if (Adapter.type === Adapters.weapp) return {} + if (Adapter.type === Adapters.weapp || Adapter.type === Adapters.swan) return {} throw codeFrameError(attr.loc, 'JSX 参数暂不支持 ...spread 表达式') } let name = attr.name.name @@ -261,13 +261,13 @@ export function parseJSXElement (element: t.JSXElement): string { obj[isDefaultComponent && !name.includes('-') && !name.includes(':') ? kebabCase(name) : name] = value } } - if (!isDefaultComponent && !specialComponentName.includes(componentName) && Adapter.type !== Adapters.weapp) { + if (!isDefaultComponent && !specialComponentName.includes(componentName) && Adapter.type !== Adapters.weapp && Adapter.type !== Adapters.swan) { obj[TRIGGER_OBSERER] = '{{ _triggerObserer }}' } return obj }, {}) } else if (!isDefaultComponent && !specialComponentName.includes(componentName)) { - if (Adapter.type !== Adapters.weapp) { + if (Adapter.type !== Adapters.weapp && Adapter.type !== Adapters.swan) { attributesTrans[TRIGGER_OBSERER] = '{{ _triggerObserer }}' } } diff --git a/packages/taro-transformer-wx/src/render.ts b/packages/taro-transformer-wx/src/render.ts index b57e61890930..ff9ccf9bc81b 100644 --- a/packages/taro-transformer-wx/src/render.ts +++ b/packages/taro-transformer-wx/src/render.ts @@ -337,7 +337,7 @@ export class RenderParser { } setProperies () { - if ([Adapters.alipay, Adapters.weapp].includes(Adapter.type)) { + if ([Adapters.alipay, Adapters.weapp, Adapters.swan].includes(Adapter.type)) { return } const properties: t.ObjectProperty[] = [] @@ -614,7 +614,7 @@ export class RenderParser { } const blockAttrs: t.JSXAttribute[] = [] - if (Adapter.type === Adapters.weapp && !this.finalReturnElement && process.env.NODE_ENV !== 'test') { + if ((Adapter.type === Adapters.weapp || Adapter.type === Adapters.swan) && !this.finalReturnElement && process.env.NODE_ENV !== 'test') { blockAttrs.push(t.jSXAttribute( t.jSXIdentifier(Adapter.if), t.jSXExpressionContainer(t.jSXIdentifier('$taroCompReady')) @@ -1015,7 +1015,7 @@ export class RenderParser { .node as t.JSXElement const componentName = JSXElement.openingElement.name if ( - Adapter.type === Adapters.weapp && + (Adapter.type === Adapters.weapp || Adapter.type === Adapters.swan) && t.isJSXIdentifier(componentName) && !DEFAULT_Component_SET.has(componentName.name) ) { @@ -1126,7 +1126,7 @@ export class RenderParser { // } if (!generate(value.expression).code.includes('.bind') && ( - Adapter.type !== Adapters.weapp || + (Adapter.type !== Adapters.weapp && Adapter.type !== Adapters.swan) || (t.isJSXIdentifier(componentName) && DEFAULT_Component_SET.has(componentName.name)) ) ) { @@ -1164,7 +1164,8 @@ export class RenderParser { if ( process.env.NODE_ENV !== 'test' && Adapter.type !== Adapters.alipay && - Adapter.type !== Adapters.weapp + Adapter.type !== Adapters.weapp && + Adapter.type !== Adapters.swan ) { const fnName = `__fn_${name.name}` element.attributes = element.attributes.concat([t.jSXAttribute(t.jSXIdentifier(fnName))]) @@ -1441,7 +1442,7 @@ export class RenderParser { }) } this.handleLoopComponents() - Adapter.type === Adapters.weapp && this.handleComponents(renderBody) + if (Adapter.type === Adapters.weapp || Adapter.type === Adapters.swan) this.handleComponents(renderBody) renderBody.traverse(this.visitors) this.setOutputTemplate() this.checkDuplicateName() @@ -1568,7 +1569,7 @@ export class RenderParser { } } - if (Adapter.type === Adapters.weapp) { + if (Adapter.type === Adapters.weapp || Adapter.type === Adapters.swan) { let loops: t.ArrayExpression | null = null blockStatementPath.traverse({ @@ -1944,7 +1945,7 @@ export class RenderParser { const componentProperies = cloneDeep(this.componentProperies) - if ([Adapters.alipay, Adapters.weapp].includes(Adapter.type)) { + if ([Adapters.alipay, Adapters.weapp, Adapters.swan].includes(Adapter.type)) { componentProperies.clear() } else { componentProperies.forEach(s => { @@ -2030,11 +2031,6 @@ export class RenderParser { } const pendingState = t.objectExpression( properties.concat( - Adapter.type === Adapters.swan && transformOptions.isRoot ? t.objectProperty( - t.identifier('_triggerObserer'), - t.booleanLiteral(false) - ) : [] - ).concat( Array.from(this.classComputedState).filter(i => { return !propertyKeys.includes(i) }).map(i => {