【React进阶之路01】- JSX演变成真实DOM

什么是 JSX

JSX 是 ECMAScript 一个类似 XML 的语法扩展。基本上,它只是为 React.createElement() 函数提供语法糖,从而让在我们在 JavaScript 中,使用类 HTML 模板的语法,进行页面描述。

JSX编译(babel)

由于JSX是javascript的一种扩展,所以这就直接决定了浏览器不能像天然支持js一样支持jsx,所以jsx需要被编译后才能被识别,这个编译工作正是由Babel来实现的

我们先来看个简单的JSX demo,看看经过babel的编译他会变成什么?
【React进阶之路01】- JSX演变成真实DOM_第1张图片
上图可看出,jsx中的每个标签都会被编译为React.crerateElement函数调用,接下来我们就要结合源码看下这个React.crerateElement函数

createElement源码

export function createElement(type, config, children) {
  let propName;  // 用来存储后面需要用到的元素属性
  const props = {};  // 用来存储元素属性的键值对集合
  
  // 以下4个属性都是React元素的属性,暂时不用管
  let key = null;
  let ref = null;
  let self = null;
  let source = null;

// config对象是传入的元素的属性
  if (config != null) {
  // 进来第一件事是依次对上述4个属性赋值
    if (hasValidRef(config)) {
      ref = config.ref;

      if (__DEV__) {
        warnIfStringRefCannotBeAutoConverted(config);
      }
    }
    
    // 此处将key值字符串化
    if (hasValidKey(config)) {
      if (__DEV__) {
        checkKeyStringCoercion(config.key);
      }
      key = '' + config.key;
    }

    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;
    
    // 接着就是把config里的属性一个个地搬到props对象中(前面定义的) 
    for (propName in config) {
      if (
      // 筛选出可以提进props对象里的属性
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        props[propName] = config[propName];
      }
    }
  }

  // childrenLength为当前元素的子元素个数,减去2是type和config占用的长度
  const childrenLength = arguments.length - 2;
  if (childrenLength === 1) {  // 文本节点
    props.children = children;
  } else if (childrenLength > 1) {
    const childArray = Array(childrenLength);
    for (let i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    if (__DEV__) {
      if (Object.freeze) {
        Object.freeze(childArray);
      }
    }
    props.children = childArray;
  }

  // 处理defaultProps
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  if (__DEV__) {
    if (key || ref) {
      const displayName =
        typeof type === 'function'
          ? type.displayName || type.name || 'Unknown'
          : type;
      if (key) {
        defineKeyPropWarningGetter(props, displayName);
      }
      if (ref) {
        defineRefPropWarningGetter(props, displayName);
      }
    }
  }
  // 最后调动ReactElement方法,传入刚处理的参数
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}

createElement方法接受3个参数

type: 用于标识节点的类型,比如‘div’、'span’等
config:以对象形式传入,组件所有的属性都会以键值对的形式存储在config对象中
children:以对象形式传入,他记录的是组件标签之间嵌套的内容

我们根据上述代码可以将createElement 函数进行拆解:【React进阶之路01】- JSX演变成真实DOM_第2张图片
createElement 每一步基本都是在格式化数据,说白点就是它更像是开发者和ReactElement函数调用之间的参数中介,它接受较为简单的参数,然后按照ReactElement函数入参的预期对参数进行相应的格式化,然后最终通过调用ReactElement函数来实现元素的创建 ,所以接下来我们重点来看下ReactElement函数内部源码

ReactElement源码:

const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    $$typeof: REACT_ELEMENT_TYPE, // 它是一个常量,用来标识该对象是一个ReactElement
    // 内置属性赋值
    type: type,
    key: key,
    ref: ref,
    props: props,
    // 记录创造该元素的组件
    _owner: owner,
  };

  if (__DEV__) {   // 这里是对__DEV__环境下的处理,对逻辑理解没什么影响,可以先不看
   
    element._store = {};
  
    Object.defineProperty(element._store, 'validated', {
      configurable: false,
      enumerable: false,
      writable: true,
      value: false,
    });
   
    Object.defineProperty(element, '_self', {
      configurable: false,
      enumerable: false,
      writable: false,
      value: self,
    });
  
    Object.defineProperty(element, '_source', {
      configurable: false,
      enumerable: false,
      writable: false,
      value: source,
    });
    if (Object.freeze) {
      Object.freeze(element.props);
      Object.freeze(element);
    }
  }

  return element;
};

所以从上面源码中可以看出,ReactElement函数也是很简单的,它只是对参数进行了组装,组装成了element对象返回,其实这里说的element对象就是我们常提到的虚拟DOM,我们可以打印一个出来看看:

function App() {
	const element =(
    <div className="App">
      hello jsx
    </div>
  );
  console.log(element,'element')
  return element;
}

export default App;

【React进阶之路01】- JSX演变成真实DOM_第3张图片
没错,上面的就是虚拟DOM,它就长这个吊样,那么它是怎么变成最终的真实DOM的呢?

此时不少同学应该已经能猜到了,没错就是ReactDOM.render方法

ReactDOM.render简单实现:

// 传入两个参数,虚拟dom, 应用容器
function render(vDom, container) {
  let dom;
  // 判断当前的节点是文本还是对象
  if (vDom.type === 'TEXT') {
    dom = document.createTextNode(vDom.props.nodeValue);
  } else {
    // 当前节点是一个对象
    dom = document.createElement(vDom.type);
  }

  // 虚拟 dom 的属性,将 vDom 的除了 children 的属性都挂在到 dom 对象上
  if (vDom.props) {
    Object.keys(vDom.props)
      // 过滤掉了 children 属性
      .filter(key => key !== 'children')
      // 循环剩余所有属性添加到dom上
      .forEach(item => {
        dom[item] = vDom.props[item];
      });
  }

  // 通过递归调用实现子元素
  if (vDom.props && vDom.props.children && vDom.props.children.length > 0) {
    vDom.props.children.forEach(child => {
      render(child, dom);
    });
  }

  container.appendChild(dom);
}

const ReactDOM = {
  render,
};

export default ReactDOM;

至此,从开始的JSX就转换成了真实的DOM节点了,整天流程梳理后如下:
【React进阶之路01】- JSX演变成真实DOM_第4张图片

你可能感兴趣的:(框架,react.js,javascript,前端)