将状态传递给组件时 Reactjs 关闭

Reactjs closure when passing state to component

我得到了一个 React 功能组件:

const DataGrid = (props) =>
{          
    const [containerName, setContainerName] = useState("");                                                                   
    const [frameworkComponents, setFrameworkComponents] = useState(
      {customLoadingOverlay: LoadingOverlayTemplate,
      customNoRowsOverlay: UxDataGridCustomNoRows,
      editButton: params => <ViewAndDeleteSetting {...params}  
                                                  openAddConfigurationsWindow={openAddConfigurationsWindow}
                                                  onDeleteSetting={onDeleteSetting}/>,
     });

useEffect(async () =>
    {
      if(props.containerName && props.containerName !== "")
      {
        setContainerName(props.containerName);
      }
    },[props.containerName]);
.
.
.
const onDeleteSetting = async (settingKey) =>
{
  console.log("ON DELETE AND CONTAINER NAME:");
  console.log(containerName); //HERE THE CONTAINER NAME IS EMPTY
   ...
}
return (
  <UxDataGrid 
            frameworkComponents={frameworkComponents}/>
);

useEffect中的容器名存在且不为空。正如您在 onDeleteSetting 中的注释中看到的,调用此回调时 containerName 为空。我尝试在 setContainerName:

之后将其添加到 useEffect
setFrameworkComponents({customLoadingOverlay: LoadingOverlayTemplate,
        customNoRowsOverlay: UxDataGridCustomNoRows,
        editButton: params => <ViewAndDeleteSetting {...params}  
                                                         openAddConfigurationsWindow={openAddConfigurationsWindow}
                                                         onDeleteSetting={onDeleteSetting}/>,
            });

那没用。

如何在回调中获取名称?没有特别需要将 frameworkComponents 结构保留在状态中.. 如果您认为它更好

也可以将其移动到其他地方

您几乎可以肯定不应该将其存储在状态中。道具基本上是由 parent 控制的状态。只需从道具中使用它。将道具复制到状态是 usually not best practice.

如果您正在寻找一种非常罕见的情况,在这种情况下,根据 props 设置派生状态是有意义的,文档中的 this page 会告诉您如何使用挂钩来做到这一点。基本上,您不使用 useEffect,而是立即进行状态更新。

以下是链接文档的完整引述:

How do I implement getDerivedStateFromProps?

While you probably don’t need it, in rare cases that you do (such as implementing a <Transition> component), you can update the state right during rendering. React will re-run the component with updated state immediately after exiting the first render so it wouldn’t be expensive.

Here, we store the previous value of the row prop in a state variable so that we can compare:

function ScrollView({row}) {
  const [isScrollingDown, setIsScrollingDown] = useState(false);
  const [prevRow, setPrevRow] = useState(null);

  if (row !== prevRow) {
    // Row changed since last render. Update isScrollingDown.
    setIsScrollingDown(prevRow !== null && row > prevRow);
    setPrevRow(row);
  }

  return `Scrolling down: ${isScrollingDown}`;
}

This might look strange at first, but an update during rendering is exactly what getDerivedStateFromProps has always been like conceptually.

如果您按照他们在该示例中所做的方式进行操作,您的组件仍会在 containerName 设置为默认状态 ("") 的情况下呈现,只是它几乎会立即呈现re-render 已更新 containerName。这对于他们的过渡示例是有意义的,但是您可以通过使道具的 initial 值成为状态的 initial 值来避免这种情况,如下所示:

const DataGrid = (props) => {
    const [containerName, setContainerName] = useState(props.containerName); // *** ONLY USES THE INITIAL PROP VALUE
    const [frameworkComponents, setFrameworkComponents] = useState(
        // ...
     });

    // *** Updates the state value (on the next render) if the prop changes
    if (containerName !== props.containerName) {
        setContainerName(props.containerName);
    }

    // ...
};

但是,每次 containerName 道具发生变化时,您的组件都会渲染两次,这使我们回到了完整的循环:不要将其存储在状态中,只需从道具中使用它。 :-)


回过头来看组件作为一个整体,我认为您根本不需要任何状态信息,但是如果您的目标是避免 frameworkComponents 您通过 UxDataGrid进行不必要的更改,您可能需要 useMemo or React.memo 而不是 state.

例如,useMemo(但请继续阅读):

const DataGrid = ({containerName}) => {
    const frameworkComponents = useMemo(() => {
        const onDeleteSetting = async (settingKey) => {
            console.log("ON DELETE AND CONTAINER NAME:");
            console.log(containerName);
            // ...
        };
        return {
            customLoadingOverlay: LoadingOverlayTemplate,
            editButton: params => <ViewAndDeleteSetting {...params}  
                openAddConfigurationsWindow={openAddConfigurationsWindow}
                onDeleteSetting={onDeleteSetting} />,
        };
    }, [containerName]);

    return (
        <UxDataGrid frameworkComponents={frameworkComponents} />
    );
};

但是如果 componentName 是你唯一的道具,那么使用 React.memo:

可能会更简单
const DataGrid = React.memo(({containerName}) => {
    const onDeleteSetting = async (settingKey) => {
        console.log("ON DELETE AND CONTAINER NAME:");
        console.log(containerName);
        // ...
    };
    return (
        <UxDataGrid frameworkComponents={{
            customLoadingOverlay: LoadingOverlayTemplate,
            editButton: params => <ViewAndDeleteSetting {...params}  
                openAddConfigurationsWindow={openAddConfigurationsWindow}
                onDeleteSetting={onDeleteSetting} />,
        }} />
    );
});

React.memo 记忆你的组件,这样你的组件函数只会在 props 改变时再次调用。由于组件中的所有内容都需要根据 componentName 属性的变化进行更新,因此看起来很匹配(但我不知道 UxDataGrid 是什么)。

在你的 useEffect 中试试这个,更新时用新的 containerName 更新 onDeleteSetting 函数

.....
useEffect(async() => {
  if (props.containerName && props.containerName !== "") {
    setContainerName(props.containerName);
    
    // move this function here
    const onDeleteSetting = async(settingKey) => {
      console.log("ON DELETE AND CONTAINER NAME:");
      // use props.containerName since the state update is async
      console.log(props.containerName);
      ...
    }

    // update your components with the updated functions
    setFrameworkComponents(prevComponents => ({
      ...prevComponents,
      editButton: params => 
              <ViewAndDeleteSetting
                {...params}                                                  
                openAddConfigurationsWindow={openAddConfigurationsWindow}
                onDeleteSetting={onDeleteSetting}
              />,
    }));
  }
}, [props.containerName]);
.....

这应该提供具有更新功能的更新状态,如果有效,我可以添加更多详细信息。

问题在于我如何尝试将道具传递给 ViewAndDeleteSetting。如果你想将 prop 传递给单元格渲染组件,你不应该在 frameworkComponents 中这样做,而是需要在列定义中这样做:

useEffect(() =>
{
  let columns = [{headerName: '', cellRenderer: 'editButton', width: 90, editable: false, 
                  cellRendererParams: {
                    openAddConfigurationsWindow: openAddConfigurationsWindow,
                    onDeleteSetting: onDeleteSetting
                }},
                .. other columns
                ]

  setColumnDefinition(columns);
},[props.containerName]);

带有 cellRendererParams 的列会在名称更改时在 useEffect 中重新创建,然后组件可以通过其 props

定期访问此参数