Skip to content

Commit

Permalink
feat(transformer): 支持在循环中使用 ref
Browse files Browse the repository at this point in the history
  • Loading branch information
yuche committed Nov 27, 2018
1 parent 005e8e2 commit 292c945
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 12 deletions.
39 changes: 31 additions & 8 deletions packages/taro-transformer-wx/src/class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import {
pathResolver,
createRandomLetters,
isContainJSXElement,
getSlotName
getSlotName,
isArrayMapCallExpression
} from './utils'
import { DEFAULT_Component_SET } from './constant'
import { kebabCase, uniqueId } from 'lodash'
import { RenderParser } from './render'
import { findJSXAttrByName } from './jsx'
import { Adapters, Adapter } from './adapter'
import { LoopRef } from './interface'
import generate from 'babel-generator'

type ClassMethodsMap = Map<string, NodePath<t.ClassMethod | t.ClassProperty>>
Expand Down Expand Up @@ -122,6 +124,7 @@ class Transformer {
private componentProperies: Set<string>
private sourcePath: string
private refs: Ref[] = []
private loopRefs: Map<t.JSXElement, LoopRef> = new Map()

constructor (
path: NodePath<t.ClassDeclaration>,
Expand Down Expand Up @@ -198,6 +201,7 @@ class Transformer {
if (!t.isJSXIdentifier(jsx.name)) {
return
}
const isLoop = path.findParent(p => isArrayMapCallExpression(p))
const componentName = jsx.name.name
const refAttr = findJSXAttrByName(attrs, 'ref')
if (!refAttr) {
Expand All @@ -206,7 +210,10 @@ class Transformer {
const idAttr = findJSXAttrByName(attrs, 'id')
let id = createRandomLetters(5)
if (!idAttr) {
attrs.push(t.jSXAttribute(t.jSXIdentifier('id'), t.stringLiteral(id)))
const expr = t.jSXExpressionContainer(
t.binaryExpression('+', t.stringLiteral(id), t.identifier('index'))
)
attrs.push(t.jSXAttribute(t.jSXIdentifier('id'), expr))
} else {
const idValue = idAttr.value
if (t.isStringLiteral(idValue)) {
Expand All @@ -216,18 +223,33 @@ class Transformer {
}
}
if (t.isStringLiteral(refAttr.value)) {
if (isLoop) {
throw codeFrameError(refAttr, '循环中的 ref 只能使用函数。')
}
this.createStringRef(componentName, id, refAttr.value.value)
}
if (t.isJSXExpressionContainer(refAttr.value)) {
const expr = refAttr.value.expression
if (t.isStringLiteral(expr)) {
if (isLoop) {
throw codeFrameError(refAttr, '循环中的 ref 只能使用函数。')
}
this.createStringRef(componentName, id, expr.value)
} else if (t.isArrowFunctionExpression(expr) || t.isMemberExpression(expr)) {
this.refs.push({
type: DEFAULT_Component_SET.has(componentName) ? 'dom' : 'component',
id,
fn: expr
})
const type = DEFAULT_Component_SET.has(componentName) ? 'dom' : 'component'
if (isLoop) {
this.loopRefs.set(path.parentPath.node as t.JSXElement, {
id,
fn: expr,
type
})
} else {
this.refs.push({
type,
id,
fn: expr
})
}
} else {
throw codeFrameError(refAttr, 'ref 仅支持传入字符串、匿名箭头函数和 class 中已声明的函数')
}
Expand Down Expand Up @@ -610,7 +632,8 @@ class Transformer {
this.loopStateName,
this.customComponentNames,
this.customComponentData,
this.componentProperies
this.componentProperies,
this.loopRefs
).outputTemplate
} else {
throw codeFrameError(this.classPath.node.loc, '没有定义 render 方法')
Expand Down
2 changes: 2 additions & 0 deletions packages/taro-transformer-wx/src/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ export const INTERNAL_INLINE_STYLE = 'internal_inline_style'

export const INTERNAL_GET_ORIGNAL = 'internal_get_original'

export const GEL_ELEMENT_BY_ID = 'getElementById'

export const LOOP_STATE = '$loopState'

export let LOOP_ORIGINAL = '$original'
Expand Down
5 changes: 3 additions & 2 deletions packages/taro-transformer-wx/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import * as ts from 'typescript'
import { Transformer } from './class'
import { setting, findFirstIdentifierFromMemberExpression, isContainJSXElement, codeFrameError } from './utils'
import * as t from 'babel-types'
import { DEFAULT_Component_SET, INTERNAL_SAFE_GET, TARO_PACKAGE_NAME, REDUX_PACKAGE_NAME, MOBX_PACKAGE_NAME, IMAGE_COMPONENTS, INTERNAL_INLINE_STYLE, THIRD_PARTY_COMPONENTS, INTERNAL_GET_ORIGNAL, setLoopOriginal } from './constant'
import { DEFAULT_Component_SET, INTERNAL_SAFE_GET, TARO_PACKAGE_NAME, REDUX_PACKAGE_NAME, MOBX_PACKAGE_NAME, IMAGE_COMPONENTS, INTERNAL_INLINE_STYLE, THIRD_PARTY_COMPONENTS, INTERNAL_GET_ORIGNAL, setLoopOriginal, GEL_ELEMENT_BY_ID } from './constant'
import { Adapters, setAdapter, Adapter } from './adapter'
import { Options, setTransformOptions } from './options'
const template = require('babel-template')
Expand Down Expand Up @@ -430,7 +430,8 @@ export default function transform (options: Options): TransformResult {
path.node.specifiers.push(
t.importSpecifier(t.identifier(INTERNAL_SAFE_GET), t.identifier(INTERNAL_SAFE_GET)),
t.importSpecifier(t.identifier(INTERNAL_GET_ORIGNAL), t.identifier(INTERNAL_GET_ORIGNAL)),
t.importSpecifier(t.identifier(INTERNAL_INLINE_STYLE), t.identifier(INTERNAL_INLINE_STYLE))
t.importSpecifier(t.identifier(INTERNAL_INLINE_STYLE), t.identifier(INTERNAL_INLINE_STYLE)),
t.importSpecifier(t.identifier(GEL_ELEMENT_BY_ID), t.identifier(GEL_ELEMENT_BY_ID))
)
}
if (
Expand Down
8 changes: 8 additions & 0 deletions packages/taro-transformer-wx/src/interface.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { NodePath } from 'babel-traverse'
import * as t from 'babel-types'

interface LoopRef {
id: string,
fn: t.FunctionExpression | t.ArrowFunctionExpression | t.MemberExpression,
type: 'component' | 'dom',
}
33 changes: 31 additions & 2 deletions packages/taro-transformer-wx/src/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ import {
buildBlockElement,
parseJSXElement
} from './jsx'
import { DEFAULT_Component_SET, MAP_CALL_ITERATOR, LOOP_STATE, LOOP_CALLEE, THIRD_PARTY_COMPONENTS, LOOP_ORIGINAL, INTERNAL_GET_ORIGNAL } from './constant'
import { DEFAULT_Component_SET, MAP_CALL_ITERATOR, LOOP_STATE, LOOP_CALLEE, THIRD_PARTY_COMPONENTS, LOOP_ORIGINAL, INTERNAL_GET_ORIGNAL, GEL_ELEMENT_BY_ID } from './constant'
import { Adapter, Adapters } from './adapter'
import { transformOptions } from './options'
import generate from 'babel-generator'
import { LoopRef } from './interface'
const template = require('babel-template')

type ClassMethodsMap = Map<string, NodePath<t.ClassMethod | t.ClassProperty>>
Expand Down Expand Up @@ -123,6 +124,7 @@ export class RenderParser {
private loopStateName: Map<NodePath<t.CallExpression>, string>
private customComponentData: Array<t.ObjectProperty>
private componentProperies: Set<string>
private loopRefs: Map<t.JSXElement, LoopRef>

private finalReturnElement!: t.JSXElement

Expand Down Expand Up @@ -986,7 +988,8 @@ export class RenderParser {
loopStateName: Map<NodePath<t.CallExpression>, string>,
customComponentNames: Set<string>,
customComponentData: Array<t.ObjectProperty>,
componentProperies: Set<string>
componentProperies: Set<string>,
loopRefs: Map<t.JSXElement, LoopRef>
) {
this.renderPath = renderPath
this.methods = methods
Expand All @@ -997,6 +1000,7 @@ export class RenderParser {
this.customComponentNames = customComponentNames
this.customComponentData = customComponentData
this.componentProperies = componentProperies
this.loopRefs = loopRefs
const renderBody = renderPath.get('body')
this.renderScope = renderBody.scope

Expand Down Expand Up @@ -1054,6 +1058,7 @@ export class RenderParser {
handleLoopComponents = () => {
const loopArrayId = incrementId()
const replaceQueue: Function[] = []
let hasLoopRef = false
this.loopComponents.forEach((component, callee) => {
if (!callee.isCallExpression()) {
return
Expand All @@ -1067,6 +1072,26 @@ export class RenderParser {
}
const blockStatementPath = component.findParent(p => p.isBlockStatement()) as NodePath<t.BlockStatement>
const body = blockStatementPath.node.body
if (this.loopRefs.has(component.node)) {
hasLoopRef = true
const ref = this.loopRefs.get(component.node)!
const id = t.binaryExpression('+', t.stringLiteral(ref.id), t.identifier('index'))
const refDeclName = '__ref'
const args: any[] = [
t.identifier('__scope'),
id
]
if (ref.type === 'component') {
args.push(t.stringLiteral('component'))
}
const refDecl = buildConstVariableDeclaration(refDeclName,
t.callExpression(t.identifier(GEL_ELEMENT_BY_ID), args)
)
const callRefFunc = t.expressionStatement(
t.callExpression(ref.fn, [t.identifier(refDeclName)])
)
body.push(refDecl, callRefFunc)
}
let stateToBeAssign = new Set<string>(
difference(
Object.keys(blockStatementPath.scope.getAllBindings()),
Expand Down Expand Up @@ -1287,6 +1312,10 @@ export class RenderParser {
)
})
})
if (hasLoopRef) {
const scopeDecl = template('const __scope = this.$scope')()
this.renderPath.node.body.body.push(scopeDecl)
}
replaceQueue.forEach(func => func())
}

Expand Down

0 comments on commit 292c945

Please sign in to comment.