以前寫過一篇文章,分享了我利用閒暇時間,使用React+Redux技術棧重構的百度某產品我的中心頁面。您能夠參考這裏,或者參考Github代碼倉庫地址。
這個工程實例中,我採用了廠內的工程構建工具-FIS3,並貫穿了react+redux基本思想。javascript
今天這篇文章給你們分享一個更加複雜,可是很是有趣的一個項目-
News Early單頁應用。html
最近我發現,React Redux生態圈項目活躍。可是做品質量「參差不齊」,不少很是熱門的項目不只沒有起到「佈道」做用,並且在必定程度上「誤導」了讀者。在這篇文章裏面我會有詳細說明。固然,我本身也是資歷淺顯,水平有限。但願大神可以給與斧正。前端
我把這個項目全部代碼託管在了我我的Github之中,感興趣的讀者能夠跟我探討。java
同時經過這個項目實例和這篇文章,一步一步說明了這個項目開發細節,而且包括了優化手段等內容。但願使你們對於React技術棧,包括:React UI框架 + Redux數據流框架+React Router路由管理+Webpack構建工具等,有一個更加清晰深入的理解。node
在國外上學和工做期間,能暢通無阻的訪問諸如:BBC,CNN,ESPN,Le Figaro等新聞媒體是一大便利,也是我我的閒暇時期一個喜愛之一。
甚至外出旅遊時,在酒店收看這些媒體衛視(尤爲CNN)居然也是放鬆休閒的一大方式。。。react
固然,國內環境對於這些境外媒體顯然不是太友好。
基於此,我設計開發了News Early項目。webpack
這個項目是一個包括:BBC,CNN,The NewYork Times等70多個國際知名媒體的即時頭條新聞聚合APP。git
News Early is a simple and easy-to-use Web APP that gathers the headlines currently published on a range of news sources and blogs (70 and counting so far).es6
整個項目我使用了包括但不限於如下技術棧和構建工具:
1)React UI框架from Facebook;
2)JSX模版;
3)Redux數據流設計;
4)Webpack構建工具;
5)Less預處理器;
......github
整個Web APP的部分使用體驗,我用如下GIF圖示來呈現:
(請耐心等待GIF圖加載)
由於我不是搞視覺設計的,也不是作頁面交互設計的(好吧,我只是一枚碼農)。因此爲了節省時間,總體APP的樣式上,包括界面顏色等,我參考了賣座網的實現。
下面,我爲你們介紹一下整個項目的設計構成和開發細節。
熟悉Redux數據流框架的同窗,應該對於store,dispatch,action,reducer,以及中間件等概念比較熟悉。這裏再也不進行講解。
這套架構中,最重要的就是數據流的設計。
首先,咱們先總體看一下在「切換頻道」這個交互發生時,整個項目的數據流向和數據結構的演示:
如圖所示:
整個項目業務代碼部分,我拆分紅9個UI組件,1個全局Store,一個actions定義文件。
10個組件包括:
我認爲,redux之因此學習曲線陡,很大程度上就在於數據流的貫通上。
「組件觸發(dispatch)各類action,單向數據流流向reducer,reducer是一個純函數(函數式編程思想),接收處理action,返回新的數據,組件進而更新」
這一套理論並不難理解。
可是落實在工程上,尤爲要結合react,那就很差作了。即便有人作出來,業務就算能夠跑得通,可是相比核心思想,倒是背道而馳。社區上我看過不少項目,在寫法上不分青紅皁白,只要能運行,胡亂設計一通,誤導初學者。
好比在整個項目中,存在多個stores這種常見的問題。
那麼,爲何不建議存在多個store呢?
答案能夠在官方FAQ中找到。內容較多,若是英文閱讀吃力,我大致翻譯一下:
熟悉Flux原始模型的讀者可能瞭解,Flux存在多個stores,每一個store都維護了不一樣層次的數據。這樣設計的問題在於,一個store須要等待另一個store的操做處理。咱們Redux實現了切分數據層次,避免了這種狀況的發生。
僅維持單個store不只可使用Redux DevTools,還能簡化數據的持久化及深加工、精簡訂閱的邏輯處理。
單一store這種方式,咱們不用考慮store模塊的導入、 Redux應用的封裝,後期支持服務器渲染也將變得更爲簡便。
若是上邊這段話過於抽象,難以理解的話,那就直接看個人代碼實現吧。
定義全局惟一的store:
const store = createStore(
combineReducers({
sideBarChange,
contents,
routing: routerReducer
}),
composeEnhancers(applyMiddleware(thunkMiddleware)),
);複製代碼
其中,我使用了redux-thunk做爲中間件,用於處理異步action。這樣,把異步過程放在action級別解決,對component沒有影響。
另外composeEnhancers是用於使用redux devtool的設置。
容器組件構建:
const mapStateToProps = (state) => {
return {
showLeftNav: state.sideBarChange.showLeftNav,
loading: state.contents.loading,
contents: state.contents.contents,
currentChanel: state.contents.currentChanel
}
}
var App = connect(mapStateToProps)(AppIndex);
render(
<Provider store={store}> <Router history={history}> <Route path="/" component={App}> <Route path="home" component={HomeView}/> </Route> </Router> </Provider>, document.getElementById('app') );複製代碼
其中,我使用了react-redux進行鏈接。AppIndex是整個項目惟一的容器組件。進行action的dispatch,以及向下傳遞props給UI組件(木偶組件)。
若是你還不理解容器組件和UI組件的區別,能夠去官方文檔學習。這兩個概念極其重要,它直接決定你是否能設計出有效且合理的組件架構。
另外,你會發現我使用了react-router進行路由管理。其實整個項目沒有必要使用單頁路由。這個路由管理的引入,說實話,比較雞肋。但並不會對項目產生任何影響。我引入他的緣由主要有兩點。
actions固然是必不可少的,我這裏選取最重要的「fetchContents」這個action creator來討論一下。
初次進入頁面時,以及左側邊欄點擊選擇新聞頻道時,都要去拉取數據。好比,APP第一次渲染,默認加載「BBC News」新聞頻道,頁面主體組件在掛載完成後:
componentDidMount() {
//獲取內容
this.props.fetchContents('bbc-news');
}複製代碼
向上調用fetchContents方法,並逐級上傳到容器組件。由容器組件進行dispatch:
fetchContents={(source)=>{this.props.dispatch(action.fetchContents(source))}}複製代碼
source表示拉取的新聞頻道。此處固然是'bbc-news'。
在actions.js文件中,進行異步action的處理並拉取數據。這裏,我使用了最新的fetch API來代替古老的XHR,並利用fetch的promise的理念,封裝了一層_get方法,用於AJAX異步請求:
const sendByGet = ({url}, dispatch) => {
let finalUrl = url + '&apiKey=1a445a0861e'
return fetch(finalUrl)
.then(res => {
if (res.status >= 200 && res.status < 300) {
return res.json();
}
return Promise.reject(new Error(res.status));
})
}複製代碼
對應的action操做:
export const fetchContents = (source) => {
const url = '...';
return (dispatch) => {
dispatch({type: FETCH_CONTENTS_START});
if (sessionStorage.getItem(source)) {
console.log('get from sessionStorage');
let articles = JSON.parse(sessionStorage.getItem(source));
dispatch({type: FETCH_CONTENTS_SUCCESS, contents: Object.assign(articles, {currentChanel: source.toUpperCase()})})
}
else {
sendByGet({url}, dispatch)
.then((json) => {
if (json.status === 'ok') {
sessionStorage.setItem(source, JSON.stringify(json.articles));
return dispatch({type: FETCH_CONTENTS_SUCCESS, contents: Object.assign(json.articles, {currentChanel: source.toUpperCase()})})
}
return Promise.reject(new Error('FETCH_CONTENTS_SUCCESS failure'));
})
.catch((error) => {
return Promise.reject(error)
})
}
}
}複製代碼
咱們知道,這些異步請求的訪問速度是很慢的。所以,我採用了幾種方法來進行優化。
原諒我使用了這麼粉嫩少女的加載圖。。。
第二個方法實際上是一個trick,個人全局圖片在初始狀態時opacity設置爲0,在onload事件觸發時設置一個fadeIn的效果:
<img ref="image" src={imgSrc} onLoad=
{this.handleImageLoaded.bind(this)}/>
handleImageLoaded() {
this.refs['image'].style.opacity = 1;
}複製代碼
這樣的一個小技巧最初來自Facebook對用戶體驗的研究。若是您對此有興趣,能夠在個人另一篇文章中找到相關內容。
具體實現方式就是在發送請求時判斷sessionStorage是否已經存在此新聞媒體(好比bbc)的數據。若是存在就使用緩存。不然就去進行AJAX請求,請求成功的回調函數裏進行緩存的種植。
代碼部分以下:
if (sessionStorage.getItem(source)) {
console.log('get from sessionStorage');
let articles = JSON.parse(sessionStorage.getItem(source));
dispatch({type: FETCH_CONTENTS_SUCCESS, contents: Object.assign(articles, {currentChanel: source.toUpperCase()})})
}
else {
sendByGet({url}, dispatch)
.then((json) => {
if (json.status === 'ok') {
sessionStorage.setItem(source, JSON.stringify(json.articles));
return dispatch({type: FETCH_CONTENTS_SUCCESS, contents: Object.assign(json.articles, {currentChanel: source.toUpperCase()})})
}
return Promise.reject(new Error('FETCH_CONTENTS_SUCCESS failure'));
})
.catch((error) => {
return Promise.reject(error)
})
}複製代碼
固然,有種植緩存,就要有清除緩存。這個按鈕我設置在裏navBar組件的最右側:
const CLEAR_SESSIONSTORAGE = 'CLEAR_SESSIONSTORAGE';
export const refresh = () => {
sessionStorage.clear();
return dispatch => dispatch({type: CLEAR_SESSIONSTORAGE});
}複製代碼
爲了使用先進的構建工具的需求,我使用了node最新版本。可是由於工做業務的須要,又要同時保留低版本node環境。爲此,我使用了:n這個利器進行node版本管理。
同時,我使用了webPack一系列強大開發功能和構建功能。包括但不限於:
...等等,可是我可不是webpack專家。在狼廠,固然使用更多的是FIS構建工具。關於FIS和webpack的比較,個人網紅同事@顏大神有過探索。
這篇文章涉及到了較爲前沿的前端開發技術棧。包括了React框架,Redux數據流框架以及函數式編程、異步action中間件,fetch異步請求,webpack配置等等。也無形中涉及到了一些成熟產品的設計理念思路。固然這個項目還遠沒有成熟。在代碼倉庫中,我會不間斷進行更新。
但願本文對你們在各個維度都有所啓發。也懇請業界大牛不吝賜教,進行斧正。
最後想跟你們談一下對於框架和前端學習的一些感覺。我記得我剛開始工做,在初次接觸前端時,是使用ionic,即Angular框架和phoneGap開發hybrid移動APP。當時我是徹底懵b的,只是感受比利時同事用的超high,6到飛起。每次他用濃重的比利時口音法語給我講解時,我聽的雲裏霧裏,不知因此。
如今想一想當時那麼菜的緣由仍是在於本身的JS基礎不夠牢固。當你面對迅速更新換代的前端技術踟躕茫然時,惟一的捷徑就是從基礎抓起,從JS原型原型鏈,this,執行環境上下文等等看起。
以爲前端知識有欠缺的讀者們,歡迎follow我。最近我會帶你們「重讀」JS經典書籍,以code demo的形式提煉知識點,並會同步到博客和我的Github上。
Happying code!