React高级话题之Refs and the DOM

前言

本文为意译,翻译过程中掺杂本人的理解,如有误导,请放弃继续阅读。

原文地址:Refs and the DOM

正文

Refs提供了一种访问在render方法里面创建的React element或者原生DOM节点的方法。

在典型的React数据流中(自上而下的数据流),props是父组件与子组件打交道的唯一途径。为了与子组件交互,你需要给子组件传递一个新的props,促使它重新渲染。然而,有不少的场景需要我们在这种props主导型的数据流之外去命令式地去修改子组件里面的东西。被修改的子组件有可能是一个React component的实例,也有可能是一个原生DOM元素。对于这两种情况,React都提供了一个“安全舱口”去访问它们。

什么时候用Refs呢?

以下几个业务场景是挺合适的:

  • 手动管理聚焦(focus),文本选择或者视频,音频的回放。
  • 命令式地触发动画。
  • 与第三方的DOM类库进行整合。

如果能用声明式的方式去实现的,就不要用refs去实现。举个例子说,能通过传递一个isOpenprop给Dialog组件来实现弹窗的打开和关闭,就不用通过暴露“open”和“close”方法来实现弹窗的打开和关闭(在结合redux数据流的背景下,我不太认同这句话所表达的观点)。

不要滥用Refs

在实际开发的过程中,如果实现上遇到困难了,你的惯性思维可能是,先使用refs实现了它(这个功能),管它三七二十一呢。在这种情况下,你不妨先让你的头脑冷静下来,以更缜密的思维去想想,能不能通过state来实现呢?如果能,state应该存放在组件树层级中的哪个层级呢?一般来说,公认为合适存放state的层级是顶级组件,也就是我们以前说的“container component”。查看提升你的state 看看该怎么做。

注意,接下来的例子已经被更新过了。更新过后的例子使用了React.createRef()这个API。这个API在React 16.3中就引入了。假如你还在使用较早的React版本,那么我们推荐你使用callback refs来代替。

Object Refs

创建Refs

使用React.createRef()来创建refs,并通过ref属性来attached to React element。一般的做法是,在组件的constructor里面,将通过React.createRef()来创建的refs直接赋值给组件的一个实例属性。这样一来,当组件实例化的时候,你就可以在组件的其他地方使用这个引用。(也就是说,组件实例的某个属性是引用着通过React.createRef()来创建的Refs的,而这个refs又是通过ref属性附加在原生DOM元素或者子组件实例上的。故通过这个实例属性,我们是可以访问到原生DOM元素或者子组件实例的)

class MyComponent extends React.Component {constructor(props) {super(props);this.myRef = React.createRef();}render() {return <div ref={this.myRef} />;}
} 
访问Refs

上面讲了如何创建refs,这一小节我们就来讲如何访问refs。其实上面已经讲到了,我们通过组件的实例属性来保存着refs的引用的。refs是一个对象,它有一个current属性。我们正是通过这个current属性来访问原生DOM元素或者子组件实例的。

const node = this.myRef.current; 

current的属性值因不同的情况而异。这个不同的情况指的是ref属性所在的React component的类型。

  • DOM component。当ref属性是负载在DOM component上的话,通过React.createRef()来创建的Refs对象的current属性的值将会是原生的DOM元素。
  • custom component。custom component又可以分为class component 和function component。注意,因为function component是没有实例的,所以,它是不能通过这种方式来使用ref属性的(这里,这种方式是指上面“创建refs”这一小节所说的方法。实际上function component也是可以消费ref属性的,这得使用后面提到的“ ref forwarding”技术)。所以这里,custom component指的是class component。当ref属性是挂载在custom component上的话,通过React.createRef()来创建的refs对象的current属性将会指向custom component的组件实例。

下面的例子将会演示这两者之间的不同。

1)把ref属性挂载在DOM component上

下面的代码使用了ref属性来保存DOM元素的引用:

class CustomTextInput extends React.Component {constructor(props) {super(props);// create a ref to store the textInput DOM elementthis.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 nodethis.textInput.current.focus();}render() {// tell React that we want to associate the <input> ref// with the `textInput` that we created in the constructorreturn (<div><inputtype="text"ref={this.textInput} /><inputtype="button"value="Focus the text input"onClick={this.focusTextInput}/></div>);} 

当ref属性所在的那个组件挂载到页面后,current属性将会被赋值为指向原生DOM元素的引用。当这个组件被卸载后,current属性又会被重置为null。ref属性值的更新发生在组件生命周期函数componentDidMountcomponentDidUpdate之前。

2)把ref属性挂载在class component上

如果你想把上面的<CustomTextInput>包裹在父组件中,并想模拟组件挂载后就自动获取焦点。那么,我们可以通过ref去访问那个<CustomTextInput>的实例,通过这个实例的focusTextInput方法手动地让对应的组件内部的input框获得焦点。

class AutoFocusTextInput extends React.Component {constructor(props) {super(props);this.textInput = React.createRef();}componentDidMount() {// 在这里this.textInput.current指向的是// CustomTextInput组件的实例this.textInput.current.focusTextInput();}render() {return (<CustomTextInput ref={this.textInput} />);}
} 

注意,<CustomTextInput>组件是class component时,这种写法才会有用。

class CustomTextInput extends React.Component {// ...
} 
3)Refs与function component的关联

注意,第三点,我们已经不使用“把ref属性挂载在xxx组件上”这个说法了。因为把ref属性直接挂载function component是没有什么用的。这里用“关联”一词,只不过在表达,refs还是可以跟function component结合起来使用的。 再次强调,应为function component没有实例,所以不要直接在function component上挂载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} />);}
} 

