React基础篇——ref&forwardRef
何时使用ref
通常,当你的组件需要“跳出” React 并与外部 API 通信时,你会用到 ref —— 通常是不会影响组件外观的浏览器 API。以下是这些罕见情况中的几个:
- 存储 timeout ID
- 存储和操作 DOM 元素
- 存储不需要被用来计算 JSX 的其他对象。
如果你的组件需要存储一些值,但不影响渲染逻辑,请选择 ref。
注意
- 在函数组件上不能使用
ref
函数组件没有实例,但是在函数组件内部是可以使用的ref
在类组件上得到类的实例,ref
在dom元素上得到dom的实例
如何产生ref
在react中有多种产生ref的方法
- 通过
React.createRef()
- 通过
useRef()
- 通过一个回调函数
(ref) => {}
类组件中使用ref
随着hook的流行,虽然类组件几乎不会在项目中使用,但是还是可以了解下 ref的使用
createRef()
在 class 组件 中调用 createRef
来声明一个 ref
createRef
返回一个对象,该对象只有一个属性:
current
:初始值为null
,你可以稍后设置为其他内容。如果你把 ref 对象作为 JSX 节点的ref
属性传递给 React,React 将设置其current
属性。
注意事项
createRef
总是返回一个 不同的 对象。这相当于你自己编写了{ current: null }
。- 在函数组件中,你可能想要使用
useRef
,因为它始终返回相同的对象。 const ref = useRef()
等同于const [ref, _] = useState(() => createRef(null))
。
使用
import { Component, createRef } from 'react';
class Form extends Component {
inputRef = createRef();
}
如果你现在将 ref={this.inputRef}
传递给 JSX 中的 <input>
,React 将把 input 的 DOM 节点赋值给 this.inputRef.current
。
import { Component, createRef } from 'react';
export default class Form extends Component {
inputRef = createRef();
handleClick = () => {
this.inputRef.current.focus();
}
render() {
return (
<>
<input ref={this.inputRef} />
<button onClick={this.handleClick}>
聚焦这个输入框
</button>
</>
);
}
}
替代方案
从使用 createRef
的 class 组件迁移到使用 useRef
的函数组件
export const Form = () => {
const inputRef = useRef(null)
const handleClick = () => {
inputRef.current.focus()
}
return <>
<input ref={inputRef}/>
<button onClick={handleClick}>
聚焦这个输入框
</button>
</>
}
为什么有了 createRef
还要设计 useRef
export const App = () => {
const [count,setCount]= useState(0)
const divRef1 = useRef(null)
const divRef2 = React.createRef()
console.log('useRef =====>',divRef1)
console.log('createRef =====>',divRef2)
return <>
<div ref={divRef1}>useRef</>
<div ref={divRef2}>createRef</div>
<button onClick={() => {setCount(count + 1)}>
点击
</button>
</>
}
第一次渲染
点击更改count重新渲染时
在上述例子中,可以看到
useRef
不会在视图更新时重新创建ref对象
但是createRef
每次更新都会创建新的ref对象
函数组件中使用ref
useRef()
使用
import React, { useRef, useState, useEffect } from "react";
function RefCom() {
const divRef1 = useRef(null);
const handleClick = () => {
console.log(divRef1)
}
return (
<>
<div ref={divRef1}>useRef</div>
<button
onClick={() => {handleClick}
>
点击
</button>
</>
);
}
export default RefCom;
- 在你的组件内,调用
useRef
Hook 并传入你想要引用的初始值作为唯一参数。例如,这里的 ref 引用的值是“null”: useRef
返回一个这样的对象:
{
current: null // 你向 useRef 传入的值
}
- 你可以用
ref.current
属性访问该 ref 的当前值。这个值是有意被设置为可变的,意味着你既可以读取它也可以写入它。就像一个 React 追踪不到的、用来存储组件信息。
特别之处
ref 像 state 一样,你可以让它指向任何东西:字符串、对象,甚至是函数。与 state 不同的是,ref 是一个普通的 JavaScript 对象,具有可以被读取和修改的 current
属性。
注意,组件不会在每次递增时重新渲染。 与 state 一样,React 会在每次重新渲染之间保留 ref。但是,设置 state 会重新渲染组件,更改 ref 不会!
ref 和 state 的不同之处
ref | state |
---|---|
useRef(initialValue) 返回 { current: initialValue } | useState(initialValue) 返回 state 变量的当前值和一个 state 设置函数 ( [value, setValue] ) |
更改时不会触发重新渲染 | 更改时触发重新渲染。 |
可变 —— 你可以在渲染过程之外修改和更新 current 的值。 | “不可变” —— 你必须使用 state 设置函数来修改 state 变量,从而排队重新渲染。 |
你不应在渲染期间读取(或写入) current 值。 | 你可以随时读取 state。但是,每次渲染都有自己不变的 state 快照。 |
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
}
return (
<button onClick={handleClick}>
你点击了 {count} 次
</button>
);
}
import { useRef } from 'react';
export default function Counter() {
let countRef = useRef(0);
function handleClick() {
// 这样并未重新渲染组件!
countRef.current = countRef.current + 1;
}
return (
<button onClick={handleClick}>
你点击了 {countRef.current} 次
</button>
);
}
ref数据的更新无法触发组件的重新渲染
ref转发
- 参数,传递的是函数组件,不能是类组件,并且,函数组件需要有第二个参数来得到ref
- 返回值,返回一个新的组件
将dom节点暴露给父组件
const App = () => {
const ref = useRef()
/* 如果不使用forwardRef ,这句代码就会报错 */
const clickHandle = () => {
/* ref.current:会得到DivDom的实例 */
consle.log(ref)
}
return <>
<Com ref={ref}></Com>
<button>点击</button>
</>
}
Const Com = forwardRef((props,ref) =>{
return <div ref={ref}>DivDom</div>
})
暴露出一个对象,而不是Dom节点实例
将收到的 ref
传递给 useImperativeHandle
并指定你想要暴露给 ref
的值:
const App = () => {
const ref = useRef()
const clickHandle = () => {
consle.log(ref)
}
/* ref.current 会指向 useImperativeHandle 返回的对象*/
return <>
<Com ref={ref}></Com>
<button>点击</button>
</>
}
Const Com = forwardRef((props,ref) =>{
const inputRef = useRef(null);
const innerFuncHandle = () => {
console.log("Com组件中的方法")
}
useImperativeHandle(ref, () => {
return {
focus() {
inputRef.current.focus();
},
scrollIntoView() {
inputRef.current.scrollIntoView();
},
innerFuncHandle
};
}, []);
return <input ref={inputRef}/>
})
下一篇分享下在使用react组件时的一些优化点
转载自:https://juejin.cn/post/7251786381483114551