深刻 react 技術棧

1.函數式編程纔是 React 的精髓

都是去年年初準備的,繼上一篇草稿以後的css

2.JSX

React 官方在早期爲 JSX 語法解析開發了一套編譯器 JSTransform,目前已經再也不維護,
如今已所有采用 Babel 的 JSX 編譯器實現。由於二者在功能上徹底重複,
而 Babel 做爲專門的JavaScript語法編譯工具,提供了更爲強大的功能,達到了「一處配置,統一運行」的目的。html

除了使用 JSX 語法,咱們還可使用 React.createElement() 和 React.cloneElement() 來構建 React 元素。前端

  • 2.1 虛擬元素能夠理解爲真實元素的對應,它的構建與更新都是在內存中完成的,並不會真正渲染到 DOM 中去。
    在 React 中建立的虛擬元素能夠分爲兩類,
    DOM 元素(DOM element)與組件元素(component element),分別對應着原生 DOM 元素與自定義元素react

    元素類型: DOM 元素和組件元素,
    對應規則是 HTML 標籤首字母是否爲小寫字母,其中小寫首字母對應 DOM 元素,而組件元素天然對應大寫首字母;
    JSX 還能夠經過命名空間的方式使用組件元素,以解決組件相同名稱衝突的問題,或是對一組組件進行歸類webpack

    • 2.1.1 DOM 元素git

    • 2.1.2 組件元素github

  • 2.2 JSX 基本語法web

    • 2.2.1 定義標籤時,只容許被一個標籤包裹
    • 2.2.2 標籤必定要閉合
    • 2.2.3 元素類型: DOM 元素和組件元素, HTML 標籤首字母是否爲小寫字母,其中小寫首字母對應 DOM 元素,而組件元素天然對應大寫首字母
    • 2.2.4 使用註釋要用 {} 包起來
    • 2.2.5 元素屬性
      • class 屬性改成 className;編程

      • for 屬性改成 htmlFor;
        寫自定義屬性的時候,都由標準寫法改成小駝峯寫法 ;數組

      • Boolean 屬性:要傳 false 時,必須使用屬性表達式。

        例如,<Checkbox checked={true} /> 能夠簡寫爲 <Checkbox checked />,  
        反之 <Checkbox checked={false} /> 就能夠省略 checked 屬性。
        複製代碼
      • 展開屬性:可使用 ES6 rest/spread 特性來提升效率:

        const data = { name: 'foo', value: 'bar' };
        const component = <Component name={data.name} value={data.value} />;
        能夠寫成:
        const data = { name: 'foo', value: 'bar' };
        const component = <Component {...data} />; 
        複製代碼
      • 自定義 HTML 屬性

        若是在 JSX 中往 DOM 元素中傳入自定義屬性,React 是不會渲染的:
         <div d="xxx">content</div>
         
         若是要使用 HTML 自定義屬性,要使用 data- 前綴,這與 HTML 標準也是一致的:
         <div data-attr="xxx">content</div>
         
         然而,在自定義標籤中任意的屬性都是被支持的:
         <x-my-component custom-attr="foo" />
         以 aria- 開頭的網絡無障礙屬性一樣能夠正常使用:
         <div aria-hidden={true}></div>
         
         不論組件是用什麼方法來寫,咱們都須要知道,組件的最終目的是輸出虛擬元素,也就是需
         要被渲染到界面的結構。其核心渲染方法,或稱爲組件輸出方法,就是 render 方法
        複製代碼
    • 2.2.6 JavaScript 屬性表達式

      屬性值要使用表達式,只要用 {} 替換 "" 便可;

      <Person name={window.isLoggedIn ? window.name : ''} />;
      複製代碼
    • 2.2.7 HTML 轉義
      React 會將全部要顯示到 DOM 的字符串轉義,防止 XSS。因此,若是 JSX 中含有轉義後的
         實體字符,好比 &copy;(©),則最後 DOM 中不會正確顯示,由於 React 自動把 &copy; 中的特
         殊字符轉義了。有幾種解決辦法:
         -  直接使用 UTF-8 字符 ©;
         -  使用對應字符的 Unicode 編碼查詢編碼;
         - 使用數組組裝 `<div>{['cc ', <span>&copy;</span>, ' 2015']}</div>`;
         - 直接插入原始的 HTML。
         
         此外,React 提供了 dangerouslySetInnerHTML 屬性。正如其名,它的做用就是避免 React 轉
         義字符,在肯定必要的狀況下可使用它:
         <div dangerouslySetInnerHTML={{__html: 'cc &copy; 2015'}} /> 
      複製代碼