如果你想在一个组件上直接挂载ref属性,那么你需要将这个组件转化为class component。这种转化,就像你如果需要使用生命周期函数或者state,你也会将组件转化为class component一样。

然而,正如我们上面提到的,ref属性还是可以跟function component结合使用的。如何结合法呢?那就是在function component的实现代码体里面使用。值得注意的是,即使在function component里面去使用,ref属还是要挂载在DOM component或者class component上:

function CustomTextInput(props) {// textInput must be declared here so the ref can refer to itlet textInput = React.createRef();function handleClick() {textInput.current.focus();}return (<div><inputtype="text"ref={textInput} /><inputtype="button"value="Focus the text input"onClick={handleClick}/></div>);
} 
将DOM Refs暴露给父组件

在很少的情况下,你可能想直接从父组件来访问子组件里面的DOM元素。而一般情况下,我们是不推荐大家这么做的。因为这么做会打破组件封装的完整性。但是呢,偶尔这么做还是挺管用的。比如想手动让输入框获取焦点或者测量子组件中DOM元素的位置和尺寸大小。

如果你正在使用React 16.3或者以上,我们推荐你使用ref forwarding来满足你的需要。Ref forwarding技术让子组件自己选择是否要把ref引用暴露给父组件(Ref forwarding lets components opt into exposing any child component’s ref as their own)。你可以查阅一下ref forwarding文档。这里的例子将会给你演示如何地将子组件中DOM元素的ref引用暴露给父组件的。

如果你再用React 16.2或者以下,或者需要一个比ref fowarding更加灵活的方案,你可以使用这个方案。通过一个不同与ref的prop名,显式地将ref引用传递下去。

我们建议尽可能少地去暴露DOM元素给外界。但是,它确实可以是一个很有用的(访问原生DOM)“安全舱口”。注意,这种访问原生DOM的方案需要你往子组件中添加一些代码。假如你对子组件的实现没有控制权(即往里面插入一些代码),那么这个时候你只剩下最后的选择了-使用findDOMNode()。原则上,findDOMNode()已经不被鼓励使用了。在 StrictMode下,这个API已经被废弃了。

Callback Refs

除了上面提到的方法外,React也支持别的方式去设置refs,其中一个就叫“callback refs”。“callback refs”能够在refs赋新值和重置的时候给你更小粒度的控制权。

相比于使用createRef()来创建refs并将它传递给ref属性,“callback refs”传递给ref属性的是一个函数,准备来说是一个callback函数。在这个callback函数里面,你可以通过参数获得一个访问React component实例或者原生的DOM元素的引用。一般的做法,使用一个组件的实例属性来保存这个引用,方便到处使用:

class CustomTextInput extends React.Component {constructor(props) {super(props);this.textInput = null;this.setTextInputRef = element => {this.textInput = element;};this.focusTextInput = () => {// Focus the text input using the raw DOM APIif (this.textInput) this.textInput.focus();};}componentDidMount() {// autofocus the input on mountthis.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><inputtype="text"ref={this.setTextInputRef}/><inputtype="button"value="Focus the text input"onClick={this.focusTextInput}/></div>);}
} 

当组件挂载到页面后,React将会给这个callback函数传入个一个组件实例或者原生DOM的引用;当组件卸载后,React再次给callback函数传入null(这里说的“给callback函数传入”代表着一次callback的调用)。React保证refs的更新会在componentDidMount和componentDidUpdate调用之前发生。

正如由React.createRef()创建出来的对象类型refs一样,你可以将函数类型的refs一路传递下去。

function CustomTextInput(props) {return (<div><input ref={props.inputRef} /></div>);
}

class Parent extends React.Component {render() {return (<CustomTextInputinputRef={el => this.inputElement = el}/>);}
} 

在上面的例子中,父组件<Parent>将函数类型的refs以一个叫inputRef的prop传递给<CustomTextInput>组件。然后<CustomTextInput>组件用真正的ref属性传递给<input>组件。从结果来看,<Parent>的实例属性inputElement引用的正是我们想要访问的,<CustomTextInput>组件里面的<input> DOM元素。

Legacy API: String Refs

如果你之前有使用过React,那么你应该知道ref属性的值可以是一个字符串,比如:“textInput”。然后你可以通过this.refs.textInput来访问原生的DOM元素。我们强烈建议你不要再使用它了。因为它存在某些问题,同时它已经被遗弃了。在未来的某个版本中,很有可能会把它的实现从代码中移除掉。

注意,如果你现在正在使用this.refs.textInput来访问refs,那么我们推荐你使用callback pattern或者createRef API来代替。

使用callback refs的注意点

如果你把一个inline function直接赋值给ref属性的话,那么这个inline function将会被调用两次。第一次是以null来调用的。第二次才是以真实的DOM元素去调用。这是因为,每一次render方法被调用的时候,inline function都会创建一个新的函数实例给ref属性。所以,React需要先移除旧的ref callback,再来设置新的。为了避免这个问题,你可以通过把这个inline function绑定成class component的方法,使之成为引用,然后将这个引用赋值给ref属性。不过大多数情况下,inline function不会造成什么大问题的。

零基础入门

对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。

同时每个成长路线对应的板块都有配套的视频提供:

因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取
CSDN大礼包:《黑客&网络安全入门&进阶学习资源包》免费分享
视频配套资料&国内外网安书籍、文档&工具
当然除了有配套的视频,同时也为大家整理了各种文档和书籍资料&工具,并且已经帮大家分好类了。
在这里插入图片描述
因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取
CSDN大礼包:《黑客&网络安全入门&进阶学习资源包》免费分享

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值