[React] 02 - Intro: why react and its design pattern

爲啥使用React,給我個理由


 

過去css

須要手動更新DOM、費力地記錄每個狀態;既不具有擴展性,又很難加入新的功能,就算能夠,也是有着冒着很大的風險。html

不過,使用這種開發方式很難打造出極佳的用戶體驗。由於不管每次用戶想要作點什麼,都須要向服務端發送請求並等待服務端的響應,這會致使用戶失去在頁面上所積累的狀態前端

Reactreact

它引入了一種新的方式來處理瀏覽器DOM。程序員

在任什麼時候間點,React都能以最小的DOM修改來更新整個應用程序。web

React本質上只關心兩件事:1). 更新DOM;2). 響應事件。ajax

 

  • 只關心View

每次狀態改變時,使用JavaScript從新渲染整個頁面會很是慢,這應該歸咎於讀取和更新DOM的性能問題。React運用一個虛擬的DOM實現了一個很是強大的渲染系統,在React中對DOM只更新不讀取。算法

React不處理Ajax、路由和數據存儲,也不規定數據組織的方式。它不是一個Model-View-Controller框架。若是非要問它是什麼,他就是MVC裏的「V」。React的精簡容許你將它集成到各類各樣的系統中 。編程

  • 工做狀態

React以渲染函數爲基礎。這些函數讀入當前的狀態,將其轉換爲目標頁面上的一個虛擬表現。redux

只要React被告知狀態有變化,他就會從新運行這些函數,計算出頁面的一個新的虛擬表現,接着自動把結果轉換成必要的DOM更新來反映新的表現。【不須要reset方式的completely update】

這種方式看上去應該比一般的JavaScript方案——按須要更新每個元素——要慢,可是React確實是這麼作的:它使用了很是高效的算法,計算出虛擬頁面當前版本和新版間的差別,基於這些差別對DOM進行必要的最少更新

公認的性能瓶頸

  1. React贏就贏在了最小化了重繪
  2. 而且避免了沒必要要的DOM操做

 

 

背後的思想:一步一步進化到 Redux


 

"React除了可以組件化開發ui,還完徹底全實現了先後端的隔離"。客官,此話怎講?

Ref: 怎樣理順react,flux,redux這些概念的關係,開發中有必要使用它們嗎?

Ref: 理解 React,但不理解 Redux,該如何通俗易懂的理解 Redux?

 

  • 原始社會的問題

1. 反覆刷新頁面,尤爲是內容複雜的頁面,對瀏覽器的渲染性能消耗很大。

2. 由交互產生的不少細膩的前端數據,其實也很難交給後臺處理,由於這是咱們無處安放的臨時狀態

 

  • Ajax技術的出現
正是因爲這兩個顯著缺陷,才致使ajax技術的出現。自從有了ajax通訊和局部頁面更新的機制之後,不用擔憂頁面總體刷新致使的用戶體驗問題了!
因而前端開發大踏步地進入web2.0時代,大量交互細膩,內容豐富的SPA(single page application)也應運而生。

1. 根據後臺數據排版生成頁面,如今只是最基本的工做。

2. 當用戶進行某種交互操做引發頁面狀態變化以後,爲了不頁面總體刷新,咱們須要當心翼翼地將各個相關的頁面局部元素揀選出來,作適當修改,再放回原處,動做通常不會太優雅。

 

  • 從上面的例子咱們得出兩個經驗

1. 根據肯定的交互狀態(state),一古腦兒決定頁面的呈現(view),這種「單向流」的開發狀態對程序員來講是思惟清晰、比較輕鬆的;一旦咱們須要不斷手動更新view,而且改變state和view的代碼還糾纏在一塊兒,咱們的心裏每每是崩潰的。

2. 爲了讓前端開發不感到崩潰,就把全部state交給後臺來維護,簡單粗暴地經過從新加載頁面來實現view的更新是不靠譜的,咱們須要找到新的方法,來實現view的自動更新。

 

  • 「改變state,讓view自動更新」的開發「觸發」思想

