React Native绘图应用:Skia与Gesture Handler实时绘制不更新问题
绘图应用实时绘制不显示,刷新后才出现的问题解决
问题描述
使用React Native结合react-native-skia和react-native-gesture-handler开发绘图应用时,遇到以下问题:手指在屏幕滑动绘制时,画布无任何内容显示;但刷新应用后,之前绘制的SVG路径会正确出现在对应位置。已尝试更换react-native-gesture-handler版本、将GestureHandlerRootView移至App.js,均未解决问题。
相关代码
Draw组件代码
import React, { useState } from "react"; import { View, Text } from "react-native"; import { Gesture, GestureDetector, GestureHandlerRootView, PanGestureHandler, } from "../node_modules/react-native-gesture-handler"; import { Canvas, Path } from "@shopify/react-native-skia"; export default function Draw() { const [paths, setPaths] = useState([[]]); const pan = Gesture.Pan() .onStart((g) => { const newPaths = paths; newPaths.push(["M " + g.x + " " + g.y]); setPaths(newPaths); }) .onUpdate((g) => { const newPaths = paths; newPaths[newPaths.length-1].push("L " + g.x + " " + g.y); setPaths(newPaths); }) .minDistance(1) return ( <GestureDetector gesture={pan}> <Canvas style={{flex:1}} collapsable={false}> {paths.map((p, index) => ( <Path key={index}z path={p.join(" ")} strokeWidth={5} style="stroke" color="black" /> ))} </Canvas> </GestureDetector> ); }
App.js代码
import React, { Component } from 'react'; import { NavigationContainer } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; import ScoutingSheet from './screens/ScoutingSheet'; import QRCodeScanner from './screens/QRCodeScanner'; import Draw from './screens/AutonCanvas'; import { GestureHandlerRootView, } from "./node_modules/react-native-gesture-handler"; const Stack = createNativeStackNavigator(); export default class App extends Component { render() { return ( <GestureHandlerRootView style={{flex:1}}> <NavigationContainer> <Stack.Navigator screenOptions={{ headerShown: false, }}> <Stack.Screen name="AutonCanvas" component={Draw} /> <Stack.Screen name="ScoutingSheet" component={ScoutingSheet} /> <Stack.Screen name="QrCodeScanner" component={QRCodeScanner} /> </Stack.Navigator> </NavigationContainer> </GestureHandlerRootView> ); } }
package.json配置
{ "name": "roboticsscoutingapp", "version": "1.0.0", "scripts": { "start": "expo start --dev-client", "android": "expo run:android", "ios": "expo run:ios", "web": "expo start --web" }, "dependencies": { "@react-native-async-storage/async-storage": "~1.17.3", "@react-native-community/cli-platform-android": "^10.2.0", "@react-navigation/native-stack": "^6.9.1", "@shopify/react-native-skia": "0.1.172", "expo": "^48.0.0", "expo-barcode-scanner": "~12.3.2", "expo-splash-screen": "~0.18.1", "expo-status-bar": "~1.4.0", "expo-updates": "~0.16.3", "i": "^0.3.7", "npm": "^9.6.1", "react": "18.2.0", "react-dom": "18.2.0", "react-native": "0.71.3", "react-native-gesture-handler": "^2.9.0", "react-native-paper": "^4.12.5", "react-native-qrcode-svg": "^6.2.0", "react-native-reanimated": "^2.3.0", "react-native-simple-radio-button": "^2.7.4", "react-native-svg": "13.4.0", "react-native-table-component": "^1.2.2", "react-native-web": "~0.18.7", "react-navigation": "^4.4.4" }, "devDependencies": { "@babel/core": "^7.18.6" }, "private": true }
问题原因
核心问题是直接修改了state中的数组引用,没有创建新的数组实例。React的状态更新依赖引用变化来检测状态变更:
- 执行
const newPaths = paths时,只是将原数组的引用赋值给newPaths,并非创建新数组 - 后续的
push操作都是在原state数组上修改,setPaths传递的还是同一个引用 - React会判定状态没有发生变化,因此不会触发组件重新渲染,Canvas也就不会实时更新内容
- 刷新应用时,state重新初始化并加载了之前修改后的数组,所以内容会显示
另外,原代码中Path组件的key属性后多了一个无效字符z(key={index}z),属于语法错误,也可能影响渲染。
修复方案
修改Draw组件中的状态更新逻辑,创建新的数组副本,同时修复语法错误:
import React, { useState } from "react"; import { View, Text } from "react-native"; import { Gesture, GestureDetector, } from "react-native-gesture-handler"; // 改为直接从包导入,无需从node_modules路径引入 import { Canvas, Path } from "@shopify/react-native-skia"; export default function Draw() { const [paths, setPaths] = useState([[]]); const pan = Gesture.Pan() .onStart((g) => { // 解构原数组创建新副本,避免修改原state const newPaths = [...paths]; newPaths.push(["M " + g.x + " " + g.y]); setPaths(newPaths); }) .onUpdate((g) => { // 先复制外层数组,再复制当前正在绘制的路径数组,保证引用变化 const newPaths = [...paths]; const currentPath = [...newPaths[newPaths.length - 1]]; currentPath.push("L " + g.x + " " + g.y); newPaths[newPaths.length - 1] = currentPath; setPaths(newPaths); }) .minDistance(1); return ( <GestureDetector gesture={pan}> <Canvas style={{ flex: 1 }} collapsable={false}> {paths.map((p, index) => ( <Path key={index} // 去掉无效的z字符 path={p.join(" ")} strokeWidth={5} style="stroke" color="black" /> ))} </Canvas> </GestureDetector> ); }
额外优化点:
- 无需从
../node_modules路径导入react-native-gesture-handler的模块,直接从包名导入即可 - 确保
react-native-reanimated已正确配置(你的package.json中版本与RN 0.71.3兼容,无需额外调整)
内容的提问来源于stack exchange,提问作者Adik Nathan
相关产品推荐
相关产品推荐

