首页 > 编程语言 >Taro源码 Taro DOM

Taro源码 Taro DOM

时间:2023-06-09 18:06:38浏览次数:60  
标签:Taro DOM newChild TaroNode public 源码 节点

浏览器的事件系统是 Web 应用程序中必不可少的部分,可以使开发人员通过编写事件监听器来响应用户操作、处理网络请求等实现交互性和动态性的 Web 应用程序。

Taro作为支持一码多端的跨端框架,支持支付宝小程序、微信小程序、h5等多个平台。为了抹平端上差异,同时支持小程序平台也能够使用dom和bom api,Taro提供了一套精简版DOM和BOM的封装,称之为Taro DOM、Taro BOM。

本文在介绍Taro DOM时,通过和浏览器类比的视角,分析Taro DOM在构思和实现上与浏览器DOM的相似之处,同时总结一些Taro自身特有的性质,来更好的理解框架本身。

为了便于阅读,读者可以查阅下表,mark一下源码中的命名细节。

name

Taro 类

浏览器

节点

TaroNode

Node

事件目标

TaroEventTarget

EventTarget

元素节点

TaroElement

Elemen


Taro DOM目录结构

Taro源码 Taro DOM_Taro

应该按照怎样的逻辑顺序看懂这些文件呢?还要从DOM树本身说起。。。

TaroNode

浏览器中支持DOM结构的最小单位是节点,因此,在理解Taro DOM在实现时,可以先从node.ts入手。篇幅原因,无法详细展开每个方法的代码,故先展示TaroNode类的结构

class TaroNode extends TaroEventTarget{
  
  public uid: string
  public sid: string
  public nodeType: NodeType
  public nodeName: string
  public parentNode: TaroNode | null = null
  public childNodes: TaroNode[] = []
  
  public constructor () {
    super()
    this.uid = '_' + nodeId() // dom 节点 id,开发者可修改
    this.sid = this.uid // dom 节点全局唯一 id,不可被修改
    eventSource.set(this.sid, this)
  }
  
	hydrate(node:TaroNode){} //用于将节点进行序列化,方便进行后续的渲染。
  updateChildNodes (isClean?: boolean){} //更新子节点,将子节点序列化后加入更新队列中,方便进行后续的渲染。
  get _root (): TaroRootElement | null{}  //获取节点所在的根节点。
  findIndex (refChild: TaroNode): number{} //查找子节点在父节点中的位置。
  get nextSibling (): TaroNode | null{} //获取节点的下一个兄弟节点。
  get previousSibling (): TaroNode | null{} //获取节点的上一个兄弟节点。
  get parentElement (): TaroElement | null{} //获取节点的父元素节点。
  get firstChild (): TaroNode | null{} //获取节点的第一个子节点。
  get lastChild (): TaroNode | null{} //获取节点的最后一个子节点。
  set textContent (text: string){} //设置节点的文本内容。
  insertBefore<T extends TaroNode> (newChild: T, refChild?: TaroNode | null,
                                    isReplace?: boolean): T{}//在当前节点前插入一个新节点。
  appendChild (newChild: TaroNode){} //在当前节点末尾添加一个新节点。
  replaceChild (newChild: TaroNode, oldChild: TaroNode){} //替换当前节点中的某一个子节点。
  removeChild<T extends TaroNode> (child: T, options: RemoveChildOptions = {}): T{} //移除当前节点中的某一个子节点。
  remove (options?: RemoveChildOptions){} //移除当前节点。
  hasChildNodes () {} //判断当前节点是否有子节点。
  public enqueueUpdate (payload: UpdatePayload){} //将节点序列化后加入更新队列中,方便进行后续的渲染。
  public get ownerDocument (): TaroDocument{} //获取节点所在的文档对象。
  
}

TaroNode类所实现的方法包括了元素节点获取相邻节点、父子节点、增删改查等方法,与浏览器DOM十分类似,很多方法的实现逻辑和业务逻辑简单易懂,本文不再赘述。其中insertBefore方法有着构建运行时Taro DOM树的作用,实现思路也值得细细探讨。

