规避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 天前
【每日学点鸿蒙知识】Taro、native层获取文件宽度、位置变化callback、数据迁移、oh_modules说明等
华为·harmonyos·taro
一条不想当淡水鱼的咸鱼10 天前
taro中实现带有途径点的路径规划
javascript·react.js·taro
京东零售技术13 天前
Taro小程序开发性能优化实践
性能优化·taro
少恭写代码18 天前
duxapp 2024-12-18更新 新增 Svg 组件 Tab 支持幻灯片切换功能
react native·小程序·taro
苍岚丨晨苏19 天前
使用Taro开发iOS App触发额外权限请求的问题
react native·taro
sir.山1 个月前
taro小程序进入腾讯验证码
小程序·taro·验证码·腾讯验证码·uniapp验证码·原生验证码
是陈哈哈噢1 个月前
Taro小程序开发随记
前端·微信小程序·taro
他的猫哎1 个月前
taro+vue3 + nut-popup微信小程序真机不显示问题
ios·微信小程序·vue·taro
他的猫哎1 个月前
taro小程序马甲包插件
小程序·notepad++·taro
@ 前端小白1 个月前
taro使用createAsyncThunk报错ReferenceError: AbortController is not defined
reactjs·taro