zoukankan      html  css  js  c++  java
  • picker(级联)组件及组件封装经验

    组件封装的几个经验

    • a、参数:最佳方式,仅一个object参数,所需要的实际参数,作为对象属性传入。

      如此,便于数据的处理和扩展。例如,后期扩展需要增加参数,或者调整参数时,如果使用的对象传入,老的调用方法也可以获得兼容;否则,容易出错。

    class Picker {
        constructor(options) {
        	// 参数处理
            Object.assign(this, {
                style: defaultStyle,
                liTemplate: defaultLiTemplate,
                defaultTarget: [],
                isCascade: true
            },
            options, {
                _target: [],
                _list: [],
                _pElem: null,
                _currSequenceNum: [], // 当前插入面板的数据为第n个数据请求,用于处理异步请求时序
                _latestSequenceNum: 0, // 最新请求的序号,用于处理异步请求时序
                _requestRstBuff: new Map(), // 缓存数据请求的结果,加速数据返回,map结构
                _touchIndex: -1, // 标记当前数据请求通过那个面板触发,用于处理异步请求时序 如果有级联,当高级面板触发的请求未结束时,不能继续操作面板
                _translateY: [],
                _lineHeight: 0
            })
        }
        ......
    }
    
    // 调用
    new Picker({
    	getList: () => {
    		return new Promise((resolve, reject) => {
    			resolve(xxx)
    		})
    	}
    })
    

    以picker的封装为例子,传入的为options对象,通过Object.assign方法,可以处理默认值、传入参数、私有参数,并且可以防止相互的覆盖。传入参数优先于默认值,私有参数又优先于传入参数。当函数扩展时,参数数量、位置的变化,不会影响到函数的调用和处理。

    • b、UI和数据的处理

      每一个UI组件,都可以分离成UI部分,与数据部分。UI部分的变更,一般在数据变更后,驱动UI变更,将编译后的DOM string 进行挂载mount。

    • c、数据的处理

    • c1、获取数据

      在做组件封装时,无法知晓相关业务场景涉及的数据是异步还是同步,所以获取数据时,可将数据的实际获取交由业务端处理,组件内只需调用获取数据的封装函数便可。该函数返回的对象为promise,从而将因业务场景不同而产生的变化作为黑盒封装起来。如上面的例子,调用时业务端需要封装getList函数,返回相应的promise。

    	// 业务逻辑中封装
    	new Picker({
    		getList: (target = [], index = 0) => {
            return new Promise((resolve, reject) => {
                let rst = {
                    list: [],
                    isDone: false,
                    success: true
                }
                ...
                resolve(rst)
            })
        	}
    	}).init()
    	
    	// 返回的promise 对象对应数据为obj,包含逻辑处理所需的三个参数:success, list, isDone
    		this.getList(this._target, index).then(({success, list, isDone}) => {
                if (success) {
                    ...
                }
            }).catch((err) => {
                console.log(err)
            })
    
    • c2、异步请求数据时序控制

      异步请求数据,每个请求到达的时间不同,如发起请求的顺序为: 请求1 -> 请求2 -> 请求3,到达的顺序为:请求3 -> 请求1 -> 请求2,如果不做任何控制,最终使用的数据将会是最终到达的数据,但这个数据却不是最新的结果,将导致错误。因此,需要做异步请求的时序控制,对请求进行编号,当到达的结果时序号小于上一到达的结果时序号时,丢弃。

     	this._getDataByNet(index).then((rst) => {
    	    // 当请求数据的序列号 大于 已插入面板的数据序列号时,数据有效;否则无效丢弃
    	    if (!mySequenceNum || mySequenceNum > this._currSequenceNum[index]) {
    	        // 存入内存中
    	        this._requestRstBuff.set(targetValue, rst)
    	        resolve(rst)
    	    }
    	})
    
    • c3、数据缓存

      异步请求是非常消耗资源的,需要额外的网络时间,并且需要进入时间循环当中。因此,如果可以对相应的数据进行缓存,将一定程度上,提升性能。如sug、picker都可以用到。

    	// 如果buff中有,则取buff中数据,否则请求数据
    	if (this._requestRstBuff.has(targetValue)) {
            rst = this._requestRstBuff.get(targetValue)
            resolve(rst)
        } else {
            this._getDataByNet(index).then((rst) => {
                // 当请求数据的序列号 大于 已插入面板的数据序列号时,数据有效;否则无效丢弃
                if (!mySequenceNum || mySequenceNum > this._currSequenceNum[index]) {
                    // 存入内存中
                    this._requestRstBuff.set(targetValue, rst)
                    resolve(rst)
                }
            })
        }
    
    • d、交互处理

      交互处理之前的一片blog有提到过,需要进行防抖、限頻的处理,特别是进行网络请求和UI频繁更新的交互操作。

    	_registerUlEvent(ulElem, index) {
            let renderTouchUi = throttle(this._renderTouchUi, 50, this)
            let handleWholePanel = throttle(this._handleWholePanel, 500, this)
            
            ......
    
            ulElem.addEventListener('touchmove', (event) => {
                event.preventDefault()
                event.stopPropagation()
                if (!(this._touchIndex != -1 && (index + 1) > this._touchIndex && this.isCascade)) {
                    this._touchIndex = index + 1
                    touchInfo.currTouchY = event.touches[0].clientY
                    renderTouchUi(touchInfo, ulElem, index, 'move')
                    handleWholePanel(index + 1)
                }
            }, false)
    
            ......
            
        }
        
        // 限頻
        _throttle(fn, delay, ctx) {
            let isAvail = true
            let movement = null
            return function() {
                let args = arguments
                if (isAvail) {
                    fn.apply(ctx, args)
                    isAvail = false
                    clearTimeout(movement)
                    movement = setTimeout(() => {
                        isAvail = true
                    }, delay)
                }
            }
        }
    

    如picker中,通过throttle对touchmove进行限頻处理,UI更新50ms一次,数据更新500ms一次。

    由于UI更新将直接作用在用户视觉上,所以更新频率需要根据用户视感需要做调整。

    而数据更新,基于以下几点条件,不宜太过频繁:

    • a、touchmove为过程操作,并非touchend一样,为结果操作;
    • b、网络请求需要消耗一定的资源,对服务器、web性能都会造成一定的影响;
    • c、过程操作中,用户需对数据进行筛选,以选择合适的结果,所以数据的更新仍然是有必要的;
    • d、综上所述,限制数据更新的频率,将更符合用户体验要求及性能要求。

    picker

    picker组件封装了两种,说是picekr组件,其实更倾向于级联组件,相应的数据,具有一定的层级关系。相应的组件说明和使用方法如下。

    picker-limited

    功能特点
    • a、受UI控制,实现有限级的数据展现;
    • b、请求数据缓存,加速数据返回;
    • c、增加数据请求的时序控制;
    • d、交互touchmove的有效性控制;
    • e、限頻:UI更新50ms/touchmove,数据更新500ms/touchmove; touchend时,立即开启数据请求及UI更新;
    • f、支持级联;
    • g、支持列表单列dom自定义;
    • h、使用getComputedStyle获取行高,保留了小数点更为精确。
    调用方法
    import Picker from 'src/libs/picker-limited'
    
    new Picker({
        // 默认值
        defaultTarget: [
            {value: 'test1', id: 1},
            {value: 'test2', id: 2},
            {value: 'test3', id: 3},
            {value: 'test4', id: 4}
        ],
        // 结束回调
        done: (info) => {
            console.log('info', info)
        },
    
        // 数据接口函数 返回promise
        getList: (target = [], index = 0) => {
            return new Promise((resolve, reject) => {
                let rst = {
                    list: [],
                    isDone: false,
                    success: true
                }
    
                if (index === 4) {
                    rst.isDone = true
                    resolve(rst)
                    return
                }
    
                rst.list = [{
                    value: 'test1',
                    id: 1
                }, {
                    value: 'test2',
                    id: 2
                }, {
                    value: 'test3',
                    id: 3
                }, {
                    value: 'test4',
                    id: 4
                }]
                resolve(rst)
            })
        }
    }).init()
    
    展现

    流程图

    picker-limitless

    功能特点
    • a、可实现无限级数据的展现;
    • b、请求数据缓存,加速数据返回;
    • c、增加数据请求的时序控制;
    • d、支持列表单列dom自定义。
    调用方法

    与picker-limited一模一样

    展现

    流程图

    因没有太复杂的交互,数据的异步、缓存等处理与picker-limited相近,所以功能流程可自行参考代码。

    代码及说明链接

    picker code

  • 相关阅读:
    Python 6 socket编程
    Python 5 面向对象进阶
    Python 4 面向对象
    Python 3 常用模块
    Python基础 2
    Python基础 1
    Django之会议室预预订
    vscode 修改快捷键 (回到上一处光标位置,下一处光标位置)
    C 库函数
    C 库函数
  • 原文地址:https://www.cnblogs.com/hity-tt/p/8945213.html
Copyright © 2011-2022 走看看