React 源码阅读 -2
在典型的 React 数据流中,props 是父组件与其子组件交互的唯一方式。要修改子项,请使用 new props 重新呈现它。但是,在某些情况下,需要在典型数据流之外强制修改子项。要修改的子项可以是 React 组件的实例,也可以是 DOM 元素。对于这两种情况,React 都提供了 api。
何时使用 refs
refs
有一些很好的用例:
- 1. 文本选择或媒体播放。
- 2. 触发势在必行的动画。
- 3. 与第三方 DOM 库集成。
避免将 refs
用于可以声明性地完成的任何操作。
不要过度使用Refs
旧版API
:字符串引用
如果您之前使用过 React
,那么您可能熟悉一个旧的API
,其中ref
属性是一个字符串textInput
,并且 DOM 节点被访问为this.refs.textInput
。建议不要使用它,因为字符串引用有一些问题,被认为是遗留问题,很可能会在未来的某个版本中删除。
回调引用
当组件安装时,React
将使用 DOM
元素调用 ref
回调,并在卸载时调用 null
。
在componentDidMoun
t 或 componentDidUpdate
触发之前,Refs
保证是最新的.
class CustomTextInput extends React.Component {constructor(props) {super(props);
this.textInput = null;
this.focusTextInput = () => {
// Focus the text input using the raw DOM API
if (this.textInput) this.textInput.focus();};
}
componentDidMount() {
// autofocus the input on mount
this.focusTextInput();}
render() {
// Use the `ref` callback to store a reference to the text input DOM
// element in an instance field (for example, this.textInput).
return (
<div>
<input
type="text"
ref={element => this.textInput = element}
/>
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
</div>
);
}
}
关于回调 refs
的说明
如果 ref
回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以React
清空旧的 ref
并且设置新的。通过将 ref
的回调函数定义成 class
的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。
refs
例子 – 点击获取 input
焦点
class Example extends React.Component {handleClick() {
// 使用原生的 DOM API 获取焦点
this.refs.myInput.focus
();}
render() {
// 当组件插入到 DOM 后,ref 属性添加一个组件的引用于到 this.refs
return (
<div>
<input type="text" ref="myInput" />
<input
type="button"
value="点我输入框获取焦点"
onClick={this.handleClick.bind(this)}
/>
</div>
);
}
}
使用React.createRef()
React.createRef()React 16.3 中引入的 API。如果您使用的是早期版本的 React,我们建议您使用回调引用。
创建React.createRef()
Refs
是使用属性创建的,React.createRef()
并通过 ref
属性附加到 React
元素。在构造组件时,通常将 Refs
分配给实例属性,以便可以在整个组件中引用它们。
class MyComponent extends React.Component {constructor(props) {super(props);
this.myRef = React.createRef();}
render() {return <div ref={this.myRef} />;
}
}
访问参考
当 ref
被传递给元素时render
,对该节点的引用变得可以在currentref
的属性处访问
const node = this.myRef.current;
ref 的值根据节点的类型而有所不同
- 当在
refHTML
元素上使用该属性时,ref 在构造函数中创建的属性将React.createRef()
接收底层 DOM 元素作为其 current 属性。 - 在
ref
自定义类组件上使用该属性时,该ref
对象将接收组件的已安装实例作为其current
。
您可能无法 ref
在函数组件上使用该属性,因为它们没有实例。
class CustomTextInput extends React.Component {constructor(props) {super(props);
// create a ref to store the textInput DOM element
this.textInput = React.createRef();
this.focusTextInput = this.focusTextInput.bind(this);
}
focusTextInput() {
// Explicitly focus the text input using the raw DOM API
// Note: we're accessing"current" to get the DOM node
this.textInput.current.focus();}
render() {
// tell React that we want to associate the <input> ref
// with the `textInput` that we created in the constructor
return (
<div>
<input
type="text"
ref={this.textInput} />
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
</div>
);
}
}
current 当组件安装时,React 将为该属性分配 DOM 元素,并 null 在卸载时将其分配回。ref 更新发生之前 componentDidMount 或 componentDidUpdate 生命周期方法。
源码
// an immutable object with a single mutable value
export function createRef(): RefObject {
const refObject = {current: null,};
if (__DEV__) {Object.seal(refObject);
}
return refObject;
}
无法 ref 在函数组件上使用该属性
function MyFunctionComponent() {return <input />;}
class Parent extends React.Component {constructor(props) {super(props);
this.textInput = React.createRef();}
render() {
// This will *not* work!
return (<MyFunctionComponent ref={this.textInput} />
);
}
}
** 如果需要引用它,则应该将组件转换为类,就像您需要生命周期方法或状态时一样。
但是,只要引用 DOM 元素或类组件,就可以在函数组件中使用该 ref
属性:
function CustomTextInput(props) {
// textInput must be declared here so the ref can refer to it
let textInput = React.createRef();
function handleClick() {textInput.current.focus();
}
return (
<div>
<input
type="text"
ref={textInput} />
<input
type="button"
value="Focus the text input"
onClick={handleClick}
/>
</div>
);
}
将 DOM 引用公开给父组件
在极少数情况下,可能希望从父组件访问子节点的 DOM 节点。通常不建议这样做,因为它会破坏组件封装,但它偶尔可用于触发焦点或测量子 DOM 节点的大小或位置。
虽然可以向子组件添加引用,但这不是一个理想的解决方案,因为只能获得组件实例而不是 DOM 节点。此外,这不适用于功能组件。
如果您使用 React 16.3 或更高版本,我们建议在这些情况下使用 ref forwarding。引用转发允许组件选择将任何子组件的引用公开为自己的组件。可以在 ref 转发文档中找到有关如何将子 DOM 节点公开给父组件的详细示例。
如果您使用 React 16.2 或更低版本,或者您需要比 ref 转发提供的更多灵活性,您可以使用此替代方法并明确地将 ref 作为不同名称的 prop 传递。
如果可能,建议不要暴露 DOM 节点,但它可以是一个有用的逃生舱。请注意,此方法要求您向子组件添加一些代码。如果您完全无法控制子组件实现,则最后一个选项是使用 findDOMNode(),但不鼓励使用它StrictMode
。