内容简介:本文为意译,翻译过程中掺杂本人的理解,如有误导,请放弃继续阅读。原文地址:Refs and the DOMRefs提供了一种访问在render方法里面创建的React element或者原生DOM节点的方法。
本文为意译,翻译过程中掺杂本人的理解,如有误导,请放弃继续阅读。
原文地址:Refs and the DOM
正文
Refs提供了一种访问在render方法里面创建的React element或者原生DOM节点的方法。
在典型的React数据流中(自上而下的数据流),props是父组件与子组件打交道的唯一途径。为了与子组件交互,你需要给子组件传递一个新的props,促使它重新渲染。然而,有不少的场景需要我们在这种props主导型的数据流之外去命令式地去修改子组件里面的东西。被修改的子组件有可能是一个React component的实例,也有可能是一个原生DOM元素。对于这两种情况,React都提供了一个“安全舱口”去访问它们。
什么时候用Refs呢?
以下几个业务场景是挺合适的:
- 手动管理聚焦(focus),文本选择或者视频,音频的回放。
- 命令式地触发动画。
- 与第三方的DOM类库进行整合。
如果能用声明式的方式去实现的,就不要用Refs去实现。举个例子说,能通过传递一个 isOpen
prop给 Dialog
组件来实现弹窗的打开和关闭,就不用通过暴露“open”和“close”方法来实现弹窗的打开和关闭(在结合redux数据流的背景下,我不太认同这句话所表达的观点)。
不要滥用Refs
在实际开发的过程中,如果实现上遇到困难了,你的惯性思维可能是,先使用Refs实现了它(这个功能),管它三七二十一呢。在这种情况下,你不妨先让你的头脑冷静下来,以更缜密的思维去想想,能不能通过state来实现呢?如果能,state应该存放在组件树层级中的哪个层级呢?一般来说,公认为合适存放state的层级是顶级组件,也就是我们以前说的“container component”。查看提升你的state看看该怎么做。
注意,接下来的例子已经被更新过了。更新过后的例子使用了React.createRef()这个API。这个API在React 16.3中就引入了。假如你还在使用较早的React版本,那么我们推荐你使用callback 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 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> ); } 复制代码
当ref属性所在的那个组件挂载到页面后,current属性将会被赋值为指向原生DOM元素的引用。当这个组件被卸载后,current属性又会被重置为null。ref属性值的更新发生在组件生命周期函数 componentDidMount
和 componentDidUpdate
之前。
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 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 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已经被废弃了。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 聊一聊个人成长这个话题
- [译]React高级话题之高阶组件
- [译]React高级话题之Context
- [译] React 高级话题之 Render Props
- [译]React高级话题之Error Boundaries
- [译]React高级话题之Forwarding Refs
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
编程之美:微软技术面试心得
《编程之美》小组 / 电子工业出版社 / 2018-9 / 79
《编程之美:微软技术面试心得》收集了约60道算法和程序设计的题目,这些题目大部分在微软的笔试、面试中出现过,有的曾被微软员工热烈地讨论过。作者试图从书中各种有趣的问题出发,引导读者发现问题、分析问题、解决问题,寻找更优的解法。《编程之美:微软技术面试心得》内容分为以下几个部分。 游戏之乐:从游戏和其他有趣问题出发,化繁为简,分析总结。 数字之魅:编程的过程实际上就是和数字及字符打交道的......一起来看看 《编程之美:微软技术面试心得》 这本书的介绍吧!