最近开始学习react,记录一下学习的过程。
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。useRef是其中一个常用的hook。
useRef最基本的用法,是在函数组件中操作DOM。在类组件中对应的api为createRef。我们来看一下两个api的基本使用、两个API的区别以及useRef的妙用。
一、DOM相关操作
我们使用createRef和useRef,分别在类组件和函数组件中去做到同一个事情:父组件获取子组件的state和方法
1、createRef
// 父组件
class ParentComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef(null);
}
render() {
return (
<div>
<ChildComponent ref={this.myRef}></ChildComponent>
<button
onClick={() => {
alert(this.myRef.current.state.childName);
}}
>
get child childName from parent
</button>
<button
onClick={() => {
this.myRef.current.sayHello();
}}
>
get child function from parent
</button>
</div>
);
}
}
// 子组件
class ChildComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
childName: "mongo",
};
}
sayHello() {
alert("hello! " + this.state.childName);
}
render() {
return (
<div>
<button
onClick={() => {
this.sayHello();
}}
>
alert function by child
</button>
</div>
);
}
}
2、useRef
const ParentComponentByUseRef = () => {
const myRef = useRef();
return (
<div>
<ChildComponentByUseRef ref={myRef}></ChildComponentByUseRef>
<button
onClick={() => {
alert(myRef.current.childName);
}}
>
get childName by parent
</button>
<button
onClick={() => {
myRef.current.sayHello();
}}
>
get function by parent
</button>
</div>
);
};
const ChildComponentByUseRef = forwardRef((props, ref) => {
// 暴露function、state和DOM结构给父元素
useImperativeHandle(ref, () => ({
sayHello,
childName,
ref
}));
const [childName, setChildName] = useState("mongo");
const sayHello = () => {
alert("hello! " + childName);
};
return (
<div ref={ref}>
<button
onClick={() => {
sayHello();
}}
>
get function by child
</button>
<button
onClick={() => {
setChildName("mongowoo");
}}
>
change childName
</button>
</div>
);
});
export default forwardRef(ParentComponentByUseRef);
tips:hooks中如果要进行refs转发,要配合fowardRef和useImperativeHandle使用
fowardRef : React.forwardRef 会创建一个React组件,这个组件能够将其接受的 ref 属性转发到其组件树下的另一个组件中。这种技术并不常见,但在以下两种场景中特别有用:
- 转发 refs 到 DOM 组件
- 在高阶组件中转发 refs
useImperativeHandle:useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle 应当与 forwardRef 一起使用
二、createRef 和 useRef的区别
我们知道useRef是hooks新增的API,在类函数中肯定无法使用。那createRef在函数组件中可以使用吗?我们试一下。写一个简单的点击button设置input focus的效果。
const createRefInFunction = () => {
const myRef = createRef(null);
return (
<div>
<input ref={myRef}></input>
<button
onClick={() => {
myRef.current.focus();
}}
>
set focus
</button>
</div>
);
};
export default createRefInFunction;
结果发现createRef在函数组件中也是可用的,那为什么还要新增一个useRef API呢?
看下useRef定义,我们发现官方文档已经说出了它区别于createRef的地方:
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。
由此可以知道,createRef 每次渲染都会返回一个新的引用,而 useRef 每次都会返回相同的引用。 从文字来看可能比较难理解,我们写一个简单demo就能明白了。
const useRefAndCreateRefDiffrent = () => {
const [renderIndex, setRenderIndex] = useState(1);
const refFromUseRef = useRef();
const refFromCreateRef = createRef();
if (!refFromUseRef.current) {
refFromUseRef.current = renderIndex;
}
if (!refFromCreateRef.current) {
refFromCreateRef.current = renderIndex;
}
return (
<div>
<p>current render index is: {renderIndex}</p>
<p>refFromUseRef render index is: {refFromUseRef.current}</p>
<p>refFromCreateRef render index is: {refFromCreateRef.current}</p>
<button
onClick={() => {
setRenderIndex((prev) => prev + 1);
}}
>
增加render index
</button>
</div>
);
};
根据结果可以看到,index已经增加到了7,createRef显示7,而useRef只显示1。可以知道,随着render重新渲染,createRef的值也每次都重新渲染了。而useRef只初始化了一次,所以才最后显示1。
三、useRef妙用
我们知道了useRef不仅可以获取转发DOM,还可以存放任何变量,且有只初始化一次的特性。可以使用这个特性做到一些特殊的需求。
比如,获取上一个state值。
const getPreCount = () => {
const [count, setCount] = useState(0);
const preCount = useRef();
useEffect(() => {
preCount.current = count;
});
return (
<div>
{console.log("in render")}
<div>precount:{preCount.current}</div>
<p>you clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click Me</button>
</div>
);
};
结果如下:
End:刚开始学习react,若文中有错误请大家指正。