Redux是一種JavaScript的狀態管理容器,是一個獨立的狀態管理庫,可配合其它框架使用,比如React。引入Redux主要為了使JavaScript中數據管理的方便,易追蹤,避免在大型的JavaScript應用中數據狀態的使用混亂情況。Redux 試圖讓 state 的變化變得可預測,為此做了一些行為限制約定,這些限制條件反映在 Redux 的三大原則中。
本文會介紹Redux的幾個基本概念和堅持的三大原則,以及完整的回路一下Redux中的數據流。在了解以上這些概念之后,用自己的代碼來實現一個簡版的Redux,并且用自己實現的Redux結合React框架,做一個簡單的TodoList應用示例。希望本文對于初識Redux的同學有一個清晰,全面的認識。
Redux就是用來管理狀態數據,所以第一個概念就是狀態數據,state就是存放數據的地方,根據應用需要,一般定義成一個對象,比如:
{ ????todos:?[], ????showType:?'ALL', ????lastUpdate:?'2019-10-30?11:56:11' }
?
web應用,所有的數據狀態變更,都是由一個行為觸發的,比如用戶點擊,網絡加載完成,或者定時事件。在簡單應用里面,我們一般都是在行為觸發的時候,直接修改對應的數據狀態,但是在大型復雜的應用里面,修改同一數據的地方可能很多,每個地方直接修改,會造成數據狀態不可維護。
Redux引入了action的概念,每個要改變數據狀態的行為,都定義成一個action對象,用一個type來標志是什么行為,行為附帶的數據,也都直接放在action對象,比如一個用戶輸入的行為:
{ ????type:?'INPUT_TEXT', ????text:?'今天下午6點活動碰頭會議' }
然后通過dispatch觸發這個action,dispatch(action)
狀態,action的概念了解了,當action觸發的時候,肯定要修改state數據,在講解action的時候有說過,不能直接修改state,我們需要定義一個reducer來修改數據,這個reducer就是一個行為響應函數,他接收當前state,和對應的action對象,根據不同的action,做相應的邏輯判斷和數據處理,然后返回一個新的state。
注意,一定是返回一個新的state,不能直接修改參數傳入的原state,這是redux的原則之一,后面會講到。
function?reducer?(?state?=?[],?action?)?{ ????switch?(?action.type?)?{ ????????case?'INPUT_TEXT': ????????????return?[...state,?{text:?action.text,?id:?Math.random()?}] ????????default: ????????????return?state; ????} }
?
數據的更新已經在reducer中完成了,在一些響應式的web應用中,我們往往需要監聽數據狀態的變化,這個時候就可以用subscribe了
redux內部保存一個監聽隊列,listeners,可以調用subscribe來往listeners里面增加新的監聽函數,每次reducer修改完state之后,會逐個執行監聽函數,而監聽函數可以獲取已經更新過的state數據了
listeners?=?[]; subscrible(?listener?)?{ ????listeners.push(?listener?); ????return?function?()?{ ????????let?index?=?listeners.index(?listener?); ????????listeners.splice(?index,?1?); ????} } dispatch(?action?)?//?觸發?action reducer(state,?action) listeners.map(?(?listener?)?=>?{ ????listener() }?)
?
整個應用的數據都在state,并且只有這一個state,這么做的目的是方便管理,整個應用的數據就這一份,調試方便,開發也方便,可以在開發的時候用本地的數據。而且開發同構應用也很方便,比如服務端渲染,把服務端的數據全部放在state,作為web端初始化時候的數據
state的數據對外只讀,不能直接修改state,唯一可以修改的方式是觸發action,然后通過reducer來處理。
因為所有的修改都被集中化處理,且嚴格按照一個接一個的順序執行,因此不用擔心競態條件(race?condition)的出現。?Action?就是普通對象而已,因此它們可以被日志打印、序列化、儲存、后期調試或測試時回放出來。
先說明下什么是純函數,純函數指的是函數內部不修改傳入的參數,無副作用,在傳參一定的情況下,返回的結果也是一定的。Redux中的Reducer需要設計成存函數,不能直接操作傳入的state,需要把改變的數據以一個新的state方式返回。
其實上面講Redux基本概念的時候已經大概的說了下數據流向方式了,就是: view->action->reducer->state->view,用文字來表述就是,首先由于頁面上的某些事件會觸發action,通過dispatch(action)來實現,然后通過reducer處理,reducer(state, action)返回一個新的state,完成state的更新,當然對于響應式的應用,會觸發listener(),在listener里面獲取最新的state狀態,完成對應視圖(view)的更新。這就是整個redux中的數據流描述,如下圖所示:
在對Redux的基本概念和幾大原則熟悉了之后,可以實現一個自己的Redux了,當然我們一般都直接用官方的npm包,這里自己實現的比較簡單,沒有做什么入參驗證,異常處理之類的,主要是加深下對Redux的理解。下面直接貼代碼了,對應的概念都有注釋。
//?redux.js //?創建state的函數 //?傳入reducer?和初始化的state function?createStore(?reducer,?initState?)?{ ????let?ref?=?{}; ????let?listeners?=?[]; ????let?currentState?=?initState; ????//?dispath函數,用來觸發action ????function?dispatch?(?action?)?{ ????????//?觸發的action,通過reducer處理 ????????currentState?=?reducer(?currentState,?action?) ????????//?處理完成后,通知listeners ????????for?(?let?i?in?listeners?)?{ ????????????let?listener?=?listener[?i?]; ????????????listener(); ????????} ????????return?action; ????} ????//?返回當前的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?}
是不是覺得怎么才這么點代碼,就是這么點代碼,而且還包含了一個combineReducers輔助函數,下面再貼一點使用示例代碼
//?reducer函數,用于處理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時候 console.log(store.getState()); store.dispatch(?{?type:?'INPUT_TEXT',?text:?'這里是一條待辦事項'?}?); console.log(store.getState()); //在用戶點擊一條Todo?Item的時候,切換完成狀態 console.log(store.getState()); store.dispatch(?{?type:?'TOGGLE_TODO',?id:?item.key?}?) console.log(store.getState());
?
下面,利用Redux結合React開發一個簡單的Todo工具,頁面主要功能點
1、可以添加Todo事項
2、點擊事項會切換事項的完成狀態
3、可以切換展示全部/已完成/待完成事項
這個實例是基于react,react-redux完成的,項目搭建用的是create-react-app,利用react-redux提供的接口,將redux中的state和action集成到組件中,需要讀者熟悉create-react-app的使用,以及react-redux的主要接口功能,以下貼出主要代碼,感興趣的同學可以自己搭建實現
首先定義好state數據結構和action以及對應的reducer
state包含兩部分,一是todos,待辦事項列表,二是showType,展示類型
action包含這么三種,一是添加新的Todo,二是切換事項完成狀態,三是切換展示類型,分別定義好
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?}
?
至此,數據狀態redux部分算完成了,接下來實現對應的Component和入口文件了,準備分這么幾個組件
1、待辦事項Todo
2、輸入框 AddTodo
3、待辦事項列表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 運行,功能截圖如下
文章同步發布:?https://www.geek-share.com/detail/2783420870.html
參考文章:
原生實現一個react-redux的代碼示例
用React實現一個完整的TodoList的示例代碼
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。