React學習-初入React世界

前端閱讀室

React 簡介

React 是 Facebook 在 2013 年開源 JavaScript 庫。它把界面抽象成一個個組件,經過組合這些組件,開發者能夠獲得功能豐富的頁面。同時引入了 JSX 語法,使得複用組件變得容易,且結構清晰。而且有了組件這層的抽象,代碼和真實渲染目標分離,除了能夠在瀏覽器端渲染到 DOM 開發網頁外,還能用於原生移動應用的開發。html

專一視圖層

React 並非完整的 MVC/MVVM 框架,它專一於 View(視圖)層解決方案。與模板引擎不一樣,React 又是一個包括 View 和 Controller 的庫。前端

Virtual DOM

React 把真實 DOM 樹轉換成 JavaScript 對象樹,也就是 Virtual DOM。每次數據更新,對比先後的 Virtual DOM,對發生變化的部分作批量更新,提高性能。而且 Virtual DOM 能夠方便地與其餘平臺集成,好比 react-native 就是基於 Virtual DOM 渲染原生控件的。react

函數式編程

命令式編程是給計算機下命名,而函數式編程,對應的是聲明式編程。聲明式編程的本質是 lambda 演算,好比咱們要操做一個數組裏的每一個元素,返回一個新數組。咱們的作法是構建一個 f 函數(規則)做用在數組上,而後返回新數組。這樣,計算能夠被重複利用。編程

JSX 語法

JSX 是 react 爲了方便 View 層組件化,承載構建 HTML 結構化頁面職責的而創立的語言(語法)。react-native

DOM 元素和組件元素

在 react 中建立的虛擬元素能夠分爲兩類,DOM 元素(DOM element)與組件元素(component element)。分別對應着原生 DOM 元素和自定義元素。數組

DOM 元素

當使用 JavaScript 來描述 Web 頁面的 HTML 元素時,能夠表示爲純粹的 JSON 對象。例如,描述一個按鈕瀏覽器

<button class="btn btn-blue">
  <em>Confirm</em>
</button>
複製代碼

->性能優化

{
  type: 'button',
  props: {
    className: 'btn btn-blue',
    children: [{
      type: 'em',
      props: {
        children: 'Confirm'
      }
    }]
  }
}
複製代碼

在 react 中,處處可見的元素並非真實的實例,它們只是頁面的描述對象。bash

組件元素

React 還能夠自定義組件元素。 類好比下架構

const Button = ({ color, text }) => {
  return {
    type: "button",
    props: {
      className: `btn btn-${color}`,
      children: [
        {
          type: "em",
          props: {
            children: text
          }
        }
      ]
    }
  };
};
複製代碼

Button 其實也能夠做爲元素而存在,方法名對應了元素類型,參數對應了元素屬性。 這也是 React 的核心思想之一,咱們可讓 DOM 元素、組件元素嵌套、組合,最後用遞歸渲染的方式構建出徹底的 DOM 元素樹。 可是這種寫法不容易閱讀和維護了,JSX 語法就應運而生了。

const Button = ({ color, text }) => {
  <button className={`btn btn-${color}`}>
    <em>{text}</em>
  </button>;
};
複製代碼

JSX 將 HTML 語法直接加入到 JavaScript 代碼中,再經過翻譯器轉換成純 JavaScript 後再由瀏覽器執行。

JSX 基本語法

JSX 的官方定義是類 XML 語法的 ECMAScript 擴展。

XML 基本語法

使用類 XML 語法,咱們能夠清晰地看到 DOM 樹狀結果及其屬性。只不過它被包裹在 JavaScript 的方法中

const List = () => (
  <ul> <li>1</li> <li>2</li> </ul>
);
複製代碼

須要注意幾點

  1. 定義標籤時,只容許被一個標籤包裹。 如
const c = () => (<span>1</span><span>2</span>);
複製代碼

會報錯,最外層沒有包裹,顯然沒法轉譯成 React.createElement 方法調用

  1. 標籤必須閉合,如<div></div><div />

元素類型

小寫字母對應 DOM 元素,大寫字母對應組件元素 此外,還有一些特殊的標籤值得討論,好比註釋和 DOCTYPE 頭 JSX 仍是 JavsScript,依然能夠用簡單的方法使用註釋,在子元素位置使用註釋要用{}包起來。 對於經常使用於判斷瀏覽器版本的條件註釋

