JSX的本质

React.createElement

jsx 仅仅只是 React.createElement(component, props, ...children) 函数的语法糖,

所有的jsx最终都会通过babel被转换成React.createElement的函数进行调用

<h2 classname="foo">Hello Wolrd</h2>

<!-- 上述JSX代码编译后的结果如下 -->

<!--
 参数1 - 当前ReactElement的类型 - type
      - 如果是标签元素,那么就使用字符串表示 如“div”
      - 如果是组件元素,那么就直接使用组件的名称(也就是组件构造函数) 如 Cpn

 参数2 - 配置对象 - config
      - 所有jsx中的属性都在config中以对象的属性和值的形式存储
      - 其实就是属性组成的对象 - 如果没有属性 可以传递null

  参数3 - 子元素 - children
       - 存放在标签中的内容,以children数组的方式进行存储
-->

<!-- 
    JSX是React.createElement的语法糖
    react解析引擎不可以直接解析JSX,需要先将其转换为React.createElement
  所以如果直接使用React.createElement进行编写的时候,可以不使用babel进行编译
-->
React.createElement('h2', {
  classname: 'foo'
}, 'Hello World')

React.createElement的源码

// 存储一些特殊的react属性值
const RESERVED_PROPS = {
  key: true,
  ref: true,
  __self: true,
  __source: true,
}

// createElement的部分源码
export function createElement(type, config, children) {
  // ... 

  // props是实际存在内容的对象
  const props = {};

  // 属性不为null的时候,将属性依次挂载到props上
  if (config != null) {
    for (propName in config) {
      if (
        // 如果config自身有propName属性,且propName属性不是特殊的react属性的时候
        // 将该属性挂载到props上
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        props[propName] = config[propName];
      }
    }
  }

  // 从第三个参数开始就是子元素,使用arguments接收所有的实参
  // 并将所有的子元素挂载到props.children上
  // 也就是说子元素在传递的时候使用 React.createElement(type, config, child1, child2, child3, ...)
  const childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    props.children = children;
  } else if (childrenLength > 1) {
    // const childArray = Array(childrenLength); === const childArray = new Array(childrenLength);
    // 在使用Array构造器创建数组的时候,new关键字可以省略
    const childArray = Array(childrenLength);
    for (let i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    props.children = childArray;
  }

  // 调用ReactElement方法返回一个ReactElement对象,也就是vNode对象
  return ReactElement(
    type,
    props,
    // ...
  );
}

hasOwnProperty --- hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性

const obj = {name: 'Klaus'};
obj.__proto__.age = 23

// hasOwnProperty的参数为对应的属性名
console.log(obj.hasOwnProperty('name')) // => true
console.log(obj.hasOwnProperty('age')) // => false

// 注意: 如果hasOwnProperty方法的参数是一个没有定义的对象,那么脚本并不会报错,而是直接返回false
console.log(obj.hasOwnProperty(name)) // => false

ReactElement

通过 React.createElement方法 最终创建出来一个 ReactElement对象

而ReactElement对象的props.children属性中记录这当前元素的所有子元素

因此ReactElement本质就是vNode,其实也是vDom

React利用ReactElement对象组成了一个JavaScript的对象树,而这个JS对象树就是大名鼎鼎的虚拟DOM(Virtual DOM)

jsx

<div className="container">
  <div className="header">
    <h1>Hello Wolrd</h1>
  </div>
  <div className="content">
    <p>Lorem ipsum dolor sit amet.</p>
  </div>
  <div className="footer">
    <button>lorem</button>
  </div>
</div>

经过编译后,会形成如下vDom对象

1.png

React会将jsx编译为在内存中运行的vDOM,随后在通过ReactDOM.render将vDOM编译成对应的真实DOM

vdom的作用

1. 操作真实DOM性能较低

在JavaScript中,通过React.createElement创建出来的js对象是一个非常庞大的对象

并且DOM操作会引起浏览器的回流和重绘,所以频繁的DOM操作必然会导致性能的损失

而传统的开发模式不可避免的会需要进行频繁的DOM操作

而vDom本质就是一个js对象,这个对象是对真实DOM在内存中的抽象和模拟

vDom在结构上和真实DOM是一一映射的

而vDOM是存在于内存中的,且vDOM比真实DOM更为的轻量级,操作起来性能也更高

因为vDOM是在内存中运行的,我们可以将所有的更新在内存中统一进行修改后,一次性映射到真实DOM中

这样就避免了频繁的操作DOM,引起的重绘和回流

