Picker (cascading) components and component packaging experience

Several experiences of component packaging

  • a. Parameters: the best way, only one object parameter, the actual parameters required, are passed in as object attributes.

    In this way, data processing and expansion are facilitated. For example, later expansion needs to add parameters, or when adjusting parameters, if the object used is passed in, the old calling method can also be compatible; otherwise, it is easy to make mistakes.

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)
        })
    }
})

Taking picker encapsulation as an example, the incoming options object, through the Object.assign method, can handle default values, incoming parameters, private parameters , and can prevent mutual overwriting . Incoming parameters take precedence over default values, and private parameters take precedence over incoming parameters. When a function is expanded, changes in the number and position of parameters will not affect the calling and processing of the function.

  • b, UI and data processing

    Each UI component can be separated into UI part and data part. Changes to the UI part generally drive UI changes after data changes, and mount the compiled DOM string.

  • c. Data processing

  • c1. Get data

    When doing component encapsulation, it is impossible to know whether the data involved in the relevant business scenario is asynchronous or synchronous. Therefore, when acquiring data, the actual acquisition of the data can be handled by the business side, and the component only needs to call the encapsulation function for acquiring the data. The object returned by this function is promise, which encapsulates the changes due to different business scenarios as a black box. As in the above example, when calling, the business side needs to encapsulate the getList function and return the corresponding 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, asynchronous request data timing control

    Asynchronous request data, each request arrives at a different time. For example, the order of initiating requests is: request 1 -> request 2 -> request 3, the order of arrival is: request 3 -> request 1 -> request 2, if you do not do any Control, the data that is finally used will be the data that finally arrives, but this data is not the latest result, which will cause an error. Therefore, it is necessary to control the sequence of asynchronous requests, number the requests, and discard when the result sequence number that arrives is smaller than the result sequence number that arrived last.

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

    Asynchronous requests are very resource-intensive, require additional network time, and enter a time loop. Therefore, if the corresponding data can be cached, the performance will be improved to a certain extent. Such as sug, picker can be used.

    // 如果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. Interactive processing

    As mentioned in a blog before interactive processing, anti-shake and frequency limiting processing is required, especially for interactive operations of network requests and frequent UI updates.

    _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)
            }
        }
    }

For example, in picker, touchmove is frequency-limited through throttle, UI is updated every 50ms, and data is updated every 500ms.

Since UI updates will directly affect the user's vision, the update frequency needs to be adjusted according to the user's visual perception.

The data update, based on the following conditions, should not be too frequent:

  • a. touchmove is a process operation, not the same as touchend, but a result operation;
  • b. Network requests need to consume certain resources, which will have a certain impact on server and web performance;
  • c. During the process operation, the user needs to filter the data to select the appropriate result, so the update of the data is still necessary;
  • d. In summary, limiting the frequency of data updates will be more in line with user experience requirements and performance requirements.

picker

The picker component encapsulates two types. It is said to be a picekr component. In fact, it is more inclined to cascade components, and the corresponding data has a certain hierarchical relationship. The corresponding component description and usage are as follows.

picker-limited

Features
  • a. Controlled by UI to realize limited data presentation;
  • b. Request data cache to speed up data return;
  • c. Increase the timing control of data requests;
  • d. Effectiveness control of interactive touchmove;
  • e. Frequency limit: UI update 50ms/touchmove, data update 500ms/touchmove; when touchend, immediately open data request and UI update;
  • f. Support cascade;
  • g. Support list single-column dom customization;
  • h. Use getComputedStyle to get the row height and retain the decimal point for more precision.
call method
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()
show

flow chart

picker-limitless

Features
  • a. It can realize the display of unlimited data;
  • b. Request data cache to speed up data return;
  • c. Increase the timing control of data requests;
  • d. Support list single-column dom customization.
call method

Exactly the same as picker-limited

show

flow chart

Because there is no too complicated interaction, the asynchronous, caching and other processing of data is similar to picker-limited, so the functional process can refer to the code by itself.

Code and description link

picker code

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324920007&siteId=291194637