您好,登錄后才能下訂單哦!
Redux是一種JavaScript的狀態(tài)管理容器,是一個(gè)獨(dú)立的狀態(tài)管理庫(kù),可配合其它框架使用,比如React。引入Redux主要為了使JavaScript中數(shù)據(jù)管理的方便,易追蹤,避免在大型的JavaScript應(yīng)用中數(shù)據(jù)狀態(tài)的使用混亂情況。Redux 試圖讓 state 的變化變得可預(yù)測(cè),為此做了一些行為限制約定,這些限制條件反映在 Redux 的三大原則中。
本文會(huì)介紹Redux的幾個(gè)基本概念和堅(jiān)持的三大原則,以及完整的回路一下Redux中的數(shù)據(jù)流。在了解以上這些概念之后,用自己的代碼來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)版的Redux,并且用自己實(shí)現(xiàn)的Redux結(jié)合React框架,做一個(gè)簡(jiǎn)單的TodoList應(yīng)用示例。希望本文對(duì)于初識(shí)Redux的同學(xué)有一個(gè)清晰,全面的認(rèn)識(shí)。
Redux就是用來(lái)管理狀態(tài)數(shù)據(jù),所以第一個(gè)概念就是狀態(tài)數(shù)據(jù),state就是存放數(shù)據(jù)的地方,根據(jù)應(yīng)用需要,一般定義成一個(gè)對(duì)象,比如:
{ ????todos:?[], ????showType:?'ALL', ????lastUpdate:?'2019-10-30?11:56:11' }
?
web應(yīng)用,所有的數(shù)據(jù)狀態(tài)變更,都是由一個(gè)行為觸發(fā)的,比如用戶點(diǎn)擊,網(wǎng)絡(luò)加載完成,或者定時(shí)事件。在簡(jiǎn)單應(yīng)用里面,我們一般都是在行為觸發(fā)的時(shí)候,直接修改對(duì)應(yīng)的數(shù)據(jù)狀態(tài),但是在大型復(fù)雜的應(yīng)用里面,修改同一數(shù)據(jù)的地方可能很多,每個(gè)地方直接修改,會(huì)造成數(shù)據(jù)狀態(tài)不可維護(hù)。
Redux引入了action的概念,每個(gè)要改變數(shù)據(jù)狀態(tài)的行為,都定義成一個(gè)action對(duì)象,用一個(gè)type來(lái)標(biāo)志是什么行為,行為附帶的數(shù)據(jù),也都直接放在action對(duì)象,比如一個(gè)用戶輸入的行為:
{ ????type:?'INPUT_TEXT', ????text:?'今天下午6點(diǎn)活動(dòng)碰頭會(huì)議' }
然后通過(guò)dispatch觸發(fā)這個(gè)action,dispatch(action)
狀態(tài),action的概念了解了,當(dāng)action觸發(fā)的時(shí)候,肯定要修改state數(shù)據(jù),在講解action的時(shí)候有說(shuō)過(guò),不能直接修改state,我們需要定義一個(gè)reducer來(lái)修改數(shù)據(jù),這個(gè)reducer就是一個(gè)行為響應(yīng)函數(shù),他接收當(dāng)前state,和對(duì)應(yīng)的action對(duì)象,根據(jù)不同的action,做相應(yīng)的邏輯判斷和數(shù)據(jù)處理,然后返回一個(gè)新的state。
注意,一定是返回一個(gè)新的state,不能直接修改參數(shù)傳入的原state,這是redux的原則之一,后面會(huì)講到。
function?reducer?(?state?=?[],?action?)?{ ????switch?(?action.type?)?{ ????????case?'INPUT_TEXT': ????????????return?[...state,?{text:?action.text,?id:?Math.random()?}] ????????default: ????????????return?state; ????} }
?
數(shù)據(jù)的更新已經(jīng)在reducer中完成了,在一些響應(yīng)式的web應(yīng)用中,我們往往需要監(jiān)聽(tīng)數(shù)據(jù)狀態(tài)的變化,這個(gè)時(shí)候就可以用subscribe了
redux內(nèi)部保存一個(gè)監(jiān)聽(tīng)隊(duì)列,listeners,可以調(diào)用subscribe來(lái)往listeners里面增加新的監(jiān)聽(tīng)函數(shù),每次reducer修改完state之后,會(huì)逐個(gè)執(zhí)行監(jiān)聽(tīng)函數(shù),而監(jiān)聽(tīng)函數(shù)可以獲取已經(jīng)更新過(guò)的state數(shù)據(jù)了
listeners?=?[]; subscrible(?listener?)?{ ????listeners.push(?listener?); ????return?function?()?{ ????????let?index?=?listeners.index(?listener?); ????????listeners.splice(?index,?1?); ????} } dispatch(?action?)?//?觸發(fā)?action reducer(state,?action) listeners.map(?(?listener?)?=>?{ ????listener() }?)
?
整個(gè)應(yīng)用的數(shù)據(jù)都在state,并且只有這一個(gè)state,這么做的目的是方便管理,整個(gè)應(yīng)用的數(shù)據(jù)就這一份,調(diào)試方便,開(kāi)發(fā)也方便,可以在開(kāi)發(fā)的時(shí)候用本地的數(shù)據(jù)。而且開(kāi)發(fā)同構(gòu)應(yīng)用也很方便,比如服務(wù)端渲染,把服務(wù)端的數(shù)據(jù)全部放在state,作為web端初始化時(shí)候的數(shù)據(jù)
state的數(shù)據(jù)對(duì)外只讀,不能直接修改state,唯一可以修改的方式是觸發(fā)action,然后通過(guò)reducer來(lái)處理。
因?yàn)樗械男薷亩急患谢幚?,且?yán)格按照一個(gè)接一個(gè)的順序執(zhí)行,因此不用擔(dān)心競(jìng)態(tài)條件(race?condition)的出現(xiàn)。?Action?就是普通對(duì)象而已,因此它們可以被日志打印、序列化、儲(chǔ)存、后期調(diào)試或測(cè)試時(shí)回放出來(lái)。
先說(shuō)明下什么是純函數(shù),純函數(shù)指的是函數(shù)內(nèi)部不修改傳入的參數(shù),無(wú)副作用,在傳參一定的情況下,返回的結(jié)果也是一定的。Redux中的Reducer需要設(shè)計(jì)成存函數(shù),不能直接操作傳入的state,需要把改變的數(shù)據(jù)以一個(gè)新的state方式返回。
其實(shí)上面講Redux基本概念的時(shí)候已經(jīng)大概的說(shuō)了下數(shù)據(jù)流向方式了,就是: view->action->reducer->state->view,用文字來(lái)表述就是,首先由于頁(yè)面上的某些事件會(huì)觸發(fā)action,通過(guò)dispatch(action)來(lái)實(shí)現(xiàn),然后通過(guò)reducer處理,reducer(state, action)返回一個(gè)新的state,完成state的更新,當(dāng)然對(duì)于響應(yīng)式的應(yīng)用,會(huì)觸發(fā)listener(),在listener里面獲取最新的state狀態(tài),完成對(duì)應(yīng)視圖(view)的更新。這就是整個(gè)redux中的數(shù)據(jù)流描述,如下圖所示:
在對(duì)Redux的基本概念和幾大原則熟悉了之后,可以實(shí)現(xiàn)一個(gè)自己的Redux了,當(dāng)然我們一般都直接用官方的npm包,這里自己實(shí)現(xiàn)的比較簡(jiǎn)單,沒(méi)有做什么入?yún)Ⅱ?yàn)證,異常處理之類的,主要是加深下對(duì)Redux的理解。下面直接貼代碼了,對(duì)應(yīng)的概念都有注釋。
//?redux.js //?創(chuàng)建state的函數(shù) //?傳入reducer?和初始化的state function?createStore(?reducer,?initState?)?{ ????let?ref?=?{}; ????let?listeners?=?[]; ????let?currentState?=?initState; ????//?dispath函數(shù),用來(lái)觸發(fā)action ????function?dispatch?(?action?)?{ ????????//?觸發(fā)的action,通過(guò)reducer處理 ????????currentState?=?reducer(?currentState,?action?) ????????//?處理完成后,通知listeners ????????for?(?let?i?in?listeners?)?{ ????????????let?listener?=?listener[?i?]; ????????????listener(); ????????} ????????return?action; ????} ????//?返回當(dāng)前的state ????function?getState?()?{ ????????return?currentState; ????} ????//?訂閱state變化,?傳入listener,返回取消訂閱的function ????function?subscribe?(?listener?)?{ ????????listeners.push(?listener?); ????????return?function?()?{ ????????????let?index?=?listeners.indexOf(?listener?); ????????????if?(?index?>?-1?)?{ ????????????????listeners.splice(?index,?1?); ????????????} ????????} ????} ???? ????ref?=?{ ????????dispatch:?dispatch, ????????subscribe:?subscribe, ????????getState:?getState ????}; ????return?ref; } function?combineReducers(?reducers?)?{ ????return?function?(?state,?action?)?{ ????????let?finalState?=?{}; ????????let?hasChanged?=?false; ????????for?(?let?key?in?reducers?)?{ ????????????let?reducer?=?reducers[?key?] ????????????if?(?typeof?reducer?===?'function'?)?{ ????????????????let?keyState?=?reducer(?state?&&?state[?key?],?action?); ????????????????hasChanged?=?hasChanged?||?keyState?!==?state[?key?]; ????????????????finalState[?key?]?=?keyState; ????????????} ????????} ????????return?hasChanged???finalState?:?state; ????} } export?{?createStore,?combineReducers?}
是不是覺(jué)得怎么才這么點(diǎn)代碼,就是這么點(diǎn)代碼,而且還包含了一個(gè)combineReducers輔助函數(shù),下面再貼一點(diǎn)使用示例代碼
//?reducer函數(shù),用于處理action function?reducer(?state?=?[],?action?)?{ ????switch(?action.type?)?{ ????????case?'INPUT_TEXT': ????????????return?[?...state,?{?text:?action.text,?key:?Math.random(),?isDo:?false?}]; ????????case?'TOGGLE_TODO': ????????????return?state.map(?(?item?)?=>?{ ????????????????if?(?item.key?===?action.id?)?{ ????????????????????return?{...item,?isDo:?!item.isDo?}; ????????????????} ????????????}?); ????????default: ????????????return?state; ????} } let?store?=?createStore(?reducer?); //?在用戶輸入一條Todo時(shí)候 console.log(store.getState()); store.dispatch(?{?type:?'INPUT_TEXT',?text:?'這里是一條待辦事項(xiàng)'?}?); console.log(store.getState()); //在用戶點(diǎn)擊一條Todo?Item的時(shí)候,切換完成狀態(tài) console.log(store.getState()); store.dispatch(?{?type:?'TOGGLE_TODO',?id:?item.key?}?) console.log(store.getState());
?
下面,利用Redux結(jié)合React開(kāi)發(fā)一個(gè)簡(jiǎn)單的Todo工具,頁(yè)面主要功能點(diǎn)
1、可以添加Todo事項(xiàng)
2、點(diǎn)擊事項(xiàng)會(huì)切換事項(xiàng)的完成狀態(tài)
3、可以切換展示全部/已完成/待完成事項(xiàng)
這個(gè)實(shí)例是基于react,react-redux完成的,項(xiàng)目搭建用的是create-react-app,利用react-redux提供的接口,將redux中的state和action集成到組件中,需要讀者熟悉create-react-app的使用,以及react-redux的主要接口功能,以下貼出主要代碼,感興趣的同學(xué)可以自己搭建實(shí)現(xiàn)
首先定義好state數(shù)據(jù)結(jié)構(gòu)和action以及對(duì)應(yīng)的reducer
state包含兩部分,一是todos,待辦事項(xiàng)列表,二是showType,展示類型
action包含這么三種,一是添加新的Todo,二是切換事項(xiàng)完成狀態(tài),三是切換展示類型,分別定義好
actions.js
//?actions.js let?nextTodoId?=?0 export?const?addTodo?=?text?=>?{ ????return?{ ????????type:?'ADD_TODO', ????????id:?nextTodoId++, ????????text ????}; }; export?const?setShowType?=?showType?=>?{ ????return?{ ????????type:?"SET_SHOW_TYPE", ????????showType ????}; }; export?const?toggleTodo?=?id?=>?{ ????return?{ ????????type:?'TOGGLE_TODO', ????????id ????}; };
reducers.js
const?todos?=?(?state?=?[],?action?)?=>?{ ????switch?(?action.type?)?{ ????????case?'ADD_TODO': ????????????return?[ ????????????????...state, ????????????????{ ????????????????????id:?action.id, ????????????????????text:?action.text, ????????????????????isDo:?false ????????????????} ????????????]; ????????case?'TOGGLE_TODO': ????????????return?state.map(?todo?=>?{ ????????????????return?todo.id?===?action.id???{...todo,?isDo:?!todo.isDo?}?:?todo; ????????????}?); ????????default: ????????????return?state; ????} } const?showType?=?(?state?=?'SHOW_ALL',?action?)?=>?{ ????switch?(?action.type?)?{ ????????case?'SET_SHOW_TYPE': ????????????return?action.showType; ????????default: ????????????return?state; ????} } const?todoList?=?combineReducers({ ????todos, ????showType }) export?{?todoList?}
?
至此,數(shù)據(jù)狀態(tài)redux部分算完成了,接下來(lái)實(shí)現(xiàn)對(duì)應(yīng)的Component和入口文件了,準(zhǔn)備分這么幾個(gè)組件
1、待辦事項(xiàng)Todo
2、輸入框 AddTodo
3、待辦事項(xiàng)列表TodoList
4、底部展示類型切換Tab
//?component.js import?{?connnect?}?from?'react-redux'; import?{?addTodo,?setShowType,?toggleTodo?}?from?'./actions' const?Todo?=?(?{?onClick,?completed,?text?}?)?=>?( ????<li?onClick={onClick}?style={{?textDecoration:?completed???'line-through'?:?'none'?}}> ????????{text} ????</li> ) const?AddTodo?=?(?{?dispatch?}?)?=>?{ ????let?input; ????return?( ????????<div> ????????????<form ????????????????onSubmit={?e?=>?{ ????????????????????e.preventDefault() ????????????????????if?(?!input.value.trim()?)?{ ????????????????????????return; ????????????????????} ????????????????????dispatch(?addTodo(?input.value?)?) ????????????????????input.value?=?'' ????????????????}} ????????????> ????????????????<input?ref={?node?=>?{input?=?node?}?}?/> ????????????????<button?type='submit'>Add?Todo</button> ????????????</form> ????????</div> ????) } AddTodo?=?connect()(?AddTodo?); const?TodoList?=??(?{?todos,?onTodoClick?}?)?=>?{ ????return?( ????????<ul> ????????????{todos.map(?todo?=>?( ????????????????<Todo?key={todo.id}?{...todo}?onClick={?()?=>?onTodoClick(?todo.id?)?}?/> ????????????)?)} ????????</ul> ????)?}; ???? const?getShowTodoList?=?(?todos,?showType?)?=>?{ ????switch(?showType?)?{ ????????case?'SHOW_ISDO': ????????????return?todos.filter(?item?=>?item.isDo?); ????????case?'SHOW_ACTIVE': ????????????return?todos.filter(?item?=>?!item.isDo?); ????????case?'SHOW_ALL': ????????default?: ????????????return?todos; ????} } const?mapStateToProps?=?state?=>?{ ????return?{ ????????todos:?getShowTodoList?(?state.todos,?state.showType) ????}; }; const?mapDispatchToProps?=?dispatch?=>?{ ????return?{ ????????onTodoClick:?id?=>?{ ????????????dispatch(?toggleTodo(?id?)?); ????????} ????}; } const?ShowTodoList?=?connect( ????mapStateToProps, ????mapDispatchToProps )(?TodoList?); ??? ?const?Tab?=?()?=>?( ????<p> ????????Show:?{?'?'?} ????????<FilterLink?filter='SHOW_ALL'>ALL</FilterLink> ????????{?',?'?} ????????<FilterLink?filter='SHOW_ACTIVE'>ACTIVE</FilterLink> ????????{?',?'?} ????????<FilterLink?filter='SHOW_ISDO'>ISDO</FilterLink> ????</p> ) export?{?AddTodo,?ShowTodoList,?Tab?}
?
入口文件 index.js
import?React?from?'react'; import?ReactDOM?from?'react-dom'; import?{?Provider?}?from?'react-redux'; import?{?createStore?}?from?'./redux'; import?todoList?from?'./reducers' import?{AddTodo,?ShowTodoList,?Tab?}?from?'./component' let?store?=?createStore(?todoApp?); ReactDOM.render( ????<Provider?store={store}> ????????<div> ????????????<AddTodo?/> ????????????<ShowTodoList?/> ????????????<Tab?/> ????????</div> ????</Provider> ????,?document.getElementById('root'));
?
主要代碼完成,npm start 運(yùn)行,功能截圖如下
文章同步發(fā)布:?https://www.geek-share.com/detail/2783420870.html
參考文章:
原生實(shí)現(xiàn)一個(gè)react-redux的代碼示例
用React實(shí)現(xiàn)一個(gè)完整的TodoList的示例代碼
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。