<!--[if IE]>
  <p>work in IE</p>
<![endif]-->
複製代碼

須要用 JavaScript 判斷來實現

{
  (!!window.ActiveXObject || 'ActiveXObject' in window) ?
  <p>work in IE</p> : ''
}
複製代碼

DOCTYPE 頭是一個很是特殊的標誌,通常會在 React 服務端渲染時用到。DOCTYPE 是沒有閉合的,咱們沒法渲染它。常見的作法是構造一個保存 HTML 的變量,將 DOCTYPE 和整個 HTML 標籤渲染後的結果串聯起來。

元素屬性

DOM 元素屬性是標準規範屬性,但 class 和 for 因爲是關鍵字,由 className 和 htmlFor 替代。 組件元素屬性是徹底自定義的屬性,也能夠理解爲實現組件所須要的參數。通常採用小駝峯寫法。 此外還有一些特有的屬性表達

  1. 省略 Boolean 屬性值會致使 JSX 認爲 bool 值設爲了 true
<Checkbox checked={true} />
複製代碼

能夠簡寫成

<Checkbox checked />
複製代碼
<Checkbox checked={false} />
複製代碼

能夠省略爲

<Checkbox />
複製代碼
  1. 展開屬性 使用 ES6 rest/spread 特性能夠提升開發效率
const data = { name: "foo", value: "bar" };
const component = <Component {...data} />; 複製代碼
  1. 自定義 HTML 屬性 往 DOM 元素傳入自定義屬性,React 是不會渲染的
<div d="xxx">content</div>
複製代碼

須要使用 data-前綴,這和 HTML 標準也是一致的

<div data-attr="xxx">content</div>
複製代碼

然而在自定義標籤中,任意屬性都是被支持的

<CustomComponent d="xxx" />
複製代碼
  1. JavaScript 屬性表達式
<Person name={true ? 1 : 2} />
複製代碼
  1. HTML 轉義 React 會將全部要顯示到 DOM 的字符串轉義,防止 XSS。如&copy;不會正確顯示。 能夠經過如下方法解決 1.直接使用 UTF-8 字符 2.使用 Unicode 編碼 3.使用功數組組裝<div>{['cc ', <span>&copy;</span>, ' 2015']}</div> 4.直接插入原始的 HTML

React 還提供了 dangerouslySetInnerHTML 屬性。它避免了 React 轉義字符,請在肯定必要的狀況下使用它

<div dangerouslySetInnerHTML={{ __html: "cc &copy; 2015" }} />
複製代碼

React 組件

組件的演變

在 MVC架構出現以前,組件主要分爲兩種

  1. 狹義上的組件,又稱 UI 組件,主要圍繞交互動做上的抽象,利用 JavaScript 操做 DOM 結構或 style 樣式來控制
  2. 廣義上的組件,帶有業務含義和數據的 UI 組件組合。它更傾向於採用分層的思想去處理 對於 UI 組件,分爲 3 部分:結構、樣式和交互行爲,對應於 HTML、CSS 和 JavaScript.

封裝的基本思路就是面向對象思想。交互基本上以操做 DOM 爲主。邏輯上是結構上哪裏須要變,咱們就操做哪裏。如下是幾項規範標準組件的信息。

  1. 基本的封裝性。儘管說 JavaScript 沒有真正面向對象的方法,可是咱們仍是能夠經過實例化的方法來製造對象。
  2. 簡單的生命週期呈現。如 contructor 和 destroy,表明了組件的掛載和卸載過程。
  3. 明確的數據流動。這裏的數據指的是調用組件的參數。一旦肯定參數的值,就會解析傳進來的參數,根據參數的不一樣做出不一樣的響應。

這個階段,前端在應用級別沒有過多複雜的交互。傳統組件的主要問題在於結構、樣式與行爲沒有很好地結合,不一樣參數下的邏輯可能會致使不一樣的渲染邏輯,這時就會存在大量的 HTML 結構與 style 樣式的拼裝。邏輯一旦複雜,開發及維護成本至關高。

因而分層思想引進了,出現了 MVC 架構。View 只關心怎麼輸出變量,因此就誕生了各類各樣的模板語言。讓模板自己承載邏輯,能夠幫咱們解決 View 上的邏輯問題。對於組件來講,能夠將拼裝 HTML 的邏輯部分解耦出去,解決了數據與界面耦合的問題。

