ReactJs笔记

来源:互联网 发布:大数据怎么做 编辑:程序博客网 时间:2024/05/21 06:28

React的特性


一个用于创建可复用,可聚合web组件的js库。只提提供前端MVC框架中的"V"。

组件化


将js逻辑与html标签紧密相连,极易理解

单向数据流


数据一旦更新,就直接重新渲染整个app,这是让React如此牛逼的关键点。这个特点是其不再有双向双向数据绑定,数据模型的肮脏检查,确切的dom操作



一个React组件可以理解成一个独立的函数,接受参数,可服用,可以传递,返回结果(渲染组件)

虚拟的DOM树


在每一次跟新时:
  1. React重建DOM树
  2. 找到与上个版本的DOM的差异
  3. 计算出最新的DOM跟新操作
  4. 从操作队列中批量地执行DOM跟新操作

jsx


javascrpt的XML语法扩展,采用你所熟悉和理解的语法来定义DOM树

ReactJs的生命周期


初始化阶段


<html><head>    <meta charset="utf-8">    <title>学习React!!</title></head><body>    <div id="app"></div>    <script src="bower_components/react/react.js"></script>    <script src="bower_components/react/JSXTransformer.js"></script><script type="text/jsx">var sb1=null,sb2=null;var MessageBox = React.createClass({//1.获取默认的属性getDefaultProps:function(){console.log('getDefaultProps');return {};},//2.获取初始化的状态getInitialState:function(){console.log('getInitialState');return {count: 0,}},//3.挂载前的操作,此时dom还没有挂载componentWillMount:function(){console.log('componentWillMount');//console.log(this.getDOMNode());//dom还没有挂载,获取dom会报错var self = this;/*this.timer = setInterval(function(){self.setState({count: self.state.count + 1,})},1000);*/},//4.挂载后的操作,这里可以获取到dom了componentDidMount:function(){console.log('componentDidMount')console.log(this.getDOMNode());},componentWillUnmount: function(){console.log('componentWillUnmount')//clearInterval(this.timer);},killMySelf: function(){React.unmountComponentAtNode(  document.getElementById('app') );},render:function(){console.log('render')return ( <div><h1 > 计数: {this.state.count}</h1> <button onClick={this.killMySelf}>卸载掉这个组件</button><Submessage name="1"/><Submessage name="2"/></div>)}});//上面有两个Submessage子组件,下面的声明周期函数会运行两遍,每个组件对应一个独立的作用域var Submessage = React.createClass({getInitialState:function(){console.log('Submessage:getInitialState');return null;},getDefaultProps:function(){console.log('Submessage:getDefaultProps');return null;},componentWillMount:function(){console.log('Submessage:componentWillMount');},componentDidMount:function(){console.log('Submessage:componentDidMount')},componentWillUnmount: function(){console.log('Submessage:componentWillUnmount')},componentDidUnmount: function(){console.log('Submessage:componentDidUnmount')},componentWillUnmount: function(){console.log('Submessage:componentWillUnmount')//clearInterval(this.timer);},render:function(){console.log("Submessage:render")if(sb1!=null&&sb2!=null){console.log(sb1==sb2);//false,相同的组件会生成不同的实例}if(this.props.name=="1")sb1=this;else if(this.props.name=="2")sb2=this;return (<h3>写代码很有意思</h3>)}});var messageBox = React.render( <MessageBox/>, document.getElementById('app'))</script></body></html>


从上面的结果可以得出以下结论:
  • react的初始化顺序:

    1. 运行组件的getDefaultPorps(),只运行一次
    2. 运行子组件的的getDefaultProps(),只运行一次
    3. 运行组件的getInitialState(),每个实例运行一次
    4. 运行组件的componentWillMount(),每个实例运行一次
    5. 运行组件的render()
    6. 运行子组件的getInitialState()和componentWillMount()
    7. 运行子组件的render()
    8. 运行子组件的componentDidMount()
    9. 运行组件的componentDidMount()
  • 每个实例会生成一个独立的作用域


运行时阶段


<html><head>    <meta charset="utf-8">    <title>学习React!!</title></head><body>    <div id="app"></div>    <script src="bower_components/react/react.js"></script>    <script src="bower_components/react/JSXTransformer.js"></script><script type="text/jsx">var MessageBox = React.createClass({getInitialState:function(){console.log("getInitialState");return {count: 0,}},getDefaultProps:function(){},//1.是否更新组件shouldComponentUpdate:function(nextProp,nextState){console.log('MessageBox:shouldComponentUpdate');if(nextState.count > 10) return false;return true;},//2.跟新组件前的操作componentWillUpdate:function(nextProp,nextState){console.log(this.getDOMNode().children[0].children[1].innerHTML);//0,获取到的是跟新前的domconsole.log('MessageBox:componentWillUpdate');},//3.跟新组件后的操作componentDidUpdate:function(){console.log('MessageBox:componentDidUpdate');},killMySelf: function(){React.unmountComponentAtNode(  document.getElementById('app') );},doUpdate:function(){this.setState({count: this.state.count + 1,});},render:function(){console.log('render')return ( <div><h1 > 计数: {this.state.count}</h1> <button onClick={this.killMySelf}>卸载掉这个组件</button><button onClick={this.doUpdate}>手动更新一下组件(包括子组件)</button><Submessage count={this.state.count}/><Submessage count={this.state.count}/></div>)}});var Submessage = React.createClass({getInitialState:function(){console.log("Submessage:getInitialState");return null;},componentWillReceiveProps:function(nextProp){console.log('Submessage:componentWillReceiveProps');},shouldComponentUpdate:function(nextProp,nextState){console.log('Submessage:shouldComponentUpdate');if(nextProp.count> 5) return false;return true;},componentWillUpdate:function(nextProp,nextState){console.log('Submessage:componentWillUpdate');},componentDidUpdate:function(){console.log('Submessage:componentDidUpdate');},render:function(){console.log('Submessage:render')return (<h3>当前计数是:{this.props.count}</h3>)}});var messageBox = React.render( <MessageBox/>, document.getElementById('app'))</script></body></html>


根据运行结果可以知道ReactJs的跟新顺序:
  1. 运行组件的shouldComponentUpdate()
  2. 运行组件的componentWillUpdate()
  3. 运行组件的render()
  4. 运行子组件的componentWillReceiveProps()
  5. 运行子组件的shouldComponentUpdate()
  6. 运行子组件的componentWillUpdate()
  7. 运行子组件的render()
  8. 运行子组件的componentDidUpdate()
  9. 运行组件的componentDidUpdate()

销毁阶段


销毁顺序:
  1. 运行组件的componentWillUnmount
  2. 运行子组件的componentWillUnmount

React Diff算法


传统 diff 算法的复杂度为 O(n^3),显然这是无法满足性能要求的。React 通过制定大胆的策略,将 O(n^3) 复杂度的问题转换成 O(n) 复杂度的问题。

diff 策略:
  1. Web UI 中 DOM 节点跨层级的移动操作特别少,可以忽略不计
  2. 拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构
  3. 对于同一层级的一组子节点,它们可以通过唯一 id 进行区分
基于以上三个前提策略,React 分别对 tree diff、component diff 以及 element diff 进行算法优化,事实也证明这三个前提策略是合理且准确的,它保证了整体界面构建的性能。

tree diff(同层级比较)


基于策略一,React 对树的算法进行了简洁明了的优化,即对树进行分层比较,两棵树只会对同一层次的节点进行比较。React 通过 updateDepth 对 Virtual DOM 树进行层级控制,只会对相同颜色方框内的 DOM 节点进行比较,即同一个父节点下的所有子节点。当发现节点已经不存在,则该节点及其子节点会被完全删除掉,不会用于进一步的比较。这样只需要对树进行一次遍历,便能完成整个 DOM 树的比较。



对应的React源码如下:

updateChildren: function(nextNestedChildrenElements, transaction, context) {  updateDepth++;  var errorThrown = true;  try {    this._updateChildren(nextNestedChildrenElements, transaction, context);    errorThrown = false;  } finally {    updateDepth--;    if (!updateDepth) {      if (errorThrown) {        clearQueue();      } else {        processQueue();      }    }  }}

出现了 DOM 节点跨层级的移动操作,如下图:

此时,React diff 的执行情况:create A -> create B -> create C -> delete A


component diff(同类型比较)


React 是基于组件构建应用的,对于组件间的比较所采取的策略也是简洁高效(对于普通html标签的比较也适用该策略)。
  • 如果是同一类型的组件,按照原策略继续比较 virtual DOM tree。
  • 如果不是,则将该组件判断为 dirty component,从而替换整个组件下的所有子节点。
  • 对于同一类型的组件,有可能其 Virtual DOM 没有任何变化,如果能够确切的知道这点那可以节省大量的 diff 运算时间,因此 React 允许用户通过 shouldComponentUpdate() 来判断该组件是否需要进行 diff。
如下图,当 component D 改变为 component G 时,即使这两个 component 结构相似,一旦 React 判断 D 和 G 是不同类型的组件,就不会比较二者的结构,而是直接删除 component D,重新创建 component G 以及其子节点。虽然当两个 component 是不同类型但结构相似时,React diff 会影响性能,但正如 React 官方博客所言:不同类型的 component 是很少存在相似 DOM tree 的机会,因此这种极端因素很难在实现开发过程中造成重大影响的。



element diff

当节点处于同一层级时,React diff 提供了四种节点操作:
  • INSERT_MARKUP,新的 component 类型不在老集合里, 即是全新的节点,需要对新节点执行插入操作。
  • MOVE_EXISTING,在老集合有新 component 类型,且 element 是可更新的类型,generateComponentChildren 已调用 receiveComponent,这种情况下 prevChild=nextChild,就需要做移动操作,可以复用以前的 DOM 节点。
  • REMOVE_NODE,老 component 类型,在新集合里也有,但对应的 element 不同则不能直接复用和更新,需要执行删除操作,或者老 component 不在新集合里的,也需要执行删除操作。
  • TEXT_CONTENT,当文本节点改变时,替换原来的文本节点(不包括动态编译的文本)
相同类型DOM的比较,如果只是属性改变,是不会触发上述操作的,React会对属性进行重设从而实现节点的转换。

这四个操作对应的源码:
function enqueueMarkup(parentID, markup, toIndex) {  console.log("enqueueMarkup")  // NOTE: Null values reduce hidden classes.  updateQueue.push({    parentID: parentID,    parentNode: null,    type: ReactMultiChildUpdateTypes.INSERT_MARKUP,    markupIndex: markupQueue.push(markup) - 1,    textContent: null,    fromIndex: null,    toIndex: toIndex  });}/** * Enqueues moving an existing element to another index. * * @param {string} parentID ID of the parent component. * @param {number} fromIndex Source index of the existing element. * @param {number} toIndex Destination index of the element. * @private */function enqueueMove(parentID, fromIndex, toIndex) {  console.log("enqueueMove")  // NOTE: Null values reduce hidden classes.  updateQueue.push({    parentID: parentID,    parentNode: null,    type: ReactMultiChildUpdateTypes.MOVE_EXISTING,    markupIndex: null,    textContent: null,    fromIndex: fromIndex,    toIndex: toIndex  });}/** * Enqueues removing an element at an index. * * @param {string} parentID ID of the parent component. * @param {number} fromIndex Index of the element to remove. * @private */function enqueueRemove(parentID, fromIndex) {  console.log("enqueueRemove")  // NOTE: Null values reduce hidden classes.  updateQueue.push({    parentID: parentID,    parentNode: null,    type: ReactMultiChildUpdateTypes.REMOVE_NODE,    markupIndex: null,    textContent: null,    fromIndex: fromIndex,    toIndex: null  });}/** * Enqueues setting the text content. * * @param {string} parentID ID of the parent component. * @param {string} textContent Text content to set. * @private */function enqueueTextContent(parentID, textContent) {  console.log("enqueueTextContent")  // NOTE: Null values reduce hidden classes.  updateQueue.push({    parentID: parentID,    parentNode: null,    type: ReactMultiChildUpdateTypes.TEXT_CONTENT,    markupIndex: null,    textContent: textContent,    fromIndex: null,    toIndex: null  });}

如下图,老集合中包含节点:A、B、C、D,更新后的新集合中包含节点:B、A、D、C,此时新老集合进行 diff 差异化对比,发现 B != A,则创建并插入 B 至新集合,删除老集合 A;以此类推,创建并插入 A、D 和 C,删除 B、C 和 D。

针对这一现象,React 提出优化策略:允许开发者对同一层级的同组子节点,添加唯一 key 进行区分,虽然只是小小的改动,性能上却发生了翻天覆地的变化!

新老集合所包含的节点,如下图所示,新老集合进行 diff 差异化对比,通过 key 发现新老集合中的节点都是相同的节点,因此无需进行节点删除和创建,只需要将老集合中节点的位置进行移动,更新为新集合中节点的位置,此时 React 给出的 diff 结果为:B、D 不做任何操作,A、C 进行移动操作,即可。
通过将老集合中对应位置的节点向后移动,最终变成新新集合的结构。


以上策略算法参考于https://zhuanlan.zhihu.com/p/20346379?refer=purerender

mixin


<html><head>    <meta charset="utf-8">    <title>学习React!!</title></head><body>    <div id="app"></div>    <script src="bower_components/react/react.js"></script>    <script src="bower_components/react/JSXTransformer.js"></script><script type="text/jsx">var stateRecordMixin = {componentWillMount:function(){this.oldStates = [];},componentWillUpdate: function(nextProp,nextState){this.oldStates.push(nextState);},previousState:function(){var index = this.oldStates.length -1;return index == -1 ? {} : this.oldStates[index];}}var MessageBox = React.createClass({mixins: [stateRecordMixin],getInitialState:function(){return {count: 0,}},doUpdate:function(){this.setState({count: this.state.count + 1,});alert('上一次的计数是:'+this.previousState().count)},render:function(){console.log('渲染')return ( <div><h1 > 计数: {this.state.count}</h1> <button onClick={this.doUpdate}>手动更新一下组件(包括子组件)</button><Submessage count={this.state.count}/></div>)}});var Submessage = React.createClass({mixins: [stateRecordMixin],getInitialState:function(){return {count: 0,}},componentWillReceiveProps:function(nextProp){this.setState({count: this.props.count *2,})},render:function(){console.log('上一次子组件的计数是:'+this.previousState().count )return (<h3>当前子组件的计数是:{this.state.count}</h3>)}});var messageBox = React.render( <MessageBox/>, document.getElementById('app'))</script></body></html>

获取真实的DOM(ref)


var MyComponent = React.createClass({  handleClick: function() {    this.refs.myTextInput.focus();  },  render: function() {    return (      <div>        <input type="text" ref="myTextInput" />        <input type="button" value="Focus the text input" onClick={this.handleClick} />      </div>    );  }});





0 0