Pan Responder 在更新父组件的状态时只触发一次?

Eva*_*nss 5 react-native

我正在使用 React Native 的 Pan Responder。当拖动其子项中的 Pan Responder 时,我需要更新父项中的某些状态。

复杂的一点是我还需要这些孩子将他们自己的元素插入可拖动区域。

对于上下文,这里是一个更简单的例子,可以正常工作 https://snack.expo.io/@jamesweblondon/drag-items

import * as React from 'react';
import { useRef, useState } from 'react';
import { Text, View, PanResponder } from 'react-native';

const items = ['1', '2', '3'];
const ITEM_HEIGHT = 100;

const Parent = () => {
  const [y, setY] = useState(0);
  const [index, setIndex] = useState(null);
  return (
    <View style={{ marginTop: 50 }}>
      <Text>Index: {index}</Text>
      <Text>Y: {y}</Text>
      <View
        style={{ height: ITEM_HEIGHT * items.length, backgroundColor: 'gold' }}>
        {items.map((item, itemIndex) => {
          const isBeingDragged = itemIndex === index;
          const top =
            isBeingDragged
              ? (ITEM_HEIGHT * itemIndex) + y
              : (ITEM_HEIGHT * itemIndex);
          return (
            <View
              style={{
                top,
                width: '100%',
                position: 'absolute',
                zIndex: isBeingDragged ? 1 : 0
              }}
              key={itemIndex}>
              <Child
                index={itemIndex}
                setIndex={setIndex}
                setY={setY}
                item={item}
              />
            </View>
          );
        })}
      </View>
    </View>
  );
};

const Child = ({ index, setIndex, setY, item }) => {
  const panResponder = useRef(
    PanResponder.create({
      onStartShouldSetPanResponder: (evt, gestureState) => true,
      onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
      onMoveShouldSetPanResponder: (evt, gestureState) => true,
      onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
      onPanResponderGrant: (evt, gestureState) => {
        setIndex(index);
      },
      onPanResponderMove: (evt, gestureState) => {
        setY(gestureState.dy);
      },
      onPanResponderTerminationRequest: (evt, gestureState) => true,
      onPanResponderRelease: (evt, gestureState) => {
        setY(0);
        setIndex(null);
      },
      onPanResponderTerminate: (evt, gestureState) => {},
      onShouldBlockNativeResponder: (evt, gestureState) => true,
    })
  ).current;

  return (
    <View
      style={{
        flexDirection: 'row',
        justifyContent: 'space-between',
        backgroundColor: 'tomato',
        padding: 10,
        borderBottomColor: 'black',
        borderBottomWidth: 1,
        height: ITEM_HEIGHT,
      }}>
      <View
        {...panResponder.panHandlers}
        style={{ background: 'grey', height: '100%', width: 40 }}
      />
      <Text>Child {item}</Text>
    </View>
  );
};

export default Parent;
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

这是我的完整示例:https : //snack.expo.io/@jamesweblondon/drag2

import * as React from 'react';
import { useRef, useState } from 'react';
import { Text, View, PanResponder } from 'react-native';

const CHILD_A = 'CHILD_A';
const CHILD_B = 'CHILD_B';
const CHILD_A_HEIGHT = 100;
const CHILD_B_HEIGHT = 200;

const items = [
  { type: CHILD_A, text: '1' },
  { type: CHILD_B, text: '2' },
  { type: CHILD_A, text: '3' },
];

const Parent = () => {
  const [y, setY] = useState(0);
  const [index, setIndex] = useState(null);

  const heights = items.map((item) =>
    item.type === CHILD_A ? CHILD_A_HEIGHT : CHILD_B_HEIGHT
  );

  let heightsSum = 0;
  const heightsCumulative = heights.map(
    (elem) => (heightsSum = heightsSum + elem)
  );

  return (
    <View style={{ marginTop: 50 }}>
      <Text>Index: {index}</Text>
      <Text>Y: {y}</Text>
      <View style={{ height: heightsSum, backgroundColor: 'gold' }}>
        {items.map((item, itemIndex) => {
          if (item.type === CHILD_A) {
            return (
              <ChildA
                index={itemIndex}
                setIndex={setIndex}
                setY={setY}
                text={item.text}
                DragHandle={(props) => (
                  <DragHandle
                    {...props}
                    index={itemIndex}
                    setIndex={setIndex}
                    setY={setY}
                  />
                )}
              />
            );
          }
          return (
            <ChildB
              index={itemIndex}
              setIndex={setIndex}
              setY={setY}
              text={item.text}
              DragHandle={(props) => (
                <DragHandle
                  {...props}
                  index={itemIndex}
                  setIndex={setIndex}
                  setY={setY}
                />
              )}
            />
          );
        })}
      </View>
    </View>
  );
};