模板做爲一個 DSL,也有其侷限性。在 Angular 中,咱們看到了在 HTML 上定義指令的方式。

W3C 將相似的思想制定成了規範,稱爲 Web Components。它經過定義 Custom Elements(自定義元素)的方式來統一組件。每一個自定義元素能夠定義本身對外提供的屬性、方法,還有事件,內部能夠像寫一個頁面同樣,專一於實現功能來完成對組件的封裝。

Web Components 由 4 個組成部分:HTML Templates 定義了以前模板的概念,Custom Elements 定義了組件的展示形式,Shadow DOM 定義了組件的做用域範圍、能夠囊括樣式,HTML Imports 提出了新的引入方式。

事實上,它仍是須要時間的考驗的。由於諸如如何包裝在這套規範之上的框架,如何得到在瀏覽器端的所有支持,怎麼與現代應用架構結合等等。但它倒是開闢了一條羅馬大道,告訴咱們組件化能夠這樣去作。

React 組件的構建

React 的本質就是關心元素的構成,React 組件即爲組件元素。組件元素被描述成純粹的 JSON 對象,意味着可使用方法或是類來構建。React 組件基本上由 3 個部分組成-屬性(props)、狀態(state)以及生命週期方法。

React 組件能夠接收參數,也可能有自身狀態。一旦接收到的參數或自身狀態有所改變,React 組件就會執行相應的生命週期方法,最後渲染。

1.React 與 Web Components 從 React 組件上看,它與 Web Components 傳達的理念是一致的,但二者的實現方式不一樣:

  1. React 自定義元素是庫本身構建的,與 Web Components 規範並不通用;
  2. React 渲染過程包括了模板的概念,即 JSX
  3. React 組件的實現均在方法與類中,所以能夠作到相互隔離,但不包括樣式。
  4. React 引用方式遵循 ES6 module 標準

React 在純 JavaScript 上下了工夫,將 HTML 結構完全引入到 JavaScript 中。這種作法褒貶不一,但有效地解決了組件所要解決的問題之一。

2.React 組件的構建方法 React 組件基本上由組件的構建方式、組件內的屬性狀態與生命週期方法組成。

React 組件構建上提供了 3 種不一樣的方法:React.createClass、ES6 classes 和無狀態函數。

React.createClass 用 React.createClass 構建組件是 React 最傳統、也是兼容性最好的方法。

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 ES6 classes 的寫法是經過 ES6 標準的類語法的方式來構建方法:

import React, { Component } from "react";

class Button extends Component {
  contructor(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>
    );
  }
}
複製代碼

與 createClass 的結果相同的是,調用類實現的組件會建立實例對象。

咱們很容易聯想到組件抽象過程當中也可使用繼承的思路。在實際應用中,咱們極少讓子類去繼承功能組件。繼承牽一髮而動全身。在 React 組件開發中,經常使用的方式是將組件拆分到合理的粒度,用組合的方式合成業務組件。

說明:React 的全部組件都繼承自頂層類 React.Component。它的定義很是簡潔,只是初始化了 React.Component 方法,聲明瞭 props、context、refs 等,並在原型上定義了 setState 和 foreUpdate 方法。內部初始化的生命週期方法與 createClass 方式使用的是同一個方法建立的。

無狀態函數 使用無狀態函數構建的組件稱爲無狀態組件

function Button({ color = "blue", text = "Confirm" }) {
  return (
    <button className={`btn btn-${color}`}> <em>{text}</em> </button>
  );
}
複製代碼

無狀態組件只傳入 props 和 context 兩個參數;也就是說,它不存在 state,也沒有生命週期方法,組件自己即上面兩種 React 組件構建方法中的 render。不過,像 propTypes 和 defaultProps 仍是能夠經過向方法設置靜態屬性來實現的。

無狀態組件不像上述兩種方法在調用時會建立新實例,它建立時始終保持了一個實例,避免了沒必要要的檢查和內存分配。

React 數據流

在 React 中,數據是自頂向下單向流動的,即從父組件到子組件。

