【问题标题】:Reactjs closure when passing state to component将状态传递给组件时的Reactjs关闭
【发布时间】:2021-04-17 11:09:15
【问题描述】:

我得到了一个反应功能组件:

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 结构留在状态中。如果您认为它更好,它也可以移动到其他地方

【问题讨论】:

  • containerNameonDeleteSetting 处理程序中为空,因为您的frameworkComponents 变量仅在最初设置。即使你在调用setContainerName 之后尝试设置它,React 的useState 也会更新isn't immediate。如果你真的想做这样的事情,你可以添加另一个 useEffectcontainerName 作为依赖并在其中调用 setFrameworkComponents。但是您的整个代码结构看起来像是不好的做法,请尝试重构以避免在您的 state 中设置整个组件
  • @Kapobajza 你能用你建议的重构方式添加答案吗?
  • 与过时关闭相关的问题,stackoverflow.com/questions/63307310/…,这可能会对您有所帮助。

标签: javascript reactjs closures ag-grid ag-grid-react


【解决方案1】:

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

这应该为更新状态提供更新功能,如果有效,我可以添加更多细节。

【讨论】:

    【解决方案2】:

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

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

    这是来自链接文档的完整引用:

    如何实现 getDerivedStateFromProps?

    虽然您可能don’t need it,但在极少数情况下(例如实现&lt;Transition&gt; 组件),您可以在渲染期间更新状态。 React 会在退出第一次渲染后立即重新运行具有更新状态的组件,因此不会很昂贵。

    在这里,我们将 row prop 的先前值存储在状态变量中,以便我们进行比较:

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

    一开始这可能看起来很奇怪,但渲染期间的更新正是 getDerivedStateFromProps 在概念上一直以来的样子。

    如果您按照他们在该示例中的操作方式进行操作,您的组件仍会在将 containerName 设置为默认状态 ("") 的情况下进行渲染,只是它几乎会立即使用更新后的状态重新渲染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 prop 发生变化时,您的组件都会渲染两次,这让我们回到了原点:不要将它存储在 state 中,只需从 props 中使用它。 :-)


    回过头来看看整个组件,我认为您根本不需要任何状态信息,但如果您的目标是避免 frameworkComponents 不必要地传递 UxDataGrid 更改,您可能想要useMemoReact.memo 而不是状态。

    例如,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 记忆你的组件,这样你的组件函数只会在道具改变时被再次调用。由于组件中的所有内容都需要根据 componentName 属性的变化进行更新,这看起来很匹配(但我不知道 UxDataGrid 是什么)。

    【讨论】:

    • 我尝试使用 useMemo 但我仍然得到一个空字符串
    • @YonatanNir - 如果是这样,那么要么你没有正确实现它,要么UxDataView 没有使用最新的onDeleteSetting
    • useMemo 本身似乎正在工作,因为我将控制台日志放在那里,我看到它首先使用空字符串呈现,但是当我使用新道具触发呈现时,它确实再次调用 useMemo。只是由于某种原因,里面的回调仍然没有使用最新的数据
    • 我什至试图在没有 useMemo 的情况下定义结构,所以它每次都会重新创建,但它仍然不起作用
    • @YonatanNir - 这听起来像是UxDataView 的问题(或这种使用方式)。特别是,我注意到其中一个属性是创建删除按钮的函数。听起来这可能会被使用,然后在重新渲染组件时不会被重用。
    【解决方案3】:

    问题在于我如何尝试将道具传递给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 定期访问此参数

    【讨论】:

      猜你喜欢
      • 2021-01-03
      • 1970-01-01
      • 1970-01-01
      • 2021-11-12
      • 1970-01-01
      • 2019-03-08
      • 2018-01-27
      • 2016-08-17
      • 2017-08-12
      相关资源
      最近更新 更多