在常规的 React 数据六中,props
是父组件与子组件交互的唯一方法。如果需要改子元素,你需要用新的props
去重新渲染子元素。 然而,在少数情况下,你需要在常规数据流外强制修改子元素。被修改的子元素可以是React组件实例,或者是一个DOM元素。React团队提供了Refs
办法
# 使用Refs 场景
在如下几种情形中,我们可以引入refs
:
处理
focus
、文本选择或者媒体播放时触发强制动画时
继承第三方DOM库时
注意:如果可以通过声明式实现,就尽量避免使用refs
。例如一个场景。如果我们想在Dialog
组件中暴露open()
和 close()
方法,则传递isOpen
状态标志即可。也就是说,我们不应该过度的使用refs,通常较高级别的state
更为清晰。
# 在元素本身的DOM元素上添加 Ref
React 支持给任何组件添加特殊的属性。 ref
属性接受回调函数,并且当组件 装载(mounted)
或者 卸载(unmounted)
之后,回调函数会立即执行。
当给HTML元素添加 ref
属性时,ref
回调接受底层的DOM 元素作为参数。例如,下面的代码使用ref
回调来存储DOM节点的引用
class CustomTextInput extends React.Component { constructor(props) { super(props); this.focus = this.focus.bind(this); } focus() { // 通过原生的API,显示地聚焦text输入框 this.textInput.focus(); } render() { // 在实例中通过使用`ref`回调函数来存储text输入框的DOM元素引用(例如:this.textInput) return ( <div> <input type="text" ref={(input) => { this.textInput = input; }} /> <input type="button" value="Focus the text input" onClick={this.focus} /> </div> ) } }
React 组件在加载时将DOM元素传入ref
的回调函数,在写在时则会传入 null
, 在 componentDidMount
或 componentDidUpdate
这些生命周期回之前执行 ref
回调
使用ref
回调只是为了在类上设置一个属性,是访问DOM元素常见模式。 首选的方法是在 ref
回调中设置属性。就像上面的例子一样,他也可以写成ref={input => this.textInput = input}
# 为 类(class)组件添加 Ref
当 ref
属性用于类声明的自定义组件时, ref
回到上述收到的参数装载(mounted)
的组件实例. 例如如果我们想包装 CustomTextInput
组件,实现组件在 装载(mounted)
后立即点击的效果:
class AutoFocusTextInput extends React.Component { componentDidMount() { this.textInput.focus(); } render() { return ( <CustomTextInput ref={(input) => { this.textInput = input; }} /> ); } }
需要注意的是,这种方法仅对以类声明的 CustomTextInput
有效,函数式声明的是无效的。
# Refs 与函数式组件
你不能在函数式组件上使用 ref 属性,因为它们没有实例
function MyFunctionalComponent() { return <input />; }class Parent extends React.Component { render() { // 这里 *不会* 执行! return ( <MyFunctionalComponent ref={(input) => { this.textInput = input; }} /> ); } }
如果你需要使用 ref
,你需要将组件转化成 类组件,就像需要 生命周期方法 或者 state
一样。然而你可以 在函数式组件内部使用ref
来引用一个 DOM 元素或者 类组件
function CustomTextInput(props) { // textInput必须在这里声明,所以 ref 回调可以引用它 let textInput = null; function handleClick() { textInput.focus(); } return ( <div> <input type="text" ref={(input) => { textInput = input; }} /> <input type="button" value="Focus the text input" onClick={handleClick} /> </div> ); }
# 对父组件暴露 DOM 节点
在极少数的情况下,你可能希望从父组件中访问子节点的DOM节点。这种情况不被推崇使用,因为他会破坏组件的封装,丢失子组件独立工作的效果。但偶尔也可用于触发焦点或测量子 DOM 节点的大小或位置。
虽然你可以向子组件添加 ref
,但这不是一个理想的解决方案,因为你只能获取组件实例而不是 DOM 节点。并且,它还在函数式组件上无效。
相反,在这种情况下,我们建议在子节点上暴露一个特殊的属性。子节点将会获得一个函数属性,并将其作为 ref
属性附加到 DOM 节点。这允许父代通过中间件将 ref
回调给子代的 DOM 节点. 这适用于类组件和函数式组件。
function CustomTextInput(props) { return ( <div> <input ref={props.inputRef} /> </div> ); }class Parent extends React.Component { render() { return ( <CustomTextInput inputRef={el => this.inputElement = el} /> ); } }
在上面的例子中,Parent
将它的ref
回调作为一个特殊的inputRef
传递给 CustomTextInput
,然后 CustomTextInput
通过ref
属性将其传递给 <input>
。最终,Parent
中的 this.inputElement
将被设置为与 CustomTextInput
中的 <input>
元素相对应的 DOM 节点。
请注意,上述示例中的 inputRef
属性没有特殊的含义,它只是一般的组件属性。然而,使用<input>
本身的ref
属性很重要,因为它告诉 React
将 ref
附加到它的 DOM 节点。
即使 CustomTextInput
是一个函数式组件,它也同样有效。这里在父组件定义的是自定义属性,而不是特殊的 ref
因此不会受影响
这种模式的另一个好处是它能作用很深。假如有个 Parent
组件不需要 DOM 节点 A,但是某个渲染 Parent
的组件(我们称之为Grandparent
)需要通过它访问。这时我们可以让 Grandparent
传递 inputRef
给 Parent
组件,然后让Parent
组件将其转发给 CustomTextInput
function CustomTextInput(props) { return ( <div> <input ref={props.inputRef} /> </div> ); }function Parent(props) { return ( <div> My input: <CustomTextInput inputRef={props.inputRef} /> </div> ); }class Grandparent extends React.Component { render() { return ( <Parent inputRef={el => this.inputElement = el} /> ); } }
总而言之,我们建议尽可能不暴露 DOM 节点,但这是一个有用的解决方式。请注意,此方法要求您向子组件添加一些代码,如果你无法完全控制子组件,最后的办法是使用findDOMNode()
,但是不推荐这样做。
作者:果汁凉茶丶
链接:https://www.jianshu.com/p/872e2af252e6
共同学习,写下你的评论
评论加载中...
作者其他优质文章