我腦補着facebook的某個程序員在一個月黑風高的晚上坐在公司電腦前,抿了一口濃濃的咖啡,忽然靈光一現,伴着屏幕上忽明忽暗的幽幽藍光,在文本編輯器裏寫下這麼一行文字:

可不能夠把瀏覽器裏的DOM tree克隆一份完整的鏡像到內存,也就是所謂的「virtual DOM」,當頁面的state發生變化之後,根據最新的state從新生成一份virtual DOM(至關於在內存裏「刷新」整個頁面),將它和以前的virtual DOM作比對(diff),而後在瀏覽器裏只渲染被改變的那部份內容,這樣瀏覽器的性能損耗和用戶體驗不就都不成問題了嗎?

而咱們知道在絕大部分網頁應用中js引擎的性能和內存徹底沒有被充分利用,咱們正好能夠火力全開,利用js的這部分性能紅利,實現內存中virtual DOM的diff工做,完美!

因而React橫空出世!伴隨着react的崛起,相似於redux這些專一於管理state的輕量級框架也變得煊赫一時起來。

 

決定頁面呈現的state能夠經過模塊屬性(props)從父模塊傳遞到子模塊。

這種"樹狀"分流機制,有點像植物將營養(state)從根部不斷運輸到細枝末葉的過程。

 

  • 架構的進化

* 傳統MVC

1. 前端開發的Model至關於後臺數據的鏡像或緩存池,它和服務器端MVC中的Model概念一脈相承;

2. View對應頁面的呈現,主要指的是和html、css相關的代碼,它和服務器端MVC中的View概念也很是相近。

3. 顯著的差異來自於controller:在後臺應用中,用戶和服務器之間的交互是經過http請求實現的,所以後臺controller的表達形式是http請求的handler,而且和router(定義網站的url規則)緊密相關; 而前端應用中,用戶和網頁之間的交互主要是經過操做事件(例如點擊鼠標、鍵盤輸入等)實現的,所以前端的controller這裏能夠簡單理解爲各類交互事件的handler。

 

* 問題表象

修改Model的Controller代碼像一把黃豆同樣散落在了各個View組件的內部。

 

* 問題緣由

若是能夠用某種方式把這些散落的代碼單獨收攏到一塊兒,是否是就讓這可讓這張圖示恢復秩序呢?好,咱們順着這個思路想下去。

不是MVC模式錯了,而是咱們壓根缺乏了一個和用戶交互行爲有關的action抽象!所以,對model的具體操做才無法從各個view組件中被剝離出來,放到一處。 

 

* Flux思想 - 單向流思想

1. flux與react沒有直接的關係,兩者是徹底獨立的概念。

2. flux不是一個js庫,而是一種前端代碼的組織思想,好比說 redux庫能夠認爲是一種flux思想的實現

 
server和client是遠程通訊的關係,所以爲了儘可能 減小通訊耦合,client每一個操做的所有信息都以http請求的形式被歸納成了精簡的「做用量」( action)。
請求的url路徑約定了用戶的 操做意圖(固然RESTful概念中,請求的method也能夠反映操做意圖),
request參數表徵了該 「意圖」的具體內容
正是基於這個action的抽象,client端的交互操做才能夠被 集中轉移到server端的controller中作統一響應。

從代碼層面而言,flux無非就是一個常見的event dispatcher,

其目的是要將以往MVC中各個View組件內的controller代碼片段提取出來放到更加恰當的地方進行集中化管理,並從開發體驗上實現了溫馨清爽、容易駕馭的「單向流」模式。 

  

  • 怎樣實現從view到state的反饋流程

用戶在view上的交互行爲(好比點擊提交按鈕等)應當引發state改變的時候,這個流程該怎麼處理?


首先,react框架爲咱們理順了 store --> view 的「單向」工做流store是state的容器);

而後,redux框架爲咱們理順了 view --> store 的**「單向」**工做流。

