Ref更新是同步且可变的
第二个不同在于,Ref的更新是同步的。更新Ref不过是在改变一个对象罢了,而这个动作在JavaScript中是同步的。但是更新状态(state),则是异步的。在React中更新状态(state)甚至更复杂:状态更新是发生在“快照”里的。它有一个复杂的系统来管理状态,来确保一个“快照”中的数据和组件是连续的、被适度更新的。Ref呢,则没有那么复杂:我们只是直接改变一个对象就行。
通过下面这个例子,我们就明白了:
const Form = () => {
const [value, setValue] = useState();
const onChange = (e) => {
console.log('before', value);
setValue(e.target.value);
console.log('after', value); // same as before
}
}
前值和后值是一样的。我们调用setvalue时,React不会立即更新这个状态。我们只是让React知道,在它完成当前正在进行的操作之后,需要用新的数据来安排一次状态更新。
而更新Ref,则相反:
const Form = () => {
const ref = useRef()
const onChange = (e) => {
console.log('before', value);
ref.current = e.target.value
console.log('after', value); // already changed
}
}
我们修改了一个对象,该对象中的数据当下就能获取到,但不会触发 React 生命周期中的任何操作。
我们可以使用Ref做什么
那么,考虑到这两者的不同,什么时候适合用Ref,而什么时候适合用state呢?需要考虑这些问题:
- 这个值在现在或者将来,是否会用于渲染?
- 个值在现在或者将来,是否会传递给其他组件?
如果这两个问题的答案都是否,那就可以使用Ref了。
比如说,我们可以使用Ref来保存组件的一些“dev”信息。我们也许想统计一个组件被渲染了多少次:
useEffect(() => {
ref.current = ref.current + 1;
console.log('Render number', ref.current);
});
又或者,你想访问一个状态的前值:
const usePrevious = (value) => {
const ref = useRef();
useEffect(() =>: {
// this will be changed after the value is returned
ref.current = value;
}, [value])
return ref.current;
}
之后,在useEffect中使用这个钩子:
useEffect(() => {
if (previousValue.length > value.length) {
console.log('Text was deleted');
} else {
console.log('Text was added');
}
}, [previousValue, value])
代码示例: advanced-react.com/examples/09…
当然,最广泛的用法,还是用Ref来访问DOM。
使用 Ref 去访问DOM元素
我们只要用useRef创建一个Ref,再把这个Ref传递给组件的ref属性即可。
const Component = () => {
const ref = useRef(null);
// assing ref to an input element
return <input ref={ref} />;
}
当这个input组件被渲染后,我们可以不使用DOM的原生API就访问input的DOM元素了:
const Component = () => {
const ref = useRef(null);
useEffect(() => {
// this will be a reference to input DOM element!
// excatly the same as if I die getElementById for it
console.log(ref.current);
})
// assing ref to an input element
return <input ref={ref} />;
}
有一个需要记住的点是:ref只会在元素被重新渲染后被赋值,或者是DOM元素被创建时赋值。我们需要把一些东西赋值给Ref,对吧?这意味着,ref.current并不是马上就有值的,这样的代码是无法运行的:
const Component = () => {
const ref = useRef(null);
// trying to access ref value before it was acttually assigned
// input will never be rendered here
if (!ref.current) return null
return <input ref={ref} />;
}
我们只应该在useEffect或者回调函数里,读取或者写入ref.current
最后,回到我们最初关于一个精美注册表单的设想。如果我要把它实现为一个大型组件,我可以像这样做(不过后面应该还有具体的实现方式相关内容没展示出来呀,你可以补充完整以便我更深入地帮你分析或者探讨相关做法哦)。
const Form = () => {
const [name, setName] = useState('');
const inputRef = useRef(null);
const onSubmitClick = () => {
if (!name) {
// focus the input field if someone tries to submit the empty name
ref.current.focus();
} else {
// submit the data here!
}
};
return (
<>
...
<input
onChange={(e) => setName(e.target.value)}
ref={ref}
/>
<button onClick={onSubmitClick}>
Submit the form!
</button>
</>
);
};
把输入的值存在状态里,为表单控件创造refs。之后每次点击submit时,就可以检查是否每个值都填写了。如果有一些值为空,就可以展示相关提示了。