useRef

useRef

  • mutable ref
  • presist

eg: 实现一个需求:点击按钮时 input 自动聚焦。

  • createRef 实现
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>
)
}
  • useRef 实现
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(() => {
/**
* 2
* 3
* 4
*/
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(() => {
/**
* 4
* 4
* 4
*/
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(() => {
// do something
}, 1000)

return () => clearInterval(timer.current as NodeJS.Timeout)
})