而且,react和redux都以組件化的形式能夠將各自負責的功能進行靈活地組裝或拆分,最大程度上確保咱們「一次只須要專一於一個局部問題」。具體來講,分爲如下步驟:

    1. 單例store的數據在react中能夠經過view組件的屬性(props)不斷由父模塊**「單向」**傳遞給子模塊,造成一個樹狀分流結構。若是咱們把redux比做整個應用的「心肺」 (redux的flux功能像心臟,reducer功能像肺部毛細血管),那麼這個過程能夠比做心臟(store)將氧分子(數據)經過動脈毛細血管(props)送到各個器官組織(view組件)
    2. 末端的view組件,又能夠經過flux機制,將攜帶交互意圖信息的action反饋給store。這個過程有點像將攜帶代謝產物的「紅細胞」(action)經過靜脈毛細血管又泵迴心髒(store)
    3. action流回到store之後,action以參數的形式又被分流到各個具體的reducer組件中,這些reducer一樣構成一個樹狀的hierarchy。這個過程像靜脈血中的紅細胞(action)被運輸到肺部毛細血管(reducer組件)
    4. 接收到action後,各個child reducer以返回值的形式,將最新的state返回給parent reducer,最終確保整個單例store的全部數據是最新的。這個過程能夠比做肺部毛細血管的血液充氧後,又被從新泵回了心臟
    5. 回到步驟1

  

  • 進一步熟悉React並理解Flux思想

從需求出發,看看使用React須要什麼:

1. React有props和state:

    props 意味着:父級分發下來的屬性;

    state 意味着:組件內部能夠自行管理的狀態;

    而且整個React沒有數據向上回溯的能力,也就是說數據只能單向向下分發,或者自行內部消化。

    理解這個是理解React和Redux的前提


2. 通常構建的React組件內部多是一個完整的應用,它本身工做良好,你能夠經過屬性做爲API控制它。可是更多的時候發現React根本沒法讓兩個組件互相交流,使用對方的數據。

    而後這時候不經過DOM溝通(也就是React體制內)解決的惟一辦法就是提高state,將state放到共有的父組件中來管理,再做爲props分發回子組件。


3. 子組件改變父組件state的辦法只能是經過onClick觸發父組件聲明好的回調,

    也就是父組件提早聲明好函數或方法做爲契約描述本身的state將如何變化,

    再將它一樣做爲屬性交給子組件使用。

    這樣就出現了一個模式:數據老是單向從頂層向下分發的,可是隻有子組件回調在概念上能夠回到state頂層影響數據。這樣state必定程度上是響應式的。


4. 爲了面臨全部可能的擴展問題,最容易想到的辦法就是全部state集中放到全部組件頂層,而後分發給全部組件。

5. 爲了有更好的state管理,就須要一個庫來做爲更專業的頂層state分發給全部React應用,這就是Redux。

    讓咱們回來看看重現上面結構的需求:

        a. 須要回調通知state (等同於回調參數) -> action【發起的通訊請求】
        b. 須要根據回調處理 (等同於父級方法) -> reducer 【對通訊請求刷選處理的過程】
        c. 須要state (等同於總狀態) -> store

    對Redux來講只有這三個要素:
        a. action是純聲明式的數據結構,只提供事件的全部要素,不提供邏輯。
        b. reducer是一個匹配函數,action的發送是全局的:全部的reducer均可以捕捉到並匹配與本身相關與否,相關就拿走action中的要素進行邏輯處理,修改store中的狀態,不相關就不對state作處理原樣返回。
        c. store負責存儲狀態並能夠被react api回調,發佈action.

    固然通常不會直接把兩個庫拿來用,還有一個binding叫react-redux, 提供一個Provider和connect。不少人其實看懂了redux卡在這裏。

        a. Provider是一個普通組件,能夠做爲頂層app的分發點,它只須要store屬性就能夠了。它會將state分發給全部被connect的組件,無論它在哪裏,被嵌套多少層。
        b. connect是真正的重點,它是一個科裏化函數,意思是先接受兩個參數(數據綁定mapStateToProps和事件綁定mapDispatchToProps),再接受一個參數(將要綁定的組件自己):

