React:深入探索(一)

1. 什么是React

React 是一个声明式,高效且灵活的用于构建用户界面的 JavaScript 库,由Facebook和一个大型开发者社区共同维护。使用 React 可以将一些简短、独立的代码片段组合成复杂的 UI 界面,这些代码片段被称作“组件”。

2. React的特点

  • 声明式设计 - React采用声明范式,可以轻松描述应用。

  • 高效 - React通过对DOM的模拟,最大限度地减少与DOM的交互。

  • 灵活 - React可以与已知的库或框架很好地配合。

  • JSX - JSX是JavaScript语法的扩展。

  • 单向响应式的数据流 - React实现了单向相应的数据流,从而减少了重复代码,这也是它为什么比传统数据绑定更简单。

  • 组件 - 通过React构建组件,使得代码更加容易得到复用,能够很好的应用在大项目的开发中。

3. React的核心

真正去了解React还是要去深入学习它的核心东西,核心内容主要包括以下几点:

  • JSX:即Javascript XML,一个看起来很像 XML 的 JavaScript 语法扩展。用自己的话来说就是可以在js里面直接写DOM元素。

  • 虚拟DOM:一种编程概念。在这个概念里, UI 以一种理想化的,或者说“虚拟的”表现形式(将真实的DOM结构映射成一个JSON数据结构 )被保存于内存中。并通过如 ReactDOM 等类库使之与“真实的” DOM 同步。这一过程叫做协调

  • Diff算法:上面说到的虚拟DOM与“真实的” DOM同步的问题,也就是协调,协调需要采用Diff算法实现。普通diff的复杂度对于大量dom对比会出现严重的性能问题,React团队对diff算法进行了优化,主要有三个方面:tree diff、component diff、element diff。

  • 组件化:Web Component通过自定义元素的方式实现组件化,React的组件元素被描述成纯粹的JSON对象,由三部分组成——属性(props),状态(state)以及生命周期方法。组件化的目的就是为了能够进行复用,减少代码的冗余。

4. JSX

const element = <h1>Hello, world!</h1>;

这个有趣的标签语法既不是字符串也不是 HTML。它被称为 JSX,是一个 JavaScript 的语法扩展。我们建议在 React 中配合使用 JSX,JSX 可以很好地描述 UI 应该呈现出它应有交互的本质形式(JSX 可以生成 React “元素”)。JSX 可能会使人联想到模版语言,但它具有 JavaScript 的全部功能。

  • 在 JSX 中嵌入表达式

    在 JSX 语法中,你可以在大括号内放置任何有效的 JavaScript 表达式

const name = 'Josh Perez';
const element = <h1>Hello, {name}</h1>;
ReactDOM.render(
  element,
  document.getElementById('root')
);
  • JSX 也是一个表达式

    你可以在 if 语句和 for 循环的代码块中使用 JSX,将 JSX 赋值给变量,把 JSX 当作参数传入,以及从函数中返回 JSX

function getGreeting(user) {
  if (user) {
    return <h1>Hello, {formatName(user)}!</h1>;  
  }
  return <h1>Hello, Stranger.</h1>;
}
  • JSX 特定属性

可以通过使用引号,来将属性值指定为字符串字面量:

const element = <div tabIndex="0"></div>;

也可以使用大括号,来在属性值中插入一个 JavaScript 表达式:

const element = <img src={user.avatarUrl}></img>;

注意:在属性中嵌入 JavaScript 表达式时,不要在大括号外面加上引号。因为 JSX 语法上更接近 JavaScript 而不是 HTML,所以 React DOM 使用 camelCase(小驼峰命名)来定义属性的名称,而不使用 HTML 属性名称的命名约定。

  • JSX 防止注入攻击
const title = response.potentiallyMaliciousInput;
// 直接使用是安全的:
const element = <h1>{title}</h1>;

React DOM 在渲染所有输入内容之前,默认会进行转义。它可以确保在你的应用中,永远不会注入那些并非自己明确编写的内容。所有的内容在渲染之前都被转换成了字符串。这样可以有效地防止 XSS(cross-site-scripting, 跨站脚本)攻击。

  • JSX 表示对象

    Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用。实际上,JSX 仅仅只是 React.createElement(component, props, ...children) 函数的语法糖,以下两种示例代码完全等效:

const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);

const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);

