<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          為什么 JSX 語法這么香?

          共 27229字,需瀏覽 55分鐘

           ·

          2022-07-07 18:54

          前言

          時下雖然接入 JSX 語法的框架(React、Vue)越來越多,但與之緣分最深的毫無疑問仍然是 React。2013 年,當(dāng) React 帶著 JSX 橫空出世時,社區(qū)曾對 JSX 有過不少的爭議,但如今,越來越多的人面對 JSX 都要說上一句“真香”!典型的“真香”系列。

          JSX 是什么?

          按照 React 官方的解釋[2],JSX 是一個 JavaScript 的語法擴(kuò)展,類似于模板語法,或者說是一個類似于 XML 的 ECMAScript 語法擴(kuò)展,并且具備 JavaScript 的全部功能。

          這段解釋可抽離兩個關(guān)鍵點(diǎn):

          • 「JavaScript 語法擴(kuò)展」
          • 「具備JavaScript 的全部功能」

          JSX 的定位是 JavaScript 的「語法擴(kuò)展」,而不是“某個版本”,這就決定了瀏覽器并不會像天然支持 JavaScript 一樣支持 JSX 。這就引出了一個問題 “JSX 是如何在 JavaScript 中生效的?”

          JSX 語法是如何在 JavaScript 中生效的?

          React

          在 React 框架中,JSX 的語法是如何在 JavaScript 中生效的呢?React 官網(wǎng)給出的解釋是,JSX 會被編譯為 React.createElement(), React.createElement() 將返回一個叫作“React Element”的 JS 對象

          對于 JSX 的編譯是由 Babel 來完成的。

          Babel 是一個工具鏈,主要用于將采用 ECMAScript 2015+ 語法編寫的代碼轉(zhuǎn)換為向后兼容的 JavaScript 語法,以便能夠運(yùn)行在當(dāng)前和舊版本的瀏覽器或其他環(huán)境中。

          當(dāng)然 Babel 也具備將 JSX 轉(zhuǎn)換為 JS 的能力,看一個例子:左邊是我們 React 開發(fā)中寫到的語法,并且包含了一段 JSX 代碼。經(jīng)過 Babel 轉(zhuǎn)換之后,就全部變成了 JS 代碼。



          其實(shí)如果仔細(xì)看,發(fā)現(xiàn) JSX 更像是一種語法糖,通過類似模板語法的描述方式,描述函數(shù)對象。其實(shí)在 React 中并不會強(qiáng)制使用 JSX 語法,我們也可以使用 React.createElement 函數(shù),例如使用 React.createElement 函數(shù)寫這樣一段代碼。

          class Test extends React.Component {
            render() {
              return React.createElement(
                "div",
                null,
                React.createElement(
                  "div",
                  null,
                  "Hello, ",
                  this.props.test
                ),
                React.createElement("div"null"Today is a fine day.")
              );
            }
          }

          ReactDOM.render(
            React.createElement(Test, {
              test"baixiaobai"
            }),
            document.getElementById("root")
          );

          在采用 JSX 之后,這段代碼會這樣寫:

          class Test extends React.Component {
            render() {
              return (
               <div>
                      <div>Hello, {this.props.test}</div> 
                      <div>Today is a fine day.</div>
               </div>

              );
            }
          }
          ReactDOM.render(
            <Test test="baixiaobai" />,
            document.getElementById('root')
          );

          通過對比發(fā)現(xiàn),在實(shí)際功能效果一致的前提下,JSX 代碼層次分明、嵌套關(guān)系清晰;而 React.createElement 代碼則給人一種非常混亂的“雜糅感”,這樣的代碼不僅讀起來不友好,寫起來也費(fèi)勁。

          JSX 語法寫出來的代碼更為的簡潔,而且代碼結(jié)構(gòu)層次更加的清晰。

          JSX 語法糖允許我們開發(fā)人員像寫 HTML 一樣來寫我們的 JS 代碼。在降低學(xué)習(xí)成本的同時還提升了我們的研發(fā)效率和研發(fā)體驗(yàn)。

          Vue

          當(dāng)然在 Vue 框架中也不例外的可以使用 JSX 語法,雖然 Vue 默認(rèn)推薦的還是模板。

          為什么默認(rèn)推薦的模板語法,引用一段 Vue 官網(wǎng)的原話如下:

          任何合乎規(guī)范的 HTML 都是合法的 Vue 模板,這也帶來了一些特有的優(yōu)勢:

          • 對于很多習(xí)慣了 HTML 的開發(fā)者來說,模板比起 JSX 讀寫起來更自然。這里當(dāng)然有主觀偏好的成分,但如果這種區(qū)別會導(dǎo)致開發(fā)效率的提升,那么它就有客觀的價值存在。
          • 基于 HTML 的模板使得將已有的應(yīng)用逐步遷移到 Vue 更為容易。
          • 這也使得設(shè)計(jì)師和新人開發(fā)者更容易理解和參與到項(xiàng)目中。
          • 你甚至可以使用其他模板預(yù)處理器,比如 Pug 來書寫 Vue 的模板。

          有些開發(fā)者認(rèn)為模板意味著需要學(xué)習(xí)額外的 DSL (Domain-Specific Language 領(lǐng)域特定語言) 才能進(jìn)行開發(fā)——我們認(rèn)為這種區(qū)別是比較膚淺的。首先,JSX 并不是沒有學(xué)習(xí)成本的——它是基于 JS 之上的一套額外語法。同時,正如同熟悉 JS 的人學(xué)習(xí) JSX 會很容易一樣,熟悉 HTML 的人學(xué)習(xí) Vue 的模板語法也是很容易的。最后,DSL 的存在使得我們可以讓開發(fā)者用更少的代碼做更多的事,比如 v-on 的各種修飾符,在 JSX 中實(shí)現(xiàn)對應(yīng)的功能會需要多得多的代碼。

          更抽象一點(diǎn)來看,我們可以把組件區(qū)分為兩類:一類是偏視圖表現(xiàn)的 (presentational),一類則是偏邏輯的 (logical)。我們推薦在前者中使用模板,在后者中使用 JSX 或渲染函數(shù)。這兩類組件的比例會根據(jù)應(yīng)用類型的不同有所變化,但整體來說我們發(fā)現(xiàn)表現(xiàn)類的組件遠(yuǎn)遠(yuǎn)多于邏輯類組件。

          例如有這樣一段模板語法。

          <anchored-heading :level="1">
            <span>Hello</span> world!
          </anchored-heading>

          使用 JSX 語法會寫成這樣。

          render: function (h{
            return (
              <AnchoredHeading level={1}>
                <span>Hello</span> world!
              </AnchoredHeading>

            )
          }

          轉(zhuǎn)換為 createElement 轉(zhuǎn)換的 JS 就變成了這樣。

          createElement(
            'anchored-heading', {
              props: {
                level1
              }
            }, [
              createElement('span''Hello'),
              ' world!'
            ]
          );

          但是不管是模板語法還是 JSX 語法,都不會得到瀏覽器純天然的支持,這些語法最后都會被編譯成相應(yīng)的 h 函數(shù)(createElement函數(shù),不泛指所有版本,在不同版本有差異)最后變成 JS 對象,這里的編譯也是和 React 一樣使用的 Babel 插件[3]來完成的。

          不管是 React 推崇的 JSX 語法,還是 Vue 默認(rèn)的模板語法,目的都是為了讓我們寫出來的代碼更為的簡潔,而且代碼接口層次更加的清晰。在降低學(xué)習(xí)成本的同時還提升了我們的研發(fā)效率和研發(fā)體驗(yàn)。

          讀到這里,相信你已經(jīng)充分理解了“JSX 是 JavaScript 的一種語法擴(kuò)展,它和模板語言很接近,并且具備 JavaScript 的全部功能。”這一定義背后的深意。

          不管是 React 還是 Vue 我們都提到了一個函數(shù) createElement,這個函數(shù)就是將我們的 JSX 映射為 DOM的。

          JSX 是如何映射為 DOM 的:起底 createElement 源碼

          對于 creatElement 源碼的分析,我們也分 React 和 Vue 來為大家解讀。

          源碼分析的具體版本沒有必要去過于詳細(xì)的討論,因?yàn)椴还苁?React 還是 Vue 對于在實(shí)現(xiàn) createElement 上在不同版本差別不大。

          React

          export function createElement(type, config, children{
            // propName 變量用于儲存后面需要用到的元素屬性
            let propName; 
            // props 變量用于儲存元素屬性的鍵值對集合
            const props = {}; 
            // key、ref、self、source 均為 React 元素的屬性,此處不必深究
            let key = null;
            let ref = null
            let self = null
            let source = null
            // config 對象中存儲的是元素的屬性
            if (config != null) { 
              // 進(jìn)來之后做的第一件事,是依次對 ref、key、self 和 source 屬性賦值
              if (hasValidRef(config)) {
                ref = config.ref;
              }
              // 此處將 key 值字符串化
              if (hasValidKey(config)) {
                key = '' + config.key; 
              }
              self = config.__self === undefined ? null : config.__self;
              source = config.__source === undefined ? null : config.__source;
              // 接著就是要把 config 里面的屬性都一個一個挪到 props 這個之前聲明好的對象里面
              for (propName in config) {
                if (
                  // 篩選出可以提進(jìn) props 對象里的屬性
                  hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName) 
                ) {
                  props[propName] = config[propName]; 
                }
              }
            }
            // childrenLength 指的是當(dāng)前元素的子元素的個數(shù),減去的 2 是 type 和 config 兩個參數(shù)占用的長度
            const childrenLength = arguments.length - 2
            // 如果拋去type和config,就只剩下一個參數(shù),一般意味著文本節(jié)點(diǎn)出現(xiàn)了
            if (childrenLength === 1) { 
              // 直接把這個參數(shù)的值賦給props.children
              props.children = children; 
              // 處理嵌套多個子元素的情況
            } else if (childrenLength > 1) { 
              // 聲明一個子元素數(shù)組
              const childArray = Array(childrenLength); 
              // 把子元素推進(jìn)數(shù)組里
              for (let i = 0; i < childrenLength; i++) { 
                childArray[i] = arguments[i + 2];
              }
              // 最后把這個數(shù)組賦值給props.children
              props.children = childArray; 
            } 

            // 處理 defaultProps
            if (type && type.defaultProps) {
              const defaultProps = type.defaultProps;
              for (propName in defaultProps) { 
                if (props[propName] === undefined) {
                  props[propName] = defaultProps[propName];
                }
              }
            }
            // 最后返回一個調(diào)用ReactElement執(zhí)行方法,并傳入剛才處理過的參數(shù)
            return ReactElement(
              type,
              key,
              ref,
              self,
              source,
              ReactCurrentOwner.current,
              props,
            );
          }

          createElement 函數(shù)有 3 個入?yún)ⅲ@ 3 個入?yún)宋覀冊趧?chuàng)建一個 React 元素的全部信息。

          • type:用于標(biāo)識節(jié)點(diǎn)的類型。可以是原生態(tài)的 div 、span 這樣的 HTML 標(biāo)簽,也可以是 React 組件,還可以是 React fragment(空元素)。
          • config:一個對象,組件所有的屬性(不包含默認(rèn)的一些屬性)都會以鍵值對的形式存儲在 config 對象中。
          • children:泛指第二個參數(shù)后的所有參數(shù),它記錄的是組件標(biāo)簽之間嵌套的內(nèi)容,也就是所謂的“子節(jié)點(diǎn)”“子元素”。

          從源碼角度來看,createElement 函數(shù)就是將開發(fā)時研發(fā)人員寫的數(shù)據(jù)、屬性、參數(shù)做一層格式化,轉(zhuǎn)化為 React 好理解的參數(shù),然后交付給 ReactElement 來實(shí)現(xiàn)元素創(chuàng)建。

          接下來我們來看看 ReactElement 函數(shù)

          const ReactElement = function(type, key, ref, self, source, owner, props{
            const element = {
              // 標(biāo)記這是個 React Element
              $$typeof: REACT_ELEMENT_TYPE,

              type: type,
              key: key,
              ref: ref,
              props: props,
              _owner: owner,
            };

            return element;
          };

          源碼異常的簡單,也就是對 createElement 函數(shù)轉(zhuǎn)換的參數(shù),在進(jìn)行一次處理,包裝進(jìn) element 對象中返給開發(fā)者。如果你試過將這個返回 ReactElement 進(jìn)行輸出,你會發(fā)現(xiàn)有沒有很熟悉的感覺,沒錯,這就是我們老生常談的「虛擬 DOM」,JavaScript 對象對 DOM 的描述。

          最后通過 ReactDOM.render 方法將虛擬DOM 渲染到指定的容器里面。

          Vue

          Vue 2

          我們在來看看 Vue 是如何映射 DOM 的。

          export function createElement (
            context: Component,
            tag: any,
            data: any,
            children: any,
            normalizationType: any,
            alwaysNormalize: boolean
          ): VNode | Array<VNode
          {
            ...
            return _createElement(context, tag, data, children, normalizationType)
          }

          createElement 函數(shù)就是對 _createElement 函數(shù)的一個封裝,它允許傳入的參數(shù)更加靈活,在處理這些參數(shù)后,調(diào)用真正創(chuàng)建 VNode 的函數(shù) _createElement:

          export function _createElement (
            context: Component,
            tag?: string | Class<Component> | Function | Object,
            data?: VNodeData,
            children?: any,
            normalizationType?: number
          ): VNode | Array<VNode
          {
            ...
            return vnode;
          }

          _createElement 方法有 5 個參數(shù):

          • context 表示 VNode 的上下文環(huán)境。
          • tag 表示標(biāo)簽,它可以是一個字符串,也可以是一個 Component。
          • data 表示 VNode 的數(shù)據(jù)。
          • children 表示當(dāng)前 VNode 的子節(jié)點(diǎn),它是任意類型的,它接下來需要被規(guī)范為標(biāo)準(zhǔn)的 VNode 數(shù)組。
          • normalizationType 表示子節(jié)點(diǎn)規(guī)范的類型,類型不同規(guī)范的方法也就不一樣,它主要是參考 render 函數(shù)是編譯生成的還是用戶手寫的。

          _createElement 實(shí)現(xiàn)內(nèi)容略多,這里就不詳細(xì)分析了,反正最后都會創(chuàng)建一個 VNode ,每個 VNode 有 children,children 每個元素也是一個 VNode,這樣就形成了一個 VNode Tree,它很好的描述了我們的 DOM Tree。

          當(dāng) VNode 創(chuàng)建好之后,就下來就是把 VNode 渲染成一個真實(shí)的 DOM 并渲染出來。這個過程是通過 vm._update 完成的。Vue 的 _update 是實(shí)例的一個私有方法,它被調(diào)用的時機(jī)有 2 個,一個是首次渲染,一個是數(shù)據(jù)更新的時候,我們這里只看首次渲染;當(dāng)調(diào)用 _update 時,核心就是調(diào)用 vm.patch 方法。

          patch:這個方法實(shí)際上在不同的平臺,比如 web 和 weex 上的定義是不一樣的

          引入一段代碼來看看具體實(shí)現(xiàn)。

          var app = new Vue({
            el'#app',
            renderfunction (createElement{
              return createElement('div', {
                attrs: {
                  id'app'
                },
              }, this.message)
            },
            data: {
              message'Hello Vue!'
            }
          });

          在 vm._update 的方法里是這么調(diào)用 patch 方法的:

          if (!prevVnode) {
            // 首次渲染
            vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
          else {
            // 更新
            vm.$el = vm.__patch__(prevVnode, vnode);
          }

          首次渲染:

          • $el 對應(yīng)的就是 id 為 app 的 DOM 元素。
          • vnode 對應(yīng)的是 render 函數(shù)通過 createElement 函數(shù)創(chuàng)建的 虛擬 DOM。
          • hydrating 在非服務(wù)端渲染情況下為 false。

          確認(rèn)首次渲染的參數(shù)之后,我們再來看看 patch 的執(zhí)行過程。一段又臭又長的源碼。

          function patch (oldVnode, vnode, hydrating, removeOnly{
                if (isUndef(vnode)) {
                  if (isDef(oldVnode)) { invokeDestroyHook(oldVnode); }
                  return
                }

                var isInitialPatch = false;
                var insertedVnodeQueue = [];

                if (isUndef(oldVnode)) {
                  // empty mount (likely as component), create new root element
                  isInitialPatch = true;
                  createElm(vnode, insertedVnodeQueue);
                } else {
                  var isRealElement = isDef(oldVnode.nodeType);
                  if (!isRealElement && sameVnode(oldVnode, vnode)) {
                    // patch existing root node
                    patchVnode(oldVnode, vnode, insertedVnodeQueue, nullnull, removeOnly);
                  } else {
                    if (isRealElement) {
                      // mounting to a real element
                      // check if this is server-rendered content and if we can perform
                      // a successful hydration.
                      if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
                        oldVnode.removeAttribute(SSR_ATTR);
                        hydrating = true;
                      }
                      if (isTrue(hydrating)) {
                        if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
                          invokeInsertHook(vnode, insertedVnodeQueue, true);
                          return oldVnode
                        } else {
                          warn(
                            'The client-side rendered virtual DOM tree is not matching ' +
                            'server-rendered content. This is likely caused by incorrect ' +
                            'HTML markup, for example nesting block-level elements inside ' +
                            '<p>, or missing <tbody>. Bailing hydration and performing ' +
                            'full client-side render.'
                          );
                        }
                      }
                      // either not server-rendered, or hydration failed.
                      // create an empty node and replace it
                      oldVnode = emptyNodeAt(oldVnode);
                    }

                    // replacing existing element
                    var oldElm = oldVnode.elm;
                    var parentElm = nodeOps.parentNode(oldElm);

                    // create new node
                    createElm(
                      vnode,
                      insertedVnodeQueue,
                      // extremely rare edge case: do not insert if old element is in a
                      // leaving transition. Only happens when combining transition +
                      // keep-alive + HOCs. (#4590)
                      oldElm._leaveCb ? null : parentElm,
                      nodeOps.nextSibling(oldElm)
                    );

                    // update parent placeholder node element, recursively
                    if (isDef(vnode.parent)) {
                      var ancestor = vnode.parent;
                      var patchable = isPatchable(vnode);
                      while (ancestor) {
                        for (var i = 0; i < cbs.destroy.length; ++i) {
                          cbs.destroy[i](ancestor);
                        }
                        ancestor.elm = vnode.elm;
                        if (patchable) {
                          for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) {
                            cbs.create[i$1](emptyNode, ancestor);
                          }
                          // #6513
                          // invoke insert hooks that may have been merged by create hooks.
                          // e.g. for directives that uses the "inserted" hook.
                          var insert = ancestor.data.hook.insert;
                          if (insert.merged) {
                            // start at index 1 to avoid re-invoking component mounted hook
                            for (var i$2 = 1; i$2 < insert.fns.length; i$2++) {
                              insert.fns[i$2]();
                            }
                          }
                        } else {
                          registerRef(ancestor);
                        }
                        ancestor = ancestor.parent;
                      }
                    }

                    // destroy old node
                    if (isDef(parentElm)) {
                      removeVnodes([oldVnode], 00);
                    } else if (isDef(oldVnode.tag)) {
                      invokeDestroyHook(oldVnode);
                    }
                  }
                }

                invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
                return vnode.elm
              }

          在首次渲染時,由于我們傳入的 oldVnode( id 為 app 的 DOM 元素 ) 實(shí)際上是一個 DOM container,接下來又通過 emptyNodeAt 方法把 oldVnode 轉(zhuǎn)換成 VNode 對象,然后再調(diào)用 createElm 方法,通過虛擬節(jié)點(diǎn)創(chuàng)建真實(shí)的 DOM 并插入到它的父節(jié)點(diǎn)中。

          通過起底 React 和 Vue 的 createElement 源碼,分析了 JSX 是如何映射為真實(shí) DOM 的,實(shí)現(xiàn)思路的整體方向都是一樣的。所以說優(yōu)秀的框架大家都在相互借鑒,相互學(xué)習(xí)。

          為什么 React 一開始就選擇 JSX?

          在 2013 年,React 帶著 JSX 語法出現(xiàn),剛出現(xiàn)時飽受爭議,為什么 React 會選擇 JSX?而不是其他的語法。比如:

          模板

          模板語法比較典型的是 AngularJS,如果你用過 AngularJS,你會發(fā)現(xiàn)對于模板會引入很多的概念,比如新的模板語法、新的模板指令。

          <div ng-controller="Ctrl1">
                Hello <input ng-model='name'<hr/>
                <span ng-bind="name"></span> <br/>
                <span ng:bind="name"></span> <br/>
                <span ng_bind="name"></span> <br/>
                <span data-ng-bind="name"></span> <br/>
                <span x-ng-bind="name"></span> <br/>
          </div>

          angular.module('test', [])
            .controller('Ctrl1', function Ctrl1($scope) {
              $scope.name = '1';
          });

          React 的設(shè)計(jì)初衷是**「關(guān)注點(diǎn)分離」,React 本身的關(guān)注基本單位是組件,在組件內(nèi)部高內(nèi)聚,組件之間低耦合。而模板語法做不到。并且 JSX 并不會引入太多的新的概念。** 也可以看出 React 代碼更簡潔,更具有可讀性,更貼近 HTML。

          const App = (props) => {
            return (
              <div>
                xxx
              </div>

            )
          }

          模板字符串

          JSX 的語法淺看有一點(diǎn)像模板字符串,如果在早幾年,使用過 PHP + JQuery 技術(shù)棧的同學(xué)可能寫過類似這樣語法的代碼。

          var box = jsx`
            <${Box}>
              ${
                true ?
                jsx`<${Box.Comment}>
                   Text Content
                  </${Box.Comment}>`
           :
                jsx`
                  <${Box.Comment}>
                   Text Content
                  </${Box.Comment}>
                `

              }

            </${Box}>
          `
          ;

          不知你怎么看,反正我當(dāng)時在寫這樣代碼的時候是很痛苦的,并且代碼結(jié)果變得更加復(fù)雜,不利于后期的維護(hù)。

          JXON

          <catalog>
            <product description="Cardigan Sweater">
              1111
            </product>

            <script type="text/javascript"><![CDATA[function matchwo(a,b) {
              if (a < b && a < 0) { return 1; }
              else { return 0; }
            }]]>
            
          </script>

          </catalog>

          但最終放棄 JXON 這一方案的原因是,大括號不能為元素在樹中開始和結(jié)束的位置,提供很好的語法提示。

          template

          <template>
            <div>1</div>
          <template>
          <script>
            ....
          <script>

          那為什么不能和 Vue 一樣使用 模板語法了?JSX 本質(zhì)就是 JavaScript,想實(shí)現(xiàn)條件渲染可以用 if else,也可以用三元表達(dá)式,還可以用任意合法的 JavaScript 語法。也就是說,JSX 可以支持更動態(tài)的需求。而 template 則因?yàn)檎Z法限制原因,不能夠像 JSX 那樣可以支持更動態(tài)的需求。這是 JSX 相比于 template 的一個優(yōu)勢。 JSX 相比于 template 還有一個優(yōu)勢,是可以在一個文件內(nèi)返回多個組件。

          但是就 Vue 來說,默認(rèn)選擇 template 語法也是有原因的,template 由于語法固定,可以在編譯層面做的優(yōu)化較多,比如靜態(tài)標(biāo)記就真正做到了按需更新;而 JSX 由于動態(tài)性太強(qiáng),只能在有限的場景下做優(yōu)化,雖然性能不如 template 好,但在某些動態(tài)性要求較高的場景下,JSX 成了標(biāo)配,這也是諸多組件庫會使用 JSX 的主要原因。

          總結(jié)

          通過對比多種方案,發(fā)現(xiàn) JSX 本身具備他獨(dú)享的優(yōu)勢,JSX 語法寫出來的代碼更為的簡潔,而且代碼結(jié)構(gòu)層次更加的清晰。JSX 語法糖允許我們開發(fā)人員像寫 HTML 一樣來寫我們的 JS 代碼。在降低學(xué)習(xí)成本的同時還提升了我們的研發(fā)效率和研發(fā)體驗(yàn)。

          并且JSX 本身沒有太多的語法,也不期待引入更多的標(biāo)準(zhǔn)。實(shí)際上,在 16 年的時候,JSX 公布過 2.0 的建設(shè)計(jì)劃與小部分新特性,但很快被 Facebook 放棄掉了。整個計(jì)劃在公布不到兩個月的時間里便停掉了。其中一個原因是 JSX 的設(shè)計(jì)初衷,即并不希望引入太多的標(biāo)準(zhǔn),也不期望 JSX 加入瀏覽器或者 ECMAScript 標(biāo)準(zhǔn)。

          最后


          歡迎關(guān)注【前端瓶子君】??ヽ(°▽°)ノ?
          回復(fù)「算法」,加入前端編程源碼算法群,每日一道面試題(工作日),第二天瓶子君都會很認(rèn)真的解答喲!
          回復(fù)「交流」,吹吹水、聊聊技術(shù)、吐吐槽!
          回復(fù)「閱讀」,每日刷刷高質(zhì)量好文!
          如果這篇文章對你有幫助,在看」是最大的支持
           》》面試官也在看的算法資料《《
          “在看和轉(zhuǎn)發(fā)”就是最大的


          瀏覽 60
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  黄色片在线| 波多野结衣无码NET,AV | 91视频做爱 | 国产七区| 中文字幕 国产精品 |