【问题标题】:Component re-renders when setState called调用 setState 时组件重新渲染
【发布时间】:2021-05-24 11:32:20
【问题描述】:

React Native PanResponder 可拖动被 setState 中断

我有一个状态变量来跟踪秒数

const [seconds, setSeconds] = useState(0)
useEffect(() => {
      const interval = setInterval(() => {
        if (seconds > 59) {
          setSeconds(0)
        } else {
          setSeconds((seconds) => seconds + 1)
        }
      }, 1000)
    
    return () => clearInterval(interval)
 }, [seconds])

我也在使用可拖动的平移响应器

import React, { useState, useRef, ReactNode } from 'react'
import {
  StyleSheet,
  Animated,
  PanResponder,
  PanResponderGestureState,
  Text
} from 'react-native'

const Draggable = (props) => {
    const { draggable, isDropZone, moveToDropArea } = props
    const [showDraggable, setshowDraggable] = useState(true)
    const pan = useRef<Animated.AnimatedValueXY>(new Animated.ValueXY()).current
    const panResponder = PanResponder.create({
      onStartShouldSetPanResponder: () => true,
      onPanResponderMove: Animated.event([
        null,
        {
          dx: pan.x,
          dy: pan.y,
        },
      ]),
      onPanResponderRelease: (e, gesture) => {
        if (isDropZone(gesture)) {
          setshowDraggable(false)
          moveToDropArea()
        } else {
          Animated.spring(pan, {
            toValue: { x: 0, y: 0 },
            useNativeDriver: false,
          }).start()
        }
        pan.flattenOffset()
      },
    })
      return (
        <Animated.View {...panResponder.panHandlers} style={[pan.getLayout()]}>
          <Text>{seconds}</Text>
        </Animated.View>
      )
  }

当我尝试拖动 draggable 时,它​​会移动,但是当状态更新时,组件会重新渲染,并且 Draggable 项会回到原来的位置。

如何预防?

提前致谢。

【问题讨论】:

  • 从依赖项中删除秒数。因为当第二个变量发生变化时,它会触发 useEffect 钩子。
  • 你检查过this吗?
  • @R.CanserYanbakan 如果我从依赖项列表中删除秒,UI 将不会更新秒
  • 问题是您每次第二次更改时都重新定义间隔。将间隔排除在效果之外。

标签: javascript reactjs react-native react-hooks


【解决方案1】:

尝试像这样编写你的 useEffect 钩子:

useEffect(() => {
    const interval = setInterval(() => {
        setSeconds(currentSeconds => currentSeconds > 59 ? 0 : currentSeconds + 1);
    }, 1000);
    
    return () => clearInterval(interval);
}, []);

这样就不会在每次秒的状态变化时触发。

但是,更改状态仍会使您的组件重新呈现。如果你想防止重新渲染,你应该使用 useRef 而不是 useState。

const seconds = useRef(0);

useEffect(() => {
    const interval = setInterval(() => {
        seconds.current = seconds.current > 59 ? 0 : seconds.current + 1;
    }, 1000);

    return () => clearInterval(interval);
}, [seconds]);

console.log(seconds.current); // will log the last saved number of seconds before the last re-render

但是如果你需要显示秒的变化,你需要你的组件在秒状态变化时重新渲染。在这种情况下,我建议您将滚动逻辑与另一个(可能是父)组件分开,这样它就不会在由秒的状态变化引起的重新渲染时被重置。

【讨论】:

    【解决方案2】:

    在 useEffect 钩子的第二个参数中传递空数组。

    useEffect hook 在组件挂载到 dom 时运行回调函数,类似于类组件中的 componentDidMount 生命周期方法。

    setInterval 函数每隔一秒运行一次 setSeconds 方法。

    在 useEffect 钩子中,我们返回一个带有计时器参数的 clearInterval 函数,因此当组件从 dom 卸载时,setInterval 函数将停止,这类似于 componentWillUnmount 方法。

    空数组将在挂载时运行 useEffect,在其生命周期内保持静默。

    useEffect(() => {
          const interval = setInterval(() => {
            if (seconds > 59) {
              setSeconds(0)
            } else {
              setSeconds((seconds) => seconds + 1)
            }
          }, 1000)
        return () => clearInterval(interval)
     },[])
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-04-28
      • 2019-01-24
      • 1970-01-01
      • 2021-12-06
      • 2019-08-10
      • 1970-01-01
      • 2019-03-05
      • 2017-07-23
      相关资源
      最近更新 更多