// 实际上React.createElement创建了一个这样的对象:
const element = {
  type: 'h1',
  props: {
    className: 'greeting',
    children: 'Hello, world!'
  }
};

这些对象被称为 “React 元素”。它们描述了你希望在屏幕上看到的内容。React 通过读取这些对象,然后使用它们来构建 DOM 以及保持随时更新。将 React 元素渲染为 DOM,通过ReactDOM.render()实现

// “根” DOM 节点,因为该节点内的所有内容都将由 React DOM 管理。
<div id="root"></div>

// 想要将一个 React 元素渲染到根 DOM 节点中,只需把它们一起传入 ReactDOM.render():
const element = <h1>Hello, world</h1>;
ReactDOM.render(element, document.getElementById('root'));
  • 解析React.createElement()
    每次新增一个组件页面,都需要引入React,而实际在代码中并未使用到,删除React又会报错,其实在渲染元素的时候调用了React.createElement(),只要我们使用了JSX,就要引入React。React.createElement()到底干了些什么事情?
<h1 id="title" className="title_class" style={{color: 'red'}}>
   hello
   <span>world</span>
</h1>

// 当我们将上面的代码经过babel转译后,调用React.createElement(component, props, ...children),如下:

React.createElement("h1", {
  id: "title",
  className: "title_class",
  style: {
    color: 'red'
  }
}, "hello", React.createElement("span", null, "world"));

// React.createElement创建了一个这样的对象:
const element = {
  type: 'h1',
  props: {
    id: 'title'
    className: 'title_class',
    style: {
        color: 'red'
    }
    children: ['Hello, world!',React.createElement("span", null, "world")]
  }
};

// React.createElement("span", null, "world")  => {type: 'span',props: {},children: 'world'}
//方法接受三个参数,第一个参数是组件类型,第二个参数是要传递给组件的属性,第三个参数是children。方法最终会返回一个具有以下属性的对象
export function createElement(type, config, children) {
  let propName;

  // Reserved names are extracted
  const props = {};   // 等同于config

  let key = null;
  let ref = null;
  let self = null;
  let source = null;

  // config对象是dom的属性总和
  if (config != null) {
    if (hasValidRef(config)) {  // 判断config自身是否有ref的属性
      ref = config.ref;
    }
    if (hasValidKey(config)) {  // 判断config自身是否有key的属性,参考for循环添加的key属性
      key = '' + config.key;
    }

    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;
    // Remaining properties are added to a new props object
    for (propName in config) {
      if (hasOwnProperty.call(config, propName) &&!RESERVED_PROPS.hasOwnProperty(propName)) {
        props[propName] = config[propName];
      }
    }
  }

  // Children can be more than one argument, and those are transferred onto
  // the newly allocated props object.
  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];
    }
    props.children = childArray;
  }

  // Resolve default props
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}

5. 虚拟DOM

virtual dom是通过JSX编译生成的一个JavaScript对象,正如上面所讲的调用React.createElement(component, props, ...children)返回的结果,这个结果其实就是虚拟DOM。

// 虚拟dom
const vdom = {
  type: 'h1',
  props: {
    id: 'title'
    className: 'title_class',
    style: {
        color: 'red'
    }
    children: ['Hello, world!',React.createElement("span", null, "world")]
  }
};
img
  • 传统web应用和React web应用

    传统web应用:

    img

    React web应用:

img
  • 虚拟DOM的原理(下节课跟diff算法一起再详细介绍)

    1. 虚拟DOM本质上是JavaScript对象,是对真实DOM的抽象

    2. 状态变更时,记录新树和旧树的差异

    3. 最后把差异更新到真正的dom中

      img
  • 虚拟DOM的优点

    最终表现在DOM上的修改只是变更的部分,可以保证非常高效的渲染。

  • 虚拟DOM的缺点

    首次渲染大量DOM时,由于多了一层虚拟DOM的计算,会比innerHTML插入慢。

引入虚拟DOM也是React提升性能的一种手段,React非常快速是因为它从不直接操作真实的DOM。虚拟DOM是在DOM的基础上建立了一个抽象层,对数据和状态所做的任何改动,都会被自动且高效的同步到虚拟DOM,最后再批量同步到DOM中。我们开发项目不用接触这个虚拟DOM,但是理解其运行机制不仅有助于更好的理解React组件的生命周期,而且对于进一步优化 React程序也有很大帮助。

