在 React 中将事件从父组件发送到子组件

问题描述 投票:0回答:1

我想将一些自定义事件从父React组件转发到一些子组件,以便它们可以根据收到的事件做出决定,而无需父组件直接管理子组件的状态。 这是一个简化的示例,用于展示我正在尝试的想法。

家长控制.tsx

export default function ParentControl() {
    const divRef = useRef<HTMLDivElement>(null);

    const dispatchEvent = (e: CustomEvent) => {
        divRef.current.dispatchEvent(e);
    };  

    return (
        <div ref={divRef} >
            <Child name="Child1" dispatcher={divRef} />
            <Child name="Child2" dispatcher={divRef} />

            <button onClick={() => dispatchEvent(new CustomEvent('prev', {detail: ''})) }>
                Prev
            </button>
            <button onClick={() => dispatchEvent(new CustomEvent('next', {detail: ''})) }>
                Next
            </button>
        </div>
    );  
}

Child.tsx

export type ChildProps = {
    name: string;
    dispatcher: RefObject<HTMLElement>;
};

export default function Child({ name, dispatcher }: ChildProps) {
    const [index, setIndex] = useState(0);

    const selectPrev = (event: CustomEvent) => {
        console.log(`Received event: ${event.type}, index: ${index}`);
        setIndex(index - 1);
    };  

    const selectNext = (event: CustomEvent) => {
        console.log(`Received event: ${event.type}, index: ${index}`);
        setIndex(index + 1);
    };  

    const printState = () => {
        console.log(`printState(): index: ${index}`);
    };  

    useEffect(() => {
        const dispatch = dispatcher.current;

        dispatch.addEventListener("next", selectNext);
        dispatch.addEventListener("prev", selectPrev);

        return () => {
            dispatch.removeEventListener("next", selectNext);
            dispatch.removeEventListener("prev", selectPrev);
        };  
    }, []);

    return (
        <div>
            <div>
                {name} Index: {index}
            </div>
            <button onClick={() => printState()}>Print</button>
        </div>
    );  
}

运行这个我看到

selectNext()
selectPrev()
方法确实在父组件调度事件时被调用,但是这些方法只能看到子组件中的初始索引状态 = 0,因此它们只能将索引更改为-1或1。这些回调如何看到孩子当前的真实状态?

是否可以使用事件(稍后可能包括多种类型)以某种方式完成此任务,而不必使用 React 的 Context API?

reactjs events
1个回答
0
投票

您的

useEffect
deps 是一个空数组 (
[]
)。如果变量在效果内部被引用并且未包含在 deps 数组中(
selectNext
selectPrev
),通常表明存在问题。这是因为效果内部使用的任何未在 deps 中引用的闭包都可能是过时的并保存过时的值。

您的代码可以通过将它们添加到

useEffect
部门来修复。请注意,由于它们会在每次渲染时发生变化(导致循环),因此我还将这些方法包装在
useCallback
中,并将
index
放入 deps 数组中,以便在索引更改时刷新闭包。这反过来又会刷新效果。

  const selectPrev = useCallback(
    (event: CustomEvent) => {
      console.log(`Received event: ${event.type}, index: ${index}`);
      setIndex(index - 1);
    },
    [index]
  );

  const selectNext = useCallback(
    (event: CustomEvent) => {
      console.log(`Received event: ${event.type}, index: ${index}`);
      setIndex(index + 1);
    },
    [index]
  );

  const printState = () => {
    console.log(`printState(): index: ${index}`);
  };

  useEffect(() => {
    const dispatch = dispatcher.current;

    dispatch.addEventListener("next", selectNext);
    dispatch.addEventListener("prev", selectPrev);

    return () => {
      dispatch.removeEventListener("next", selectNext);
      dispatch.removeEventListener("prev", selectPrev);
    };
  }, [selectPrev, selectNext]);

但是我必须强调,你所做的事情在 React 中被认为是一种非常奇怪的模式,主要有两个原因:

  • 希望组件管理自己的状态,但根据某些父级中的控制逻辑同步该状态的总体目标是“通常”通过提升状态来解决。如果不这样做,最好了解您的用例和驱动程序。可能有一个合法的理由,因此是“通常”。 在子级的 DOM 元素上调度事件有点滥用该机制。这通常不应该被用作“免费事件总线”。其一,它的语义/启发式作为通用事件总线没有意义。您可能应该传递一个与 DOM 无关的简单事件总线实现。另外,
  • useImperativeHandle
    可能会更好地满足你想要做的事情。
© www.soinside.com 2019 - 2024. All rights reserved.