溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Redux的核心概念,實(shí)現(xiàn)代碼與應(yīng)用示例

發(fā)布時(shí)間:2020-05-22 09:29:48 來(lái)源:網(wǎng)絡(luò) 閱讀:452 作者:xiao_lili 欄目:web開(kāi)發(fā)

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的幾個(gè)基本概念

一、數(shù)據(jù)存儲(chǔ) - state

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'
}

?

二、行為觸發(fā) - action

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)

三、行為響應(yīng) - reducer

狀態(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ù)監(jiān)聽(tīng) - subscribe

數(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()
}?)

?

Redux的幾大原則

一、單一數(shù)據(jù)原則

整個(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只讀

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)。

三、使用純函數(shù)

先說(shuō)明下什么是純函數(shù),純函數(shù)指的是函數(shù)內(nèi)部不修改傳入的參數(shù),無(wú)副作用,在傳參一定的情況下,返回的結(jié)果也是一定的。Redux中的Reducer需要設(shè)計(jì)成存函數(shù),不能直接操作傳入的state,需要把改變的數(shù)據(jù)以一個(gè)新的state方式返回。

Redux中的數(shù)據(jù)流

其實(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ù)流描述,如下圖所示:

Redux的核心概念,實(shí)現(xiàn)代碼與應(yīng)用示例

Redux的實(shí)現(xiàn)代碼(非官方)

在對(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與React的結(jié)合應(yīng)用示例

下面,利用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)行,功能截圖如下

Redux的核心概念,實(shí)現(xiàn)代碼與應(yīng)用示例

文章同步發(fā)布:?https://www.geek-share.com/detail/2783420870.html

參考文章:

原生實(shí)現(xiàn)一個(gè)react-redux的代碼示例

用React實(shí)現(xiàn)一個(gè)完整的TodoList的示例代碼


向AI問(wèn)一下細(xì)節(jié)

免責(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)容。

AI