const DragHandle = ({ index, setIndex, setY, children }) => {
  const panResponder = useRef(
    PanResponder.create({
      onStartShouldSetPanResponder: (evt, gestureState) => true,
      onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
      onMoveShouldSetPanResponder: (evt, gestureState) => true,
      onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
      onPanResponderGrant: (evt, gestureState) => {
        setIndex(index);
      },
      onPanResponderMove: (evt, gestureState) => {
        console.log(gestureState.dy); // This works when the line below is removed :)
        setY(gestureState); // This does not work :(
      },
      onPanResponderTerminationRequest: (evt, gestureState) => true,
      onPanResponderRelease: (evt, gestureState) => {},
      onPanResponderTerminate: (evt, gestureState) => {},
      onShouldBlockNativeResponder: (evt, gestureState) => true,
    })
  ).current;
  return (
    <View
      {...panResponder.panHandlers}
      style={{ background: 'grey', height: '100%', width: 40, padding: 10 }}>
      {children}
    </View>
  );
};

const ChildA = ({ index, setIndex, setY, text, DragHandle }) => {
  return (
    <View
      style={{
        flexDirection: 'row',
        justifyContent: 'space-between',
        backgroundColor: 'gold',
        padding: 10,
        borderBottomColor: 'black',
        borderBottomWidth: 1,
        height: CHILD_A_HEIGHT,
      }}>
      <DragHandle>
        <View
          style={{ backgroundColor: 'goldenrod', width: '100%', height: '100%' }}
        />
      </DragHandle>
      <Text>Child A: {text}</Text>
    </View>
  );
};

const ChildB = ({ index, setIndex, setY, text, DragHandle }) => {
  return (
    <View
      style={{
        flexDirection: 'row',
        justifyContent: 'space-between',
        backgroundColor: 'green',
        padding: 10,
        borderBottomColor: 'black',
        borderBottomWidth: 1,
        height: CHILD_B_HEIGHT,
      }}>
      <DragHandle>
      <View
          style={{ backgroundColor: 'lawngreen', width: '100%', height: '100%' }}
        />
      </DragHandle>
      <Text>Child B: {text}</Text>
    </View>
  );
};

export default Parent;
Run Code Online (Sandbox Code Playgroud)

当您拖动DragHandle组件时,这两个函数最初会触发一次,但在您继续拖动时不会再次触发:

console.log(gestureState.dy); // This works when the line below is removed :)
setY(gestureState); // This does not work :(
Run Code Online (Sandbox Code Playgroud)

如果我注释掉这一行: setY(gestureState); // This does not work :(

然后它上面的console.log 就起作用了。当您拖动它时,它会继续每秒记录多次:console.log(gestureState.dy); // This works when the line below is removed :)

因此,我认为这与在父状态更改时重新创建 Pan Responder 有关,但我不确定如何修复它。我也不确定为什么更简单的例子没有这个问题。

Muh*_*man 2

我通过重写你的代码来修复你的例子。您需要注意不要在每次渲染时重新创建泛响应器(这发生在您调用时setY)。您可以在每次渲染时切换乞讨者。实际上,setY正在重新渲染您的父组件,这会导致子组件的渲染

演示: https: //snack.expo.io/@nomi9995/drag2fix

import * as React from 'react';
import { useRef, useState, useEffect } from 'react';
import { Text, View, PanResponder } from 'react-native';

const CHILD_A = 'CHILD_A';
const CHILD_B = 'CHILD_B';
const CHILD_A_HEIGHT = 100;
const CHILD_B_HEIGHT = 200;

