React Native的panResponder是否具有useState的过时价值?

时间:2020-04-03 14:30:57

标签: react-native

我需要读取useStateonPanResponderMove的值。在页面加载时,onPanResponderMove正确记录了0的初始值。

但是,在我单击TouchableOpacity以递增foo之后,onPanResponderMove仍然注销了0,而不是新值。

export default function App() {
  const [foo, setFoo] = React.useState(0);

  const panResponder = React.useRef(
    PanResponder.create({
      onStartShouldSetPanResponder: (evt, gestureState) => true,
      onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
      onMoveShouldSetPanResponder: (evt, gestureState) => true,
      onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
      onPanResponderGrant: (evt, gestureState) => {},
      onPanResponderMove: (evt, gestureState) => {
        console.log(foo); // This is always 0
      },
      onPanResponderTerminationRequest: (evt, gestureState) => true,
      onPanResponderRelease: (evt, gestureState) => {},
      onPanResponderTerminate: (evt, gestureState) => {},
      onShouldBlockNativeResponder: (evt, gestureState) => {
        return true;
      },
    })
  ).current;

  return (
    <View style={{ paddingTop: 200 }}>
      <TouchableOpacity onPress={() => setFoo(foo + 1)}>
        <Text>Foo = {foo}</Text>
      </TouchableOpacity>
      <View
        {...panResponder.panHandlers}
        style={{ marginTop: 200, backgroundColor: "grey", padding: 100 }}
      >
        <Text>Text for pan responder</Text>
      </View>
    </View>
  );
}

4 个答案:

答案 0 :(得分:3)

pan响应器取决于值'foo'。 useRef在这里不是一个好选择。您应该将其替换为useMemo

const panResponder = useMemo(
     () => PanResponder.create({
       [...]
        onPanResponderMove: (evt, gestureState) => {
          console.log(foo); // This is now updated
        },
       [...]
      }),
     [foo] // dependency list
  );

答案 1 :(得分:2)

问题

问题是您使用当时拥有的PanResponder创建了foo一次。但是,每次调用setFoo时,您都会从useState挂钩中收到一个新的foo。 PanResponder不会知道这个新的foo。发生这种情况的原因是useRef的工作原理,因为它为您提供了一个可变对象,该对象在整个组件生命周期中均存在。 (这在react docs here中有解释)

(您可以在此sandbox中解决问题和简单的解决方案。)

解决方案

在您的情况下,最简单的解决方案是使用从useState获得的新foo更新PanResponder函数。在您的示例中,它看起来像这样:

export default function App() {
  const [foo, setFoo] = React.useState(0);

  const panResponder = React.useRef(
    PanResponder.create({
      onStartShouldSetPanResponder: (evt, gestureState) => true,
      onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
      onMoveShouldSetPanResponder: (evt, gestureState) => true,
      onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
      onPanResponderGrant: (evt, gestureState) => {},
      onPanResponderMove: (evt, gestureState) => {
        console.log(foo);
      },
      onPanResponderTerminationRequest: (evt, gestureState) => true,
      onPanResponderRelease: (evt, gestureState) => {},
      onPanResponderTerminate: (evt, gestureState) => {},
      onShouldBlockNativeResponder: (evt, gestureState) => {
        return true;
      },
    })
  ).current;

  // update the onPanResponderMove with the new foo
  panResponder.onPanResponderMove = (evt, gestureState) => {
     console.log(foo);
  },

  return (
    <View style={{ paddingTop: 200 }}>
      <TouchableOpacity onPress={() => setFoo(foo + 1)}>
        <Text>Foo = {foo}</Text>
      </TouchableOpacity>
      <View
        {...panResponder.panHandlers}
        style={{ marginTop: 200, backgroundColor: "grey", padding: 100 }}
      >
        <Text>Text for pan responder</Text>
      </View>
    </View>
  );
}

注意

如果某些可变性取决于您的组件状态,请务必格外小心。如果确实需要这样做,通常最好使用getter和setter编写适当的类或对象。例如这样的东西:



const createPanResponder = (foo) => {

  let _foo = foo;

  const setFoo = foo => _foo = foo;
  const getFoo = () => _foo;

  return {
     getFoo,
     setFoo,
     onPanResponderMove: (evt, gestureState) => {
        console.log(getFoo());
      },
     ...allYourOtherFunctions
  }

}

const App = () => {
  const [foo, setFoo] = React.useState(0);
  const panResponder = useRef(createPanResponder(foo)).current;
  panResponder.setFoo(foo);

  return ( ... )

}

答案 2 :(得分:0)

您似乎正在传递foo,以尝试更新现有状态。相反,请传递先前的状态并进行相应的更新(functional update)。像这样:

<TouchableOpacity onPress={() => setFoo(f =>  f + 1)}>
  <Text>Foo = {foo}</Text>
</TouchableOpacity>

要在foo处理程序中提供onPanResponderMove的当前值。创建一个ref

根据文档:

“ ref”对象是一个通用容器,其current属性是可变的,可以包含任何值

因此,考虑到这一点,我们可以编写:

const [foo, setFoo] = React.useState(0);

const fooRef = React.useRef()
React.useEffect(() => {
  fooRef.current = foo
},[foo])

然后在onPanResponderMove处理程序中,您可以访问之前设置的参考的当前值:

onPanResponderMove: (evt, gestureState) => {
  console.log('fooRef', fooRef.current)
  alert(JSON.stringify(fooRef))
},

工作示例here

有关过时状态处理的更多信息here

答案 3 :(得分:0)

使用useEffect,您可以在PanResponder更改时创建一个新的foo。但是,我不确定这的表现如何。

export default function App() {
  const [foo, setFoo] = React.useState(0);

  const pan = PanResponder.create({
    onStartShouldSetPanResponder: (evt, gestureState) => true,
    onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
    onMoveShouldSetPanResponder: (evt, gestureState) => true,
    onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
    onPanResponderGrant: (evt, gestureState) => {},
    onPanResponderMove: (evt, gestureState) => {
      console.log(foo); // This is always 0
    },
    onPanResponderTerminationRequest: (evt, gestureState) => true,
    onPanResponderRelease: (evt, gestureState) => {},
    onPanResponderTerminate: (evt, gestureState) => {},
    onShouldBlockNativeResponder: (evt, gestureState) => {
      return true;
    },
  });

  const [panResponder, setPanResponder] = React.useState(pan);

  useEffect(() => {
    setPanResponder(pan);
  }, [foo]);

  return (
    <View style={{ paddingTop: 200 }}>
      <TouchableOpacity onPress={() => setFoo(foo + 1)}>
        <Text>Foo = {foo}</Text>
      </TouchableOpacity>
      <View
        {...panResponder.panHandlers}
        style={{ marginTop: 200, backgroundColor: "grey", padding: 100 }}
      >
        <Text>Text for pan responder</Text>
      </View>
    </View>
  );
}