据我所知,我可以像这样为单个元素使用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>
我见过这个,所以才会这样。但是,我仍然不知道如何在这个简单的案例中实现这个建议。
ref最初只是一个{current: null}对象。useRef在组件渲染之间保持对该对象的引用。当前值主要用于组件引用,但可以保存任何内容。
在某些时候应该有一个refs数组。如果数组的长度可能在不同的渲染之间变化,数组应该相应地缩放:
const arrLength = arr.length;
const [elRefs, setElRefs] = React.useState([]);
React.useEffect(() => {
// add or remove refs
setElRefs((elRefs) =>
Array(arrLength)
.fill()
.map((_, i) => elRefs[i] || createRef()),
);
}, [arrLength]);
return (
<div>
{arr.map((el, i) => (
<div ref={elRefs[i]} style={...}>
...
</div>
))}
</div>
);
这段代码可以通过展开useEffect并将useState替换为useRef来优化,但应该注意的是,在呈现函数中执行副作用通常被认为是一种糟糕的做法:
const arrLength = arr.length;
const elRefs = React.useRef([]);
if (elRefs.current.length !== arrLength) {
// add or remove refs
elRefs.current = Array(arrLength)
.fill()
.map((_, i) => elRefs.current[i] || createRef());
}
return (
<div>
{arr.map((el, i) => (
<div ref={elRefs.current[i]} style={...}>
...
</div>
))}
</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>
您可以使用数组(或对象)来跟踪所有的引用,并使用方法向数组添加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属于哪个元素了。
React使用键道具来跟踪项目。因此,如果你按键存储你的引用,就不会有任何问题:
const useRefs = () => {
const refs = useRef<Record<string,HTMLElement | null>>({})
const setRefFromKey = (key: string) => (element: HTMLElement | null) => {
refs.current[key] = element;
}
return {refs: refs.current, setRefFromKey};
}
const Comp = ({ items }) => {
const {refs, setRefFromKey} = useRefs()
const refsArr = Object.values(refs) // your array of refs here
return (
<div>
{items.map(item => (
<div key={item.id} ref={setRefFromKey(item.id)}/>
)}
</div>
)
}
请注意,React在卸载一个项目时,将使用null调用所提供的函数,该函数将在对象中将匹配的键项设置为null,因此所有内容都将是最新的。