深入理解react(源码分析)

前端之家收集整理的这篇文章主要介绍了深入理解react(源码分析)前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

原文链接

@H_404_2@
  • 理解ReactElement和ReactClass的概念

    @H_404_2@
  • ReactElement

  • ReactClass

  • react渲染过程

  • react更新机制

  • reactdiff算法

  • react的优点与总结

  • 理解ReactElement和ReactClass的概念

    首先让我们理解两个概念:

    ReactElement

    一个描述DOM节点或component实例的字面级对象。它包含一些信息,包括组件类型type属性props。就像一个描述DOM节点的元素(虚拟节点)。它们可以被创建通过React.createElement方法jsx写法

    分为DOM ElementComponent Elements两类:

    @H_404_2@
  • DOM Elements

  • 当节点的type属性为字符串时,它代表是普通的节点,如div,span

    1. {
    2. type: 'button',props: {
    3. className: 'button button-blue',children: {
    4. type: 'b',props: {
    5. children: 'OK!'
    6. }
    7. }
    8. }
    9. }
    @H_404_2@
  • Component Elements

  • 当节点的type属性为一个函数或一个类时,它代表自定义的节点

    1. class Button extends React.Component {
    2. render() {
    3. const { children,color } = this.props;
    4. return {
    5. type: 'button',props: {
    6. className: 'button button-' + color,children: {
    7. type: 'b',props: {
    8. children: children
    9. }
    10. }
    11. }
    12. };
    13. }
    14. }
    15.  
    16. // Component Elements
    17. {
    18. type: Button,props: {
    19. color: 'blue',children: 'OK!'
    20. }
    21. }

    ReactClass

    ReactClass是平时我们写的Component组件(类或函数),例如上面的Button类。ReactClass实例化后调用render方法可返回DOM Element

    react渲染过程

    过程理解:

    1. // element是 Component Elements
    2. ReactDOM.render({
    3. type: Form,props: {
    4. isSubmitted: false,buttonText: 'OK!'
    5. }
    6. },document.getElementById('root'));
    1. 调用React.render方法,将我们的element根虚拟节点渲染到container元素中。element可以是一个字符串文本元素,也可以是如上介绍的ReactElement(分为DOM Elements,Component Elements)。

    2. 根据element的类型不同,分别实例化ReactDOMTextComponent,ReactDOMComponent,ReactCompositeComponent类。这些类用来管理ReactElement,负责将不同的ReactElement转化成DOM(mountComponent方法),负责更新DOM(receiveComponent方法,updateComponent方法,如下会介绍)等。

    3. ReactCompositeComponent实例调用mountComponent方法后内部调用render方法,返回了DOM Elements。再对如图的步骤2️⃣递归。

    react更新机制

    每个类型的元素都要处理好自己的更新:

    1. 自定义元素的更新,主要是更新render出的节点,做甩手掌柜交给render出的节点的对应component去管理更新。

    2. text节点的更新很简单,直接更新文案。

    3. 浏览器基本元素的更新,分为两块:

      1. 先是更新属性,对比出前后属性的不同,局部更新。并且处理特殊属性,比如事件绑定。

      2. 然后是子节点的更新,子节点更新主要是找出差异对象,找差异对象的时候也会使用上面的shouldUpdateReactComponent来判断,如果是可以直接更新的就会递归调用子节点的更新,这样也会递归查找差异对象。不可直接更新的删除之前的对象或添加新的对象。之后根据差异对象操作dom元素(位置变动,删除添加等)。

    第一步:调用this.setState

    1. ReactClass.prototype.setState = function(newState) {
    2. //this._reactInternalInstance是ReactCompositeComponent的实例
    3. this._reactInternalInstance.receiveComponent(null,newState);
    4. }

    第二步:调用内部receiveComponent方法

    这里主要分三种情况,文本元素,基本元素,自定义元素。

    自定义元素:

    receiveComponent方法源码

    1. // receiveComponent方法
    2. ReactCompositeComponent.prototype.receiveComponent = function(nextElement,transaction,nextContext) {
    3. var prevElement = this._currentElement;
    4. var prevContext = this._context;
    5.  
    6. this._pendingElement = null;
    7.  
    8. this.updateComponent(
    9. transaction,prevElement,nextElement,prevContext,nextContext
    10. );
    11.  
    12. }

    updateComponent方法源码

    1. // updateComponent方法
    2. ReactCompositeComponent.prototype.updateComponent = function(
    3. transaction,prevParentElement,nextParentElement,prevUnmaskedContext,nextUnmaskedContext
    4. ) {
    5. // 简写.....
    6. // 不是state更新而是props更新
    7. if (prevParentElement !== nextParentElement) {
    8. willReceive = true;
    9. }
    10.  
    11. if (willReceive && inst.componentWillReceiveProps) {
    12. // 调用生命周期componentWillReceiveProps方法
    13. }
    14. // 是否更新元素
    15. if (inst.shouldComponentUpdate) {
    16. // 如果提供shouldComponentUpdate方法
    17. shouldUpdate = inst.shouldComponentUpdate(nextProps,nextState,nextContext);
    18. } else {
    19. if (this._compositeType === CompositeTypes.PureClass) {
    20. // 如果是PureClass,浅层对比props和state
    21. shouldUpdate =
    22. !shallowEqual(prevProps,nextProps) ||
    23. !shallowEqual(inst.state,nextState);
    24. }
    25. }
    26. if (shouldUpdate) {
    27. // 更新元素
    28. this._performComponentUpdate(
    29. nextParentElement,nextProps,nextContext,nextUnmaskedContext
    30. );
    31. } else {
    32. // 不更新元素,但仍然设置props和state
    33. this._currentElement = nextParentElement;
    34. this._context = nextUnmaskedContext;
    35. inst.props = nextProps;
    36. inst.state = nextState;
    37. inst.context = nextContext;
    38. }
    39. // .......
    40.  
    41. }

    内部_performComponentUpdate方法源码

    1. // 内部_updateRenderedComponentWithNextElement方法
    2. ReactCompositeComponent.prototype._updateRenderedComponentWithNextElement = function() {
    3. // 判定两个element需不需要更新
    4. if (shouldUpdateReactComponent(prevRenderedElement,nextRenderedElement)) {
    5. // 如果需要更新,就继续调用子节点的receiveComponent的方法,传入新的element更新子节点。
    6. ReactReconciler.receiveComponent(
    7. prevComponentInstance,nextRenderedElement,this._processChildContext(context)
    8. );
    9. } else {
    10. // 卸载之前的子节点,安装新的子节点
    11. var oldHostNode = ReactReconciler.getHostNode(prevComponentInstance);
    12. ReactReconciler.unmountComponent(
    13. prevComponentInstance,safely,false /* skipLifecycle */
    14. );
    15.  
    16. var nodeType = ReactNodeTypes.getType(nextRenderedElement);
    17. this._renderedNodeType = nodeType;
    18. var child = this._instantiateReactComponent(
    19. nextRenderedElement,nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */
    20. );
    21. this._renderedComponent = child;
    22.  
    23. var nextMarkup = ReactReconciler.mountComponent(
    24. child,this._hostParent,this._hostContainerInfo,this._processChildContext(context),debugID
    25. );
    26. }

    shouldUpdateReactComponent函数源码

    1. function shouldUpdateReactComponent(prevElement,nextElement){
    2. var prevEmpty = prevElement === null || prevElement === false;
    3. var nextEmpty = nextElement === null || nextElement === false;
    4. if (prevEmpty || nextEmpty) {
    5. return prevEmpty === nextEmpty;
    6. }
    7.  
    8. var prevType = typeof prevElement;
    9. var nextType = typeof nextElement;
    10. if (prevType === 'string' || prevType === 'number') {
    11. // 如果先前的ReactElement对象类型是字符串或数字,新的ReactElement对象类型也是字符串或数字,则需要更新,新的ReactElement对象类型是对象,则不应该更新,直接替换。
    12. return (nextType === 'string' || nextType === 'number');
    13. } else {
    14. // 如果先前的ReactElement对象类型是对象,新的ReactElement对象类型也是对象,并且标签类型和key值相同,则需要更新
    15. return (
    16. nextType === 'object' &&
    17. prevElement.type === nextElement.type &&
    18. prevElement.key === nextElement.key
    19. );
    20. }
    21. }

    文本元素:

    receiveComponent方法源码

    1. ReactDOMTextComponent.prototype.receiveComponent(nextText,transaction) {
    2. //跟以前保存的字符串比较
    3. if (nextText !== this._currentElement) {
    4. this._currentElement = nextText;
    5. var nextStringText = '' + nextText;
    6. if (nextStringText !== this._stringText) {
    7. this._stringText = nextStringText;
    8. var commentNodes = this.getHostNode();
    9. // 替换文本元素
    10. DOMChildrenOperations.replaceDelimitedText(
    11. commentNodes[0],commentNodes[1],nextStringText
    12. );
    13. }
    14. }
    15. }

    基本元素:

    receiveComponent方法源码

    1. ReactDOMComponent.prototype.receiveComponent = function(nextElement,context) {
    2. var prevElement = this._currentElement;
    3. this._currentElement = nextElement;
    4. this.updateComponent(transaction,context);
    5. }

    updateComponent方法源码

    1. ReactDOMComponent.prototype.updateComponent = function(transaction,context) {
    2. // 略.....
    3. //需要单独的更新属性
    4. this._updateDOMProperties(lastProps,isCustomComponentTag);
    5. //再更新子节点
    6. this._updateDOMChildren(
    7. lastProps,context
    8. );
    9.  
    10. // ......
    11. }

    this._updateDOMChildren方法内部调用diff算法,请看下一节........

    react Diff算法

    diff算法源码

    1. _updateChildren: function(nextNestedChildrenElements,context) {
    2. var prevChildren = this._renderedChildren;
    3. var removedNodes = {};
    4. var mountImages = [];
    5. // 获取新的子元素数组
    6. var nextChildren = this._reconcilerUpdateChildren(
    7. prevChildren,nextNestedChildrenElements,mountImages,removedNodes,context
    8. );
    9. if (!nextChildren && !prevChildren) {
    10. return;
    11. }
    12. var updates = null;
    13. var name;
    14. var nextIndex = 0;
    15. var lastIndex = 0;
    16. var nextMountIndex = 0;
    17. var lastPlacedNode = null;
    18.  
    19. for (name in nextChildren) {
    20. if (!nextChildren.hasOwnProperty(name)) {
    21. continue;
    22. }
    23. var prevChild = prevChildren && prevChildren[name];
    24. var nextChild = nextChildren[name];
    25. if (prevChild === nextChild) {
    26. // 同一个引用,说明是使用的同一个component,所以我们需要做移动的操作
    27. // 移动已有的子节点
    28. // NOTICE:这里根据nextIndex,lastIndex决定是否移动
    29. updates = enqueue(
    30. updates,this.moveChild(prevChild,lastPlacedNode,nextIndex,lastIndex)
    31. );
    32. // 更新lastIndex
    33. lastIndex = Math.max(prevChild._mountIndex,lastIndex);
    34. // 更新component的.mountIndex属性
    35. prevChild._mountIndex = nextIndex;
    36. } else {
    37. if (prevChild) {
    38. // 更新lastIndex
    39. lastIndex = Math.max(prevChild._mountIndex,lastIndex);
    40. }
    41. // 添加新的子节点在指定的位置上
    42. updates = enqueue(
    43. updates,this._mountChildAtIndex(
    44. nextChild,mountImages[nextMountIndex],context
    45. )
    46. );
    47. nextMountIndex++;
    48. }
    49. // 更新nextIndex
    50. nextIndex++;
    51. lastPlacedNode = ReactReconciler.getHostNode(nextChild);
    52. }
    53. // 移除掉不存在的旧子节点,和旧子节点和新子节点不同的旧子节点
    54. for (name in removedNodes) {
    55. if (removedNodes.hasOwnProperty(name)) {
    56. updates = enqueue(
    57. updates,this._unmountChild(prevChildren[name],removedNodes[name])
    58. );
    59. }
    60. }
    61. }

    react的优点与总结

    优点

    @H_404_2@
  • 虚拟节点。在UI方面,不需要立刻更新视图,而是生成虚拟DOM后统一渲染。

  • 组件机制。各个组件独立管理,层层嵌套,互不影响,react内部实现的渲染功能

  • 差异算法。根据基本元素的key值,判断是否递归更新子节点,还是删除旧节点,添加新节点。

  • 总结

    想要更好的利用react的虚拟DOM,diff算法的优势,我们需要正确的优化、组织react页面。例如将一个页面render的ReactElement节点分解成多个组件。在需要优化的组件手动添加 shouldComponentUpdate 来避免不需要的 re-render

    猜你在找的React相关文章