state 與 props 是組件中最重要的概念。若是頂層組件初始化 props,那麼 React 會向下遍歷整棵組件樹,從新嘗試渲染全部相關的子組件。state 只關心組件本身內部的狀態,這些狀態只能在組件內改變。把組件當作一個函數,props 就是它的參數,內部由 state 做爲函數的內部參數,返回一個 Virtual DOM 的實現。

state

在 React 中,state 爲組件內部狀態。當組件內部使用 setState 方法時,該組件會嘗試從新渲染。

值得注意的,setState 是一個異步方法,一個生命週期內全部的 setState 方法會合並操做。

咱們思考一個常規的 Tabs 組件,對於 activeIndex 做爲 state,就有兩種不一樣的視角。

  1. 在內部更新。當咱們切換 tab 標籤時,能夠看做是組件內部的交互行爲,被選擇後經過回調函數返回具體選擇的索引。
  2. 在外部更新。當咱們切換 tab 標籤時,能夠看做是組件外部在傳入具體的索引,而組件就像「木偶」同樣被操控着。 這兩種情形在 React 組件的設計中很是常見,咱們分別稱爲智能組件和木偶組件

固然,實現組件時,能夠同時考慮兼容這兩種

constructor(props) {
  super(props);

  const currProps = this.props;

  let activeIndex = 0;
  // 來源於須要外部更新的
  if (activeIndex in currProps) {
    activeIndex = currProps.activeIndex;
    // 來源於使用內部更新的
  } else if ('defaultActiveIndex' in currProps) {
    activeIndex = currProps.defaultActiveIndex;
  }

  this.state = {
    activeIndex,
    prevIndex: activeIndex,
  };
}
複製代碼

props

props 是 React 用來讓組件之間互相聯繫的一種機制,通俗地說就像方法的參數同樣。

props 的傳遞過程,對於 React 組件來講很是直觀。React 的單向數據流,主要的流動管道就是 props。props 自己是不可變的。組件的 props 必定來自於默認屬性或經過父組件傳遞而來。

React 爲 props 提供了默認配置,經過 defaultProps 靜態變量的方式來定義。

static defaultProps = {
  classPrefix: 'tabs',
  onChange: () => {},
};
複製代碼

子組件 prop

在 React 中有一個重要且內置的 props——children,它表明組件的子組件集合。

實現的基本思路以 TabContent 組件渲染 TabPane 子組件集合爲例來說

getTabPanes() {
  const { classPrefix, activeIndex, panels, isActive } = this.props;

  return React.Children.map(panels, (child) => {
    if (!child) { return; }

    const order = parseInt(child.props.order, 10);

    return React.cloneElement(child, {
      classPrefix,
      isActive,
      children: child.props.children,
      key: `tabpane-${order}`,
    });
  });
}
複製代碼

它是經過 React.Children.map 方法遍歷子組件,同時利用 React 的 cloneElement 方法克隆到 TabPane 組件,最後返回這個 TabPane 組件集合。

React.Children 是 React 官方提供的一系列操做 children 的方法。它提供諸如 map、forEach、count 等實用函數。 使用 getTabPanes

render () {
  return (<div>{this.getTabPanes()}</div>);
}
複製代碼

假如咱們把 render 方法中的 this.getTabPanes 方法中對子組件的遍歷直接放進去

render() {
  return (<div>{React.Children.map(this.props.children, (child) => {...})}</div>)
}
複製代碼

這種調用方式稱爲 Dynamic Children(動態子組件)。

組件props

也能夠將子組件以props的形式傳遞。通常咱們會用這種方法讓開發者定義組件的某一個prop,讓其具有多種類型,來作到簡單配置和自定義配置組合在一塊兒的效果。

用function prop與父組件通訊

this.props.onChange(activeIndex, prevIndex)
複製代碼

觸發了onChange prop回調函數給父組件必要的值。

propTypes

propTypes用於規範props的類型與必需的狀態。它會在開發環境下,對組件的prop值的類型做檢查。

static propTypes = {
  classPrefix: React.PropTypes.string,
}
複製代碼

propTypes有不少類型支持,不只有基本類型,還包括枚舉和自定義類型。

React生命週期

掛載和卸載過程

1.組件的掛載 這個過程主要作組件狀態的初始化,咱們推薦如下面例子爲模板寫初始化組件:

import React, { Component, PropTypes } from 'react';

