useRef
eg: 实现一个需求:点击按钮时 input 自动聚焦。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const HomePage = () => { const input = createRef<HTMLInputElement>()
const handle = () => { if (input.current) { input.current.focus() } }
return ( <div> <input ref={input} /> <button onClick={handle}>focus</button> </div> ) }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const HomePage = () => { const input = useRef<HTMLInputElement | null>(null)
const handle = () => { if (input.current) { input.current.focus() } }
return ( <div> <input ref={input} /> <button onClick={handle}>focus</button> </div> ) }
|
两者的区别在于:createRef 每次渲染会返回一个新的引用,而 useRef 返回的是相同的引用(persist)。对于函数式组件,每次 useState 会造成整个组件的重新渲染,但是 uesRef 可以保证引用不变,不会触发 re-render。
eg:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| const HomePage = () => { const [index, setIndex] = useState(1)
const click = () => { setTimeout(() => {
console.log(index) }, 3000) }
return ( <div> <span>current: {index}</span> <button onClick={() => setIndex((prev) => prev + 1)}>increase</button> <button onClick={click}>log index</button> </div> ) }
|
log 出来的并不是实时的 index,每次触发 setState 都会重新渲染,timeout 中拿到的都是当时的 index。使用 useRef 就可以拿到实时更改的结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| const HomePage = () => { const [index, setIndex] = useState(1) const ref = useRef<number | null>(null)
useEffect(() => { ref.current = index })
const click = () => { setTimeout(() => {
console.log(ref.current) }, 3000) }
return ( <div> <span>current: {index}</span> <button onClick={() => setIndex((prev) => prev + 1)}>increase</button> <button onClick={click}>log index</button> </div> ) }
|
useRef 还可拿到前一个值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const HomePage = () => { const [index, setIndex] = useState(1) const ref = useRef<number | null>(null)
useEffect(() => { ref.current = index })
return ( <div> <span>current: {index}</span> <span>prev: {ref.current}</span> <button onClick={() => setIndex((prev) => prev + 1)}>increase</button> </div> ) }
|
React 渲染 JSX 比 useEffect 快,所以已经渲染完成,才给 ref.current 赋值,ref 引用不变,不引起 re-render,所以渲染出来的是上一个值。
通过这个特性可以封装一个 usePrevious:
1 2 3 4 5 6 7 8 9
| const usePrevious = (value: any) => { const ref: any = useRef()
useEffect(() => { ref.current = value }, [value])
return ref.current }
|
还有一个特别的用处,可以用于记录 timer:
1 2 3 4 5 6 7 8 9
| const timer = useRef<NodeJS.Timeout | null>(null)
useEffect(() => { timer.current = setInterval(() => { }, 1000)
return () => clearInterval(timer.current as NodeJS.Timeout) })
|