规避taro微信小程序ios端input输入闪烁

项目依赖

perl 复制代码
"@tarojs/taro": "3.6.25",
"react": "^18.0.0",

问题

问题描述: input框在输入时,内容不跟手,内容闪烁的问题,在查找issues后发现此问题并没有得到官方的解决,于是开始寻找规避此问题的方案

问题issue

复现:

  • 因为出现问题的手机为iphone7,我目前只有iphone12,所以这里demo采用渲染500个input框的方式来加大压力
tsx 复制代码
export default function Index() {
    const [list, setList] = useState<number[]>(() => {
        const arr = []
        for (let i = 0; i < 500; i++)
            arr.push(i)

        return arr
    })

    function setValue(value, index) {
        setList((e) => {
            e.splice(index, 1, value)
            return [...e]
        })
    }

    return (
        <View className={Style.index}>
            {
                list.map((item, index) => (
                    <View className="diyInput">
                        <Input
                            className="input"
                            value={String(item)}
                            onInput={e => setValue(e.detail.value, index)}
                        />
                    </View>
                ),
                )
            }
        </View>
    )
}

问题演示视频

排查

第一个想到的点是,taro3打包后的小程序代码是通过<template>渲染的,所有的 setData 都由页面对象调用。如果页面结构比较复杂,应用更新的性能就会下降。再加上react的更新模式是全量更新

首先想到的方案是使用CustomWrapper组件嵌套input,将input独立出来,并且使用memo缓存此组件,避免不必要的渲染

使用CustomWrappermemo

于是有了以下代码

tsx 复制代码
// index.tsx

export default function Index() {
    const [list, setList] = useState<number[]>(() => {
        const arr = []
        for (let i = 0; i < 500; i++)
            arr.push(i)

        return arr
    })

    function setValue(value, index) {
        setList((e) => {
            e.splice(index, 1, value)
            return [...e]
        })
    }

    const callbackSetValue = useCallback((e, index) => {
        setValue(e, index)
    }, [])

    return (
        <View className={Style.index}>
            {
                list.map((item, index) => (
                    <CustomWrapper key={index}>
                        <CustomWrapperInput value={String(item)} index={index} onInput={callbackSetValue} />
                    </CustomWrapper>
                ),
                )
            }
        </View>
    )
}
tsx 复制代码
// customWrapperInput.tsx

function CoustomWrapperInput(props: {
    value: string
    onInput: (e: string, i: number) => void
    index: number
}) {
    console.log(props.value)
    return (
        <View className="diyInput">
            <Input
                className="input"
                value={props.value}
                onInput={e => props.onInput(e.detail.value, props.index)}
            />
        </View>
    )
}

export default memo(CoustomWrapperInput)

注意点

这里为了保证更改后只重新传染当前这一条数据,传入的props中如果带有函数,需要将函数使用useCallback包裹

在手机端验证

验证后(iphone12)发现问题已修复,跑去找使用的iphone7朋友验证,得到的回复是仍然输入卡顿。

于是我打开了开发者工具的低端机模式

开发者工具低端机模式验证

得到的也是依旧卡顿

低端机模式验证视频

从图片中可以看出,在组件重新渲染后会打印当前传入的value,打印出来的值有断节,并不是和上一条打印结果衔接上的

因为props.value更新,重新渲染了CoustomWrapperInput组件内的Input,因为低端机性能太差,可能出现输入已经输入到了第九个字母,重新渲染的任务才到第4个

既然这样,那么如果使用input.value = xx的方案会不会好一点

顺着这个思路,有了以下代码

延迟赋值value

tsx 复制代码
// index.tsx

export default function Index() {
    const [list, setList] = useState<number[]>(() => {
        const arr = []
        for (let i = 0; i < 500; i++)
            arr.push(i)

        return arr
    })

    function setValue(value, index) {
        setList((e) => {
            e.splice(index, 1, value)
            return [...e]
        })
    }

    const callbackSetValue = useCallback((e, index) => {
        setValue(e, index)
    }, [])

    return (
        <View className={Style.index}>
            {
                list.map((item, index) => (
                    <CustomWrapper>
                        <DiyInput value={String(item)} index={index} onInput={callbackSetValue} key={index} />
                    </CustomWrapper>
                ),
                )
            }
        </View>
    )
}
tsx 复制代码
// diyInput.tsx

const timer: {
    [key: number]: number
} = {}

function Index(props: {
    value: string
    onInput: (e: string, i: number) => void
    index: number
}) {
    console.log(props.value)
    const inputRef = useRef<{ value: string }>({ value: '' })
    const timerId = useId()
    if (!timer[timerId])
        timer[timerId] = 0

    useEffect(() => {
        return () => {
            delete timer[timerId]
        }
    }, [])

    useEffect(() => {
        clearTimeout(timer[timerId])
        inputRef.current.value = props.value || ''
    }, [props.value])

    const beforeSetValue = useCallback((e) => {
        clearTimeout(timer[timerId])
        timer[timerId] = setTimeout(() => {
            if (props.value !== null && props.value !== 'null') {
                if (inputRef.current)
                    props.onInput && props.onInput(e.detail.value, props.index)
            }
        }, 600)
    }, [])

    return (
        <View className="diyInput">
            <CustomWrapper>
                <input
                    ref={inputRef}
                    className="input"
                    onInput={beforeSetValue}
                />
            </CustomWrapper>
        </View>
    )
}

export default memo(Index)

这里的思路是防抖节流,延迟去设置value的值,只要props没有改变,那么当前的组件就不会重新渲染。

为什么采用ref.value的方式来赋值呢?

因为useRef的更新并不会让组件重新渲染,这样是为了减少重新渲染带来的性能消耗。

延迟赋值视频

从视频中看到虽然输入时还是会有抖动,因为页面渲染500条input,实际情况并不会有这么多数据。此方案也帮我在项目中解决了iphone7输入卡顿的问题

ps

多数情况下使用CustomWrapper包裹后就不会有此问题,奈何我的甲方使用的是iphone7😮‍💨

在处理这个问题的时候微信skyline方案还没有公布,在公布后尝试使用更改为skyline渲染,但是出现的样式问题太多,当时项目已经完成了80%,就没有再去更改。

但是我做了一个skyline渲染500个输入框的demo验证了一下,效果确实好,完全不卡。后续会考虑将项目升级为此渲染方式。

最后附上仓库地址

相关推荐
楽码5 天前
AI提问:进行深度交互
aigc·openai·taro
不爱吃糖的程序媛13 天前
鸿蒙版Taro 搭建开发环境
华为·harmonyos·taro
PyAIGCMaster13 天前
react+taro 开发第五个小程序,解决拼音的学习
react.js·小程序·taro
程序员小刘14 天前
【HarmonyOS 5】鸿蒙Taro跨端框架
华为·harmonyos·taro
君的名字22 天前
怎么判断一个Android APP使用了taro 这个跨端框架
android·react native·taro
PyAIGCMaster22 天前
项目 react+taro 编写的微信 小程序,什么命令,可以减少console的显示
react.js·微信·taro
蓉妹妹25 天前
React+Taro 微信小程序做一个页面,背景图需贴手机屏幕最上边覆盖展示
react.js·微信小程序·taro
时间识人1 个月前
【无标题】
vue.js·微信小程序·taro
Dragon Wu1 个月前
Taro Error: chunk common [mini-css-extract-plugin]
前端·小程序·前端框架·react·taro
Dragon Wu1 个月前
Taro 安全区域
前端·小程序·前端框架·taro