const all_items = [
  { type: CHILD_A, text: 'A', height:CHILD_A_HEIGHT },
  { type: CHILD_B, text: 'B', height:CHILD_B_HEIGHT },
  { type: CHILD_A, text: 'C', height:CHILD_A_HEIGHT },
];

const Parent = () => {
  const [y, setY] = useState(0);
  const [index, setIndex] = useState(null);
    const [items, setItems] = useState(all_items);
    
    const setPosition=(index,y)=>{
        const _items=items;
        _items[index]['position']=y;
        setItems(_items)
    }

  const heights = items.map((item) =>
    item.type === CHILD_A ? CHILD_A_HEIGHT : CHILD_B_HEIGHT
  );

  let heightsSum = 0;
  const heightsCumulative = heights.map(
    (elem) => (heightsSum = heightsSum + elem)
  );

  return (
    <View style={{ marginTop: 50 }}>
      <Text>Index: {index}</Text>
      <Text>Y: {y}</Text>
      <View style={{ height: heightsSum, backgroundColor: 'gold' }}>
        {items.map((item, itemIndex) => {
                    const isBeingDragged = itemIndex === index;
          const top = isBeingDragged ? item.position + y : item.position;
          if (item.type === CHILD_A) {
            return (
                            <Child
                                top={top}
                                isBeingDragged={isBeingDragged}
                                height={item.height}
                                backgroundColor="gold"
                                childBackgroundColor="goldenrod"
                                position={item.position}
                                setPosition={setPosition}
                                index={itemIndex}
                setIndex={setIndex}
                setY={setY}
                                text={item.text}
              />
            );
          }
          return (
                        <Child
                            top={top}
                            isBeingDragged={isBeingDragged}
                            height={item.height}
                            backgroundColor="green"
                            childBackgroundColor="lawngreen"
                            position={item.position}
                            setPosition={setPosition}
              index={itemIndex}
              setIndex={setIndex}
              setY={setY}
              text={item.text}
            />
          );
        })}
      </View>
    </View>
  );
};

const DragHandle = ({ index, setIndex, setY, children }) => {
  const panResponder = useRef(
    PanResponder.create({
      onStartShouldSetPanResponder: (evt, gestureState) => true,
      onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
      onMoveShouldSetPanResponder: (evt, gestureState) => true,
      onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
      onPanResponderGrant: (evt, gestureState) => {
        setIndex(index);
      },
      onPanResponderMove: (evt, gestureState) => {
        setY(gestureState.dy); // This does not work :(
      },
      onPanResponderTerminationRequest: (evt, gestureState) => true,
      onPanResponderRelease: (evt, gestureState) => {
                setY(0);
        setIndex(null);
            },
      onPanResponderTerminate: (evt, gestureState) => {},
      onShouldBlockNativeResponder: (evt, gestureState) => true,
    })
  ).current;
  return (
    <View
      {...panResponder.panHandlers}>
      {children}
    </View>
  );
};
 
const Child = ({ top, isBeingDragged, position, height, backgroundColor, childBackgroundColor, setPosition, text2, index, setIndex, setY, text }) => {
    return (
        <View 
        style={{
            top,
            width: '100%',
            position: position?'absolute':'relative',
            zIndex: isBeingDragged ? 1 : 0,
        }}
        key={index}
        onLayout={(e)=>position!==undefined?null:setPosition(index,e.nativeEvent.layout.y)}
        >
        <View
      style={{
        flexDirection: 'row',
        justifyContent: 'space-between',
        backgroundColor: backgroundColor,
        padding: 10,
        borderBottomColor: 'black',
        borderBottomWidth: 1,
        height: height,
            }}>
            <DragHandle index={index} setIndex={setIndex} setY={setY}>
        <View
          style={{ backgroundColor: childBackgroundColor, width: 20, height: '100%' }}
                />
            </DragHandle>
      <Text>Child {text2}: {index+1}</Text>
        </View>
        </View>
  );
};

export default Parent;

Run Code Online (Sandbox Code Playgroud)

在此输入图像描述