mapStateToProps:構建好Redux系統的時候,它會被自動初始化,可是你的React組件並不知道它的存在,所以你須要分揀出你須要的Redux狀態,因此你須要綁定一個函數,它的參數是state,簡單返回你關心的幾個值。
mapDispatchToProps:聲明好的action做爲回調,也能夠被注入到組件裏,就是經過這個函數,它的參數是dispatch,經過redux的輔助方法bindActionCreator綁定全部action以及參數的dispatch,就能夠做爲屬性在組件裏面做爲函數簡單使用了,不須要手動dispatch。這個mapDispatchToProps是可選的,若是不傳這個參數redux會簡單把dispatch做爲屬性注入給組件,能夠手動當作store.dispatch使用。這也是爲何要科裏化的緣由。

 
作好以上流程Redux和React就能夠工做了。簡單地說就是:
1. 頂層分發狀態,讓React組件被動地渲染。
2. 監聽事件,事件有權利回到全部狀態頂層影響狀態。

 

--------------------------------------------------------------------------------------------------------

接下來看看Redux/React與這個故事的聯繫:
  • view(React) = 傢俱的擺放在視覺的效果上
  • store(state) = 每一個傢俱在空間內的座標(如:電視的位置是x:10, y: 400)
  • action = 小明分配任務(誰應該幹什麼)
  • reducer = 具體任務都幹些什麼(把電視搬到沙發正對面而後靠牆的地方)

 

因此這個過程應該是這樣的:

view ---> action ---> reducer ---> store(state) ---> view

若是放入一個web app中,
首先,store(state)決定了view, 【預期效果】
而後,用戶與view的交互會產生action, 【根據效果作計劃】
接着,這些action會觸發reducer於是改變state, 【計劃實施】
最後,state的改變又形成了view的變化。 【實施後的新效果】


 

都是函數式編程裏的設計模式的東西,只是換了個馬甲。

 

先了解下MVP模式[Android Module] 03 - Software Design and Architecture

Presenter的角色就是:從View收集loadData這樣的請求,而後交由Model的一個具體對應的函數去執行。

 

 

React 組件 API


 

React 組件 API。咱們將講解如下7個方法:

    • 設置狀態:setState
    • 替換狀態:replaceState
    • 設置屬性:setProps
    • 替換屬性:replaceProps
    • 強制更新:forceUpdate
    • 獲取DOM節點:findDOMNode
    • 判斷組件掛載狀態:isMounted

 

例子:設置狀態:setState

<body>
    <div id="message" align="center"></div>

    <script type="text/babel">
      var Counter = React.createClass({
getInitialState:
function () { return { clickCount: 0 }; },
handleClick:
function () { this.setState( function(state) { return {clickCount: state.clickCount + 1}; } ); },
render:
function () { return (<h2 onClick={this.handleClick}>點我!點擊次數爲: {this.state.clickCount}</h2>); }
});
ReactDOM.render(
<Counter />, document.getElementById('message') );
</script> </body>

  

 

React 組件生命週期


 

組件的生命週期可分紅三個狀態

    • Mounting:已插入真實 DOM
    • Updating:正在被從新渲染
    • Unmounting:已移出真實 DOM

 

生命週期的方法

    • componentWillMount 在渲染前調用,在客戶端也在服務端。

    • componentDidMount : 在第一次渲染後調用,只在客戶端。以後組件已經生成了對應的DOM結構,能夠經過this.getDOMNode()來進行訪問。 若是你想和其餘JavaScript框架一塊兒使用,能夠在這個方法中調用setTimeout, setInterval或者發送AJAX請求等操做(防止異部操做阻塞UI)。

    • componentWillReceiveProps 在組件接收到一個新的 prop (更新後)時被調用。這個方法在初始化render時不會被調用。

    • shouldComponentUpdate 返回一個布爾值。在組件接收到新的props或者state時被調用。在初始化時或者使用forceUpdate時不被調用。 能夠在你確認不須要更新組件時使用。

    • componentWillUpdate在組件接收到新的props或者state但尚未render時被調用。在初始化時不會被調用。

    • componentDidUpdate 在組件完成更新後當即調用。在初始化時不會被調用。

    • componentWillUnmount在組件從 DOM 中移除的時候馬上被調用。

 

 

