<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                > 原文:http://www.infoq.com/cn/articles/react-jsx-and-component 通過前兩篇文章的介紹,相信大家對JSX和組件已經有了一定的了解。JSX這種混合使用JavaScript和XML的語言第一眼看上去很“丑”,也很神奇,但是其語法和背后的邏輯卻極其簡單。相信讀完本文你就可以對JSX和組件有一個全面的了解,并能夠用JSX來直觀的構造用戶界面。 **目錄** [TOC] ## 什么是JSX React的核心機制之一就是虛擬DOM:可以在內存中創建的虛擬DOM元素。React利用虛擬DOM來減少對實際DOM的操作從而提升性能。類似于真實的原生DOM,虛擬DOM也可以通過JavaScript來創建,例如: ~~~ var child1 = React.createElement('li', null, 'First Text Content'); var child2 = React.createElement('li', null, 'Second Text Content'); var root = React.createElement('ul', { className: 'my-list' }, child1, child2); ~~~ 使用這樣的機制,我們完全可以用JavaScript構建完整的界面DOM樹,正如我們可以用JavaScript創建真實DOM。但這樣的代碼可讀性并不好,于是React發明了JSX,利用我們熟悉的HTML語法來創建虛擬DOM: ~~~ var root =( <ul className="my-list"> <li>First Text Content</li> <li>Second Text Content</li> </ul> ); ~~~ 這兩段代碼是完全等價的,后者將XML語法直接加入到JavaScript代碼中,讓你能夠高效的通過代碼而不是模板來定義界面。之后JSX通過翻譯器轉換到純JavaScript再由瀏覽器執行。在實際開發中,JSX在產品打包階段都已經編譯成純JavaScript,JSX的語法不會帶來任何性能影響。另外,由于JSX只是一種語法,因此JavaScript的關鍵字class, for等也不能出現在XML中,而要如例子中所示,使用className, htmlFor代替,這和原生DOM在JavaScript中的創建也是一致的。 因此,JSX本身并不是什么高深的技術,可以說只是一個比較高級但很直觀的語法糖。它非常有用,卻不是一個必需品,沒有JSX的React也可以正常工作:只要你樂意用JavaScript代碼去創建這些虛擬DOM元素。 ## 為什么使用JSX 前端界面的最基本功能在于展現數據,為此大多數框架都使用了模板引擎,例如在[AngularJS](https://angularjs.org/)中: ~~~ <div ng-if="person != null"> Welcome back, <b>{{person.firstName}} {{person.lastName}}</b>! </div> <div ng-if="person == null"> Please log in. </div> ~~~ 在[EmberJS](http://emberjs.com/)中: ~~~ {{#if person}} Welcome back, <b>{{person.firstName}} {{person.lastName}}</b>! {{else}} Please log in. {{/if}} ~~~ 在[Knockoutjs](http://knockoutjs.com/)中: ~~~ <div data-bind="if: person != null"> Welcome back, <b>{{person.firstName}} {{person.lastName}}</b>! </div> <div data-bind="if: person == null"> Please log in. </div> ~~~ 模板可以直觀的定義UI來展現Model中的數據,你不必手動的去拼出一個很長的HTML字符串,幾乎每種框架都有自己的模板引擎。傳統MVC框架強調界面展示邏輯和業務邏輯的分離,因此為了應對復雜的展示邏輯需求,這些模板引擎幾乎都不可避免的需要發展成一門獨立的語言,如上面代碼所示,每個框架都有自己的模板語言語法。而這無疑增加了框架的門檻和復雜度。 如果說掌握一種模板語言并不是很大的問題,那么其實由模板帶來的架構復雜性則是讓框架也變得復雜的重要原因之一,例如: * 模板需要對應數據模型,即上下文,如何去綁定和實現? * 模板可以嵌套,不同部分界面可能來自不同數據模型,如何處理? * 模板語言終究是一個輕量級語言,為了滿足項目需求,你很可能需要擴展模板引擎的功能。 為了解決這些復雜度,框架本身需要精心的設計,以及創造新的概念(例如Angular的Directive)。這些都會讓框架變得復雜和難以掌握,不僅增加了開發成本,各種難以調試的Bug還會降低開發質量。 正因為如此,React直接放棄了模板而發明了JSX。看上去很像模板語言,但其本質是通過代碼來構建界面,這使得我們不再需要掌握一門新的語言就可以直觀的去定義用戶界面:掌握了JavaScript就已經掌握了JSX。這里不妨再引用之前文章舉過的例子,在展示一個列表時,模板語言通常提供名為Repeat的語法,例如在Angular中: ~~~ <ul class="unstyled"> <li ng-repeat="todo in todoList.todos"> <input type="checkbox" ng-model="todo.done"> <span class="done-{{todo.done}}">{{todo.text}}</span> </li> </ul> ~~~ 而使用JSX,則代碼如下: ~~~ var lis = this.todoList.todos.map(function (todo) { return ( <li> <input type="checkbox" checked={todo.done}> <span className={'done-' + todo.done}>{todo.text}</span> </li> ); }); var ul = ( <ul class="unstyled"> {lis} </ul> ); ~~~ 可以看到,JSX完美利用了JavaScript自帶的語法和特性,我們只要記住HTML只是代碼創建DOM的一種語法形式,就很容易理解JSX。而這種使用代碼構建界面的方式,完全消除了業務邏輯和界面元素之間的隔閡,讓代碼更加直觀和易于維護。 ## JSX的語法 JSX本身就和XML語法類似,可以定義屬性以及子元素。唯一特殊的是可以用大括號來加入JavaScript表達式,例如: ~~~ var person = <Person name={window.isLoggedIn ? window.name : ''} />; ~~~ 一般每個組件都定義了一組屬性(props,properties的簡寫)接收輸入參數,這些屬性通過XML標記的屬性來指定。大括號中的語法就是純JavaScript表達式,返回值會賦予組件的對應屬性,因此可以使用任何JavaScript變量或者函數調用。上述代碼經過JSX編譯后會得到: ~~~ var person = React.createElement( Person, {name: window.isLoggedIn ? window.name : ''} ); ~~~ 對于子元素也是類似,大括號中使用JavaScript表達式來返回需要展現的元素,例如文章開頭提到的例子使用JSX可以寫成: ~~~ var node = ( <div className="container"> { person ? <span>Welcome back, <b>{person.firstName} {person.lastName}</b>!</span> : <span>Please log in</span> } </div> ); ~~~ 既然大括號中是JavaScript,而JSX又允許在JavaScript中使用XML,因此在大括號中仍然可以使用XML來聲明組件,不斷遞歸使用。 如果需要展現一組子節點,只需表達式返回一個JavaScript數組,數組的每個元素都是一個React組件,例如上一節的例子,其中lis就是有多個“li”元素的數組。: ~~~ var ul = ( <ul class="unstyled"> {lis} </ul> ); ~~~ ## 在JSX中使用事件 如果你在90年代寫過HTML,那么也許會有點懷念那時的事件綁定是多么的直觀和簡單: ~~~ <button onclick="checkAndSubmit(this.form)">Submit</button> ~~~ 那時的JavaScript應用范圍非常有限,最有用的也許就是做表單有效性驗證。因為邏輯都很簡單,直接寫到HTML中并沒有問題,而且這種方式非常直觀易讀。但是現在因為Web程序變的越來越復雜,我們就需要使用JavaScript來綁定事件,例如在jQuery中: ~~~ $('#my-button').on('click', this.checkAndSubmit.bind(this)); ~~~ 在看到這段事件綁定和驗證邏輯之前,你無法直觀的看到有事件綁定在某個元素上,這種隱藏的界面元素和業務邏輯的耦合是很多Bug和內存泄露產生的根源。幸運的是,現在JSX可以讓事件綁定返璞歸真: ~~~ <button onClick={this.checkAndSubmit.bind(this)}>Submit</button> ~~~ 和原生HTML定義事件的唯一區別就是JSX采用駝峰寫法來描述事件名稱,大括號中仍然是標準的JavaScript表達式,返回一個事件處理函數。在JSX中你不需要關心什么時機去移除事件綁定,因為React會在對應的真實DOM節點移除時就自動解除了事件綁定。 React并不會真正的綁定事件到每一個具體的元素上,而是采用事件代理的模式:在根節點document上為每種事件添加唯一的Listener,然后通過事件的target找到真實的觸發元素。這樣從觸發元素到頂層節點之間的所有節點如果有綁定這個事件,React都會觸發對應的事件處理函數。這就是所謂的React模擬事件系統。 盡管整個事件系統由React管理,但是其API和使用方法與原生事件一致。這種機制確保了跨瀏覽器的一致性:在所有瀏覽器(IE8及以上)都可以使用符合[W3C標準](http://www.w3.org/TR/DOM-Level-3-Events/)的API,包括stopPropagation(),preventDefault()等等。對于事件的冒泡(bubble)和捕獲(capture)模式也都完全支持。 ## 在JSX中使用樣式 盡管在大部分場景下我們應該將樣式寫在獨立的CSS文件中,但是有時對于某個特定組件而言,其樣式相當簡單而且獨立,那么也可以將其直接定義在JSX中。在JSX中使用樣式和真實的樣式也很類似,通過style屬性來定義,但和真實DOM不同的是,屬性值不能是字符串而必須為對象,例如: ~~~ <div style={{color: '#ff0000', fontSize: '14px'}}>Hello World.</div> ~~~ 乍一看,這段JSX中的大括號是雙的,有點奇怪,但實際上里面的大括號只是標準的JavaScript對象表達式,外面的大括號是JSX的語法。所以,樣式你也可以先賦值給一個變量,然后傳進去,代碼會更易讀: ~~~ var style = { color: '#ff0000', fontSize: '14px' }; var node = <div style={style}>HelloWorld.</div>; ~~~ 在JSX中可以使用所有的的樣式,基本上屬性名的轉換規范就是將其寫成駝峰寫法,例如“background-color”變為“backgroundColor”, “font-size”變為“fontSize”,這和標準的JavaScript操作DOM樣式的API是一致的。 ## 使用自定義組件 在JSX中,我們不僅可以使用React自帶div, input...這些虛擬DOM元素,還可以自定義組件。組件定義之后,也都可以利用XML語法去聲明,而能夠使用的XML Tag就是在當前JavaScript上下文的變量名,這一點非常好用,你不必再去考慮某個Tag是如何對應到相應的組件實現。例如React官方教程中的例子: ~~~ class HelloWorld extends React.Component{ render() { return ( <p> Hello, <input type="text" placeholder="Your name here" />! It is {this.props.date.toTimeString()} </p> ); } }; setInterval(function() { React.render( <HelloWorld date={new Date()} />, document.getElementById('example') ); }, 500); ~~~ 其中聲明了一個名為HelloWorld的組件,那么就可以在XML中使用,這個Tag就是JavaScript變量名,我們可以用任意變量名: ~~~ var MyHelloWorld = HelloWorld; React.render(<MyHelloWorld />, …); ~~~ 甚至,我們還可以引入命名空間: ~~~ var sampleNameSpace = { MyHelloWorld: HelloWorld }; React.render(<sampleNameSpace.MyHelloWorld />, …); ~~~ 這些語法看上去有點怪,但是如果我們記住JSX語法只是JavaScript語法的一個語法映射,那么這些就非常容易理解了。 ## 組件的概念和生命周期 React使用組件來封裝界面模塊,整個界面就是一個大組件,開發過程就是不斷優化和拆分界面組件、構造整個組件樹的過程。可以認為組件類似于其他框架中Widget(或Control)的概念,但又有所不同。React中的界面一切皆為組件,而Widget一般只是嵌入到界面中為完成某個功能的獨立模塊。 如下圖,整個頁面是一個大的組件,然后再將其拆分成很多小的組件。組件機制加上JSX的語法,讓你在構造界面時就像有一套符合項目需求的HTML標記,界面定義變得非常直觀。 ![](https://box.kancloud.cn/2015-07-31_55baec3a21598.png) 組件自身定義了一組props作為對外接口,展示一個組件時只需要指定props作為XML節點的屬性。組件很少需要對外公開方法,唯一的交互途徑就是props。這使得使用組件就像使用函數一樣簡單,給定一個輸入,組件給定一個界面輸出。當給予的參數一定時,那么輸出也是一定的。而傳統控件通常提供很多方法讓你在外部改變控件的狀態和行為,當控件的狀態在不同場景不同邏輯中可以被隨意控制時,開發和調試也會變得復雜。 而React組件通過唯一的props接口避免了邏輯復雜性,讓開發測試都更加容易。這種特性完全得益于虛擬DOM機制,讓你可以每次props改變都能以整體刷新頁面的思路去考慮界面展現邏輯。 如果整個項目完全采用React,那么界面上就只有一個組件根節點;如果局部使用React,那么每個局部使用的部分都有一個根節點。在Render時,根節點由React.render函數去觸發: ~~~ React.render( <App />, document.getElementById('react-root') ); ~~~ 而所有的子節點則都是通過父節點的render方法去構造的。每個組件都會有一個render方法,這個方法返回組件的實例,最終整個界面得到一個虛擬DOM樹,再由React以最高效的方式展現在界面上。 除了props之外,組件還有一個很重要的概念:state。組件規范中定義了setState方法,每次調用時都會更新組件的狀態,觸發render方法。需要注意,render方法是被異步調用的,這可以保證同步的多個setState方法只會觸發一次render,有利于提高性能。和props不同,state是組件的內部狀態,除了初始化時可能由props來決定,之后就完全由組件自身去維護。在組件的整個生命周期中,React強烈不推薦去修改自身的props,因為這會破壞UI和Model的一致性,props只能夠由使用者來決定。 對于自定義組件,唯一必須實現的方法就是render(),除此之外,還有一些方法會在組件生命周期中被調用,如下圖所示: ![](https://box.kancloud.cn/2015-07-31_55baec3b4138e.png) 圖中的方法幾乎已經包括了React的所有API,自定義組件時根據需要在組件生命周期的不同階段實現不同的邏輯。除了必須的render方法之外,其它常用的方法包括: **componentDidMount**: 在組件第一次render之后調用,這時組件對應的DOM節點已被加入到瀏覽器。在這個方法里可以去實現一些初始化邏輯。 **componentWillUnmount**: 在DOM節點移除之后被調用,這里可以做一些相關的清理工作。 **shouldComponentUpdate**: 這是一個和性能非常相關的方法,在每一次render方法之前被調用。它提供了一個機會讓你決定是否要對組件進行實際的render。例如: ~~~ shouldComponentUpdate(nextProps, nextState) { return nextProps.id !== this.props.id; } ~~~ 當此函數返回false時,組件就不會調用render方法從而避免了虛擬DOM的創建和內存中的Diff比較,從而有助于提高性能。當返回true時,則會進行正常的render的邏輯。 組件是React的核心,雖然功能很強大,但是其API和概念卻十分簡單,以至于你只要實現一個render方法就可以創建一個組件。這大大降低了React學習門檻。 ## 使用Babel進行JSX編譯 就在本文撰寫過程中,React官方博客發布了[一篇文章](http://facebook.github.io/react/blog/2015/06/12/deprecating-jstransform-and-react-tools.html),聲明其自身用于JSX語法解析的編譯器[JSTransform](https://github.com/facebook/jstransform)已經過期,不再維護,React JS和React Native已經全部采用第三方[Babel](http://babeljs.io/)的JSX編譯器實現。原因是兩者在功能上已經完全重復,而Babel作為專門的JavaScript語法編譯工具,提供了更為強大的功能。在這里筆者也不得不感嘆Facebook的胸懷,以非常開放的態度去擁抱開源社區,從而達到共贏的目的。 JSX是一種新的語法,瀏覽器并不能直接運行,因此需要這種翻譯器。在[上一篇文章](http://www.infoq.com/cn/articles/react-and-webpack)中我們推薦使用Webpack進行React的開發,要將JSX的編譯器從JSTransform切換到Babel非常簡單,首先通過npm安裝Babel: ~~~ npm install —save-dev babel-loader ~~~ 只需稍微改變一下webpack.config.js的配置,將原來的jsx-loader變為babel-loader: ~~~ module: { loaders: [ { test: /\.jsx?$/, loaders: ['babel-loader']} ] } ~~~ ## 小結 本文主要介紹了React中最重要的組件機制,以及聲明組件的語法JSX。看似有點神秘的JSX背后的原理非常簡單:只是一種用于創建組件的XML語法。讓代碼直觀易懂是軟件項目質量的重要保證之一,這意味著代碼更加容易理解和維護,出現Bug時更容易調試和修復。因此React這種采用JSX語法,以聲明式的方法來直觀的定義用戶界面的方式,正是其最大的價值。 整個組件機制運行的基礎是虛擬DOM,正因為React能夠以極高的性能去比較兩個虛擬DOM樹的Diff,才實現了每次局部更新都通過刷新整個頁面這種思考模式,降低了開發復雜度。在下一篇文章中就將會和大家一起研究虛擬DOM的Diff算法,了解其背后的運行原理。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看