function insertBefore<T extends TaroNode> (newChild: T, refChild?: TaroNode | null, isReplace?: boolean): T {
    if (newChild.nodeName === DOCUMENT_FRAGMENT) {
      newChild.childNodes.reduceRight((previousValue, currentValue) => {
        this.insertBefore(currentValue, previousValue)
        return currentValue
      }, refChild)
      return newChild
    }

    // Parent release newChild
    //   - cleanRef: false (No need to clean eventSource, because newChild is about to be inserted)
    //   - update: true (Need to update parent.childNodes, because parent.childNodes is reordered)
    newChild.remove({ cleanRef: false })

    // Data structure
    newChild.parentNode = this
    if (refChild) {
      // insertBefore & replaceChild
      const index = this.findIndex(refChild)
      this.childNodes.splice(index, 0, newChild)
    } else {
      // appendChild
      this.childNodes.push(newChild)
    }

    // Serialization
    if (this._root) {
      if (!refChild) {
        // appendChild
        const isOnlyChild = this.childNodes.length === 1
        if (isOnlyChild) {
          this.updateChildNodes()
        } else {
          this.enqueueUpdate({
            path: newChild._path,
            value: this.hydrate(newChild)
          })
        }
      } else if (isReplace) {
        // replaceChild
        this.enqueueUpdate({
          path: newChild._path,
          value: this.hydrate(newChild)
        })
      } else {
        // insertBefore
        this.updateChildNodes()
      }
    }
  
    return newChild
  }

插入节点的方法在实现时考虑三个case,其中第一个case DOCUMENT_FRAGMENT(文档碎片,定义与浏览器中类似),代表插入节点无父节点,此时利用reduceRight方法从最右子节点遍历递归插入整个文档碎片,根据传入的参考节点(refChild)以及是否需要替换节点(isReplace)来确定新节点的插入位置。如果参考节点为空,则表示将新节点追加到当前节点的子节点列表末尾;否则根据参考节点的位置将新节点插入到相应位置(在参考节点之前)。如果需要替换旧节点,则需要先找到旧节点的位置并将其从子节点列表中删除,然后再将新节点插入到相应位置。

TaroEventTarget

与浏览器DOM类似,TaroNode继承于TaroEventTarget,意味着每个节点都具有监听事件的能力。TaroEventTarget类有以下三个方法,addEventListener和removeEventListener控制监听事件的增删,与浏览器DOM不同的是,Taro在实现判断是否有监听事件时没有围绕监听事件作过多方法定义(例如浏览器dom中的 getEventListeners ),仅通过isAnyEventBinded方法通过判断__handlers中各个键的EventHandler长度是否都大于零来判断该元素是否存在监听事件。

class TaroEventTarget{
	public __handlers: Record<string, EventHandler[]> = {}
  
  //增加监听事件
	public addEventListener (type: string, handler: EventHandler, 
                           options?: boolean | AddEventListenerOptions) {}
  
  public removeEventListener (type: string, handler: EventHandler) {} //移除监听事件
  
  public isAnyEventBinded (): boolean {   //判断是否有监听事件
  	const handlers = this.__handlers
    const isAnyEventBinded = Object.keys(handlers).find(key => handlers[key].length)
    return Boolean(isAnyEventBinded)
  }   
}

TaroElement

继承于TaroNode,元素节点TaroElement负责承担html结构的属性存储。在属性上除了继承TaroNode节点应用于Taro DOM树的构造信息(例如,parent、child、uid等),还定义了style、props、tagName等html标签信息。

tagName: string
props: Record<string, any> = {}
style: Style
dataset: Record<string, unknown> = EMPTY_OBJ
innerHTML: string

可以看到style属性的特殊性,为了达到小程序和h5之间的一致性,Taro定义了独特的Style类,其中包含了一些方法,例如 setProperty、removeProperty、getPropertyValue 等,用于设置、获取和删除样式属性。Style 类的实例会被绑定到具体的元素节点上,并且在元素节点的属性值发生改变时,会自动更新相应的样式属性。

