【译】在 React 组件中使用 Refs 指南
原文: Fullstack React’s Guide to using Refs in React Components
作者:Yomi Eluwande
译者:博轩
使用 React
时,我们的默认思维方式应该是 不会强制修改 DOM
,而是通过传入 props
重新渲染组件。但是,有些情况却无法避免修改 DOM
。
React
中的 Refs
提供了一种访问 render()
方法中创建的 React
元素(或 DOM
节点)的方法。
当父组件需要与子组件交互时,我们通常使用 props 来传递相关信息。 但是, 在某些情况下,我们可能需要修改子项,而不用新的 props
重新呈现 (re-rendering) 它。 这时候就需要 refs
出场了。
我什么时候应该使用 Refs ?
我们建议在以下情况下使用 refs
:
DOM
译注:第三点是否也可以理解为使用 event
对象呢?在 React 中就是合成事件(SyntheticEvent)。
官方文档中提到:避免使用 refs
来做任何可以通过声明式实现来完成的事情。
所以一旦我们确定我们真的应该使用 refs
,我们需要如何使用它们呢?
在 React 中使用 Refs
您可以通过多种方式使用 refs
:
- React.createRef()
- 回调引用 (Callback refs)
- String refs(已过时)
- 转发
refs
(Forwarding refs)
接下来,让我们看看每一种实现方式:
React.createRef()
可以使用该 React.createRef()
函数创建 Refs
,并通过该 ref
属性附加到 React
组件中的 HTML
元素。
通常在组件的构造函数内创建 ref
,使其在整个组件中可用。例如:
class MyComponent extends React.Component { constructor(props) { super(props); this.firstRef = React.createRef(); } render() { return ; } }
如上所示:
- 一个
ref
实例在构造函数中创建,并赋值给this.firstRef
- 在
render()
方法内部,将构造函数中创建的ref
传递给div
接下来,让我们看一个在 React
组件中使用 refs
的示例。
使用 Refs
聚焦输入
这是另一个例子:
// Ref.js 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 ref // with the `textInput` that we created in the constructor return (); } }
在上面的代码块中,我们构建了一个按钮,当单击它时, 该页面会自动聚焦在输入框上。
首先,我们在构造方法中创建一个 ref
实例,并将其赋值给 this.textInput
,然后通过 ref
属性将其分配给 input
元素。
注意,当 ref
属性被一个 HTML
元素使用时(比如当前示例中的 input
元素),在 constructor
中使用 React.createRef()
创建的 ref
会接收 来自底层 DOM
元素的 current
值。
译注:这里的 current 应该是 合成事件(SyntheticEvent)
这意味着访问 DOM
值,我们需要写这样的东西:
this.textInput.current;
第二个元素是一个按钮,点击它之后会自动聚焦到第一个输入框上面。我们为 onClick
属性设置了 this.focusTextInput
函数。
函数 focusTextInput()
使用了 JavaScript
构建 DOM
的标准函数。 .focus()
方法会将光标聚焦于文本输入框上。
focusTextInput() { this.textInput.current.focus(); }
最后, focusTextInput
函数绑定在这样的 constructor
方法中的:
this.focusTextInput = this.focusTextInput.bind(this);
从 ref
中获取值
在这个例子中,我们将看到如何为 input
输入框设置 ref
属性,并通过 ref
来获取值。示例如下:
在这个例子中,我们创建了一个 input
输入框来输入值。然后,当单击提交按钮时,我们将读取此值,并在控制台打印。
// Ref.js class CustomTextInput extends React.Component { constructor(props) { super(props); // create a ref to store the textInput DOM element this.textInput = React.createRef(); } handleSubmit = e => { e.preventDefault(); console.log(this.textInput.current.value); }; render() { // tell React that we want to associate the ref // with the `textInput` that we created in the constructor return (); } }
同样,我们使用该 React.createRef()
函数创建一个 ref
实例,然后将它分配给实例变量 this.textInput
。
在 render
函数中,我们希望读取 form
下输入框的值。 我们如何读取这个值? 通过为 input
指定一个 ref
,然后读取 ref
的值。
点击提交按钮,上面示例中 form
元素会通过 onSubmit
方法,调用 this.handleSubmit
函数 ,并在控制台打印输入框中的信息。
handleSubmit = e => { e.preventDefault(); console.log(this.textInput); };
e
包含事件对象。我们使用e.preventDefault()
来告诉浏览器我们正在处理被点击的提交按钮,我们不希望这个事件“冒泡”(意思就是说,阻止浏览器的默认行为)。
译注:这里可以看一下 React 对于 事件的处理 :在 React 中另一个不同点是你不能通过返回 false 的方式阻止默认行为。你必须显式的使用 preventDefault
在上面示例中,我们打印了 this.textInput
,在控制台可以看到一个 ref
对象。
> Object {current: HTMLInputElement}
请注意,它有一个 current
属性,即 HTMLInputElement
。这是 input
DOM 元素本身, 而不是实际值。 我们必须使用 this.textInput.current.value
来获取 input
标签的实际值:
handleSubmit = e => { e.preventDefault(); console.log(this.textInput.current.value); };
使用 refs
是一种从表单中直接提取值的方式:只需要给 input
标签设置 ref
,并在你需要的时候将值提取出来。
Refs 回调
Refs 回调是在 React
中使用 ref
的另一种方式。要以这种方式使用 ref
,我们需要为 ref
属性设置回调函数。当我们设置 ref
时, React
会调用这个函数,并将 element
作为第一个参数传递给它。
这是另一个例子的代码。像上面的示例一样,此代码获取 input
标签的文本值,但在这里我们使用回调引用:
// Refs.js class CustomTextInput extends React.Component { constructor(props) { super(props); this.textInput = null; this.setTextInputRef = element => { this.textInput = element; }; } handleSubmit = e => { e.preventDefault(); console.log(this.textInput.value); }; render() { return (); } }
上面的示例中,我们将 input
标签的 ref
设置为 this.setTextInputRef
。
当组件安装时, React
会将 DOM
元素传递给 ref
的回调;当组件卸载时,则会传递 null
。( ref
回调会在 componentDidMount
和 componentDidUpdate
生命周期之前调用。)
String Ref(已过时)
还有另一种设置 refs
的方法,但它被认为是过时的,可能很快就会被弃用。但是你可能会在其他人的代码中看到它,所以这里说一下。
使用 string refs
,你将会看到这样的 input
标签:
然后,我们可以在组建上得到这样的值: this.refs.textInput.value
– 但是,再次声明,这不应该在新代码中使用,因为这个 API
将被弃用。
转发 Refs (Forwarding Refs)
Ref forwarding
是一种将 ref
通过组件传递给其子节点的技术。它对于可复用组件库和高阶组件(HOC)等情况非常有用。
您可以使用 React.forwardRef
函数将 ref
转发到组件。我们来看下面的例子:
// Ref.js const TextInput = React.forwardRef((props, ref) => ( )); const inputRef = React.createRef(); class CustomTextInput extends React.Component { handleSubmit = e => { e.preventDefault(); console.log(inputRef.current.value); }; render() { return (); } }
Ref forwarding
允许组件接收一个 ref
,并将它向下传递(换句话说,“转发”它)给子组件。
在上面的示例中,我们使用 input
标签创建了一个名为 TextInput
的组件。那么,我们如何将 ref
传递或转发到 input
标签呢?
首先,我们使用下面的代码创建一个 ref
:
const inputRef = React.createRef();
然后,我们将 ref
通过为组件
指定一个同名的 JSX
的属性,将 ref
向下传递。然后 React
将会把 ref
作为第二个参数转发给 forwardRef
函数。
接下来,我们将此 ref
参数转发给 。现在可以在外层组件通过
inputRef.current
访问DOM节点的值了。
转发 refs
和高阶组件
最后,让我们看一下使用 refs
的另一个例子,但这次是使用高阶组件(HOC)。
在上面的示例应用程序中,会将所有 input
标签中输入的值在控制台打印。这里已经为 input
标签设置了 ref
属性,接下来,让我们看一下需要如何在高阶组件中传递 / 转发 ref
。
const Input = InputComponent => { const forwardRef = (props, ref) => { const onType = () => console.log(ref.current.value); return; }; return React.forwardRef(forwardRef); };
这里有一个名为 Input
的高阶组件 ,它接受 InputComponent
作为参数。当用户输入的时候,他还会将 ref
的值在控制台打印。
在 Input
高阶组件内, forwardRef
函数会返回 InputComponent
。 forwardRef
函数中所包含的 ref
参数,是由 React.forwardRef
函数创建的。 高阶组件最终会将包装好的组件作为值返回。
接下来,我们创建一个组件,将 input
作为子组件包含进来。
const TextInput = ({ forwardedRef, children, ...rest }) => ({children});
上面的组件会将 forwardedRef
分配给 ref
属性, 当渲染子组件的时候, input
输入框就会接收到这个 ref
。 ...rest
是 props
的解构(也就是说,我们会将 rest
数组中的所有参数作为 props
传递给 input
组件)。那么我们该如何使用 TextInput
组件呢?像这样:
const InputField = Input(TextInput); class CustomTextInput extends Component { render() { const inputRef = React.createRef(); return; } }
最后,将 TextInput
传入 Input
高阶组件,会返回一个 InputField component
。
创建一个 ref
,并作为参数传递给 InputField
组件。
结论
与通过 props
和 state
不同, Refs
是一种将数据传递给特定子实例的好方法。
你必须要小心,因为 refs
操纵实际的 DOM
,而不是虚拟的 DOM
,这与 React
思维方式相矛盾。因此,虽然 refs
不应该是通过应用程序流动数据的默认方法,但是当您需要时,它们是可以从 DOM
元素读取数据的好方法。
译注:推荐下「司徒正美」大佬的 React v16.3.0: New lifecycles and context API ,createRef API,forwardRef API 中的示例可以作为补充阅读。
本文已经联系原文作者,并授权翻译,转载请保留原文链接