简体   繁体   English

将 state 传递给组件时 Reactjs 关闭

[英]Reactjs closure when passing state to component

I got a react functional component:我有一个反应功能组件:

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}/>
);

The container name inside useEffect exists and is not empty. useEffect 中的容器名称存在且不为空。 As you can see in the comment in onDeleteSetting , the containerName is empty when this callback is invoked.正如您在onDeleteSetting的注释中看到的那样,调用此回调时 containerName 为空。 I tried adding this to the useEffect after setContainerName :我尝试在setContainerName之后将此添加到useEffect

setFrameworkComponents({customLoadingOverlay: LoadingOverlayTemplate,
        customNoRowsOverlay: UxDataGridCustomNoRows,
        editButton: params => <ViewAndDeleteSetting {...params}  
                                                         openAddConfigurationsWindow={openAddConfigurationsWindow}
                                                         onDeleteSetting={onDeleteSetting}/>,
            });

That didn't work.那没有用。

How can I get the name inside the callback?如何在回调中获取名称? There is no special need to leave that frameworkComponents struct in the state.. it can also be moved to somewhere else if you think its better没有特别需要将frameworkComponents结构留在 state 中。如果您认为它更好,它也可以移动到其他地方

You almost certainly shouldn't be storing it in state.您几乎可以肯定不应该将其存储在 state 中。 Props are essentially state controlled by the parent. Props 本质上是由父级控制的 state。 Just use it from props.只需从道具中使用它。 Copying props to state is usually not best practice .将道具复制到 state 通常不是最佳实践

If you're looking at one of the very rare situations where it makes sense to set derived state based on props, this page in the documentation tells you how to do that with hooks.如果您正在查看一种非常罕见的情况,其中基于 props 设置派生 state 是有意义的,文档中的此页面将告诉您如何使用钩子来做到这一点。 Basically, you don't use useEffect , you do your state update right away.基本上,您不使用useEffect ,而是立即进行 state 更新。

Here's a full quote from the linked documentation:这是链接文档的完整引用:

How do I implement getDerivedStateFromProps?如何实现 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.虽然您可能不需要它,但在极少数情况下您需要它(例如实现<Transition>组件),您可以在渲染期间立即更新 state。 React will re-run the component with updated state immediately after exiting the first render so it wouldn't be expensive. React 将在退出第一个渲染后立即使用更新的 state 重新运行组件,因此不会很昂贵。

Here, we store the previous value of the row prop in a state variable so that we can compare:在这里,我们将 row prop 的先前值存储在 state 变量中,以便我们进行比较:

 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.起初这可能看起来很奇怪,但渲染期间的更新正是 getDerivedStateFromProps 在概念上一直以来的样子。

If you did it the same way they did in that example, your component would still render with containerName set to the default state ( "" ), it's just that it will then almost immediately re-render with the updated containerName .如果您按照他们在该示例中的操作方式进行操作,您的组件仍将使用设置为默认 state ( "" ) 的containerName进行渲染,只是它几乎会立即使用更新后的containerName重新渲染。 That makes sense for their example of a transition, but you could avoid that by making the prop's initial value the state's initial value, like this:这对于他们的过渡示例是有意义的,但是您可以通过将道具的初始值设为状态的初始值来避免这种情况,如下所示:

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);
    }

    // ...
};

Every time the containerName prop changes, though, your component will render twice, which brings us back full circle to: Don't store it in state, just use it from props.但是,每次containerName属性更改时,您的组件都会渲染两次,这让我们回到了原点:不要将其存储在 state 中,只需从属性中使用它即可。 :-) :-)


Stepping back and looking at the component as a whole, I don't think you need any state information at all, but if your goal is to avoid having the frameworkComponents you pass UxDataGrid change unnecessarily, you probably want useMemo or React.memo rather than state.退一步看整个组件,我认为您根本不需要任何 state 信息,但如果您的目标是避免让您传递UxDataGridframeworkComponents发生不必要的更改,您可能需要useMemoReact.memo而不是state。

For instance, with useMemo (but keep reading):例如,使用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} />
    );
};

But if componentName is your only prop, it may well be even simpler with React.memo :但是如果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 memoizes your component, so that your component function is only ever called again when the props change. React.memo记住您的组件,以便您的组件 function 仅在道具更改时才会再次调用。 Since everything in the component needs to update based on the componentName prop changing, that looks like a good match (but I don't know what UxDataGrid is).由于组件中的所有内容都需要根据componentName属性的变化进行更新,这看起来很匹配(但我不知道UxDataGrid是什么)。

Try this in your useEffect, update the onDeleteSetting function with the new containerName when it's updated在你的 useEffect 中试试这个,更新 onDeleteSetting function 时使用新的 containerName

.....
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]);
.....

This should provide the updated state with the updated function, if it works, I can add more details.这应该提供更新的 state 和更新的 function,如果有效,我可以添加更多细节。

The problem was with how I tried passing props to ViewAndDeleteSetting .问题在于我如何尝试将道具传递给ViewAndDeleteSetting If you want to pass prop to a cell rendered component, you shouldn't be doing it in frameworkComponents , but rather you need to do it in the column definition like this:如果要将 prop 传递给单元格渲染组件,则不应在frameworkComponents中执行此操作,而是需要在列定义中执行此操作,如下所示:

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

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

The columns with the cellRendererParams do gets recreated in the useEffect when the name changes, and then the component can access this params regularly via its props当名称更改时,具有cellRendererParams的列会在useEffect中重新创建,然后组件可以通过其 props 定期访问此参数

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM