React进阶笔记3(Refs & DOM)

refs提供了一种方法,用于访问在render中创建的dom节点或者React元素。

在典型的React数据流中,属性(props)是父组件与子组件交互的唯一方式。要修改子组件,你需要使用新的props重新渲染他。
但是某些情况下,你需要在典型的数据流外,强制修改子组件。要修改的子组件可以是React的实例,也可以是DOM元素,对于这两种情况,React提供了解决办法。

何时使用Refs

①处理焦点,文本选择或者媒体控制
②触发强制动画
③集成第三方的DOM库
如果可以通过声明方式实现,尽量避免使用Refs
例如,不要在 Dialog 组件上直接暴露 open()close() 方法,最好传递 isOpen 属性。

不要过度使用Refs

你可能首先会想到在应用中使用Refs来更新组件,如果是这种情况,思考一下,state属性在组件层级中的位置,通常你会想明白,状态提升:提升组件state所在层级会是更加合适的办法。有关示例,请参考状态提升.

Note

下面的例子已经用 React v16.3 引入的 React.createRef() API 更新。如果你正在使用 React 更早的发布版,我们推荐使用回调形式的 refs

创建Refs

①使用React.creatRef()来创建refs,通过ref属性来获得React元素。
②当构造组件时,refs通常被赋值给组件的一个属性。这样可以在组件的任意一处使用他。

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

当一个ref属性被传递给一个render()函数中的元素时,可以使用ref中的current属性对节点的引用进行访问。

const current = this.myRef.current;

ref的值取决于节点类型
①当 ref 属性被用于一个普通的 HTML 元素时,React.createRef() 将接收底层 DOM 元素作为它的 current 属性以创建 ref
②当 ref被用于一个自定义的类组件的时候,ref对象将接收该组件已挂载的实例,作为他的current
③我们不能在函数形式的组件上面使用ref,因为他们是没有实例的。

demo

以下的代码存储了ref属性对节点的引用

class CustomTextInput extends React.Component{
    constructor(props){
        super(props);
        //创建ref存储 textInput DOM元素
        this.textInput = React.createRef();
        this.focusTextInput = this.focusTextInput.bind(this);
    }

    focusTextInput(){
        //直接使用原生的api让text获得焦点
        //这里使用current来获取dom节点
        this.textInput.current.focus();
    }

    render(){
        //这里告诉React 我们把 input ref 关联到构造器里面创建的  textInput 上
        return(
            <div>
                <input type="text" ref={this.textInput} />
                <input type="text" type="button" value="focus ths text"
                onClick={this.focusTextInput} />
            </div>
        )
    }
}

ReactDOM.render(
    <CustomTextInput />,document.getElementById('root')
)

React会在组件加载时将DOM元素传入current属性,在组件卸载的时候回改成null,ref的更新会发生在componentDidMountedcomponentDidUpdate的生命周期钩子之前。

为类组件添加ref

如果我们想要包装上面的CustomTextInput,来模拟挂载之后立即被点击的话,我们可以使用ref来访问自定义输入,并且手动调用他们的focusTextInput 方法。

class AutoFocusTextInput extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = React.createRef();
  }

  componentDidMount() {
    this.textInput.current.focusTextInput();
  }

  render() {
    return (
      <CustomTextInput ref={this.textInput} />
    );
  }
}

需要注意的是,这种方法只对于使用class声明的组件有效。

Refs与函数式组件

我们不能在函数式组件上面使用ref属性,因为在函数式组件上面没有他们的实例。

如果你想使用ref或者state以及其他声明周期方法的时候,就需要将函数式组件转化成class声明的组件。

注意:我们可以在函数式组件内部使用ref,只要他指向一个DOM或者class组件。

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="hello" onClick={handleClick} />
       </div>
   )
}

对父组件暴露DOM节点

在极少数的情况下, 你可能希望通过父组件访问子组件的节点。通常是不推荐这样做的,因为这样会破坏组件的封装。但是偶尔也可以用于触发焦点后者测量子节点dom大小或者位置。

虽然你可以向子组件添加 ref,但是这不是一个理想的解决方案,因为只能获取组件实例,而不是dom节点,并且这方法在函数式组件上是无效的。

如果你使用 React 16.3 或更高, 这种情况下我们推荐使用 ref 转发
Ref转发可以让组件像暴露自己的ref一样暴露子组件的ref,关于怎样对父组件暴露子组件的 DOM 节点,在 ref 转发文档 中有一个详细的例子。

如果你使用 React 16.2 或更低,或者你需要比 ref 转发更高的灵活性,你可以使用 这个替代方案 将 ref 作为特殊名字的 prop 直接传递。

可能的话,我们不建议暴露 DOM 节点,但有时候它会成为救命稻草。注意这些方案需要你在子组件中增加一些代码。如果你对子组件的实现没有控制权的话,你剩下的选择是使用 findDOMNode(),但是不推荐。

回调refs

React也支持另一种设置ref的方式,称为回调ref,这可以更加细致的控制什么时候设置和移除 ref

这不同于创建createRef创建的ref属性,“回调 ref”会传递一个函数,这个函数接收React组件的实例,或者html中的dom元素作为参数,来存储他们以便于被其他地方来访问。

demo:

使用ref回调函数,在实例的属性中,存储对dom节点的应用。


class CustomTextInput extends React.Component{
    constructor(props){
        super(props);
        this.textInput = null;
        this.setTextInputRef=element=>{
            this.textInput=element;
        }

        this.focusTextInput=()=>{
            if(this.textInput) this.textInput.focus();
        }
    }

    componentDidMount(){
        // 渲染后文本框自动获得焦点
        this.focusTextInput();
    }

    render(){
        //使用ref的回调将text输入框的dom节点存储到react
        return(
            <div>
                <input type="text" ref={this.setTextInputRef} />
                <input type="button" value=""focus the test  onClick={this.focusTextInput} />
            </div>
        )
    }
}

React将在组件挂载时,将dom元素传入ref的回调函数并且调用,在卸载的时候传入null并且调用他。
ref的回调函数会在componentDidMountcomponentDidUpdate的生命周期函数被调用之前。

我们可以在组件之间传递回调形式的refs,就像你可以传递React.createRef()创建的refs对象一样。

function CustomTextInput(props){
    return(
        <div>
            <input type="text" 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 将被设置为与 CustomTextIput 中的 <input> 元素相对应的 DOM 节点

注意

如果 ref 回调以内联函数的方式定义,在更新期间它会被调用两次,第一次参数是 null ,之后参数是 DOM 元素。这是因为在每次渲染中都会创建一个新的函数实例。因此,React 需要清理旧的 ref 并且设置新的。通过将 ref 的回调函数定义成类的绑定函数的方式可以避免上述问题,但是大多数情况下无关紧要。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容