來自 GitChat 做者:餘博倫
更多IT技術分享,盡在微信公衆號:GitChat 技術雜談html
Facebook 官方對 React 定義是:用來構建用戶界面的庫(Library)。注意到這裏的用詞是庫(Library)而不是框架(Framework)。React 不像早期版本的 Angular 這樣功能很是完備的 mvvm 框架,它主要只專一於解決 MVC 當中 V 層,也就是視圖層(View)方面的問題。react
不過咱們也沒必要太過糾結庫(Library)或框架(Framework)的定義。複雜的,給出你一整套解決方案的就叫框架(Framework);簡單的,專一解決一個問題並作到極致(Do one thing and do it well)的就叫庫(Library)。git
不過咱們仍是習慣性地稱 React 是一個 JavaScript 框架,由於除了 React 核心庫自己,在 React 的生態圈當中,還有不少其餘能夠搭配協同的工具庫,好比在此次分享當中咱們要介紹的用來解決狀態管理問題的 Redux ;用來提供前端路由功能的 react-router 。咱們把這些工具庫統稱爲 React 技術棧,組合使用 React 技術棧也就徹底撐得起一個框架提供的功能了。github
聲明式npm
說白了聲明式就是你告訴程序你要一個什麼樣的東西的編寫代碼的方式。這也是在開發構建用戶界面時最友好的方式。在 React 當中,你能夠很輕鬆地告訴 React 你想要一個什麼樣的界面。咱們使用一種叫作 JSX 的相似於 HTML/XML 的 JavaScript 語法擴展來和 React 交流:redux
<應用> <輸入框></輸入框> <按鈕></按鈕> </應用>
這就好像咱們能夠直接對 React 說:後端
這裏我要一個按鈕!
這裏我要一個表單!react-native
是否是很是的直觀明瞭呢?前端工程化
組件化
在 React 當中,咱們是以組件(Component)的概念來劃分用戶界面的。一般咱們開發的頁面均可以拆成一個個通用的組件,例如導航、表單、列表項、頁腳等等。
使用 React 能夠在很大程度上提升你代碼的可複用性,編寫頁面就如搭積木通常簡單:
<應用> <導航/> <註冊表單/> <頁腳/> </應用>
這也一樣意味着,咱們除了能夠在開發頁面時複用本身編寫的組件之外,還能把別人編寫好的通用組件直接拿過來用。自定義一下樣式,傳個數據進去,組合起來,一個頁面分分鐘就搞定。
一次學習,隨處編寫
React 最強大的地方在於,其內部實現的虛擬DOM屏蔽了全部的底層實現,經過不一樣的渲染器(renderer),你編寫的同一套代碼能夠用來構建包含 瀏覽器/桌面操做系統/Android/iOS 等幾乎全部平臺的用戶界面。也就是說,掌握了 React 以後,你的能力將不止侷限於寫網頁,而是能夠在幾乎全部的平臺上開發用戶界面。
這也就是爲何咱們使用 React 的時候須要調用兩個庫 react 和 react-dom,react 庫文件用來實現 React 的核心功能,react-dom 則用來把它渲染到瀏覽器當中。
目前已有的其餘平臺的解決方案還包括:
例如在使用 React Native 的時候,咱們一樣是使用 react 核心庫來實現基礎功能,而後經過 react-native 庫將咱們編寫的界面渲染到移動端上。
也就是說,有了 React 以後,咱們能夠用一種統一的描述方式來開發用戶界面,至於在什麼平臺上實現,只要有相應的渲染器(renderer),咱們就可以把咱們開發的界面在對應的平臺上面渲染出來。例如在美劇《西部世界》當中,React 甚至能夠用來編寫人工智能 Host 的故事線:
React 組件
咱們用 React 編寫的代碼絕大多數都是組件的代碼。編寫 React 組件須要遵循 React 內部的一系列規範,所以用 React 編寫出來的應用自帶前端工程化屬性。無論新手仍是老司機,只要是用 React 寫組件,咱們都能保證他寫出來的代碼是差很少的。這也就很是有利於一個項目組當中多個開發者之間進行協做。很是適合高級作架構,中級封組件,初級寫業務的模式。
React 組件其實就至關於 JavaScript 當中的一種函數,接受應用數據做爲參數,內部進行一系列處理(包含事件處理函數、生命週期函數等,此處不展開講),返回一個 React 元素。
React 元素
這裏要注意到,React 組件和 React 元素是兩個不一樣的概念。React 元素是 React 組件的一部分,也就是 React 組件返回的要拿來渲染的內容。
在 React 當中,咱們經過一種叫作 JSX 的 JavaScript 語法擴展來描述 React 元素。
const title = <h1>Counter</h1>;
這裏特別要注意的是,JSX 既不是原生的 HTML,也不是 jQuery 當中的字符串 $('<h1>Counter</h1>')
,更不是 pug(jade) 當中的模板 h1 Counter
。這是 React 內部本身的一套實現,能夠容許你像寫 HTML 同樣,在 JavaScript 代碼當中直接寫頁面,React 會在隨後的渲染過程中自動把 JSX 轉譯成頁面當中真實的 DOM 元素。
在 React 當中,有兩種定義組件的方式。(注:在 react@15.6 當中已經廢棄了 createClass
方法,若是你歷來沒用過 React 請自動忽略)
函數定義組件
比較簡單的一些,只接受外部傳入的數據的組件,咱們通常經過函數定義的方式來編寫:
var Button = function(props) { return <button onClick={props.onClick}>+</button>; } // 固然也能夠用 ES6 的 箭頭函數 arrow function const Number = ({ number }) => <p>{number}</p>;
props & state
上述示例當中的 props 就是組件數據的一種。在 React 當中,最經常使用的組件數據有兩種:props 和 state.
其中 props 是從外部傳入的,內部沒法修改,用來渲染展現的數據。
而 state 則是組件內部維護,能夠跟隨應用狀態改變而改變的數據(例如用戶輸入的表單項)。
類定義組件
比較複雜的,須要處理事件,調用生命週期函數,與服務器交互數據的組件,咱們經過類定義組件的方式來聲明:
// 從 React 庫當中獲取組件的基礎支持 const { Component } = React; // 使用 ES6 當中的 class 關鍵字來聲明組件 class Container extends Component { /* 類中的構造方法,調用super方法來確保咱們可以獲取到this,組件自身的 state 數據也在構造方法當中初始化。*/ constructor() { super(); this.state = { number: 0 } } /* 事件處理方法,在 React 當中咱們經過調用 `setState` 方法來修改 state 數據,這樣才能觸發組件在界面當中自動從新渲染更新 */ handleClick() { this.setState({number: this.state.number+1}); } // 渲染方法,返回 React 元素 render() { return ( <div> <Title /> <Number number={this.state.number} /> <Button onClick={() => this.handleClick()} /> </div> ); } }
在本文的開頭咱們已經介紹過了,React 是一個視圖層的框架,也就是說它只有 V,而真正在編寫前端代碼的時候,除了頁面展現的內容之外,咱們還須要進行處理用戶輸入、驗證表單、和服務器進行數據交互之類的操做。
那麼在實際的編碼過程中,咱們要如何解耦這些應用的業務邏輯和用戶界面的結構樣式呢?
這時咱們就須要引入一組展現組件和容器組件的概念。
展現組件
容器組件
例如:
// 展現組件 const Button = props => <button onClick={props.onClick}>+</button>; // 容器組件 class Counter extends Component { constructor() { super(); this.state = { number: 0 } } handleClick() { this.setState({number: this.state.number + 1}); } render() { return ( <div> <Title /> <Number number={this.state.number} /> <Button onClick={() => this.handleClick()} /> </div> ) } }
在上述 React 部分的介紹當中,咱們已經提到了,React 用來處理數據的方式主要有 props 和 state 兩種(另外還有一種不經常使用的 context)。
其中的 props 必須是從父組件傳遞到子組件,若是嵌套層級不少,props 必須逐級從保存數據的組件層層傳遞到使用 props 的組件當中。而 state 在使用的時候,必須經過調用 this.setState()
方法,在改變 state 值的同時,觸發 React 組件運行的生命週期,來觸發界面的更新。 this.setState()
方法能夠傳遞數據、方法、回調函數。在同一次操做中,連續調用屢次 this.setState()
方法也會形成許多難以預料的結果,僅僅經過看代碼你很難判斷出最後值會變成什麼。
而咱們使用 React 開發界面的主要場景是在Web應用當中,不一樣於傳統的之內容爲主的網頁。Web應用涉及到很是多的狀態數據的改變,包括用戶的交互、服務器通訊、界面的動畫、樣式的改變等等內容。
咱們在開頭也提到了,React 是一個專一於視圖層的庫,在數據的改變,狀態管理方面,它並無作得很好。所以,當咱們的應用複雜到必定程度時,就須要引入一些其餘的工具庫來幫助咱們解決狀態管理的問題。
Component(state) = View
咱們經過一個簡單的公式來講明這個問題。在 React 的理念當中,組件其實就是一個方法,咱們向組件方法傳入數據得出要渲染的視圖內容。
這裏之因此把傳入的數據稱爲 狀態(state) ,是由於在一個應用當中,許多數據都是處於變化當中的,根據用戶的不一樣操做響應發生改變(好比說在咱們計數器的示例當中,點擊按鈕,計數器的數字就會隨之改變增長)。
也就是說,咱們看到的視圖,網頁的內容,若是把應用狀態數據不一樣的改變不一樣的時刻看做是一段動畫的話,頁面在某一刻顯示的內容其實就是動畫的某一幀。
因此,咱們在開發構建界面時,除了界面的樣式和邏輯之外,如何處理狀態數據就成了另一個咱們須要主要關注的問題。這一問題的解決方案,天然也就叫作狀態管理了。
React 應用的開發理念告訴咱們,在一個應用當中,若是有兩個組件須要使用同一數據,那麼咱們須要把這一組數據提高到它們共同的父組件當中保存;在實際開發當中,應該儘可能控制有狀態組件(含有 state 的組件)的數量。
在一個Web應用當中,會涉及到顯示數據的增刪改查、服務器數據獲取、界面切換顯示內容等各類各樣類型的狀態數據改變。
那麼咱們爲何不把全部的狀態數據改變,用一種統一的方式描述;既然要控制有狀態組件的數量,那麼咱們爲何不乾脆直接把一個應用的全部狀態數據存儲在一個統一的地方集中管理?
這也就是 Redux 的理念。
Action 就是咱們上述的,用統一的形式,描述全部改變應用狀態數據的操做的方法。說白了,它其實就是一個帶有 type
屬性的 Javascript 對象:
{ type: 'INCREMENT', value: 1 }
例如在咱們的計數器當中,點擊按鈕數字增長1的操做能夠用上述格式內容的對象來描述表示。Redux 對 Action 的要求並非很是嚴格,你只須要保證它包含 type
屬性,其他的內容徹底由你本身決定。固然若是你但願你制定的 Action 更加符合規範,能夠遵循 Flux Standard Action 標準。
Reducer 則是 Redux 的設計理念當中最核心的方法,它接受當前的狀態數據以及觸發的 Action 做爲參數,根據內部 switch
結構的邏輯判斷,返回一個新的狀態數據:
(previousState, action) => newState
例如在咱們的計數器當中,能夠抽象編寫出這樣一個 Reducer 方法:
function counter(state = 0, action) { switch (action.type) { case 'INCREMENT': return state + action.value default: return state } }
咱們能夠看到 counter 函數接受 state 和 action 兩個參數,返回值則是通過一個 switch 結構判斷的新的 state 數據。這樣結構的函數也就是咱們在使用 Redux 時編寫的 Reducer 方法。
這是 Redux 理念當中最核心的一個部分,它決定了一個應用當中的狀態數據在不一樣的 Action 被觸發時具體會如何改變。
有關 Reducer 的更詳細解釋,能夠參閱我以前發表的 Redux 中的 reducer 究竟是什麼,以及它爲何叫 reducer 一文。
Store 則是 Redux 當中咱們用來存儲狀態數據的地方,它提供了3個主要的方法:
getState()
dispatch(action)
subscribe(listener)
而在使用 Redux 時,咱們能夠經過它提供的 createStore
方法,直接從 reducer 函數生成對應的 store :
const { createStore } = Redux; const store = createStore(counter);
咱們能夠直接在 React 項目當中使用 Redux:
// 把以前 React 的渲染函數命名爲 render const render = () => { /* 傳入 store.getState() 獲取 Redux 當中存儲的狀態數據 * 傳入 store.dispatch() 方法來執行對應 action 修改狀態數據 */ ReactDOM.render(<Counter number={store.getState()} onIncrement={() => store.dispatch({ type: 'INCREMENT', value: 1 })} />, document.getElementById('root')); } // 調用一次 render 方法進行初次渲染 render() // 使用 store.subscribe 方法訂閱 render 這樣每次 store.dispatch 方法觸發時就會自動調用 render store.subscribe(render);
固然,每次 Redux 當中的狀態數據改變時都強制執行 ReactDOM 的 render
方法並非最優選擇。事實上,社區已經開發出了一個名爲 react-redux 的庫專門來輔助咱們對 React 和 Redux 進行協同使用。
/* Provider 充當爲整個 React 應用傳入 Redux 當中 store 的容器組件 * connect 用來爲須要使用 store 的組件提供相應的狀態數據或 dispatch 方法 */ const { Provider, connect } = ReactRedux; /* 咱們經過 mapStateToProps 來將 Redux 當中的狀態數據映射到 React 相應的 props 當中 */ const mapStateToProps = state => ({ number: state }); class Counter extends Component { constructor(props) { super(props); } handleClick() { // 在這裏調用傳入組件的 dispatch 方法 this.props.dispatch({ type: 'INCREMENT', value: 1 }); } render() { return ( <div> <Title /> <Number number={this.props.number} /> <Button onClick={() => this.handleClick()} /> </div> ) } } /* 咱們須要經過 connect 方法來包裝一下 React 的 Counter 組件,使其獲取到 Redux 的 store 當中的方法和數據 */ Counter = connect(mapStateToProps)(Counter);
react-router 是 React 生態圈當中前端路由功能的實現。它最大的特色是能夠不用添加額外的路由配置文件,像使用全部其餘 React 組件的方式同樣,只須要引入幾個組件就能夠輕鬆爲你的 React 應用添加前端路由的功能。
咱們都知道,在傳統的網站當用,一個 URL 就對應着某個特定的頁面。當咱們在瀏覽器地址欄當中輸入這個 URL 的時候,瀏覽器就會從網站的服務器請求該頁面,獲取相應的內容。
而在Web應用的開發當中,咱們能夠經過操縱瀏覽器暴露給咱們的 history 接口以及異步服務器數據請求等方式,在前端就實現路由的切換,而不須要每次都讓服務器後端解析 URL 路由請求再返回內容。
使用前端路由能夠很大程度上提高Web應用,尤爲是單頁面應用的使用體驗。
react-router 的使用很是簡單,它目前已經發行到了 v4 版本,而以前的3個版本在網絡上也能找到很是多的應用。在這裏咱們僅拿最新的版本做爲示例。在 react-router@4 版本當中,專門爲Web端提供了高度封裝好的 react-router-dom 庫,這下咱們幾乎不須要任何的配置就能夠直接使用前端路由功能了:
/* 這裏引入的3個方法所有都是封裝好的 React 組件,使用方法和其餘 React 組件幾乎沒有任何差異 */ const { HashRouter, Route, Redirect } = ReactRouterDOM;
HashRouter
爲咱們的應用提供了 hash 形式(也就是帶#的路由)路由的功能支持。
{/* 在一般狀況下,咱們不須要爲 HashRouter 進行任何設置,直接引入使用便可。 */} <HashRouter> <App/> </HashRouter>
主要到在 react-router 提供的全部類型的 Router
組件當中,第一級的子組件有且只能有一個。所以咱們在使用的時候,一般在咱們應用組件的最外層包裹上一個 <div>
標籤:
<HashRouter> <div> ... </div> </HashRouter>
這裏爲了方便在線演示,因此咱們使用了 HashRouter
組件,在實際的開發當中,更常用的是 BrowserRouter
組件,它能夠爲咱們提供不帶 #
的前端路由支持,更加友好。
前端路由的主要功能就是經過判斷不一樣的瀏覽器地址顯示不一樣的內容,那麼具體某個路由地址要怎麼展現某個組件呢?
這就是 Route
組件爲咱們提供的功能:
<HashRouter> <div> <Route path='/:title?' component={App} /> </div> </HashRouter>
其中的 path 屬性用來設置匹配的目標路由地址,路由地址能夠是固定的字符串,例如 home/about/user 之類的,也能夠像咱們示例中同樣,以冒號開頭將路由的地址做爲參數,以後咱們能夠在組件當中獲取到對應的路由參數(以 ?
結尾則表示這一參數是可選的):
const Title = props => <h1>{props.title}</h1>; const App = ({ match }) => ( <Provider store={store}> <Counter title={match.params.title} /> </Provider> );
這裏的 match.params.title
也就是咱們路由參數當中對應的值了。
有了前端路由的內容,咱們還須要相應的前端路由的導航。前端路由導航的主要功能是實現瀏覽器地址欄 URL 的切換,並觸發Web應用展現對應的內容,而不是像原生的 HTML 超連接試圖向服務器發起對應 URL 的請求。
react-router 一樣爲咱們提供了現成的 Link
導航組件:
<HashRouter> <div> <ul> <li><Link to='/react'>react</Link></li> <li><Link to='/redux'>redux</Link></li> <li><Link to='/react-router'>react-router</Link></li> </ul> <Route path='/:title?' component={App} /> </div> </HashRouter>
直接在瀏覽器中使用
爲了方便咱們在線演示,更快地直接上手,在本文的示例當中,咱們均採用了直接在瀏覽器當中使用這些庫的方法。在 Codepen 示例當中,我已經事先引入了全部庫的 CDN 文件,這些庫都會向頁面暴露一個全局的對象,而後咱們能夠經過解構賦值的方式,獲取到對象當中咱們要使用的方法,例如:
const { Component } = React;
若是你是在本地進行練習,也能夠經過 <script>
標籤引入相應庫的 CDN 文件,以後經過相同的方式進行調用。
P.S. 若是你使用最新版的 Chrome 進行調試,這些 ES6 的新特性均可以直接在瀏覽器當中運行,無需編譯。
經過 npm 來使用
在正式的開發項目當中,咱們會使用 npm 來管理安裝各個庫,以後經過 import
的方式來調用:
首先安裝:
npm install react react-dom --save
而後調用:
import React from 'react';
本文全部代碼完整示例能夠在 GitChat React Examples 在線查看調試。包含: