ref使用的情况
你需要在典型数据流之外强制修改子组件。被修改的子组件可能是一个 React 组件的实例 当我们需要在父组件去调用子组件的方法或者获取dom的数据的时候我们可以使用ref
ref的几种用法
使用ref的字符串
如果你之前使用过 React,你可能了解过之前的 API 中的 string 类型的 ref 属性,例如 "textInput"。你可以通过 this.refs.textInput 来访问 DOM 节点。我们不建议使用它,因为 string 类型的 refs 存在 一些问题。它已过时并可能会在未来的版本被移除
<input ref="inputRef" /> //this.refs['inputRef']来访问
回调函数
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = null;
this.setTextInputRef = element => { this.textInput = element; };
this.focusTextInput = () => { if (this.textInput) this.textInput.focus(); };
}
componentDidMount() {
// 组件挂载后,让文本框自动获得焦点
this.focusTextInput(); }
render() {
// 使用 `ref` 的回调函数将 text 输入框 DOM 节点的引用存储到 React
// 实例上(比如 this.textInput)
return (
<div>
<input
type="text"
ref={this.setTextInputRef} />
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput} />
</div>
);
}
}
使用最新的React.creteRef()
Refs 是使用 React.createRef() 创建的,并通过 ref 属性附加到 React 元素。
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
focusTextInput=()=> {
// 直接使用原生 API 使 text 输入框获得焦点
// 注意:我们通过 "current" 来访问 DOM 节点
this.myRef.current.focus();
}
render() {
return <input ref={this.myRef} onClick={this.focusTextInput}/>;
}
}
React 会在组件挂载时给 current 属性传入 DOM 元素,并在组件卸载时传入 null 值。ref 会在 componentDidMount 或 componentDidUpdate 生命周期钩子触发前更新。
注意: current获取的时候父子组件生命周期的执行顺序,而且这种写法只能在类组件里面使用,函数组件是访问不到的
如果要在函数组件中使用 ref,你可以使用 forwardRef(可与 useImperativeHandle 结合使用),或者可以将该组件转化为 class 组件。
使用useRef
function CustomTextInput(props) {
// 这里必须声明 textInput,这样 ref 才可以引用它 const textInput = useRef(null);
function handleClick() {
textInput.current.focus(); }
return (
<div>
<input
type="text"
ref={textInput} /> <input
type="button"
value="Focus the text input"
onClick={handleClick}
/>
</div>
);
}
请记住,当 ref 对象内容发生变化时,useRef 并不会通知你。变更 .current 属性不会引发组件重新渲染。如果想要在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,则需要使用回调 ref 来实现。
ref转发
forwardRef,它可以将 ref 自动地通过组件传递到子组件。作为子组件第二个参数。
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
// 你可以直接获取 DOM button 的 ref:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
搭配 useImperativeHandle 一起使用,暴露给调用者只能使用的方法
useImperativeHandle(ref, () => ({
validate: () => {
return new Promise((resolve, reject) => {
field.validate((errors, values: any) => {
if (errors) return reject(errors);
// TODO
return resolve(values$);
});
});
}
}), []);
useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle 应当与 forwardRef 一起使用:
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);
在本例中,渲染 <FancyInput ref={inputRef} /> 的父组件可以调用 inputRef.current.focus()。
在高阶组件中转发 refs
function logProps(Component) {
class LogProps extends React.Component {
componentDidUpdate(prevProps) {
console.log('old props:', prevProps);
console.log('new props:', this.props);
}
render() {
const {forwardedRef, ...rest} = this.props;
// 将自定义的 prop 属性 “forwardedRef” 定义为 ref
return <Component ref={forwardedRef} {...rest} />; }
}
// 注意 React.forwardRef 回调的第二个参数 “ref”。
// 我们可以将其作为常规 prop 属性传递给 LogProps,例如 “forwardedRef”
// 然后它就可以被挂载到被 LogProps 包裹的子组件上。
return React.forwardRef((props, ref) => { return <LogProps {...props} forwardedRef={ref} />; });}
class FancyButton extends React.Component {
focus() {
// ...
}
// ...
}
// 我们导出 LogProps,而不是 FancyButton。
// 虽然它也会渲染一个 FancyButton。
export default logProps(FancyButton);