class App extends Component {
  static propTypes = {
    // ...
  }

  static defaultProps = {
    // ...
  }

  constructor(props) {
    super(props)

    this.state = {
      // ...
    }
  }

  componentWillMount() {
    // ...
  }

  componentDidMount() {
    // ...
  }

  render () {
    return <div>This is a demo</div>
  }
}
複製代碼

若是咱們在componentWillMount中執行setState方法,組件會更新state,但組件只渲染一次。所以,這是無心義的執行,徹底能夠放在constructor初始化state中。 若是咱們在componentDidMount中執行setState方法,組件會再次更新,不過在初始化過程就渲染了兩次組件,這並非一次好事。但實際狀況,有一些場景必須這麼作,好比須要獲取組件的位置。 2. 組件的卸載 componentWillUnmount,咱們經常會執行一些清理方法,好比事件回收、清除定時器。

數據更新過程

更新過程指的是父組件向下傳遞props或組件自身執行setState方法時發生的一系列更新動做。

import React, { Component, PropTypes } from 'react'

class App extends Component {
  componentWillReceiveProps(nextProps) {
    // this.setState({})
  }

  shouldComponentUpdate(nextProps, nextState) {
    // return true
  }

  componentWillUpdate(nextProps, nextState) {

  }

  componentDidUpdate(prevProps, prevState) {

  }

  render() {

  }
}
複製代碼

若是組件自身的state更新了,會依次執行shouldComponentUpdate、componentWillUpdate、render和componentDidUpdate

shouldComponentUpdate接收須要更新的props和state,讓開發者增長判斷邏輯,不須要更新方法最終返回false便可,這是性能優化的手段之一。

無狀態組件是沒有生命週期方法的,這也意味着它沒有shouldComponentUpdate。渲染該類組件,每次都會從新渲染。

componentWillUpdate方法提供的是須要更新的props和state,而componentDidUpdate提供更新前的props和state。

注意不能在componentWillUpdate執行setState方法,會致使循環執行render。

若是組件是由父組件更新props而更新的,那麼在shouldComponentUpdate以前會先執行componentWillRecieveProps方法。此方法能夠做爲React在props傳入後,渲染以前setState的機會,在此方法中調用setState是不會二次渲染的。

componentWillReceiveProps(nextProps) {
  if ('activeIndex' in nextProps) {
    this.setState({
      activeIndex: nextProps.activeIndex
    })
  }
}
複製代碼

React與DOM

ReactDOM

ReactDOM中的API很是少,只有findDOMNode、unmountComponentAtNode和render。 1.findDOMNode Reactz提供的獲取DOM元素的方法有兩種,其中一種就是ReactDOM提供的findDOMNode:

DOMElement findDOMNode(ReactComponent component)
複製代碼

當組件被渲染到DOM後,findDOMNode返回該React組件實例相應的DOM節點。它能夠用於獲取表單的value以及用於DOM的測量。

class App extends Component {
  componentDidMount() {
    const dom = ReactDOM.findDOMNode(this)
  }

  render() {}
}
複製代碼

render

ReactComponent render(
  ReactElement element,
  DOMElement container,
  [function callback] ) 複製代碼

該方法把元素掛載到container中,而且返回element的實例(即refs引用)。若是是無狀態組件,render會返回null。當組件裝載完畢時,callback被調用。

與render相反,React還提供了一個不多使用的unmountComponentAtNode方法來進行卸載操做。

ReactDOM的不穩定方法

unstable_renderSubtreeIntoContainer。它能夠更新組件到傳入的DOM節點。它與render方法相比,區別在因而否傳入父節點。

另外一個ReactDOM中的不穩定方法unstable_batchedUpdates是關於setState更新策略的。

refs

它是React組件中很是特殊的prop,能夠附加到任何一個組件上。組件被調用時會新建一個該組件的實例,而refs就會指向這個實例。

findDOMNode和refs都沒法用於無狀態組件中,無狀態組件掛載只是方法調用,沒有新建實例。

React以外的DOM操做

調用HTML5 Audio/Video的play方法和input的focus方法,React就無能爲力了,須要使用相應的DOM方法來實現。

還有組件之外區域(通常指document、body)的事件綁定、DOM的尺寸計算。

參考

《深刻React技術棧》

前端閱讀室
相關文章
相關標籤/搜索