前言
強(qiáng)烈推薦的學(xué)習(xí)資源:《深入淺出React和Redux》
此篇學(xué)習(xí)筆記記錄了對(duì)書中第三章節(jié)的學(xué)習(xí)心得
在學(xué)習(xí)之前佳励,對(duì)react并沒有實(shí)踐經(jīng)驗(yàn)黍判,有過Vuex開發(fā)經(jīng)驗(yàn)惠毁。但是讀完還是收獲很大灯蝴,是用于前期理解Redux概念的一個(gè)很好的讀物颜骤。
總結(jié)
1、在學(xué)習(xí)之前呆躲,有過拿redux和vuex進(jìn)行比較的念頭异逐,但是在看完一遍redux之后,發(fā)現(xiàn)redux的單向數(shù)據(jù)流與vuex的雙向數(shù)據(jù)流思想相差還是蠻大的插掂,所以就不比較了.....
2灰瞻、在閱讀這一本書的redux章節(jié)當(dāng)中,書中的講述順序十分的棒辅甥,從redux前身flux介紹起酝润,逐步深入地過渡到redux,最后再介紹了成熟的react-redux庫(kù)璃弄,這種思路還是蠻不錯(cuò)的要销。
3、感覺直接看概念谢揪,還是沒有vuex的思路清晰蕉陋,因此做了一些擬人化的比喻捐凭,個(gè)人覺得比較受用...
目錄
- flux框架介紹
1.1 flux四大元素
1.2 flux的擬人化描述
1.3 flux總結(jié) - Redux框架介紹
2.1 Redux基本原則
2.2 Redux要素分析
2.3 Redux的可改進(jìn)之處
2.4 搭上大神的順風(fēng)車—————— react-redux
1拨扶、flux框架介紹
1.1 flux四大元素:
- Dispatcher:根據(jù)注冊(cè)派發(fā)動(dòng)作(action)
- Store: 存儲(chǔ)數(shù)據(jù),處理數(shù)據(jù)
- Action:用于驅(qū)動(dòng)Dispatcher
- View: 用戶界面視圖
Dispatcher
是全局唯一的Dispatcher對(duì)象茁肠,關(guān)系網(wǎng)的中心
// AppDispatcher.js
// 完成聲明即可患民,后續(xù)無需改動(dòng)
// 注冊(cè)action等事件主要在store中被調(diào)用完成
import {Dispatcher} from 'flux';
export default new Dispatcher ();
Store
注冊(cè)(register):把當(dāng)前store注冊(cè)到Dispatcher下,加入dispatcher關(guān)系網(wǎng)
通過emit廣播垦梆、on掛載事件
store需要注冊(cè)到全局唯一的Dispatcher上才有效
flux核心部分:當(dāng)register函數(shù)把一個(gè)回調(diào)函數(shù)注冊(cè)到Dispatcher后匹颤,所有派發(fā)給Dispatcher的action對(duì)象
仅孩,都會(huì)傳遞到這個(gè)回調(diào)函數(shù)中
const counterValues = {
'First': 0,
'Second': 10,
'Third': 30
}
// 聲明、生成store對(duì)象
const CounterStore = object.assign({}, EventEmitter.prototype, {
getCounterValues: function() {
return counterValues;
},
emitChange: function(){
this.emit(CHANGE_EVENT); // 廣播事件
},
addChangeListener: function(){
this.on(CHANGE_EVENT, callback); // 掛載事件
},
removeChangeListener: fucntion(){
this.removeListener(CHANGE_EVENT, callback); // 移除監(jiān)聽
}
})
// 把CounterStore注冊(cè)到全局唯一的Dispatcher上印蓖,register函數(shù)接受一個(gè)回調(diào)函數(shù)做參數(shù)
//注冊(cè)token(控制權(quán)令牌)
CounterStore.dispatchToken = AppDispathcer.register((action)=>{
if(action.type === ActionTypes.INCREMENT){
// do increment
// 根據(jù)action對(duì)象辽慕,修改當(dāng)前store中的counterValues變量
} else if(action.type === ActionTypes.DECREMENT){
// do decrement
// 根據(jù)action對(duì)象,修改當(dāng)前store中的counterValues變量
}
})
使用waitFor()函數(shù)赦肃,通過dispatchToken的傳遞溅蛉,實(shí)現(xiàn)同步調(diào)用,滿足多個(gè)store之間的相互依賴關(guān)系他宛;
常用于獲取store中的最新鮮的數(shù)據(jù)
Action
代表一個(gè)動(dòng)作的純數(shù)據(jù)對(duì)象
是js對(duì)象船侧,且不自帶方法,用于驅(qū)動(dòng)Dispatcher厅各,來自用戶的請(qǐng)求
Action并不包含數(shù)據(jù)處理邏輯镜撩,而是調(diào)用函數(shù),來創(chuàng)建對(duì)應(yīng)的action對(duì)象
// ActionTypes.js
export const INCREMENT = 'increment';
export const DECREMENT = 'decrement';
// action.js
import * as ActionTypes form './ActionTypes.js';
import AppDispatcher from './AppDispatcher.js'
export const increment = (counterCaption) => {
AppDispatcher.dispatch({
type: ActionTypes.INCREMENT, // action對(duì)象類型
counterCaption:counterCaption // 用于標(biāo)識(shí)發(fā)出action的來源(即頁面元素)
})
}
View
用戶在界面中調(diào)用action
// react組件中的事件
onClickBtn() {
// increment已經(jīng)在對(duì)應(yīng)的store中完成注冊(cè)队塘,Dispactcher可識(shí)別
Actions.increment(this, props.caption)袁梗;
}
1.2 flux的擬人化描述
Dispatcher:
在十字路口中央指揮交通的交警,不會(huì)離開工作地點(diǎn)人灼,是唯一的围段;
當(dāng)有人督促我派發(fā)(dispatch)一下action,我就要打電話給我的小協(xié)警了投放,叫他趕緊來把這個(gè)家伙的事處理一下奈泪;
Store:
注冊(cè):協(xié)警把自己的電話號(hào)碼給了十字路口的交警并告訴他:“發(fā)生交通事故,就打這個(gè)電話找我灸芳,我來處理現(xiàn)場(chǎng)”涝桅;
emit廣播事件:有人叫我去處理交通事故;
on掛載事件:如果有人叫我去處理交通事故烙样,我要給出的反應(yīng)冯遂;
view:
路上的車主,一旦和別人的車撞上了谒获,我要發(fā)出一個(gè)action(下車跑去找交警告訴交警我的事故屬于哪種類型)讓交警知道我撞車了蛤肌,不然他不會(huì)理我的;
action:
車主督促交警批狱,趕緊把我的action派發(fā)(dispatch)出去裸准,讓協(xié)警快來處理一下;
1.3 flux總結(jié)
flux的目的:糾正MVC框架的無法禁絕view與model通信的缺點(diǎn)赔硫;
flux的做法:store只有g(shù)et方法炒俱,沒有set方法;因此view只能通過get獲取store狀態(tài),不能修改狀態(tài)权悟;如果想要修改store狀態(tài)砸王,只能派發(fā)一個(gè)action給Dispatcher,由action中ActionTypes對(duì)應(yīng)的store方法去修改store本身峦阁。
flux的缺點(diǎn):
- store之間的依賴:需要建立依賴馏段,需要token
- 可以但是很難進(jìn)行服務(wù)器端渲染(尚不理解)
- store替換后婶博,無法保持原有存儲(chǔ)狀態(tài)(指在開發(fā)中皂股,store邏輯的修改無法熱加載)
2途戒、Redux框架介紹
2.1 Redux基本原則:
- 繼承Flux基本原則:?jiǎn)蜗驍?shù)據(jù)流
- 唯一數(shù)據(jù)源
- 保持狀態(tài)只讀
- 只有純函數(shù)能改變數(shù)據(jù)
1.唯一數(shù)據(jù)源
Flux:利用Dispatcher的waitFor方法,保證多個(gè)store之間的依賴與更新順序 ==> 數(shù)據(jù)冗余件豌、應(yīng)用復(fù)雜
Redux:所有state只保存在一個(gè)store中疮方,整個(gè)應(yīng)用只有一個(gè)store,狀態(tài)是一個(gè)樹形對(duì)象茧彤,每個(gè)組件使用狀態(tài)樹上的一部分?jǐn)?shù)據(jù)
2.保持狀態(tài)只讀
狀態(tài)只可讀骡显,不可直接修改
渲染原則:UI = render(state) <===> 界面只根據(jù)State進(jìn)行渲染
與Flux相同,必須通過action對(duì)象才能修改store狀態(tài)
3.只有純函數(shù)能改變數(shù)據(jù)
純函數(shù):指不依賴于且不改變它作用域之外的變量狀態(tài)的函數(shù)曾掂,也就是說函數(shù)的返回結(jié)果必須完全由傳入的參數(shù)決定惫谤。
reducer(state, action)
reducer函數(shù),接受兩個(gè)參數(shù)珠洗,第一個(gè)參數(shù)state是當(dāng)前的狀態(tài)(最新的store.state)溜歪,第二個(gè)參數(shù)action是接收到的action對(duì)象;reducer根據(jù)state與action的值產(chǎn)生并返回一個(gè)新的對(duì)象许蓖,返回的這一個(gè)對(duì)象用來組裝新狀態(tài)對(duì)象蝴猪。
2.2 Redux要素分析
- Store:存儲(chǔ)數(shù)據(jù)
- Reducer:根據(jù)Action+state替換state,而非直接修改
- Aciton:定義Action對(duì)象
- Component:調(diào)用action
1.Store
在Flux中膊爪,Dispatcher的作用:把a(bǔ)ction對(duì)象分發(fā)給不同的自阱、已注冊(cè)的store;
在redux中米酬,只有一個(gè)store沛豌,是唯一的分發(fā)對(duì)象,因此將Dispatcher對(duì)象簡(jiǎn)化為store對(duì)象中的一個(gè)函數(shù)dispatch赃额。
使用createStore創(chuàng)建整個(gè)應(yīng)用唯一的store加派,并且暴露到全局;
組件使用getOwnState函數(shù)用于從store中獲得狀態(tài)
import {createStore} from 'redux';
import reducer from './Reducer.js';
const counterValues = {
'First': 0,
'Second': 10,
'Third': 20
}
const store = createStore(reducer, initValues); // reducer表示更新狀態(tài)的reducer跳芳,initValues是狀態(tài)的初始值
export default store;
2.Reducer
reducer <===> redux與flux對(duì)state的操作差異:
flux:直接修改state的值
redux:修改應(yīng)用狀態(tài)芍锦,并不能直接修改狀態(tài)上的值,而是創(chuàng)建一個(gè)新的狀態(tài)對(duì)象返回給Redux筛严,由Redux組裝新狀態(tài)對(duì)象醉旦。
// Flux版本的action
// Flux: 直接修改store狀態(tài)值
CounterStore.dispatchToken = AppDispatcher.register( (action) => {
if(action.type === AcitonTypes.INCREMENT) {
counterValues[action.counterCaption]++;
CounterStore.emitChange();
} else if(action.type === AcitonTypes.DECREMENT){
counterValues[action.counterCaption]--;
CounterStore.emitChange();
}
})
// Redux版本的action
// 根據(jù)state與action的值產(chǎn)生并返回一個(gè)新的對(duì)象
// 返回的這一個(gè)對(duì)象用來組裝新狀態(tài)對(duì)象。
function reducer (state,action) {
const {counterCaption} = action;
// 返回的對(duì)象是一個(gè)新的狀態(tài)樹
// ... 擴(kuò)展操作符
// ...state表示擴(kuò)展拆分state數(shù)據(jù)桨啃,去除了最頂層的樹結(jié)構(gòu)车胡,暴露二級(jí)節(jié)點(diǎn)
// [counterCaption]: newData 則體現(xiàn)了redux狀態(tài)樹的設(shè)計(jì)
// 使用組件的caption作為狀態(tài)樹的對(duì)應(yīng)子數(shù)據(jù)字段
switch (action.type) {
case ActionTypes.INCREMENT:
return {...state, [counterCaption]: state[counterCaption] +1};
case ActionTypes.DECREMENT:
return {...state, [counterCaption]: state[counterCaption] -1};
default:
return state
}
}
Action
flux:Action構(gòu)造函數(shù)不返回什么,而是把構(gòu)造的動(dòng)作函數(shù)立刻通過調(diào)用dispatcher函數(shù)派發(fā)出去
// Flux之Action
export const increment = (counterCaption) => {
AppDispatcher.dispatch({
type: ActionTypes.INCREMENT, // action對(duì)象類型
counterCaption:counterCaption // 用于標(biāo)識(shí)發(fā)出action的來源(即頁面元素)
})
}
reudx:每個(gè)Action構(gòu)造函數(shù)返回一個(gè)action對(duì)象 == > 返回一個(gè)對(duì)象照瘾,把處理對(duì)象的工作交給調(diào)用者
// Redux之Action
export const increment = (counterCaption) => {
return {
type: ActionTypes.INCREMENT, // action對(duì)象類型
counterCaption:counterCaption // 用于標(biāo)識(shí)發(fā)出action的來源(即頁面元素)
})
}
Component
用于聲明綁定派發(fā)action事件
onIncrement() {
// 通過dispatch派發(fā)aciton
store.dispatch(Actions.increment(this.props.caption));
}
render() {
const value = this.state.value;
const {caption} = this.props;
return (
<div>
<button onclick={this.onIncrement}>+</button>
</div>
);
}
2.3 Redux的可改進(jìn)之處
1. 組件功能單一化
當(dāng)前組件具有兩個(gè)功能:1匈棘、派發(fā)Action,更新state樹析命;2主卫、根據(jù)state與props渲染用戶界面
為了使組件專注于單一功能 ===> 拆分組件(容器組件、展示組件)
容器組件:外層組件鹃愤,負(fù)責(zé)與store交互簇搅;
展示組件:內(nèi)層組件,負(fù)責(zé)渲染界面软吐,無狀態(tài)瘩将;
store 《====》 容器組件 ===(傳遞props)==》 展示組件 《====》 React界面
2.context全局訪問對(duì)象
理想目標(biāo):
單個(gè)應(yīng)用最好只導(dǎo)入一次全局Store,在最頂層React組件的位置凹耙;
為提高組件的復(fù)用性姿现,其余組件應(yīng)該避免直接導(dǎo)入Store;
為了滿足以上原則而出現(xiàn)的缺點(diǎn):在一個(gè)多層嵌套組件結(jié)構(gòu)中肖抱,當(dāng)只有最里層組件需要使用store备典,為了將sotre從最外層傳到最里層,必須在所有中間組件中使用props逐級(jí)傳遞state意述。
Context:在樹狀組件中的所有組件都可訪問的一個(gè)共同對(duì)象提佣,上下文環(huán)境
當(dāng)上級(jí)組件宣稱自己支持context,并且提供一個(gè)函數(shù)來返回代表context的對(duì)象荤崇,所有子孫組件可在宣稱(import)后通過this.context訪問到這個(gè)共同的環(huán)境變量镐依。
Provider類組件實(shí)現(xiàn)Context
// Provider類組件實(shí)現(xiàn)Context
import {PropTypes, Component} from 'react';
class Provider extends Component {
getChildContext() {
return {
store: this.props.store
};
}
render() {
// 渲染子組件 props.children代表子組件
return this.props.children;
}
}
// 使Provider被React認(rèn)可,成為一個(gè)Context提供者天试,必須指定childContextTypes屬性
Provider.childContextTypes = {
store: PropTypes.object
};
Provider提供Context,暴露到所有子組件中
// Provider的實(shí)踐使用
// index.js 應(yīng)用入口文件
import store from './Store.js'
import Provider from './Provider.js';
// ControlPanel是ReactDOM的頂層組件槐壳,現(xiàn)在被Provider組件包住后,Provider成為頂層組件
// Provider內(nèi)層包裹的所有
ReactDOM.render (
<Provider store={store}>
<ControlPanel />
</Provider>,
document.getElementById('root')
)
在子組件中使用Context
// 第一步: 給組件類的contextTypes屬性賦值
CounterContainer.contextTypes = {
store: PropTypes.object
}
// 第二步:在構(gòu)造函數(shù)中用上第二個(gè)參數(shù)context
constructor(props, context) {
// 寫法一:
super(props, context);
// 寫法二:
super(...arguments);
}
// 第三步:通過this.context.store訪問store
getOwnState() {
return {
// [this.props.catption]用于獲取狀態(tài)樹中的某個(gè)二級(jí)狀態(tài)
value: this.context.store.getState()[this.props.catption]
}
}
2.4 搭上大神的順風(fēng)車—————— react-redux
對(duì)于Redux的兩個(gè)改進(jìn)在實(shí)現(xiàn)在實(shí)現(xiàn)上仍然具有一定的復(fù)雜性與機(jī)械性喜每,因此已經(jīng)有人創(chuàng)建了一個(gè)庫(kù)來幫我們完成這些工作(組件拆分與context)
react-redux庫(kù)兩大功能:
1务唐、connect:連接容器組件與展示組件
2、Provider:我們不再需要自己實(shí)現(xiàn)Provider來獲取context带兜,可以使用庫(kù)提供的Provider
connect
包含了兩個(gè)函數(shù)中執(zhí)行:connect
與connect返回函數(shù)
connect(mapStateToProps, mapDispatchToProps)的傳入?yún)?shù)是兩個(gè)映射函數(shù),返回值是一個(gè)函數(shù)
connect函數(shù)作用:
1枫笛、向內(nèi)傳遞state:把store上的狀態(tài)轉(zhuǎn)化為內(nèi)層展示組件的props;
2刚照、向外轉(zhuǎn)發(fā)Action:把內(nèi)層展示組件中的用戶動(dòng)作轉(zhuǎn)化為派送給store的動(dòng)作
export default connect(mapStateToProps, mapDispatchToProps)(Counter);
// mapStateToProps函數(shù)刑巧,向內(nèi)傳遞state => props
function mapStateToProps(state, ownProps) {
return {
value: state[ownProps.caption]
}
}
// mapDispatchToProps函數(shù),向外轉(zhuǎn)發(fā)Action
// ownProps屬性就是,直接傳遞給外層容器組件的props
function mapDispatchToProps(dispatch, ownProps) {
return {
onIncrement: () => {
dispatch(Actions.increment(ownProps.caption));
},
onDecrement: () => {
dispatch(Actions.decrement(ownProps.captions))
}
}
}
Provider
import {Provider} from 'react-redux';
react-redux庫(kù)的提供的Provider幾乎相同啊楚,但更加嚴(yán)謹(jǐn)吠冤;
react-redux庫(kù)要求store必須是一個(gè)包含以下三個(gè)函數(shù)的object:
- subscribe
- dispatch
- getState
react-redux還提供了componentWillReceiveProps鉤子,用于每次重新渲染時(shí)調(diào)用
redux總結(jié) & 與flux的比較
- 全局唯一數(shù)據(jù)源恭理,store
- reducer替換狀態(tài)樹拯辙,而非直接修改值
- Provider優(yōu)化props傳遞
- 組件拆解,功能單一化
react項(xiàng)目常見組織結(jié)構(gòu)
- ReactApp
|---reducers // 所有的reducer
|---actions // 所有的action構(gòu)造函數(shù)
|---components // 所有的展示組件
|---containers // 所有的容器組件