Style还定义了一些辅助方法,例如 enqueueUpdate、recordCss 等。enqueueUpdate 用于将样式属性变化的更新操作添加到元素节点的更新队列中,以便在下一次更新时更新样式属性;recordCss 则用于记录元素节点的样式属性变化,以便在下一次更新时进行比较,判断是否需要更新样式属性。initStyle 方法用于初始化样式属性,将样式属性定义为对应的 get 和 set 方法,以便在元素节点的属性值发生改变时,能够自动更新相应的样式属性。isCssVariable 方法用于判断样式属性是否为 CSS 变量。如果是 CSS 变量,则用 setCssVariables 方法将其定义为可枚举的样式属性。

标签:Taro,DOM,newChild,TaroNode,public,源码,节点
From: https://blog.51cto.com/u_16116793/6449647

相关文章

  • DataTable DataRow String Tips...
       与datatable奋战了一天,记录一下。。。      1.查看得到的datatable是否为空datatable.Rows.Count   2.查看得到的DataRow[]是否为空,可用DataRow.Length   3.DataTable在进行select的时候,默认是CaseSensitive为false   4.......
  • 直播源码开发,使用动画设置ProgressBar进度
    直播源码开发,使用动画设置ProgressBar进度布局文件: <?xmlversion="1.0"encoding="utf-8"?><LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"  xmlns:app="http://schemas.android.com/apk/res-auto"  ......
  • taro使用taro3-echarts-react报错,图表不出来
    问题taro版本3.6.2taro3-echarts-react版本1.0.4在taro小程序开发中,使用taro3-echarts-react时,echarts版本需要选择4.9.0,但是我们往往需要使用高版本的echarts,但是引入echarts.js文件后,控制台报错:el.addEventListenerisnotafunction或者t.addEventListenerisnotaf......
  • 直播商城源码,点击复制功能和长按复制功能
    直播商城源码,点击复制功能和长按复制功能一、点击复制+长按复制效果 index.wxml 注意:data-title=我是被复制的文本内容<textselectable="true">我是被复制的文本内容/text><viewbindtap="copyBtn"data-title="{{title}}">复制</view>index.js  //不使用封装方法  ......
  • 直播平台搭建源码,LintCode 大小写转换
    直播平台搭建源码,LintCode大小写转换一、直接利用C++中的tolower(大写转小写)函数。 classSolution{public:  /**   *@paramstr:theinputstring   *@return:Thelowercasestring   */  stringtoLowerCase(string&str){    //Wri......
  • UE5 源码编译开发注意事项
    工程清理编译 针对UE5源码版,项目级别的清理步骤:(1)删除项目根目录中:Binaries、Build、DerivedDataCache和Intermediate文件夹,以及sln文件。(2)通过.uproject重新生成VisualStudio项目文件,此步骤会重新生成sln文件和Intermediate文件夹。(3)打开sln项目解决方案,将自建项......
  • 0012.有监督学习之随机森林(Random Forest)
    一、概述随机森林:最为新兴起的、高度灵活的一种机器学习算法,随机森林(RandomForset,简称RF)拥有广泛的应用前景,从市场销售到医疗保健保险,既可以用来做市场销售模拟的建模,统计客户来源,保留和流失,也可用预测疾病的风险和病患者的易感性。随机森林算法是一种重要的基于bagging的集成......
  • [spring-boot] 源码解读#org.springframework.boot.ApplicationArguments [转发]
    1ApplicationArguments概述1.1简述org.springframework.boot.ApplicationArguments接口提供对用于运行org.springframework.boot.SpringApplication的参数访问。ApplicationArguments接口只有一个实现类DefaultApplicationArguments。1.2使用示例示例1@SpringBootA......
  • 海外直播源码技术文字聊天功能的配置
    今天我要分享的知识和我们日常生活中常用到的一个东西有关,它就是文字。目前已知发现我国最早的文字是距今5000多年的甲骨文,随着历史的发展,文字也在不断的发展,一直发展到我们现在所使用的文字。网络时代的到来,让文字进入到了网络中,在网上我们可以用文字去写文章、搜索问题、聊天等,其......
  • 海外直播源码技术文字聊天功能的配置
     今天我要分享的知识和我们日常生活中常用到的一个东西有关,它就是文字。目前已知发现我国最早的文字是距今5000多年的甲骨文,随着历史的发展,文字也在不断的发展,一直发展到我们现在所使用的文字。网络时代的到来,让文字进入到了网络中,在网上我们可以用文字去写文章、搜索问题、聊天......