2.很难跟踪状态发生的改变

传统的开发中,跟踪界面中状态的改变是十分麻烦的,只能通过debugger和console

但是有了vDOM后,React DevTool就可以通过新旧vDOM的对比,进行页面状态的改变的跟踪

3.vDOM便于跨平台

UI在真正渲染之前会先被编译为vdom,

此时就可以根据不同的平台渲染成不同的内容

例如:

在web端,使用web端的ReactDOM.render,将VDOM渲染为真实DOM

在移动端,使用移动端的ReactDOM.render, 将VDOM渲染为原生控件( 如IOS的UIButton,Android的Button)

4 声明式编程

使用了vDOM后,UI以一种理想化或者说虚拟化的方式保存在内存中,并且它是一个相对简单的JavaScript对象

我们可以通过ReactDOM.render让 虚拟DOM 和 真实DOM同步起来,这个过程中叫做协调(Reconciliation)

因为大部分的DOM操作都交给React来帮助我们进行管理

所以这就意味着我们只需要维护和定义对应的状态即可,React会确保DOM和这些状态是匹配的

我们不需要直接进行DOM操作,可以从手动更改DOM、属性操作、事件处理中解放出来

阶段案例

  1. 以表格形式显示书籍数据
  2. 底部显示书籍总价
  3. 点击+或-可以更改数据数量,如果数量为1的时候,静止减少
  4. 点击移除按钮可以移除对应数据,如果全部移除,显示购物车为空
1.png
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
  <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
  <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
  <style>
    table {
      border-collapse: collapse;
    }

    th,   td {
      border: 1px solid #333;
      padding: 10px 15px;
    }

    .count {
      padding: 0 10px;
    }
  </style>
</head>
<body>
  <div id="app"></div>

  <script type="text/babel">
    class App extends React.Component {
      constructor(props) {
        super(props)

        this.state = {
          books: [
            {
              id: 1,
              name: '《算法导论》',
              date: '2006-9',
              price: 85.00,
              count: 1
            },
            {
              id: 2,
              name: '《UNIX编程艺术》',
              date: '2006-2',
              price: 59.00,
              count: 1
            },
            {
              id: 3,
              name: '《编程珠玑》',
              date: '2008-10',
              price: 39.00,
              count: 1
            },
            {
              id: 4,
              name: '《代码大全》',
              date: '2006-3',
              price: 128.00,
              count: 1
            },
          ]
        }
      }

      // 和渲染相关的函数写在render函数的前边
      // 渲染相关的函数推荐以render开头
      renderTable() {
        return (
          <table>
            <thead>
              <tr>
                <th></th>
                <th>书籍名称</th>
                <th>出版日期</th>
                <th>价格</th>
                <th>购买数量</th>
                <th>操作</th>
              </tr>
            </thead>

            <tbody>
              { this.state.books.map((book, index) => (
                <tr key={book.id}>
                  <td>{ book.id }</td>
                  <td>{ book.name }</td>
                  <td>{ book.date }</td>
                  <td>{ `¥${ book.price.toFixed(2) }` }</td>
                  <td>
                    <button 
                      onClick={() => this.changeCount(-1, index)} 
                      disabled={ book.count <= 1 }
                    >
                      -
                    </button>
                   <span className="count"> { book.count }</span>
                    <button onClick={() => this.changeCount(1, index)}>+</button>
                  </td>
                  <td>
                    <button onClick={() => this.deleteBook(index)}>移除</button>
                  </td>
                </tr>
              )) }
            </tbody>
          </table>
        )
      }

      render() {
        return <div>
          { this.state.books.length ? this.renderTable() : <h2>购物车为空</h2> }
          <p>总价格: { this.calcTotalPrice() }</p>
        </div>
      }

      // 和逻辑相关的函数,推荐写在render函数的后边
      changeCount(step, index) {
        // React中有一个非常重要的设计原则
        // 就是state中的数据的不可变性
        // 也就是永远不要主动去修改state中的状态
        // 如果需要修改state中的状态,一定要通过setState方法
        const books = [...this.state.books]
        books[index].count += step
        this.setState({
          books
        })
      }

      deleteBook(index) {
        const books = [...this.state.books]
        books.splice(index, 1)
        this.setState({
          books
        })
      }

      calcTotalPrice() {
        return '¥' + this.state.books.reduce((totalPrice, book) 
              => totalPrice + book.price * book.count, 0).toFixed(2)
      }
    }

    ReactDOM.render(<App />, document.getElementById('app'))
  </script>
</body>
</html>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容