【问题标题】:React creating dynamically refs with TypescriptReact 使用 Typescript 动态创建 refs
【发布时间】:2020-01-14 00:53:25
【问题描述】:

我有一个table 列表,其中每一行都有一个menu 按钮,为此我需要一个ref。我在我的项目中使用了 react mui,它是menu。我尝试过像这样创建参考:

const {rows} = props;
const refs = Array.from({length: rows.length}, a => React.useRef<HTMLButtonElement>(null));

然后尝试在每个button上像这样使用map里面的函数:

<Button
   ref={refs[index]}
   aria-controls="menu-list-grow"
   aria-haspopup="true"
   onClick={() => handleToggle(row.id)}
>Velg
</Button>
  <Popper open={!!checkIfOpen(row.id)} anchorEl={refs[index].current} keepMounted transition disablePortal>
    {({TransitionProps, placement}) => (
       <Grow
        {...TransitionProps}
        style={{transformOrigin: placement === 'bottom' ? 'center top' : 'center bottom'}}>
       <Paper id="menu-list-grow">
         <ClickAwayListener onClickAway={(e) => handleClose(e, refs[index].current)}>
          <MenuList>                                                        
            <MenuItem
             onClick={(e) => handleClose(e, refs[index].current)}>Profile</MenuItem>
           <MenuItem onClick={(e) => handleClose(e, refs[index].current)}>My account</MenuItem>
           <MenuItem onClick={(e) => handleClose(e, refs[index].current)}>Logout</MenuItem>
        </MenuList>
      </ClickAwayListener>
    </Paper>
  </Grow>
 )}
</Popper>

但是,然后我得到一个错误:

无法在回调中调用 React Hook "React.useRef"。反应 Hooks 必须在 React 函数组件或自定义 React 中调用 钩子函数 react-hooks/rules-of-hooks

我怎样才能动态地做到这一点,以便我可以在 map 函数中使用 refs。我已经尝试过答案中的建议,但我无法让它发挥作用。 这是codesandbox of the example

【问题讨论】:

标签: javascript reactjs react-ref


【解决方案1】:

useRef 与 React.createRef 并不完全相同。 最好叫它useInstanceField :)

所以,您的代码可能有点不同。

第一步:我们使用useRef来保存refs数组:

const {rows} = props;
const refs = useRef(Array.from({length: rows.length}, a => React.createRef()));

然后,在您的 map 函数中,我们将每个 ref 保存到 refs 数组中的索引:

<Button
   ref={refs.current[index]}
   aria-controls="menu-list-grow"
   aria-haspopup="true"
   onClick={() => handleToggle(row.id)}
>Velg
</Button>
  <Popper open={!!checkIfOpen(row.id)} anchorEl={refs.current[index].current} keepMounted transition disablePortal>
    {({TransitionProps, placement}) => (
       <Grow
        {...TransitionProps}
        style={{transformOrigin: placement === 'bottom' ? 'center top' : 'center bottom'}}>
       <Paper id="menu-list-grow">
         <ClickAwayListener onClickAway={(e) => handleClose(e, refs.current[index].current)}>
          <MenuList>                                                        
            <MenuItem
             onClick={(e) => handleClose(e, refs.current[index].current)}>Profile</MenuItem>
           <MenuItem onClick={(e) => handleClose(e, refs.current[index].current)}>My account</MenuItem>
           <MenuItem onClick={(e) => handleClose(e, refs.current[index].current)}>Logout</MenuItem>
        </MenuList>
      </ClickAwayListener>
    </Paper>
  </Grow>
 )}
</Popper>

如果你的长度改变了,你应该在useEffect处理它来改变refs的长度

您也可以使用其他方式:

1) 创建一个引用数组,但没有 React.createRef:

const {rows} = props;
const refs = useRef(new Array(rows.length));

在map中我们使用ref={el =&gt; refs.current[index] = el}来存储ref

<Button
   ref={el => refs.current[index] = el}
   aria-controls="menu-list-grow"
   aria-haspopup="true"
   onClick={() => handleToggle(row.id)}
>Velg
</Button>
  <Popper open={!!checkIfOpen(row.id)} anchorEl={refs.current[index].current} keepMounted transition disablePortal>
    {({TransitionProps, placement}) => (
       <Grow
        {...TransitionProps}
        style={{transformOrigin: placement === 'bottom' ? 'center top' : 'center bottom'}}>
       <Paper id="menu-list-grow">
         <ClickAwayListener onClickAway={(e) => handleClose(e, refs.current[index])}>
          <MenuList>                                                        
            <MenuItem
             onClick={(e) => handleClose(e, refs.current[index])}>Profile</MenuItem>
           <MenuItem onClick={(e) => handleClose(e, refs.current[index])}>My account</MenuItem>
           <MenuItem onClick={(e) => handleClose(e, refs.current[index])}>Logout</MenuItem>
        </MenuList>
      </ClickAwayListener>
    </Paper>
  </Grow>
 )}
</Popper>

【讨论】:

  • 我无法编译这个,我得到 Typescript 错误:Expected 0 arguments, but got 1. 对于这一行:React.createRef(null)
  • 对不起,是的,只需删除空参数:) 我已经更新了答案
  • 我也这样做了,但后来我得到:Type 'RefObject&lt;{}&gt;' is not assignable to type '((instance: HTMLButtonElement | null) =&gt; void) | RefObject&lt;HTMLButtonElement&gt; | null | undefined'. Type 'RefObject&lt;{}&gt;' is not assignable to type 'RefObject&lt;HTMLButtonElement&gt;'.
  • 看来打字稿不允许使用这种方式。所以我用 TypeScript codesandbox.io/s/wizardly-goldstine-33nw5 做了一个例子
  • 我尝试将您的解决方案实施到我的示例中,但随后出现错误:Type 'HTMLButtonElement | null' is not assignable to type 'never'. Type 'null' is not assignable to type 'never'. 如果我将参考更改为:const refs = React.useRef&lt;Array&lt;React.RefObject&lt;HTMLButtonElement&gt; | null&gt;&gt;([]);,则会出现错误:Type 'HTMLButtonElement | null' is not assignable to type 'RefObject&lt;HTMLButtonElement&gt; | null'. Property 'current' is missing in type 'HTMLButtonElement' but required in type 'RefObject&lt;HTMLButtonElement&gt;'跨度>
【解决方案2】:

这里是另一种选择:

const textInputRefs = useRef<(HTMLDivElement | null)[]>([])

...

const onClickFocus = (event: React.BaseSyntheticEvent, index: number) => {
    textInputRefs.current[index]?.focus()
};

...
{items.map((item, index) => (
    <textInput
        inputRef={(ref) => textInputs.current[index] = ref}
    />
    <Button
        onClick={event => onClickFocus(event, index)}
    />
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-09-22
    • 2021-06-08
    • 1970-01-01
    • 1970-01-01
    • 2021-01-22
    • 2022-12-20
    • 2016-04-02
    • 1970-01-01
    相关资源
    最近更新 更多