3.React 組件

  • 3.1 React 組件的構建
    • React.createClass
    const Button = React.createClass({
            getDefaultProps() {
                return {
                color: 'blue',
                text: 'Confirm',
                };
            },
            render() {
                const { color, text } = this.props;
                return (
                    <button className={`btn btn-${color}`}>
                        <em>{text}</em>
                    </button>
                );
            }
        }); 
    複製代碼
  • ES6 classes
    import React, { Component } from 'react';
        class Button extends Component {
            constructor(props) {
                super(props);
            }
            static defaultProps = {
                color: 'blue',
                text: 'Confirm',
            };
            render() {
                const { color, text } = this.props;
                return (
                    <button className={`btn btn-${color}`}>
                        <em>{text}</em>
                    </button>
                );
            }
        } 
    複製代碼
  • 無狀態函數
    使用無狀態函數構建的組件稱爲無狀態組件,這種構建方式是 0.14 版本以後新增的,且官方頗爲推崇。示例代碼以下:
    function Button({ color = 'blue', text = 'Confirm' }) {
      return (
        <button className={`btn btn-${color}`}>
          <em>{text}</em>
        </button>
      );
    }
    無狀態組件只傳入 props 和 context 兩個參數;也就是說,它不存在 state,也沒有生命周
    期方法,組件自己即上面兩種 React 組件構建方法中的 render 方法;
    
    它建立時始終保持了一個實例,避免了沒必要要的檢查和內存分配,作到了內部優化;
    複製代碼

6.ReactDOM

ReactDOM 中的 API 很是少,只有 findDOMNode、unmountComponentAtNode 和 render。

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class App extends Component {
  componentDidMount() {
    // myComp 是 Comp 的一個實例,所以須要用 findDOMNode 轉換爲相應的 DOM
    const myComp = this.refs.myComp;
    const dom = findDOMNode(myComp);
  }
  render() {
    return (
      <div>
        <Comp ref="myComp" />
      </div>
    );
  }
}

複製代碼

7.事件系統

7.1 合成事件的綁定方式

React 合成事件的事件類型是 JavaScript 原生事件類型的一個子集。

<button onClick={this.handleClick}>Test</button> 

在 JSX 中,咱們必須使用駝峯的形式來書寫事件的屬性名(好比
onClick),而 HTML 事件則須要使用所有小寫的屬性名(好比 onclick)。

React 並不會像 DOM0 級事件那樣將事件處理器直接綁定到 HTML 元素之上。React 僅僅是
借鑑了這種寫法而已
複製代碼

7.2合成事件的實現機制

在 React 底層,主要對合成事件作了兩件事:事件委派和自動綁定。

  1. 事件委派

在使用 React 事件前,必定要熟悉它的事件代理機制。
它並不會把事件處理函數直接綁定到真實的節點上,而是把全部事件綁定到結構的最外層,
使用一個統一的事件監聽器,這個事件監聽器上維持了一個映射來保存全部組件內部的事件監聽和處理函數。
當組件掛載或卸載時,只是在這個統一的事件監聽器上插入或刪除一些對象;
當事件發生時,首先被這個統一的事件監聽器處理,而後在映射裏找到真正的事件處理函數並調用。
這樣作簡化了事件處理和回收機制,效率也有很大提高;

  1. 自動綁定