6. 渲染DOM

我们拿到了虚拟DOM,最终要渲染到页面中去,生成真实的DOM,具体是怎么实现的呢?

  • 创建元素createElement
export function createElement(
  type: string,  // 标签名
  props: Object,  // 属性
  rootContainerElement: Element | Document,  // 根节点
  parentNamespace: string,
): Element {
  let isCustomComponentTag;
  // 获取ownerDocument
  const ownerDocument: Document = getOwnerDocumentFromRootContainer(
    rootContainerElement,
  );
  let domElement: Element;
  let namespaceURI = parentNamespace;
  if (namespaceURI === HTML_NAMESPACE) {
    namespaceURI = getIntrinsicNamespace(type);
  }
  // 命名空间是否等于W3C规范 http://www.w3.org/1999/xhtml
  if (namespaceURI === HTML_NAMESPACE) {
    if (type === 'script') {  // 创建script标签
      const div = ownerDocument.createElement('div');
      div.innerHTML = '<script><' + '/script>'; // eslint-disable-line
      const firstChild = ((div.firstChild: any): HTMLScriptElement);
      domElement = div.removeChild(firstChild);
    } else if (typeof props.is === 'string') {
      domElement = ownerDocument.createElement(type, {is: props.is});
    } else {
      domElement = ownerDocument.createElement(type);
      // 对select标签做处理
      if (type === 'select') {
        const node = ((domElement: any): HTMLSelectElement);
        if (props.multiple) {
          node.multiple = true;
        } else if (props.size) {
          node.size = props.size;
        }
      }
    }
  } else {
    domElement = ownerDocument.createElementNS(namespaceURI, type);
  }

  return domElement;
}
  • 配置属性、样式以及渲染内容
function setInitialDOMProperties(
  tag: string,
  domElement: Element,
  rootContainerElement: Element | Document,
  nextProps: Object,
  isCustomComponentTag: boolean,
): void {
  for (const propKey in nextProps) {
    if (!nextProps.hasOwnProperty(propKey)) {
      continue;
    }
    const nextProp = nextProps[propKey];
    if (propKey === STYLE) {
      // 配置style里面的样式
      setValueForStyles(domElement, nextProp);  // 下文有贴出源码setValueForStyles
    } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
      // 富文本渲染 dangerouslySetInnerHTML属性
      const nextHtml = nextProp ? nextProp[HTML] : undefined;
      if (nextHtml != null) {
        setInnerHTML(domElement, nextHtml);
      }
    } else if (propKey === CHILDREN) {
      if (typeof nextProp === 'string') {
        // 单纯文本渲染
        const canSetTextContent = tag !== 'textarea' || nextProp !== '';
        if (canSetTextContent) {
          setTextContent(domElement, nextProp);  // 下文有贴出源码setTextContent
        }
      } else if (typeof nextProp === 'number') {
        // 遇到数字转成字符串再渲染
        setTextContent(domElement, '' + nextProp);
      }
    }else if (registrationNameDependencies.hasOwnProperty(propKey)) {
      if (nextProp != null) {
        if (propKey === 'onScroll') {
          listenToNonDelegatedEvent('scroll', domElement);
        }
      }
    } else if (nextProp != null) {
      // setAttribute 设置属性以及对应的内容
      setValueForProperty(domElement, propKey, nextProp, isCustomComponentTag);
    }
  }
}
export function setValueForStyles(node, styles) {
  const style = node.style;
  for (let styleName in styles) {
    if (!styles.hasOwnProperty(styleName)) {
      continue;
    }
    const isCustomProperty = styleName.indexOf('--') === 0;
    const styleValue = dangerousStyleValue(
      styleName,
      styles[styleName],
      isCustomProperty,
    );
    if (styleName === 'float') {
      styleName = 'cssFloat';
    }
    if (isCustomProperty) {
      style.setProperty(styleName, styleValue);
    } else {
      style[styleName] = styleValue;
    }
  }
}
const setTextContent = function(node: Element, text: string): void {
  if (text) {
    const firstChild = node.firstChild;

    if (firstChild && firstChild === node.lastChild && firstChild.nodeType === TEXT_NODE) {
      firstChild.nodeValue = text;
      return;
    }
  }
  node.textContent = text;
};
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容