React:DOM操作

由 TG 创建, 最后一次修改 2017-06-19
      虚拟DOM(virtual DOM)是React的一大亮点。正是因为虚拟DOm,在大多数的应用场景中,我们都只要关注设置组件的状态(setState),不需要直接操作DOM。

那么虚拟DOM到底是什么呢?

其实,虚拟DOM(virtual DOM)是一个模拟DOM树的JavaScript对象。React使用虚拟DOM来渲染UI,当组件状态state有更新时,React会自动调用组件的render方法重新渲染整个组件的UI。

大多数时候,我们都应该呆在React的“虚拟浏览器”的世界里,因为它性能更加好,并且容易思考。但是,在某些情况下,为了实现某些需要,我们不得不去操作底层的DOM。比如:需要与一个没有使用React的第三方类库进行整合,或者执行一个React没有原生支持的操作(canvas)。

我们需要了解ReactDOM.render组件返回的是什么?
它会返回对组件的引用,也就是组件实例(对于无状态组件来说,返回null)。

注意:JSX返回的不是组件实例,它只是一个ReactElement对象。

在React内,它提供了一个可用于处理受其自身控制的DOM节点的方法,不过要注意的是,这些方法仅在组件声明周期的特定阶段才能被访问到。

1、refs
要访问受React控制的DOM节点,首先必须能够访问到负责控制这些DOM的组件,我们可以通过为子组件添加一个ref属性来实现。

var MyInput = React.createClass({

  render: function(){

    returen (

      <input type="text" ref="myInput" />

    );

  }

});

为子组件添加了ref属性后,我们就可以通过this.refs.myInput访问到<input>组件了。

this返回的是当前组件。

注意:赋给每个子组件的ref值在所有子组件中必须是唯一的,也可以说是全局唯一。

到这里,我们已经可以访问到需要的子组件了,然后就可以通过它的getDOMNode()方法来访问到底层的DOM节点了。

但是请注意,我们不能在render方法中使用getDOMNode()方法,只有在render方法执行之后,并且react已经完成了DOM的更新,才能通过 this.refs.city.getDOMNode() 来拿到原生的DOM元素,也可以这样说,getDOMNode()仅在挂载的组件上有效(挂载:组件已经被放进DOM中),否则抛出异常。

一般我们会在 componentDidMount 事件回调中使用 getDOMNode 方法,当然,componentDidMount内部并不是getDOMNode方法的唯一执行环境。事件处理器也是在组件挂载后触发的,所以也可以在事件处理器中调用getDOMNode()方法。

var MyInput = React.createClass({   

  render: function(){   

    return (<input type="text" ref="myInput" onKeyUp={this.handleKeyUp}/>);

  },

  handleKeyUp: function(){   

    var input = this.refs.myInput;   

    console.log(input.value);   

  },   

  componentDidMount: function(){   

    console.log(this.refs.myInput);   

  }   

});   

ReactDOM.render(   

  <MyInput />,   

  document.body   

);

上面的代码中,给input添加了一个keyup事件处理器,在handleKeyUp()方法里,我们也可以用this.refs.myInput访问到对应的input元素。
每一个挂载的React组件都有一个 getDOMNode() 方法。

如果不了解React的生命周期,可以看这里:React:组件的生命周期

特别注意:(getDOMNode()方法在v0.14版本中使用会报提醒,而在 v0.15版本中已经移除了)。因此,如果你使用的是 v0.15版本及以上的,this.refs.myInput获取到的已经是对应的DOM元素了,不过refs的表现和行为还是和之前的一致的。

2、ReactDOM.findDOMNode()
除了使用refs外,我们还可以使用react-dom提供的 findDOMNode() 方法拿到组件对应的DOM元素。

var MyInput = React.createClass({   

  render: function(){   

    return (<input type="text" ref="myInput" onKeyUp={this.handleKeyUp}/>);

  },

  handleKeyUp: function(){   

    var input = ReactDOM.findDOMNode(this);

    //或者

    input = ReactDOM.findDOMNode(this.refs.myInput);   

    console.log(input.value);   

  } 

});   

ReactDOM.render(   

  <MyInput />,   

  document.body   

);


3、整合非React类库

要使用非React类库,保持它们的状态和React的状态之间的同步是成功整合的关键。

假如我们需要使用一个autocomplete类库:

(function(){   

  var autocomplete = function(params){   

    params = params || {};   

    if(!params.target) return;   

      var parent = params.target;   

      var data = params.data;   

      var list = '';   

      for(var i = 0; i < params.data.length; i++){   

        list += '<li>' + params.data[i] + '</li>';   

      }   

      parent.innerHTML = list;   

      if(params.events){   

        parent.addEventListener('click',function(e){   

          if(e.target.nodeName == 'LI'){   

            params.events.select(e.target.textContent);   

          }   

        });   

      }   

  };   

  window.autocomplete = autocomplete;  

})();

调用示例代码:

autocomplte({

  target: document.getElementById('cities'),

  data: [

    '广州','北京','深圳'

  ],

  events: {

    select: function(city){

      alert('你选择的城市是' + city);

    }

  }

});

上面的autocomplete函数需要一个目标DOM节点、一个用作数据展现的字符串清单,以及一些事件监听器。

接下来,我们需要创建一个使用这两个库的React组件,然后需要添加一个componentDidMount处理器,在这个处理器内,可以将autocompleteTarget所指向子组件的底层DOM节点来连接这两个接口:

var AutocompleteCities = React.createClass({

  render: function(){

    return (<div id="cities" ref="autocompleteTarget" />);

  },

  getDefaultProps: function(){

    return { 

      data: ['广州','北京','深圳']

    };

  },

  handleSelect: function(city){

    alert('你选择的城市是' + city);

  },

  componentDidMount: function(){

    autocomplete({

      target: this.refs.autocompleteTarget,  //也可以使用ReactDOM.findDOMNode(this.refs.autocompleteTarget)

      data: this.props.data,

      events: {

        select: this.handleSelect

      }

    });

  }

});

注意:componentDidMount方法只会为每个DOM节点调用一次。因此我们不用担心一个节点上两次调用autocomplete方法是否会有副作用。

对于简单的插件,最好是通过将其重写为React组件的形式来封装它。

总结
  • 当仅使用虚拟DOM无法满足需求时,可以考虑ref属性,它允许访问指定的元素
  • 在render方法执行之后,并且react已经完成了DOM的更新(一般是componentDidMount执行后)时,可以使用this.refs.name或者ReactDOM.findDOMNode()方法来访问底层的DOM节点。
  • 可以将简单的第三方类库(非React类库)重写为React组件的形式来封装它。



以上内容是否对您有帮助:

二维码
建议反馈
二维码