Bro*_*nBe 13 javascript reactjs react-hooks
I looking for a solution about get an array of DOM elements with react useRef() hook.
example:
const Component = () =>
{
// In `items`, I would like to get an array of DOM element
let items = useRef(null);
return <ul>
{['left', 'right'].map((el, i) =>
<li key={i} ref={items} children={el} />
)}
</ul>
}
Run Code Online (Sandbox Code Playgroud)
How can I achieve this?
sky*_*yer 16
useRef只是部分类似于React的ref(只是具有的字段的对象的结构current)。
useRef挂钩旨在在渲染之间存储一些数据,并且更改数据不会触发重新渲染(与之不同useState)。
还要提醒您:最好避免在循环或中初始化钩子if。这是钩子的第一法则。
考虑到这一点,我们:
useRefcreateRef()我们可以使用.current符号来引用列表
const Component = () => {
let refs = useRef([React.createRef(), React.createRef()]);
useEffect(() => {
refs.current[0].current.focus()
}, []);
return (<ul>
{['left', 'right'].map((el, i) =>
<li key={i}><input ref={refs.current[i]} value={el} /></li>
)}
</ul>)
}
Run Code Online (Sandbox Code Playgroud)这是我们可以安全地修改数组(例如,通过更改其长度)。但是请不要忘记,存储由突变的数据useRef不会触发重新渲染。因此,要更改长度以重新渲染,我们需要参与useState。
const Component = () => {
const [length, setLength] = useState(2);
const refs = useRef([React.createRef(), React.createRef()]);
function updateLength({ target: { value }}) {
setLength(value);
refs.current = refs.current.splice(0, value);
for(let i = 0; i< value; i++) {
refs.current[i] = refs.current[i] || React.createRef();
}
refs.current = refs.current.map((item) => item || React.createRef());
}
useEffect(() => {
refs.current[refs.current.length - 1].current.focus()
}, [length]);
return (<>
<ul>
{refs.current.map((el, i) =>
<li key={i}><input ref={refs.current[i]} value={i} /></li>
)}
</ul>
<input value={refs.current.length} type="number" onChange={updateLength} />
</>)
}
Run Code Online (Sandbox Code Playgroud)
另外,请勿refs.current[0].current在初次渲染时尝试访问-这会引发错误。
说
return (<ul>
{['left', 'right'].map((el, i) =>
<li key={i}>
<input ref={refs.current[i]} value={el} />
{refs.current[i].current.value}</li> // cannot read property `value` of undefined
)}
</ul>)
Run Code Online (Sandbox Code Playgroud)
所以你要么守卫
return (<ul>
{['left', 'right'].map((el, i) =>
<li key={i}>
<input ref={refs.current[i]} value={el} />
{refs.current[i].current && refs.current[i].current.value}</li> // cannot read property `value` of undefined
)}
</ul>)
Run Code Online (Sandbox Code Playgroud)
或通过useEffect钩子访问它。原因:refs是在元素渲染之后绑定的,因此在渲染第一次运行时尚未初始化。
beq*_*eqa 14
我会稍微扩展一下skyboyer的回答。对于性能优化(并避免潜在的莫名其妙的错误),你可能更愿意使用useMemo来代替useRef。因为 useMemo 接受回调作为参数而不是值,所以React.createRef只会在第一次渲染后初始化一次。在回调中,您可以返回一个createRef值数组并适当地使用该数组。
初始化:
const refs= useMemo(
() => Array.from({ length: 3 }).map(() => createRef()),
[]
);
Run Code Online (Sandbox Code Playgroud)
这里的空数组(作为第二个参数)告诉 React 只初始化 refs 一次。如果引用计数发生变化,您可能需要[x.length]作为“deps 数组”传递并动态创建引用:Array.from({ length: x.length }).map(() => createRef()),
用法:
refs[i+1 % 3].current.focus();
Run Code Online (Sandbox Code Playgroud)
小智 5
获取父引用并操纵孩子的
const Component = () => {
const ulRef = useRef(null);
useEffect(() => {
ulRef.current.children[0].focus();
}, []);
return (
<ul ref={ulRef}>
{['left', 'right'].map((el, i) => (
<li key={i}>
<input value={el} />
</li>
))}
</ul>
);
};Run Code Online (Sandbox Code Playgroud)
我以这种方式工作,我认为这比其他建议的答案更简单。
您可以将每个映射项分隔为组件,而不是使用引用数组或类似的东西。当你分开它们时,你可以useRef独立使用 s:
const DATA = [
{ id: 0, name: "John" },
{ id: 1, name: "Doe" }
];
//using array of refs or something like that:
function Component() {
const items = useRef(Array(DATA.length).fill(createRef()));
return (
<ul>
{DATA.map((item, i) => (
<li key={item.id} ref={items[i]}>
{item.name}
</li>
))}
</ul>
);
}
//seperate each map item to component:
function Component() {
return (
<ul>
{DATA.map((item, i) => (
<MapItemComponent key={item.id} data={item}/>
))}
</ul>
);
}
function MapItemComponent({data}){
const itemRef = useRef();
return <li ref={itemRef}>
{data.name}
</li>
}Run Code Online (Sandbox Code Playgroud)
如果您提前知道数组的长度(在示例中这样做),您可以简单地创建一个引用数组,然后按索引分配每个引用:
const Component = () => {
const items = Array.from({length: 2}, a => useRef(null));
return (
<ul>
{['left', 'right'].map((el, i)) => (
<li key={el} ref={items[i]}>{el}</li>
)}
</ul>
)
}
Run Code Online (Sandbox Code Playgroud)