摘要:架構(gòu)為了和我們線上代碼保持一致,我采用了來做工程化組織。這個刪除過程并不是一個單純組件內(nèi)行為,因?yàn)檫@個會使得已選數(shù)據(jù)發(fā)生變化,進(jìn)而影響組件。
在前幾天的一篇文章中總結(jié)部分提到了學(xué)習(xí)過程中基礎(chǔ)的重要性。當(dāng)然,并不是不支持大家學(xué)習(xí)新的框架,這篇文章就分享一下react+redux工程實(shí)例。
一直在學(xué)習(xí)研究react.js,前前后后做了幾次分享。并在我參與的公司產(chǎn)品私信項(xiàng)目也使用了這套技術(shù)棧 。學(xué)習(xí)過程期間,感覺react+redux初級DEMO不多,社區(qū)上多是用爛了的todolist教程,未免乏味。
這篇文章主要實(shí)現(xiàn)一個簡單的例子,難度不大,但是貫穿了react+redux基本思想。
他將會是一個連續(xù)教程,這只是第一篇,不涉及redux中間件,redux處理異步等內(nèi)容,也不涉及react性能優(yōu)化,不可變數(shù)據(jù)immutable.js的內(nèi)容。但這些不涉及到的內(nèi)容,都會隨著這個demo的復(fù)雜度一步一步提升,在后續(xù)章節(jié)有分析和使用。
整個項(xiàng)目的代碼,你可以在GitHub中找到。
簡介百度經(jīng)驗(yàn)個人中心(WAP端)是經(jīng)驗(yàn)流量較多的頁面群,其中的個人定制頁面是重要的頁面之一,請用手機(jī)點(diǎn)擊這里查看效果,頁面的截圖如下:
功能一目了然。主要分為兩大塊:
1)可以在『選擇分類』區(qū)塊中選擇自己喜歡的經(jīng)驗(yàn)分類條目來訂閱。在該區(qū)塊中,我們可以點(diǎn)擊『換一換』按鈕來切換分類條目。
2)已選結(jié)果會再『已選分類』區(qū)塊里面展示。在『已選分類』區(qū)塊里,我們可以點(diǎn)擊相關(guān)經(jīng)驗(yàn)分類條目來取消訂閱。
在現(xiàn)在線上版本中,我們采用了傳統(tǒng)的操作DOM方式(zepto類庫)來實(shí)現(xiàn)這一系列交互。使用react,又是一種全新的思想。孰優(yōu)孰劣,可以在結(jié)尾處大家自己總結(jié)。
好了,廢話不多說,我們馬上進(jìn)入正題。
為了和我們線上代碼保持一致,我采用了fisp來做工程化組織。現(xiàn)在社區(qū)上大多都是采用webpack,其實(shí)這些工具用哪個都一樣,解決的問題也都類似,這里不再展開。即使你不懂FIS,也不妨礙繼續(xù)閱讀。
app/ app文件夾下action.es定義了頁面交互中dispatch的所有actions;
app.jsx是頁面入口腳本;
component.jsx定義了頁面的組件;
reducer.jsx接收action,該文件定義了所有用到的reducer。
這兩個文件夾是我們要用的框架源碼,比如react.js+redux.js等等;
該項(xiàng)目用到的是react15.3.1版本未壓縮版,這個版本比較穩(wěn)定。
采用未壓縮版的原因是想使用react addons 的perf,因?yàn)樵诤罄m(xù)章節(jié)中,會有性能優(yōu)化部分的分析;
我們知道,react和redux其實(shí)獨(dú)立存在,我們使用流行的react-redux.js庫來實(shí)現(xiàn)兩者的連接。
其他還有 fis-conf.js文件:這是用來做fis配置的,比如打包規(guī)則,發(fā)布規(guī)則,編譯配置等;
同時,我們配置了babel來編譯es6和jsx等,還配置了autoprefixer;
server.conf是fis的附屬文件,用來做數(shù)據(jù)mock;
build.sh和BCLOUD是上線腳本相關(guān),這里我們并不上線,只是學(xué)習(xí)react的用法。
我們部門后端是PHP,采用Smarty模板。這個頁面會在請求時同步給出一些數(shù)據(jù),比如用戶信息等,輸出在模版里。我們的同步數(shù)據(jù)如截圖(由于機(jī)密性原因,數(shù)據(jù)進(jìn)行了精簡、重命名和重新設(shè)計(jì)):
我們關(guān)心selectList和likedList:
1)likedList給出當(dāng)前用戶已經(jīng)選則的訂閱分類條目;
2)selectList給出所有可選的分類條目,一共從1-127,127個可選條目,數(shù)據(jù)格式如上。
說了這么多,終于可以進(jìn)入具體代碼層面了。如果上邊的內(nèi)容你似懂非懂,也沒有關(guān)系。因?yàn)樯婕傲艘恍╉?xiàng)目組織上的內(nèi)容。下邊的內(nèi)容,就是具體的代碼分析。
數(shù)據(jù)設(shè)計(jì)react+redux開發(fā)前端的思想是頁面由數(shù)據(jù)驅(qū)動。
上邊已經(jīng)分析到我們的頁面主要由兩種數(shù)據(jù):
1)一個是selectList,我們姑且叫做選擇池?cái)?shù)據(jù);
2)另一個是likedList,我們叫做已選數(shù)據(jù)。
這兩處數(shù)據(jù)初始由reducer拿到,設(shè)置為容器組件的初始狀態(tài),并由容器組件傳遞給相應(yīng)展示組件。
組件設(shè)計(jì)如下截圖:
按照react-redux思想,組件分為:
1)容器組件,負(fù)責(zé)接收數(shù)據(jù);
2)展示(木偶)組件負(fù)責(zé)向上接收數(shù)據(jù),根據(jù)數(shù)據(jù)展現(xiàn)組件UI。
其實(shí)很明顯,我們主要就是兩個展示組件,叫做:
1)SelectedBlock,負(fù)責(zé)展示用戶已選已訂閱內(nèi)容;
2)SelectListBlock,展示頁面選擇池可供選擇的內(nèi)容。
他們一起被套在叫做DemoApp的父組件里面。
有了以上劃分,我們有了:
1)SelectedBlock組件需要關(guān)心已選數(shù)據(jù)likedList;
2)SelectListBlock則選擇池?cái)?shù)據(jù)和已選數(shù)據(jù)都需要關(guān)心。
你可能會問『SelectListBlock關(guān)心選擇池?cái)?shù)據(jù)不就夠了嗎?』
但是,產(chǎn)品經(jīng)理要求在選擇池里,當(dāng)渲染用戶已選條目時,需要樣式置灰,并且在點(diǎn)擊已選分類條目時不在觸發(fā)action。所以選擇池SelectListBlock組件也要依賴已選數(shù)據(jù),進(jìn)而做出相應(yīng)的變化。
這兩項(xiàng)數(shù)據(jù)由react-redux派分給容器組件,并由容器組件按需分給展示組件;
有了以上基礎(chǔ),我們看最外層的DemoApp組件全部代碼:
class DemoApp extends React.Component { constructor(props) { super(props); } render() { const { dispatch } = this.props; return () } }{dispatch(action.deleteLikeItem(item))}}> {dispatch(action.addLikeItem(index, item))}}>
我們看他的render()部分,很明顯,他平行嵌套了:
1)SelectedBlock組件,并把likedList數(shù)據(jù)作為屬性向其傳遞;
2)同時,包含了SelectListBlock,并把selectList,likedList數(shù)據(jù)作為屬性向其傳遞。
那么SelectedBlock設(shè)計(jì)如下:
class SelectedBlock extends React.Component { constructor(props) { super(props); } deleteItem(event, index) { this.props.onDeleteLikeItem(index); } render() { let likedList = this.props.likedList; let likedListArray = []; let likedListKey = Object.keys(likedList); likedListKey.forEach(function(index){ likedListArray.push(likedList[index]); }) return () } }已選分類({likedListArray.length})
{ likedListArray.length > 0 ? likedListArray.map((item, index) => { return (
- {item} {this.deleteItem(event, likedListKey[index])}}>
) }) :- 還沒有任何訂閱
}
請從下方選擇訂閱
我們把likedList轉(zhuǎn)換成likedListArray數(shù)組,在render()里面,直接使用map循環(huán)輸出;
當(dāng)用戶刪除某一條目時,觸發(fā)deleteItem(event, index)方法,該方法向上傳遞,并在DemoApp父組件中,觸發(fā)相應(yīng)action。這個刪除過程并不是一個單純組件內(nèi)行為,因?yàn)檫@個action會使得已選數(shù)據(jù)發(fā)生變化,進(jìn)而影響SelectListBlock組件。所以一系列邏輯需要在reducer中處理,處理完后重置已選數(shù)據(jù),進(jìn)而頁面更新。
SelectListBlock組件也很好理解:
class SelectListBlock extends React.Component { constructor(props) { super(props); this.state = { flag: 0 } } onChangeGroup(event) { event.stopPropagation(); let flagNow = this.state.flag; if (flagNow == 117) { this.setState({ flag: 0 }); } else { this.setState({ flag: flagNow + 9 }); } } onSelectItem(index, item) { var likedList = this.props.likedList; var likedListKey = Object.keys(likedList); if ( likedListKey.indexOf(index.toString()) >= 0 ) { return; } this.props.onAddLikeItem(index, item); } render() { let selectListArray = []; for (var i in this.props.selectList) { selectListArray.push(this.props.selectList[i]) } let likedList = this.props.likedList; let likedListKey = Object.keys(likedList); return () } }{this.onChangeGroup(event)}}>換一換 選擇分類
{ selectListArray.slice(this.state.flag, this.state.flag+9).map((item, index)=>{ return (
- {this.onSelectItem((index + this.state.flag), item)}} key={index + this.state.flag}> {(likedListKey.indexOf((index + this.state.flag).toString()) >= 0 ? {item} : {item} )}
) }) }
我們首先把后端打過來的127條可供選擇的數(shù)據(jù)轉(zhuǎn)換為selectListArray數(shù)組。這127個分類內(nèi)容都對應(yīng)一個index(1-127)。
在render()時候,因?yàn)轫撁嬉淮沃徽故?項(xiàng)待選項(xiàng),所以我們把selectListArray用slice方法按順序切割出來9項(xiàng)輸出。
點(diǎn)擊『換一換』按鈕時,觸發(fā)onChangeGroup()方法,這個方法是個組件內(nèi)方法,他負(fù)責(zé)將slice參數(shù)+9,當(dāng)?shù)?27(一共127項(xiàng)分類)時,還原回0。
我們知道,點(diǎn)擊『換一換』觸發(fā)的onChangeGroup方法改變flag時,因?yàn)閒lag為該組件內(nèi)部state,他的變化,將會引起該組件重新render(),所以數(shù)據(jù)池就會毫無壓力的切換了。
同時,我們給數(shù)據(jù)池里每一項(xiàng)分類都綁定onSelectItem方法,該方法會向上傳遞給父組件,由父組件發(fā)出相應(yīng)action。因?yàn)檫@個動作將會改變已選數(shù)據(jù),從而影響平行的SelectedBlock組件。因此需要在reducer中處理。
action設(shè)計(jì)有了以上組件的設(shè)計(jì),很明顯我們需要定義兩個action:
1)第一個是添加某一條目到已選分類
export const ADD_LIKE_ITEM = "ADD_LIKE_ITEM";
對應(yīng)action creator:
export function addLikeItem (index, item) { return { type: ADD_LIKE_ITEM, obj: { index: index, item: item } } }
返回action對象,包括type命名為ADD_LIKE_ITEM和負(fù)載數(shù)據(jù):條目名item及其index。
2)另一個是在已選分類刪除某一條目:
export const DELETE_LIKE_ITEM = "DELETE_LIKE_ITEM";
對應(yīng)action creator:
export function deleteLikeItem (index) { return { type: DELETE_LIKE_ITEM, index } }
返回action對象,包括type和負(fù)載數(shù)據(jù)。
到此為止,action腳本只需要定義action,不需要進(jìn)一步處理,對所有action的處理都會由reducer接受。
再次強(qiáng)調(diào)reducer是一個純函數(shù),他接受兩個參數(shù),一個是state,一個是action;并對相應(yīng)的action,返回一個新的state,從而促使頁面里訂閱相關(guān)state的組件再次render();
我們把同步模板數(shù)據(jù)initialLikeBlockState設(shè)為初始state:
var initialLikeBlockState = F.context("likedList"); function likeBlockReducer (state = initialLikeBlockState, action) { switch (action.type) { case actionType.ADD_LIKE_ITEM: { var addIndex = action.obj.index; var newLikedList = Object.assign({}, state, { [addIndex]: action.obj.item }) return newLikedList; } case actionType.DELETE_LIKE_ITEM: { var newLikedList = {}; for (var key in state) { var val = state[key]; newLikedList[key] = val; } var index = action.index; delete newLikedList[index]; return newLikedList; } default: { return state; } } }
當(dāng)匹配ADD_LIKE_ITEM action時,我們把當(dāng)前的state和action帶來的數(shù)據(jù)(item,index)進(jìn)行merge,從而return 一個新的已選數(shù)據(jù)狀態(tài),即添加了新分類item的state;
當(dāng)匹配DELETE_LIKE_ITEM action時,我們把a(bǔ)ction負(fù)載帶來要刪除item的index刪除掉。返回刪除該條目之后的新state。
截至目前,我們介紹了基本設(shè)計(jì)和開發(fā)思路。教程里面已經(jīng)基本包含了全部代碼。
對比線上已有代碼1)和線上的zepto實(shí)現(xiàn)對比完全是兩種思路,經(jīng)過比較,用react設(shè)計(jì)的代碼代碼量上有明顯的優(yōu)勢。
2)開發(fā)思路上,是個蘿卜青菜各有所愛的問題。但是對于寫慣了$()的我來說,這種全新的開發(fā)方式還是帶來了很大的驚喜。
3)線上實(shí)現(xiàn)這一套邏輯,可能對于一個簡單的UI交互,我們都需要選取很多dom元素,進(jìn)行處理。整體上看,比較復(fù)雜且凌亂,不是很容易進(jìn)行維護(hù)。
當(dāng)然,這只是第一步。后邊還有更多的路要走。比如:
1)我們在選擇或刪除一個條目時,如何給后端發(fā)異步請求并沒有涉及。因此,redux異步流程并沒有展現(xiàn)。后續(xù)章節(jié)會進(jìn)一步講解。
2)我們的數(shù)據(jù)都是后端模板通過同步的方式傳遞過來的,數(shù)據(jù)量也不大,結(jié)構(gòu)也不復(fù)雜,因此這一章為了簡單并未使用immutable.js。當(dāng)然,后續(xù)章節(jié)會進(jìn)一步講解。
3)這里我并沒有介紹使用redux dev tool,這真的是一個很漂亮的利器。
尤其在數(shù)據(jù)復(fù)雜時候,對于調(diào)試能幫上很大作用。后面我會多帶帶介紹一下關(guān)于這個工具的使用。
4)最后,這么簡單的交互還并不會涉及頁面性能的問題。在后續(xù)章節(jié),我會構(gòu)造出極端CASE進(jìn)行一些邊緣測試,并使用一些方法結(jié)合chrome dev tool進(jìn)行性能優(yōu)化,請進(jìn)一步關(guān)注。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/81289.html
摘要:前端每周清單半年盤點(diǎn)之與篇前端每周清單專注前端領(lǐng)域內(nèi)容,以對外文資料的搜集為主,幫助開發(fā)者了解一周前端熱點(diǎn)分為新聞熱點(diǎn)開發(fā)教程工程實(shí)踐深度閱讀開源項(xiàng)目巔峰人生等欄目。與求同存異近日,宣布將的構(gòu)建工具由遷移到,引發(fā)了很多開發(fā)者的討論。 前端每周清單半年盤點(diǎn)之 React 與 ReactNative 篇 前端每周清單專注前端領(lǐng)域內(nèi)容,以對外文資料的搜集為主,幫助開發(fā)者了解一周前端熱點(diǎn);分為...
摘要:作者滬江前端開發(fā)工程師本文原創(chuàng)翻譯,有不當(dāng)?shù)牡胤綒g迎指出。管理數(shù)據(jù),而提供服務(wù)器上的數(shù)據(jù),因此應(yīng)用于處理網(wǎng)絡(luò)請求。結(jié)論使用建立的應(yīng)用都是模塊化的會成為其中一個模塊,庫是另一個模塊。原文原創(chuàng)新書移動前端高效開發(fā)實(shí)戰(zhàn)已在亞馬遜京東當(dāng)當(dāng)開售。 作者:Oral (滬江Web前端開發(fā)工程師)本文原創(chuàng)翻譯,有不當(dāng)?shù)牡胤綒g迎指出。轉(zhuǎn)載請指明出處。 當(dāng)你問起有關(guān)AJAX與React時,老司機(jī)們首先就會...
摘要:聲明有助于保持我們的同步與底層狀態(tài)的聲明性質(zhì)。值得注意的是,這些挑戰(zhàn)并非特定于。這導(dǎo)致或上出現(xiàn)不一致或意外錯誤。崩潰監(jiān)控我們使用在和上進(jìn)行崩潰報(bào)告。橋接有一個橋接,用于在本機(jī)和之間進(jìn)行通信。 showImg(https://segmentfault.com/img/bVbd0FA?w=740&h=433);在Android,iOS,Web和跨平臺框架的橫向?qū)Ρ戎校琑eact Nativ...
摘要:聲明有助于保持我們的同步與底層狀態(tài)的聲明性質(zhì)。值得注意的是,這些挑戰(zhàn)并非特定于。這導(dǎo)致或上出現(xiàn)不一致或意外錯誤。崩潰監(jiān)控我們使用在和上進(jìn)行崩潰報(bào)告。橋接有一個橋接,用于在本機(jī)和之間進(jìn)行通信。 showImg(https://segmentfault.com/img/bVbd0FA?w=740&h=433);在Android,iOS,Web和跨平臺框架的橫向?qū)Ρ戎校琑eact Nativ...
閱讀 1855·2021-11-25 09:43
閱讀 1498·2021-09-02 15:21
閱讀 3462·2019-08-30 15:52
閱讀 1507·2019-08-30 12:48
閱讀 1304·2019-08-30 10:57
閱讀 2934·2019-08-26 17:41
閱讀 684·2019-08-26 11:59
閱讀 1374·2019-08-26 10:41