React 表單與事件


 

1. 在輸入框值發生變化時咱們能夠更新 state。

var HelloMessage = React.createClass({
getInitialState:
function() { return {value: 'Hello Runoob!'}; },
------------------------------------------------------ handleChange:
function(event) { this.setState({value: event.target.value}); },
------------------------------------------------------ render:
function() { var value = this.state.value; return <div> <input type="text" value={value} onChange={this.handleChange} />   // 監聽到value發生了變化,觸發handleChange. <h4>{value}</h4> </div>; } });
ReactDOM.render(
<HelloMessage />, document.getElementById('example') );

2. 在子組件上使用表單。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>菜鳥教程 React 實例</title>
    <script src="https://cdn.bootcss.com/react/15.4.2/react.min.js"></script>
    <script src="https://cdn.bootcss.com/react/15.4.2/react-dom.min.js"></script>
    <script src="https://cdn.bootcss.com/babel-standalone/6.22.1/babel.min.js"></script>
  </head>
  <body>
    <div id="example"></div>
<script type="text/babel">

var Content = React.createClass({ render: function() { return <div>   <input type="text" value={this.props.myDataProp} onChange={this.props.updateStateProp} />   <h4>{this.props.myDataProp}</h4> </div>; } });

-- 以上是子組件 --
======================================================================
-- 如下是父組件 --
var HelloMessage = React.createClass({
getInitialState:
function() { return {value: 'Hello Runoob!'};   },
---------------------------------------------------------------------- handleChange:
function(event) { this.setState({value: event.target.value}); },
---------------------------------------------------------------------- render:
function() { var value = this.state.value; return <div>   <Content myDataProp = {value} updateStateProp = {this.handleChange}>
</Content> </div>; } });
ReactDOM.render(
<HelloMessage />, document.getElementById('example') );
</script> </body> </html>

 

 

React 事件


 

1. 經過 onClick 事件來修改數據:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>菜鳥教程 React 實例</title>
    <script src="https://cdn.bootcss.com/react/15.4.2/react.min.js"></script>
    <script src="https://cdn.bootcss.com/react/15.4.2/react-dom.min.js"></script>
    <script src="https://cdn.bootcss.com/babel-standalone/6.22.1/babel.min.js"></script>
  </head>
<body> <div id="example"></div> <script type="text/babel">
------------------------------------------------------------------- var HelloMessage = React.createClass({ getInitialState: function() { return {value: 'Hello Runoob!'}; },
------------------------------------------------------------------- handleChange:
function(event) { this.setState({value: '菜鳥教程'}) },
-------------------------------------------------------------------- render:
function() { var value = this.state.value; return <div> <button onClick={this.handleChange}>點我</button> <h4>{value}</h4> </div>; } }); ReactDOM.render( <HelloMessage />, document.getElementById('example') ); </script> </body>
</html>

2. 從子組件中更新父組件的 state 時,你須要在父組件經過建立事件句柄 (handleChange) ,並做爲 prop (updateStateProp) 傳遞到你的子組件上。

  <body>
<div id="example"></div> <script type="text/babel">
var Content = React.createClass({ render: function() { return <div> <button onClick = {this.props.updateStateProp}>點我</button> <h4>{this.props.myDataProp}</h4> </div> } });
 -- 以上是子組件 --
=======================================================================================
-- 如下是父組件 --
var HelloMessage = React.createClass({
      getInitialState: function() {
        return {value: 'Hello Runoob!'};
      },
--------------------------------------------------------------------------------------- handleChange:
function(event) { this.setState({value: '菜鳥教程'}) },
--------------------------------------------------------------------------------------- render:
function() { var value = this.state.value; return <div> <Content myDataProp = {value} updateStateProp = {this.handleChange}></Content> </div>; } });
ReactDOM.render(
<HelloMessage />, document.getElementById('example') ); </script> </body>
相關文章
相關標籤/搜索