useRef简单介绍
const refContainer = useRef(initialValue);
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内持续存在。
一个常见的用例便是命令式地访问子组件:
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
本质上,useRef 就像是可以在其 .current 属性中保存一个可变值的“盒子”。
你应该熟悉 ref 这一种访问 DOM 的主要方式。如果你将 ref 对象以
然而,useRef() 比 ref 属性更有用。它可以很方便地保存任何可变值,其类似于在 class 中使用实例字段的方式。
这是因为它创建的是一个普通 Javascript 对象。而 useRef() 和自建一个 {current: ...} 对象的唯一区别是,useRef 会在每次渲染时返回同一个 ref 对象。
请记住,当 ref 对象内容发生变化时,useRef 并不会通知你。变更 .current 属性不会引发组件重新渲染。如果想要在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,则需要使用回调 ref 来实现。
useRef的特性
useRef可以存储任意类型的值
const initialValue = 1;
const ref = useRef(initialValue); // 初始化
const refList = useRef([]);
const refObj = useRef({});
console.log(ref.current); // 1
ref.current = 2; // 更新ref值
useRef方法返回的对象,且这个对象在组件生命周期中保持不变
const ref = useRef(1);
useEffect(() => {
ref.current++;
console.log(ref.current); // 2
}, [ref])
样例中的代码并不会发出死循环,因为ref.current更新后,ref的引用还是没有变
更新current的值不会触发组件重新渲染
const ref = useRef(1);
return <div><span>{ref.current}</span><a onClick={() => ref.current++}>+</a></div>
样例中不断点击a标签触发current更新,但是页面上任然显示1
useRef的用法
存储不会影响页面渲染的值
获取dom对象
const ref = useRef();
useEffect(() => {
console.log(ref.current.innerText); // 输出结果为 123
}, [ref])
return <div ref={ref}>123</div>
将ref作为参数传入到HTMLElement中就可以获取它的dom对象.
注: HTMLElement*指的是html*原生的对象元素,例如 *div* *span*
在闭包函数中获取最新的值
const ref = useRef(1);
const [state, setState] = useState(1);
// useEffect中嵌套两个箭头函数,表示组件被销毁后执行
useEffect(() => () => {
setTimeout(() => {
console.log(ref.current, state); // 假定在3s内点击3次,输出结果为 4 1
}, 3000)
// 无论触发了多少次onClick,打印的state值永远是1,而ref.current值显示正常
}, [])
return <a onClick={() => { ref.current++;setState(state + 1); }}>+</a>
与useImperativeHandle组合使用,父组件可以直接获取子组件的方法
import { useImperativeHandle, forwardRef, useEffect, useRef } from 'react';
const Child = forwardRef((props, ref) => {
const childFC = () => {
console.log('Child');
};
useImperativeHandle(ref, () => ({
childFC: childFC,
}));
return <div>子组件</div>;
});
const Parent = () => {
const ref = useRef();
useEffect(() => {
ref.current.childFC(); // 打印结果为 Child
}, []);
return <Child ref={ref}>child</Child>;
};
与useEffect组合使用,可以产生类似Class组件的componentDidUpdate的效果
const ref = useRef(false);
// 监听变量a
useEffect(() => {
if (!ref.current) {
ref.current = true;
return;
}
// 变量a更新后的逻辑
...
}, [a]);
注:这种写法可能会导致代码逻辑混乱,非必要不使用. 应该优先在触发a 更新的event handler方法中处理a的更新逻辑
注意事项
useRef 和其他的hook一样,必须创建在组件的在外层
// 正确使用
const App = () => {
const ref = useRef();
}
// 错误使用
const App = () => {
const init = () => {
const ref = useRef();
}
}
// 错误使用
const App = () => {
useEffect(() => {
const ref = useRef();
}, [])
}
更新useRef是副作用,不能在主函数,而应该放在useEffect或 event handler等可以处理副作用的方法中
// 正确使用
const App = () => {
const [state, setState] = useState(1);
const ref = useRef(1);
useEffect(() => () => {
ref.current++;
}, [])
return <a onClick={() => { ref.current++;setState(state + 1); }}>+</a>
}
// 错误使用
const App = () => {
const [state, setState] = useState(1);
const ref = useRef(1);
ref.current = ref.current + 1;
return <a onClick={() => { setState(state + 1); }}>+</a>
}
关于forwardRef
由于ref不能被直接分配给函数式组件,所以我们需要用forwardRef方法包裹函数式组件,使其能够接收到ref_
const App = forwardRef((props, ref) => {
return <div ref={ref}>app</div>
})
createRef和useRef的区别
useRef 仅能用在 FunctionComponent,因为 Hooks 不能用在 ClassComponent.
createRef 仅能用在 ClassComponent.因为createRef 并没有 Hooks 的效果,其值会随着 FunctionComponent 重复执行而不断被初始化:
const App = () => {
// 错误用法,永远也拿不到 ref
const valueRef = React.createRef();
return <div ref={valueRef} />;
}
// 正确用法
class App extends Component {
constructor(props) {
super(props);
this.ref = React.createRef();
}
render() {
return <div ref={this.ref}>app</div>
}
}