import { Container } from '../container' ; import { TYPE_TEXT , TYPE_SUBTREE } from '../constants' ; import { gojiEvents } from '../events' ; import { getNextInstanceId } from '../utils/id' ; import { styleAttrStringify } from '../utils/styleAttrStringify' ; import { findSimplifyId } from '../utils/simplify' ; import { shallowEqual } from '../utils/shallowEqual' ; import { subtreeMaxDepth } from '../components/subtree' ; import { batchedUpdates } from '.' ; export type PuredValue = undefined | null | boolean | number | string | object | Array < any > ; export type Updates = Record < string , PuredValue | ElementNode | TextNode > ; export type InstanceProps = Record < string , any > ; export type ElementNode = { type : string ; props : InstanceProps ; c : Array < ElementNode | TextNode > ; id : number ; sid ? : number ; } ; export type TextNode = { type : string ; text : string ; } ; export enum UpdateType { CREATED , UPDATED , REMOVED } export abstract class BaseInstance { public constructor ( public type , public container ) { this . tag = UpdateType . CREATED ; this . id = getNextInstanceId ( ) ; } public id : number ; protected parent ? : Container | ElementInstance ; public setParent ( parent ) { this . parent = parent ; } public abstract pure ( path , parentTag ? ) ; public abstract registerEventHandler ( ) ; public abstract unregisterEventHandler ( ) ; public tag : UpdateType | undefined ; } const removeChildrenFromProps = ( props ) => { if ( typeof props . children !== 'undefined' ) { const { children : uselessChildren , ... restProps } = props ; return restProps ; } return props ; } ; const shouldUseSubtree = ( ) => process . env . GOJI_TARGET === 'wechat' || process . env . GOJI_TARGET === 'qq' ; export class ElementInstance extends BaseInstance { public constructor ( public override type , public props , public children = [ ] , public override container ) { super ( type , container ) ; this . props = removeChildrenFromProps ( props ) ; } public previous : InstanceProps | undefined ; private previousSimplifyId : number | undefined ; private hasChildrenUpdate = false ; public subtreeDepth ? : number ; public getSubtreeId ( ) { if ( ! shouldUseSubtree ( ) ) { return undefined ; } const wrappedComponentsFromWebpack = process . env . GOJI_WRAPPED_COMPONENTS as any ; if ( wrappedComponentsFromWebpack . includes ( this . type ) ) { return this . id ; } const ancestors = [ ] ; let cursor = this . parent ; let topGojiId ; while ( ! ( cursor instanceof Container ) ) { if ( ! cursor ) { console . warn ( 'Cannot find parent in ElementInstance. This might be an internal error in GojiJS.' ) ; return undefined ; } if ( cursor . type === TYPE_SUBTREE || wrappedComponentsFromWebpack . includes ( cursor . type ) ) { topGojiId = cursor . id ; break ; } ancestors . unshift ( cursor ) ; cursor = cursor . parent ; } const subtreePosition = ancestors . length - ( ancestors . length % subtreeMaxDepth ) - 1 ; const subtreeElement = ancestors [ subtreePosition ] ; if ( ! subtreeElement ) { return topGojiId ; } return subtreeElement . id ; } public pure ( path , parentTag ? ) { const { type , children , id , props } = this ; let { tag } = this ; this . tag = undefined ; if ( this . hasChildrenUpdate || parentTag === UpdateType . CREATED ) { tag = UpdateType . CREATED ; } this . hasChildrenUpdate = false ; const prefix = path ? `${ path }.` : path ; let updates = { } ; const c = [ ] ; children . forEach ( ( child , index ) => { const [ child_ , update ] = child . pure ( `${ prefix }c[${ index }]` , tag ) ; updates = Object . assign ( updates , update ) ; c . push ( child_ ) ; } ) ; const node = { type , props : this . pureProps ( ) , c , id , } ; const simplifyId = findSimplifyId ( type , props ) ; if ( simplifyId !== undefined ) { node . sid = simplifyId ; } if ( this . previous && simplifyId !== this . previousSimplifyId ) { updates [ `${ path }.sid` ] = typeof simplifyId === 'undefined' ? null : simplifyId ; } this . previousSimplifyId = simplifyId ; if ( parentTag === UpdateType . CREATED ) { return [ node , updates ] ; } if ( tag === UpdateType . UPDATED && ! shallowEqual ( this . previous , node . props ) ) { updates [ `${ path }.props` ] = node . props ; } if ( tag === UpdateType . CREATED ) { if ( path ) { updates [ path ] = node ; } else { Object . assign ( updates , node ) ; } } return [ node , updates ] ; } public registerEventHandler ( ) { const { id } = this ; gojiEvents . registerEventHandler ( id , this ) ; } public unregisterEventHandler ( ) { gojiEvents . unregisterEventHandler ( this . id ) ; if ( this . children ) { this . children . forEach ( child => { child . unregisterEventHandler ( ) ; } ) ; } } public triggerEvent ( propKey , data ) { const listener = this . props [ propKey ] ; if ( listener ) { if ( typeof listener !== 'function' ) { if ( process . env . NODE_ENV !== 'production' ) { console . warn ( `Expected \`${ propKey }\` listener to be a function, instead got a value of \`${ typeof listener }\` type.` ) ; } return ; } batchedUpdates ( ( ) => listener ( data ) ) ; } } public updateProps ( newProps ) { this . previous = this . pureProps ( ) ; this . props = removeChildrenFromProps ( newProps ) ; this . tag = UpdateType . UPDATED ; } public removeChild ( child ) { child . unregisterEventHandler ( ) ; const index = this . children . indexOf ( child ) ; this . children . splice ( index , 1 ) ; child . setParent ( undefined ) ; this . hasChildrenUpdate = true ; } public appendChild ( child ) { const existedIndex = this . children . indexOf ( child ) ; if ( existedIndex !== - 1 ) { this . children . splice ( existedIndex , 1 ) ; } this . children . push ( child ) ; child . setParent ( this ) ; this . hasChildrenUpdate = true ; } public insertBefore ( child , beforeChild ) { const existedIndex = this . children . indexOf ( child ) ; if ( existedIndex !== - 1 ) { this . children . splice ( existedIndex , 1 ) ; } const index = this . children . indexOf ( beforeChild ) ; this . children . splice ( index , 0 , child ) ; child . setParent ( this ) ; this . hasChildrenUpdate = true ; } public clear ( ) { for ( const child of this . children ) { this . removeChild ( child ) ; } } private pureProps ( ) { const { props } = this ; const result = { } ; for ( const propKey of Object . keys ( props ) ) { if ( typeof props [ propKey ] === 'function' ) { } else if ( propKey === 'style' || propKey . endsWith ( 'Style' ) ) { result [ propKey ] = styleAttrStringify ( props [ propKey ] ) ; } else if ( propKey === 'children' ) { } else if ( propKey === 'className' ) { result [ propKey ] = props . className ; if ( process . env . GOJI_TARGET === 'baidu' && ! result [ propKey ] ) { result [ propKey ] = 'BD_FIX_CLS' ; } } else { result [ propKey ] = props [ propKey ] ; } } return result ; } } export class TextInstance extends BaseInstance { public constructor ( public text , public override container ) { super ( TYPE_TEXT , container ) ; } public previous : string | undefined ; public registerEventHandler ( ) { } public unregisterEventHandler ( ) { } public updateText ( newText ) { this . previous = this . text ; this . text = newText ; this . tag = UpdateType . UPDATED ; } public pure ( path , parentTag ? ) { const { id , type , text , tag } = this ; this . tag = undefined ; const node = { id , type , text , } ; const updates = { } ; if ( parentTag === UpdateType . CREATED ) { return [ node , updates ] ; } if ( tag === UpdateType . UPDATED && this . text !== this . previous ) { updates [ `${ path }.text` ] = node . text ; } if ( tag === UpdateType . CREATED ) { updates [ path ] = node ; } return [ node , updates ] ; } }