Refs提供了一种访问DOM节点或在render方法中创建的React元素的方法。
典型的React数据流中,props是父组件作用子组件的唯一的方式。要更改子组件,你需要用新的props来重新渲染他们。然而,存在一些少数情形需要你绕过这种典型的数据流来强制更改。被更改的子组件可以是React组件的实例,或者是DOM元素。对于这两种情况,React提供了“逃生舱”(处理方法)。

什么时候应用Refs

关于应用Refs有少许不错的情形:

  • 管理焦点,文本区域或媒体回放;
  • 触发强制动画;
  • 继承第三方DOM库;
    当可以用直接声明解决问题的时候,一定要避免使用refs。
    比方说,与其将open()和close()方法从Dialog中暴露出来,不如直接传一个props isOpen给它。

不要过度使用Refs

你的第一倾向也许会是使用refs来做点什么。这种时候,三思,并进一步思考下state状态应该在组件层级结构中的什么地方。通常情况下,拥有state的合适地方是上级组件。你可以看看Lifting State Up是关于这里的指导。

注意:下面的例子中已经更新到16.3版本的React.createRef()API,如果你使用早期版本,我们推荐使用callback refs代替

创建Refs

使用React.createRef()方法来创建Refs,通过ref属性绑定到React元素。通常当某元素呗创建后,它的实例属性就分配给了Refs,所以在组件各处都有了它的引用。

  1. class MyComponent extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.myRef = React.createRef();
  5. }
  6. render() {
  7. return <div ref={this.myRef} />;
  8. }
  9. }

访问Refs

当一个Ref在render时被分配,那么对于某节点的引用通过访问ref的current属性。

  1. const node = this.myRef.current;

ref的值依据节点类型不同而不同:

  • 当ref属性应用在Html元素上时,在构造器内通过React.createRef()创造的ref引用接收基本的DOM元素作为自己的current属性。
  • 当ref应用在由class创建的自定义的组件上时,ref的current属性接收已挂载的组件实例。
  • 你不能把ref应用在函数组件上,因为他们没有实例。

添加DOM元素的Ref

下面的代码用ref保存DOM节点的引用:

  1. class CustomTextInput extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. // create a ref to store the textInput DOM element
  5. // 创建 Ref
  6. this.textInput = React.createRef();
  7. this.focusTextInput = this.focusTextInput.bind(this);
  8. }
  9. focusTextInput() {
  10. // Explicitly focus the text input using the raw DOM API
  11. // Note: we're accessing "current" to get the DOM node
  12. // 明确的获得焦点使用原生API
  13. // 注意,使用current属性来得到DOM节点
  14. this.textInput.current.focus();
  15. }
  16. render() {
  17. // tell React that we want to associate the <input> ref
  18. // with the `textInput` that we created in the constructor
  19. return (
  20. <div>
  21. <input
  22. type="text"
  23. ref={this.textInput} />
  24. <input
  25. type="button"
  26. value="Focus the text input"
  27. onClick={this.focusTextInput}
  28. />
  29. </div>
  30. );
  31. }
  32. }
  33. // 运行结果,点击button之后,input中获得焦点

React将会在组件mount挂载的时候分配current属性,同时,unmounted卸载的时候再把current置为null。ref的更新发生在componentDidMount和componentDidUpdate这两个生命周期函数之前。

给Class组件添加Ref

如果我们想封装上例中CustomTextInput组件,在它被挂载之后就模拟点击了(获得焦点button),我们应该使用ref引用自定义的input并手动调用方法focusTextInput 。

  1. class AutoFocusTextInput extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.textInput = React.createRef();
  5. }
  6. componentDidMount() {
  7. this.textInput.current.focusTextInput();
  8. }
  9. render() {
  10. return (
  11. <CustomTextInput ref={this.textInput} />
  12. );
  13. }
  14. }

要注意,这只有在组件是用class声明时才可用:

  1. class CustomTextInput extends React.Component {
  2. // ...
  3. }

Ref和函数组件

你不能把ref应用在函数组件上,他们没有实例:

  1. function MyFunctionalComponent() {
  2. return <input />;
  3. }
  4. class Parent extends React.Component {
  5. constructor(props) {
  6. super(props);
  7. this.textInput = React.createRef();
  8. }
  9. render() {
  10. // This will *not* work!
  11. // 这没用
  12. return (
  13. <MyFunctionalComponent ref={this.textInput} />
  14. );
  15. }
  16. }