在 React 組件中,每一個方法的上下文都會指向該組件的實例,即自動綁定 this 爲當前組件。
並且 React 還會對這種引用進行緩存,以達到 CPU 和內存的最優化。
在使用 ES6 classes 或者純函數時,這種自動綁定就不復存在了,咱們須要手動實現 this 的綁定。

  • bind 方法。這個方法能夠幫助咱們綁定事件處理器內的 this ,並能夠向事件處理器中傳遞參數,好比:

    import React, { Component } from 'react';
    class App extends Component {
      handleClick(e, arg) {
        console.log(e, arg);
      }
      render() {
        // 經過bind方法實現,能夠傳遞參數
        return <button onClick={this.handleClick.bind(this, 'test')}>Test</button>;
      }
    }
    複製代碼

    若是方法只綁定,不傳參,那 stage 0 草案中提供了一個便捷的方案①——雙冒號語法,
    其做用與 this.handleClick.bind(this) 一致,而且 Babel 已經實現了該提案。好比:

    ——————————————————————————————————————————

    ① ECMAScrip This-Binding Syntanx,詳見 github.com/zenparsing/…

    import React, { Component } from 'react';
    class App extends Component {
      handleClick(e) {
        console.log(e);
      }
      render() {
        return <button onClick={::this.handleClick}>Test</button>;
      }
    } 
    複製代碼
  • 構造器內聲明。

    在組件的構造器內完成了 this的綁定,這種綁定方式的好處在於僅須要進行一次綁定,而不須要每次調用事件監聽器時去執行綁定操做:

    import React, { Component } from 'react';
    class App extends Component {
      constructor(props) {
        super(props);
        this.handleClick = this.handleClick.bind(this);
      }
      handleClick(e) {
        console.log(e);
      }
      render() {
        return <button onClick={this.handleClick}>Test</button>;
      }
    }
    複製代碼
  • 箭頭函數。

    箭頭函數不只是函數的「語法糖」,它還自動綁定了定義此函數做用域的 this,
    所以咱們不須要再對它使用 bind 方法。好比,如下方式就能運行:

    import React, { Component } from 'react';
    class App extends Component {
      const handleClick = (e) => {
        console.log(e);
      };
      render() {
        return <button onClick={this.handleClick}>Test</button>;
      }
    }
    或
    import React, { Component } from 'react';
    class App extends Component {
      handleClick(e) {
        console.log(e);
      }
      render() { 
        return <button onClick={() => this.handleClick()}>Test</button>
      }
    }
    使用上述幾種方式,都可以實如今類定義的組件中綁定 this 上下文的效果。
    複製代碼
    • 3.在 React 中使用原生事件

      React 提供了很好用的合成事件系統,但這並不意味着在 React 架構下沒法使用原生事件。 React 提供了完備的生命週期方法,其中 componentDidMount 會在組件已經完成安裝而且在瀏覽器 中存在真實的 DOM 後調用,此時咱們就能夠完成原生事件的綁定;

      import React, { Component } from 'react';
      class NativeEventDemo extends Component {
        componentDidMount() {
        this.refs.button.addEventListener('click', e => {
          this.handleClick(e);
        });
        }
        handleClick(e) {
          console.log(e);
        }
        componentWillUnmount() {
          this.refs.button.removeEventListener('click');
        }
        render() {
          return <button ref="button">Test</button>;
        }
      }
      值得注意的是,在 React 中使用 DOM 原生事件時,必定要在組件卸載時手動移除,
      不然極可能出現內存泄漏的問題。  
      而使用合成事件系統時則不須要,由於 React 內部已經幫你妥善地處理了;
      複製代碼

      儘可能避免在 React 中混用合成事件和原生 DOM 事件,另外,用 reactEvent.nativeEvent.stopPropagation() 來阻止冒泡是不行的。 阻止 React 事件冒泡的行爲只能用於 React合成事件系統中,且沒辦法阻止原生事件的冒泡。 反之,在原生事件中的阻止冒泡行爲,卻能夠阻止 React 合成事件的傳播;

      實際上,React 的合成事件系統只是原生 DOM 事件系統的一個子集。它僅僅實現了 DOM Level 3 的事件接口,而且統一了瀏覽器間的兼容問題。有些事件 React 並無實現,
      或者受某些限制沒辦法去實現,好比 window 的 resize 事件。
      對於沒法使用 React 合成事件的場景,咱們還須要使用原生事件來完成

      瀏覽器原生 DOM 事件的傳播能夠分爲 3 個階段:事件捕獲階段、目標對象自己的事件處理程序調用以及事件冒泡。
      React的合成事件則並無實現事件捕獲,僅僅支持了事件冒泡機制;
      阻止原生事件傳播須要使用 e.preventDefault(),不過對於不支持該方法的瀏覽器(IE9 如下),只能使用 e.cancelBubble = true 來阻止。而在 React 合成事件中,只須要使用 e.preventDefault() 便可。

    • 8.受控組件和非受控組件

      每當表單的狀態發生變化時,都會被寫入到組件的 state 中,這種組件在React 中被稱爲受控組件(controlled component)。
      在受控組件中,組件渲染出的狀態與它的 value 或 checked prop 相對應; 若是一個表單組件沒有 value props(單選按鈕和複選框對應的是 checked prop)時,就能夠稱爲非受控組件;
      不提倡在 React 中使用非受控組件

    • 9.樣式處理

      const style = {
        color: 'white',
        backgroundImage: `url(${imgUrl})`,
        // 注意這裏大寫的 W,會轉換成 -webkit-transition
        WebkitTransition: 'all',
        // ms 是惟一小寫的瀏覽器前綴
        msTransition: 'all',
      };
      const component = <Component style={style} />; 
      複製代碼
      • 9.1 樣式中的像素值

      當設置 width 和 height 這類與大小有關的樣式時,大部分會以像素爲單位,此時若重複輸 入 px,會很麻煩。爲了提升效率,React 會自動對這樣的屬性添加 px。好比:
      // 渲染成 height: 10px
      const style = { height: 10 };

      • 9.2 使用 classnames 庫

        給組件動態設置 className

        render() {
            const btnClass = classNames({
              'btn': true,
              'btn-pressed': this.state.isPressed,
              'btn-over': !this.state.isPressed && this.state.isHovered,
            });
            return <button className={btnClass}>{this.props.label}</button>;
          } 
        複製代碼
      • 9.3 CSS Modules
        啓用 CSS Modules 的代碼以下:
        // webpack.config.js
        css?modules&localIdentName=[name]__[local]-[hash:base64:5]
        加上 modules 即爲啓用,其中 localIdentName 是設置生成樣式的命名規則
        複製代碼
        CSS Modules 實現瞭如下幾點:
        • 全部樣式都是局部化的,解決了命名衝突和全局污染問題;
        • class 名的生成規則配置靈活,能夠以此來壓縮 class 名;
        • 只需引用組件的 JavaScript,就能搞定組件全部的 JavaScript 和 CSS;
        • 依然是 CSS,學習成本幾乎爲零。
        使用了 CSS Modules 後,就至關於給每一個 class 名外加了 :local,以此來實現樣式的局部化。
          若是咱們想切換到全局模式,可使用 :global 包裹。示例代碼以下:
          .normal {
            color: green;
          }
          /* 以上與下面等價 */
          :local(.normal) {
            color: green;
          }
          /* 定義全局樣式 */
          :global(.btn) {
            color: red;
          }
          /* 定義多個全局樣式 */
          :global { 
            .link {
             color: green;
            }
            .box {
             color: yellow;
            }
          } 
        複製代碼
        下面是具體項目中使用的 webpack 部分配置代碼:
        module: {
           loaders: [{
             test: /\.jsx?$/,
             loader: 'babel',
           }, {
             test: /\.scss$/,
             exclude: path.resolve(__dirname, 'src/styles'),
             loader: 'style!css?modules&localIdentName=[name]__[local]!sass?sourceMap=true',
           }, {
             test: /\.scss$/,
             include: path.resolve(__dirname, 'src/styles'),
             loader: 'style!css!sass?sourceMap=true',
           }]
          }
          
          /* src/app.js */
          import './styles/app.scss';
          import Component from './view/Component'
          /* src/views/Component.js */
          import './Component.scss';
          
          目錄結構以下:
          src
          ├── app.js
          ├── styles
          │ ├── app.scss
          │ └── normalize.scss
          └── views
           ├── Component.js
           └── Component.scss
          這樣全部全局的樣式都放到 src/styles/app.scss 中引入就能夠了,其餘全部目錄(包括
          src/views)中的樣式都是局部的。
          CSS Modules 很好地解決了 CSS 目前面臨的模塊化難題
        複製代碼
    • 10.組件間通訊

      • 10.1 父組件向子組件通訊
      • 10.2 子組件向父組件通訊
      • 10.3 跨級組件通訊
      • 10.4 沒有嵌套關係的組件通訊
    • 11.組件間抽象

      在 React 組件的構建過程當中,經常有這樣的場景,有一類功能須要被不一樣的組件公用,此時就涉及抽象的話題。
      在不一樣的設計理念下,有許多的抽象方法,而針對 React,咱們重點討論兩種:mixin 和高階組件;

      • 11.1 mixin(混入)

        React 在使用 createClass 構建組件時提供了 mixin 屬性,好比官方封裝的 PureRenderMixin:
          import React from 'react';
          import PureRenderMixin from 'react-addons-pure-render-mixin';
          React.createClass({
              mixins: [PureRenderMixin],
              render() {
                  return <div>foo</div>;
              }
          });
          在 createClass 對象參數中傳入數組 mixins,裏面封裝了咱們所須要的模塊
        複製代碼
        • mixin 最大的一些問題
        破壞了原有組件的封裝
          命名衝突
          增長複雜性
        複製代碼

        針對這些困擾,React 社區提出了新的方式來取代 mixin,那就是高階組件

        • 高階組件

          高階組件(higher-order component),相似於高階函數,它接受 React 組件做爲輸入,輸出一個新的 React 組件;
          實現高階組件的方法有以下兩種。

          • 屬性代理(props proxy)。高階組件經過被包裹的 React 組件來操做 props。
            import React, { Component } from 'React';
              const MyContainer = (WrappedComponent) =>
              class extends Component {
                  render() {
                      return <WrappedComponent {...this.props} />;
                  }
              } 
            複製代碼
          • 反向繼承(inheritance inversion)。高階組件繼承於被包裹的 React 組件。
            const MyContainer = (WrappedComponent) =>
                class extends WrappedComponent {
                  render() {
                    return super.render();
                  }
                } 
            複製代碼
    • 12.組件性能優化

      ,咱們都知道影響網頁性能最大的因素是瀏覽器的重繪(reflow)和 重排版(repaint)。React 背後的 Virtual DOM 就是儘量地減小瀏覽器的重繪與重排版。 對於性能優化這個主題,咱們每每會基於「不信任」的前提,即咱們須要提升 React Virtual DOM 的效率。從 React 的渲染過程來看,如何防止不避要的渲染多是最須要去解決的問題。 然而,針對這個問題,React 官方提供了一個便捷的方法來解決,那就是 PureRender。

    • 12.1 純函數

    • 12.2 PureRender

    • 12.3 Immutable

    • 12.4 key

    • 12.5 react-addons-perf

    14.自動化測試

    React 對測試有完善的支持,目前比較完善的 React 測試框架有 Jest 和 Enzyme

    Enzyme 是由 Airbnb 開源的 React 組件測試框架。與 Jest 相比,Enzyme 提供相似 jQuery 操做 DOM 的語法,在作測試斷言時更靈活、易用

    參考

相關文章
相關標籤/搜索