You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Native Gesture Handler回调不一致报错及worklet添加后崩溃问题

解决React Native Gesture Handler回调不一致导致的错误及崩溃问题

问题说明

运行React Native Gesture Handler相关代码时,触发以下错误:

ERROR [react-native-gesture-handler] Some of the callbacks in the gesture are worklets and some are not. Either make sure that all calbacks are marked as 'worklet' if you wish to run them on the UI thread or use '.runOnJS(true)' modifier on the gesture explicitly to run all callbacks on the JS thread

尝试给handleGestureEvent添加'worklet'标记后,点击应用直接崩溃。

原代码如下:

import * as React from 'react';
import { View, Text, Dimensions } from 'react-native';
import styles from '../styles/style_home'
import * as Animatable from 'react-native-animatable'
import {Canvas, Path, Skia} from '@shopify/react-native-skia';
import {curveBasis, line, scaleLinear, scalePoint} from 'd3';
import DataPalmas from '../../assets/csvjson.json'
import Gradient from './Gradient'
import XAxisText from './XAxisText';
import { clamp, runOnJS, useSharedValue, withDelay, withTiming } from 'react-native-reanimated';
import Cursor from './Cursor'
import {
Gesture,
GestureDetector,
PanGestureHandlerEventPayload,
} from 'react-native-gesture-handler';
import {getYForX, parse} from 'react-native-redash';

export default function () {
    const [showCursor, setShowCursor] = React.useState(false)
    const animationLine = useSharedValue(0)
    const animationGradient = useSharedValue({x: 0, y: 0})
    const cx = useSharedValue(0)
    const cy = useSharedValue(0)

    React.useEffect(() => {
        animationLine.value = withTiming(1, {duration:2000})
        animationGradient.value = withDelay(2000, withTiming({x: 0, y: chartHeight}, {duration: 1000}))
    }, [])

    const chartHeight = 150
    const chartWidth = Dimensions.get('window').width
    const chartMargin = 20
    const data = DataPalmas

    const xDomain = data.map((dataPoint) => dataPoint.Data)
    const xRange = [chartMargin, chartWidth - chartMargin]
    const x = scalePoint().domain(xDomain).range(xRange).padding(0)
    const stepX = x.step()
    const max = Math.max(...data.map(val => val.Precipitacao))
    const min = Math.min(...data.map(val => val.Precipitacao))

    const yDomain = [min, max]
    const yRange = [chartHeight, 0]
    const y = scaleLinear().domain(yDomain).range(yRange)

    const curvedLine = line()
    .x(d => x(d.Data))
    .y(d => y(d.Precipitacao))
    .curve(curveBasis)(data)

    const linePath = Skia.Path.MakeFromSVGString(curvedLine)
    const path = parse(linePath.toSVGString())

    function handleGestureEvent(e){
        const clampValue = clamp(
            Math.floor(e.absoluteX / stepX) * stepX + chartMargin,
            chartMargin,
            chartWidth - chartMargin,
        )
        cx.value = clampValue
        cy.value = getYForX(path, Math.floor(clampValue))
    }
    const pan = Gesture.Pan()
        .onTouchesDown(() => {
            setShowCursor(true)
        })
        .onTouchesUp(() => {
            setShowCursor(false)
        })
        .onBegin(handleGestureEvent)
        .onChange(handleGestureEvent)

    return (
    <Animatable.View style={styles.container__graphic} animation="fadeInLeft">
        <GestureDetector gesture={pan}>
            <Canvas
            style={{
                width: chartWidth, 
                height: chartHeight+30,
            }}>
                <Path 
                path={linePath} 
                style={'stroke'} 
                strokeWidth={2} 
                color={'#0bb861'} 
                strokeCap={'round'}
                start={0}
                end={animationLine}/>
                <Gradient
                    chartHeight = {chartHeight}
                    chartMargin = {chartMargin}
                    chartWidth = {chartWidth}
                    curvedLine = {curvedLine}
                    animationGradient = {animationGradient}
                />
                {data.map((dataPoint, index) => (
                    <XAxisText
                        x={x(dataPoint.Data)}
                        y={chartHeight}
                        text={dataPoint.Data}
                        key={index}
                    />
                ))}
                {showCursor && 
                <Cursor 
                    cx = {cx}
                    cy = {cy}
                    chartHeight = {chartHeight}
                />}
                
            </Canvas>
        </GestureDetector>
    </Animatable.View>
    );
}

添加worklet标记后的崩溃代码片段:

function handleGestureEvent(e){
    'worklet'
    const clampValue = clamp(
        Math.floor(e.absoluteX / stepX) * stepX + chartMargin,
        chartMargin,
        chartWidth - chartMargin,
    )
    cx.value = clampValue
    cy.value = getYForX(path, Math.floor(clampValue))
}

解决方案

方案一:统一使用Worklet(UI线程执行)

崩溃原因是:setShowCursor是React的状态更新函数,只能在JS线程调用,而Worklet运行在UI线程,直接调用会导致崩溃。需要用runOnJS包装状态更新函数,让它回到JS线程执行。

修改步骤:

  1. 用runOnJS包装setShowCursor,生成可在UI线程调用的函数
  2. 给所有手势回调添加'worklet'标记
  3. 在Worklet中调用包装后的状态更新函数

修改后的代码:

// 包装状态更新函数
const setShowCursorOnJS = runOnJS(setShowCursor);

// 修改handleGestureEvent为Worklet
function handleGestureEvent(e){
    'worklet'
    const clampValue = clamp(
        Math.floor(e.absoluteX / stepX) * stepX + chartMargin,
        chartMargin,
        chartWidth - chartMargin,
    )
    cx.value = clampValue
    cy.value = getYForX(path, Math.floor(clampValue))
}

// 修改手势回调,全部标记为Worklet
const pan = Gesture.Pan()
    .onTouchesDown(() => {
        'worklet';
        setShowCursorOnJS(true);
    })
    .onTouchesUp(() => {
        'worklet';
        setShowCursorOnJS(false);
    })
    .onBegin(handleGestureEvent)
    .onChange(handleGestureEvent);

方案二:强制所有回调在JS线程执行(更简单)

直接给手势添加.runOnJS(true)修饰符,这样所有回调都在JS线程运行,不需要标记Worklet,也不用处理跨线程调用问题,适合对性能要求不高的场景。

修改后的代码:

function handleGestureEvent(e){
    const clampValue = clamp(
        Math.floor(e.absoluteX / stepX) * stepX + chartMargin,
        chartMargin,
        chartWidth - chartMargin,
    )
    cx.value = clampValue
    cy.value = getYForX(path, Math.floor(clampValue))
}

// 添加.runOnJS(true)
const pan = Gesture.Pan()
    .onTouchesDown(() => {
        setShowCursor(true)
    })
    .onTouchesUp(() => {
        setShowCursor(false)
    })
    .onBegin(handleGestureEvent)
    .onChange(handleGestureEvent)
    .runOnJS(true);

内容的提问来源于stack exchange,提问作者Heitor Fiuza Oliveira

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 01:02:36