要是真的要用ref,那就把组件转换成class组件,就像需要声明周期方法和state时候做的那样。
不过,你能在函数组件的“里面”使用ref,只有引用的组件是一个class组件,或者原生DOM元素:

  1. function CustomTextInput(props) {
  2. // textInput must be declared here so the ref can refer to it
  3. // ref必须声明在这里
  4. let textInput = React.createRef();
  5. function handleClick() {
  6. textInput.current.focus();
  7. }
  8. return (
  9. <div>
  10. <input
  11. type="text"
  12. ref={textInput} />
  13. <input
  14. type="button"
  15. value="Focus the text input"
  16. onClick={handleClick}
  17. />
  18. </div>
  19. );
  20. }

暴露DOM的ref引用给父级元素

少数情形下,你可能想从父组件中访问子元素的DOM节点。这通常不被推荐因为这打破了组件的封装性,但有时确实有用,比如触发焦点或测量子元素的尺寸或位置。

当然你可以想上文中那样添加子元素的引用,但这也不是理想的解决方案,因为你只能得到组件实例而不是DOM节点。此外,这种方法再函数组件中也不可用。

如果你在使用React版本16.3或更高版本,我们推荐使用ref forwarding处理这种情形。这种方法可以使组件选择暴露子组件的ref,在专门介绍Ref Forwarding的文章中,你能发现一个详细的例子介绍具体方法。

你要是使用16.2或更低版本,或者说你想比ref forwarding更加弹性化,你可以使用这种方法代替,明确传递一个ref作为一个prop。

如果可能,我们建议不要暴露DOM节点,尽管有时候是一种捷径。注意这种方法需要你添加一些代码在子组件中。如果你完全没有对于子组件的的控制权,最后你可以使用findDOMNod()方法,这也是不被鼓励的!

回调Refs

React还提供了另外的设置ref的方法叫做“callback ref”,这是一种在设置ref和取消设置ref时,更细粒度的控制。

和传递通过createRef()创建的ref不一样,你现在需要传递一个函数。该函数接受React组件实例或者HTML DOM元素作为参数。

下面的例子实现了一个通用的模式:在一个实例属性中,用ref回调存储对DOM节点的引用。

  1. class CustomTextInput extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. // 这就是以后要存储引用的实例属性
  5. this.textInput = null;
  6. this.setTextInputRef = element => {
  7. this.textInput = element;
  8. };
  9. this.focusTextInput = () => {
  10. // Focus the text input using the raw DOM API
  11. if (this.textInput) this.textInput.focus();
  12. };
  13. }
  14. componentDidMount() {
  15. // autofocus the input on mount
  16. this.focusTextInput();
  17. }
  18. render() {
  19. // Use the `ref` callback to store a reference to the text input DOM
  20. // element in an instance field (for example, this.textInput).
  21. // 用 ref 回调来存储inputDOM的引用
  22. return (
  23. <div>
  24. <input
  25. type="text"
  26. ref={this.setTextInputRef}
  27. />
  28. <input
  29. type="button"
  30. value="Focus the text input"
  31. onClick={this.focusTextInput}
  32. />
  33. </div>
  34. );
  35. }
  36. }

当组件挂载时,React将会调用DOM元素的ref的回调函数,当组件卸载使,置之为null。ref回调函数在生命周期方法componentDidMount或componentDidUpdate之前调用。
你也可以在组件之间传递回调ref,如同你能传递那些经由React.createRef()创建的对象ref引用一样。

  1. function CustomTextInput(props) {
  2. return (
  3. <div>
  4. <input ref={props.inputRef} />
  5. </div>
  6. );
  7. }
  8. class Parent extends React.Component {
  9. render() {
  10. return (
  11. <CustomTextInput
  12. inputRef={el => this.inputElement = el}
  13. />
  14. );
  15. }
  16. }

上面的例子中,父组件将它的回调ref作为props(inputRef)传递给子组件CustomTextInput,与此同时,子组件也把相同的函数作为ref属性传递给了input元素。结果就是,父组件中的this.inputElement会被设置为CustomTextInput中的input的DOM节点。

遗产API:String Refs

如果你早先用过React,你一定熟悉早先的API—ref作为一个字符串,像“textInput”,而且对DOM的访问是这样:this.refs.textInput。我们不建议使用字符串refs,因为字符串refs有些历史遗留问题,而且可能未来版本就会移除这种形式。

注意,你要是现在还用this.refs.textInput形式来访问refs,我们建议使用回调模式(callback refs)或者 createRef API代替。

回调refs的告诫 Caveats with callback refs

如果回调ref被定义成行内函数(箭头函数? inline function),在更新期间它就会被调用两次,第一次调用传递null,再一次是传递DOM节点。这是因为每一次render函数执行时一个新的函数实例会被创建出,所以,React需要清理旧的ref引用同时分配新的。你可以通过把函数定义为class方法来避免这种情况,不过注意大多数时候这个也没什么关系。

官网文章 Advanced Guides :Static Type Checking