Skip to content

Commit

Permalink
feat(alita-core wx-react): 支持 onLayout
Browse files Browse the repository at this point in the history
affects: @areslabs/alita-core, @areslabs/wx-react
  • Loading branch information
ykforerlang committed Apr 18, 2020
1 parent b04a83b commit 6f9cd2d
Show file tree
Hide file tree
Showing 11 changed files with 286 additions and 27 deletions.
1 change: 0 additions & 1 deletion packages/alita-core/src/precheck/checkJSX.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ const supportRNAPI = new Set([
])

const notSupportCommonAttris = new Set([
'onLayout',
'onStartShouldSetResponder',
'onMoveShouldSetResponder',
'onResponderGrant',
Expand Down
7 changes: 6 additions & 1 deletion packages/alita-core/src/tran/geneAllTemplate.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import errorLogTraverse from '../util/ErrorLogTraverse'

import * as t from '@babel/types'
import {decTemlate, isJSXChild, isChildCompChild, isChildComp, isRenderReturn} from '../util/uast';
import {decTemlate, isJSXChild, isChildCompChild, isChildComp, isRenderReturn, elementAddClass} from '../util/uast';
import { isEventProp } from '../util/util';
import {wxBaseComp} from "../constants";
import {allBaseComp} from "../util/getAndStorecompInfos";
Expand Down Expand Up @@ -190,6 +190,11 @@ export default function(ast, info) {
jsxOp.attributes.push(
t.jsxAttribute(t.jsxIdentifier('style'), t.stringLiteral(`{{t.s(${diuuKey}style)}}`))
)

if (info.isPageComp) {
// 页面组件的外层节点,添加 统一类名:"page-container", onLayout和以后会使用
elementAddClass(jsxOp, 'page-container')
}
}
}

Expand Down
3 changes: 3 additions & 0 deletions packages/alita-core/src/tran/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import RNCompHandler from './RNCompHandler'
import cptCompHandler from './cptCompHandler'
import literalTemplate from './literalTemplate'
import classNameHandler from './classNameHandler'
import onLayoutHandler from './onLayoutHandler'


import {setRFModuleInfo} from '../util/cacheModuleInfos'
Expand Down Expand Up @@ -63,6 +64,8 @@ export default function (ast, filepath, isFuncComp, isPageComp, webpackContext)

ast = classNameHandler(ast, info)

ast = onLayoutHandler(ast, info)

ast = geneAllTemplate(ast, info)

// 设置React 组件信息
Expand Down
75 changes: 75 additions & 0 deletions packages/alita-core/src/tran/onLayoutHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import * as t from '@babel/types'
import errorLogTraverse from "../util/ErrorLogTraverse"
import {elementAddClass} from '../util/uast'


/**
* Copyright (c) Areslabs.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/


/**
* 处理onLayout
*
* <View onLayout={() => {}}/> 转化为: <View class="m-lt" data-parent="{{parentDiuu}}"/>
*
* @param ast
* @param info
* @returns {any}
*/
export default function (ast, info) {

errorLogTraverse(ast, {
exit: path => {
if (path.type === 'JSXOpeningElement') {

const attris = path.node.attributes || []

let hasLayout = false
let hasDataDiuu = false
let diuuValue = ""
path.node.attributes = attris.filter(attr => {

if (attr.type === 'JSXAttribute' && attr.name.name === 'onLayout') {
hasLayout = true
return false
}

if (attr.type === 'JSXAttribute' && attr.name.name === 'data-diuu') {
hasDataDiuu = true
}

if (attr.type === 'JSXAttribute' && attr.name.name === 'diuu') {
diuuValue = attr.value.value
}

return true
})

if (!hasLayout) {
// 没有onLayout属性
return
}

elementAddClass(path.node, 'm-lt')


path.node.attributes.push(
t.jsxAttribute(t.jsxIdentifier('data-parent'), t.stringLiteral(`{{parentDiuu}}`)),
)

if (!hasDataDiuu) {
path.node.attributes.push(
t.jsxAttribute(t.jsxIdentifier('data-diuu'), t.stringLiteral(`{{${diuuValue}}}`)),
)
}
}
}
})
return ast
}

25 changes: 25 additions & 0 deletions packages/alita-core/src/util/uast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,5 +269,30 @@ export function getOriginal(path) {
return ''
}

/**
* 给JSXOpeningElement 添加class类名,
*
* 1. <view/> ---> <view class="xx"/>
* 2. <View class="aa"/> ---> <view class="aa xx"/>
*
* @param jsxOp
* @param className
*/
export function elementAddClass(jsxOp, className) {
let hasClassAttr = false
jsxOp.attributes.forEach(attr => {
if (attr.type === 'JSXAttribute' && attr.name.name === 'class') {
hasClassAttr = true
attr.value.value = `${attr.value.value} ${className}`
}
})

if (!hasClassAttr) {
jsxOp.attributes.push(
t.jsxAttribute(t.jsxIdentifier('class'), t.stringLiteral(className))
)
}
}



13 changes: 7 additions & 6 deletions packages/wx-react/src/UpdateStrategy.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*
*/

import {getCurrentContext, invokeWillUnmount} from './util'
import {getCurrentContext, invokeWillUnmount, getMpCurrentPage} from './util'
import createElement from './createElement'
import {mpRoot, STYLE_EFFECT, INIT_EFFECT, UPDATE_EFFECT, STYLE_WXINIT_EFFECT} from './constants'
import render, {renderNextValidComps} from './render'
Expand All @@ -15,6 +15,8 @@ import instanceManager from "./InstanceManager";
import getChangePath from './getChangePath'
import {HocComponent} from './AllComponent'

import {invokeAllLayout} from './rnLayout'

let inRenderPhase = false
let shouldMerge = false

Expand Down Expand Up @@ -122,6 +124,7 @@ export function renderApp(appClass) {
*/
function commitWork(firstEffect, lastEffect) {
if (!firstEffect) {
//TODO effect应该参考React的形式,把生命周期也算上, 现在这种直接返回,会漏掉生命周期的执行
// 没有产生任何更新
return
}
Expand Down Expand Up @@ -205,16 +208,14 @@ function commitWork(firstEffect, lastEffect) {
currentPage.setData({}, () => {
unstable_batchedUpdates(() => {
commitLifeCycles(lastEffect)

// 任何时候渲染结束都需要检查,包括初次渲染
invokeAllLayout()
})
})
})
}

function getMpCurrentPage() {
const pages = getCurrentPages()
return pages[pages.length - 1]
}


function commitLifeCycles(lastEffect) {
let effect = lastEffect
Expand Down
21 changes: 6 additions & 15 deletions packages/wx-react/src/WxNormalComp.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
*
*/

import {renderPage, createElement, HocComponent, unstable_batchedUpdates, instanceManager} from "./index"
import {renderPage, createElement, unstable_batchedUpdates, instanceManager} from "./index"
import geneUUID from "./geneUUID"
import {cleanPageComp} from './util'
import {cleanPageComp, getEventHandler} from './util'
import {cleanPageLayoutElements} from './rnLayout'


export default function (compPath) {
Expand All @@ -32,20 +33,9 @@ export default function (compPath) {
methods: {
// 基本组件回调函数处理
eventHandler(e) {
const eventKey = e.currentTarget.dataset.diuu + e.type
let compInst = instanceManager.getCompInstByUUID(this.data.diuu)
while (compInst && compInst instanceof HocComponent) {
compInst = compInst._c[0]
}
let eh = compInst.__eventHanderMap[eventKey]

// map地图组件的regionchange事件 type为begin/end
if (!eh && (e.type === 'begin' || e.type === 'end')) {
eh = compInst.__eventHanderMap[e.currentTarget.dataset.diuu + 'regionchange']
}

const eh = getEventHandler(this.data.diuu, e.currentTarget.dataset.diuu, e.type)
if (eh) {
//TODO event参数
//TODO 适配 event参数
eh(e)
}
}
Expand Down Expand Up @@ -117,6 +107,7 @@ export default function (compPath) {
compInst.componentWillUnfocus && compInst.componentWillUnfocus()

cleanPageComp(compInst)
cleanPageLayoutElements(this.data.diuu)
}
}
return o
Expand Down
4 changes: 0 additions & 4 deletions packages/wx-react/src/createElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,6 @@ export default function createElement(comp, props, ...args) {
// __source, __self 由metro-react-native-babel-preset在编译阶段添加,需要移除
const {animation, ref, key, tempName, tempVnode, CPTVnode, datakey, diuu, __source, __self, ...rprops} = props || {}

// 通用的不支持属性
if (props.onLayout) {
console.warn('小程序不支持onLayout属性')
}
if (typeof props.ref === 'string') {
console.warn('ref只支持函数形式,不支持字符串')
}
Expand Down
5 changes: 5 additions & 0 deletions packages/wx-react/src/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -887,6 +887,11 @@ function updateBaseView(vnode, parentInst, parentContext, data, oldData, dataPat

eventProps.forEach(k => {
const v = props[k]

if (k === 'onLayout') { // onLayout 事件,需要设置parentDiuu, 方便invokeAllLayout里面的方法寻找
data.parentDiuu = parentInst.__diuu__
}

parentInst.__eventHanderMap[`${diuu}${getWxEventType(k)}`] = reactEnvWrapper(v)
})
}
Expand Down
123 changes: 123 additions & 0 deletions packages/wx-react/src/rnLayout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/**
* Copyright (c) Areslabs.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* onLayout: Invoked on mount and layout changes with
*
* class A extends Component {
* state = {
* height: 100
* }
*
* render() {
* <View style={{height: this.state.height}}>
* <Text
* onPress={() => {
* this.setState({
* height: this.state.height + 100
* })
* }}
* >HW</Text>
* </View>
* }
* }
*
* class App extends Component {
* render() {
* return (
* <View>
* <View onLayout={() => {console.log('one'}}/>
* <A/>
* <View onLayout={() => {console.log('two'}}/>
* </View>
*
* )
* }
* }
*
* 根据onLayout的定义,首先在初始渲染的时候,需要打印`one`, `two`
*
* 当不断的点击A组件的时候,由于A组件高度的变化,将会导致 two不断的被打印
*
* 在小程序上如何实现类似onLayout的机制呢?小程序无法在渲染之前获取布局,但是在渲染之后是有机会获取的。
* 我们在每一次渲染之后,都对含有onLayout标签的节点,进行一次布局检测,发现布局变化就调用onLayout 回调(感谢canfoo[https://github.com/canfoo]的启发性工作)
*
* 具体的,我们通过微信小程序API wx.createSelectorQuery,获取元素的布局。 为了减少调用createSelectorQuery次数(是个异步操作),利用跨自定义组件的后代选择器 特性使用如下的选择元素代码:
* wx.createSelectorQuery().selectAll('.page-container, .page-container >>> .m-lt')
*
* 此外:如上面的例子所示,一个更新可能导致n(n >= 0)个毫无父子关系节点的布局变化,故会同时发生多个节点调用onLayout,而onLayout方法回调之中通常存在setState调用,
* 我们需要合并这些setState,故对onLayout的调用,需要包裹在`unstable_batchedUpdates`中
*
*/

import {unstable_batchedUpdates} from './UpdateStrategy'
import {getEventHandler, getMpCurrentPage} from './util'


const pageLayoutElements = {}

/**
* 当页面销毁的时候,需要把缓存的数据清空
* @param pageDiuu
*/
export function cleanPageLayoutElements(pageDiuu) {
delete pageLayoutElements[pageDiuu]
}

export function invokeAllLayout() {
// 我们只检查当前页面所有的onLayout节点布局变化
wx.createSelectorQuery()
.selectAll('.m-lt, .page-container >>> .m-lt') // 通过`跨自定义组件的后代选择器` 减少createSelectorQuery的次数
.boundingClientRect()
.exec(res => {
const allItems = res[0]
if (allItems.length === 0) return

// 批量更新,减少实际的更新次数
unstable_batchedUpdates(() => {
const curPage = getMpCurrentPage()
const pageDiuu = curPage.data.diuu

const oldPageElements = pageLayoutElements[pageDiuu]
const newPageElements = {}

allItems.forEach(item => {
const diuu = item.dataset.diuu

if (oldPageElements && oldPageElements[diuu] && layoutEqual(oldPageElements[diuu], item)) {
// 布局没有变化
newPageElements[diuu] = oldPageElements[diuu]
return
}

newPageElements[diuu] = {
x: item.left,
y: item.top,
width: item.width,
height: item.height
}

const method = getEventHandler(item.dataset.parent, diuu, 'onLayout')
if (method) {
method({
nativeEvent: {
layout: newPageElements[diuu]
}
})
}
})
pageLayoutElements[pageDiuu] = newPageElements
})
})
}

function layoutEqual(layout1, layout2) {
return (layout1.x === layout2.left
&& layout1.y === layout2.top
&& layout1.width === layout2.width
&& layout1.height === layout2.height
)
}
Loading

0 comments on commit 6f9cd2d

Please sign in to comment.