据我所知,我可以像这样为单个元素使用refs:
const { useRef, useState, useEffect } = React;
const App = () => {
const elRef = useRef();
const [elWidth, setElWidth] = useState();
useEffect(() => {
setElWidth(elRef.current.offsetWidth);
}, []);
return (
<div>
<div ref={elRef} style={{ width: "100px" }}>
Width is: {elWidth}
</div>
</div>
);
};
ReactDOM.render(
<App />,
document.getElementById("root")
);
<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
我如何实现这个元素数组?显然不是这样的:(我知道,即使我没有尝试:)
const { useRef, useState, useEffect } = React;
const App = () => {
const elRef = useRef();
const [elWidth, setElWidth] = useState();
useEffect(() => {
setElWidth(elRef.current.offsetWidth);
}, []);
return (
<div>
{[1, 2, 3].map(el => (
<div ref={elRef} style={{ width: `${el * 100}px` }}>
Width is: {elWidth}
</div>
))}
</div>
);
};
ReactDOM.render(
<App />,
document.getElementById("root")
);
<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
我见过这个,所以才会这样。但是,我仍然不知道如何在这个简单的案例中实现这个建议。
最简单和最有效的方法是根本不使用useRef。只需使用一个回调ref,在每次渲染时创建一个新的ref数组。
function useArrayRef() {
const refs = []
return [refs, el => el && refs.push(el)]
}
Demo
<div id="root"></div>
<script type="text/babel" defer>
const { useEffect, useState } = React
function useArrayRef() {
const refs = []
return [refs, el => el && refs.push(el)]
}
const App = () => {
const [elements, ref] = useArrayRef()
const [third, setThird] = useState(false)
useEffect(() => {
console.log(elements)
}, [third])
return (
<div>
<div ref={ref}>
<button ref={ref} onClick={() => setThird(!third)}>toggle third div</button>
</div>
<div ref={ref}>another div</div>
{ third && <div ref={ref}>third div</div>}
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
</script>
<script src="https://unpkg.com/@babel/standalone@7/babel.min.js"></script>
<script src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
更新
新React文档显示了一个使用地图的推荐方法。
点击这里查看测试版(2022年12月)
有两种方法
对多个当前元素使用一个引用
const inputRef = useRef([]);
inputRef.current[idx].focus();
<input
ref={el => inputRef.current[idx] = el}
/>
const {useRef} = React;
const App = () => {
const list = [...Array(8).keys()];
const inputRef = useRef([]);
const handler = idx => e => {
const next = inputRef.current[idx + 1];
if (next) {
next.focus()
}
};
return (
<div className="App">
<div className="input_boxes">
{list.map(x => (
<div>
<input
key={x}
ref={el => inputRef.current[x] = el}
onChange={handler(x)}
type="number"
className="otp_box"
/>
</div>
))}
</div>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>
使用ref的数组
正如上面的帖子所说,不建议这样做,因为官方指南(以及内部棉绒检查)不允许它通过。
不要在循环、条件或嵌套函数中调用hook。相反,总是在React函数的顶层使用Hooks。通过遵循此规则,您可以确保在每次组件呈现时以相同的顺序调用hook。
然而,由于这不是我们目前的情况,下面的演示仍然可以工作,只是不推荐。
const inputRef = list.map(x => useRef(null));
inputRef[idx].current.focus();
<input
ref={inputRef[idx]}
/>
const {useRef} = React;
const App = () => {
const list = [...Array(8).keys()];
const inputRef = list.map(x => useRef(null));
const handler = idx => () => {
const next = inputRef[idx + 1];
if (next) {
next.current.focus();
}
};
return (
<div className="App">
<div className="input_boxes">
{list.map(x => (
<div>
<input
key={x}
ref={inputRef[x]}
onChange={handler(x)}
type="number"
className="otp_box"
/>
</div>
))}
</div>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>
您可以使用数组(或对象)来跟踪所有的引用,并使用方法向数组添加ref。
注意:如果你添加和删除引用,你必须在每个渲染周期清空数组。
import React, { useRef } from "react";
const MyComponent = () => {
// intialize as en empty array
const refs = useRefs([]); // or an {}
// Make it empty at every render cycle as we will get the full list of it at the end of the render cycle
refs.current = []; // or an {}
// since it is an array we need to method to add the refs
const addToRefs = el => {
if (el && !refs.current.includes(el)) {
refs.current.push(el);
}
};
return (
<div className="App">
{[1,2,3,4].map(val => (
<div key={val} ref={addToRefs}>
{val}
</div>
))}
</div>
);
}
工作示例
https://codesandbox.io/s/serene-hermann-kqpsu
我们可以使用数组ref来记住ref列表:
import { RefObject, useRef } from 'react';
type RefObjects<T> = RefObject<T>[];
function convertLengthToRefs<T>(
length: number,
initialValue: T | null,
): RefObjects<T> {
return Array.from(new Array(length)).map<RefObject<T>>(() => ({
current: initialValue,
}));
}
export function useRefs<T>(length: number, initialValue: T | null = null) {
const refs = useRef<RefObjects<T>>(convertLengthToRefs(length, initialValue));
return refs.current;
}
这是一个演示:
const dataList = [1, 2, 3, 4];
const Component: React.FC = () => {
const refs = useRefs<HTMLLIElement>(dataList.length, null);
useEffect(() => {
refs.forEach((item) => {
console.log(item.current?.getBoundingClientRect());
});
}, []);
return (
<ul>
{dataList.map((item, index) => (
<li key={item} ref={refs[index]}>
{item}
</li>
))}
</ul>
);
};