React 是 Facebook 在 2013 年開源 JavaScript 庫。它把界面抽象成一個個組件,經過組合這些組件,開發者能夠獲得功能豐富的頁面。同時引入了 JSX 語法,使得複用組件變得容易,且結構清晰。而且有了組件這層的抽象,代碼和真實渲染目標分離,除了能夠在瀏覽器端渲染到 DOM 開發網頁外,還能用於原生移動應用的開發。html
React 並非完整的 MVC/MVVM 框架,它專一於 View(視圖)層解決方案。與模板引擎不一樣,React 又是一個包括 View 和 Controller 的庫。前端
React 把真實 DOM 樹轉換成 JavaScript 對象樹,也就是 Virtual DOM。每次數據更新,對比先後的 Virtual DOM,對發生變化的部分作批量更新,提高性能。而且 Virtual DOM 能夠方便地與其餘平臺集成,好比 react-native 就是基於 Virtual DOM 渲染原生控件的。react
命令式編程是給計算機下命名,而函數式編程,對應的是聲明式編程。聲明式編程的本質是 lambda 演算,好比咱們要操做一個數組裏的每一個元素,返回一個新數組。咱們的作法是構建一個 f 函數(規則)做用在數組上,而後返回新數組。這樣,計算能夠被重複利用。編程
JSX 是 react 爲了方便 View 層組件化,承載構建 HTML 結構化頁面職責的而創立的語言(語法)。react-native
在 react 中建立的虛擬元素能夠分爲兩類,DOM 元素(DOM element)與組件元素(component element)。分別對應着原生 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 的官方定義是類 XML 語法的 ECMAScript 擴展。
使用類 XML 語法,咱們能夠清晰地看到 DOM 樹狀結果及其屬性。只不過它被包裹在 JavaScript 的方法中
const List = () => (
<ul> <li>1</li> <li>2</li> </ul>
);
複製代碼
須要注意幾點
const c = () => (<span>1</span><span>2</span>);
複製代碼
會報錯,最外層沒有包裹,顯然沒法轉譯成 React.createElement 方法調用
<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 替代。 組件元素屬性是徹底自定義的屬性,也能夠理解爲實現組件所須要的參數。通常採用小駝峯寫法。 此外還有一些特有的屬性表達
<Checkbox checked={true} />
複製代碼
能夠簡寫成
<Checkbox checked />
複製代碼
<Checkbox checked={false} />
複製代碼
能夠省略爲
<Checkbox />
複製代碼
const data = { name: "foo", value: "bar" };
const component = <Component {...data} />; 複製代碼
<div d="xxx">content</div>
複製代碼
須要使用 data-前綴,這和 HTML 標準也是一致的
<div data-attr="xxx">content</div>
複製代碼
然而在自定義標籤中,任意屬性都是被支持的
<CustomComponent d="xxx" />
複製代碼
<Person name={true ? 1 : 2} />
複製代碼
©
不會正確顯示。 能夠經過如下方法解決 1.直接使用 UTF-8 字符 2.使用 Unicode 編碼 3.使用功數組組裝<div>{['cc ', <span>©</span>, ' 2015']}</div>
4.直接插入原始的 HTMLReact 還提供了 dangerouslySetInnerHTML 屬性。它避免了 React 轉義字符,請在肯定必要的狀況下使用它
<div dangerouslySetInnerHTML={{ __html: "cc © 2015" }} />
複製代碼
在 MVC架構出現以前,組件主要分爲兩種
封裝的基本思路就是面向對象思想。交互基本上以操做 DOM 爲主。邏輯上是結構上哪裏須要變,咱們就操做哪裏。如下是幾項規範標準組件的信息。
這個階段,前端在應用級別沒有過多複雜的交互。傳統組件的主要問題在於結構、樣式與行爲沒有很好地結合,不一樣參數下的邏輯可能會致使不一樣的渲染邏輯,這時就會存在大量的 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 組件即爲組件元素。組件元素被描述成純粹的 JSON 對象,意味着可使用方法或是類來構建。React 組件基本上由 3 個部分組成-屬性(props)、狀態(state)以及生命週期方法。
React 組件能夠接收參數,也可能有自身狀態。一旦接收到的參數或自身狀態有所改變,React 組件就會執行相應的生命週期方法,最後渲染。
1.React 與 Web Components 從 React 組件上看,它與 Web Components 傳達的理念是一致的,但二者的實現方式不一樣:
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 中,數據是自頂向下單向流動的,即從父組件到子組件。
state 與 props 是組件中最重要的概念。若是頂層組件初始化 props,那麼 React 會向下遍歷整棵組件樹,從新嘗試渲染全部相關的子組件。state 只關心組件本身內部的狀態,這些狀態只能在組件內改變。把組件當作一個函數,props 就是它的參數,內部由 state 做爲函數的內部參數,返回一個 Virtual DOM 的實現。
在 React 中,state 爲組件內部狀態。當組件內部使用 setState 方法時,該組件會嘗試從新渲染。
值得注意的,setState 是一個異步方法,一個生命週期內全部的 setState 方法會合並操做。
咱們思考一個常規的 Tabs 組件,對於 activeIndex 做爲 state,就有兩種不一樣的視角。
固然,實現組件時,能夠同時考慮兼容這兩種
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 是 React 用來讓組件之間互相聯繫的一種機制,通俗地說就像方法的參數同樣。
props 的傳遞過程,對於 React 組件來講很是直觀。React 的單向數據流,主要的流動管道就是 props。props 自己是不可變的。組件的 props 必定來自於默認屬性或經過父組件傳遞而來。
React 爲 props 提供了默認配置,經過 defaultProps 靜態變量的方式來定義。
static defaultProps = {
classPrefix: 'tabs',
onChange: () => {},
};
複製代碼
在 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的形式傳遞。通常咱們會用這種方法讓開發者定義組件的某一個prop,讓其具有多種類型,來作到簡單配置和自定義配置組合在一塊兒的效果。
this.props.onChange(activeIndex, prevIndex)
複製代碼
觸發了onChange prop回調函數給父組件必要的值。
propTypes用於規範props的類型與必需的狀態。它會在開發環境下,對組件的prop值的類型做檢查。
static propTypes = {
classPrefix: React.PropTypes.string,
}
複製代碼
propTypes有不少類型支持,不只有基本類型,還包括枚舉和自定義類型。
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
})
}
}
複製代碼
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() {}
}
複製代碼
ReactComponent render(
ReactElement element,
DOMElement container,
[function callback] ) 複製代碼
該方法把元素掛載到container中,而且返回element的實例(即refs引用)。若是是無狀態組件,render會返回null。當組件裝載完畢時,callback被調用。
與render相反,React還提供了一個不多使用的unmountComponentAtNode方法來進行卸載操做。
unstable_renderSubtreeIntoContainer。它能夠更新組件到傳入的DOM節點。它與render方法相比,區別在因而否傳入父節點。
另外一個ReactDOM中的不穩定方法unstable_batchedUpdates是關於setState更新策略的。
它是React組件中很是特殊的prop,能夠附加到任何一個組件上。組件被調用時會新建一個該組件的實例,而refs就會指向這個實例。
findDOMNode和refs都沒法用於無狀態組件中,無狀態組件掛載只是方法調用,沒有新建實例。
調用HTML5 Audio/Video的play方法和input的focus方法,React就無能爲力了,須要使用相應的DOM方法來實現。
還有組件之外區域(通常指document、body)的事件綁定、DOM的尺寸計算。
《深刻React技術棧》