一、什么情況需要redux?
? ? 1、用戶的使用方式復雜
? ? 2、不同身份的用戶有不同的使用方式(比如普通用戶和管理員)
? ? 3、多個用戶之間可以協作
? ? 4、與服務器大量交互,或者使用了WebSocket
? ? 5、View要從多個來源獲取數據
簡單說,如果你的UI層非常簡單,沒有很多互動,Redux就是不必要的,用了反而增加復雜性。多交互、多數據源的場景就比較適合使用Redux。
二、設計思想
? ? 1、Web應用是一個狀態機,視圖與狀態是一一對應的。
? ? 2、所有的狀態,保存在一個對象里。
三、Redux的工作流程
首先,用戶發出Action
store.dispatch(action);
然后,Store自動調用Reducer,并且傳入兩個參數:當前State和收到的Action。Reducer會返回新的State。
let nextState = todoApp (previousState , action);
State一旦變化,Store就會調用監聽函數。
// 設置監聽函數
store.subscribe(listener);
lisrener 可以通過store.getState()得到當前狀態。如果使用的是React,這時可以觸發重新渲染View。
function listener () {
? ? let nextState=store.getState();
? ? component.setState( newState );
}
如果現在沒理解以上流程,不要急,看完以下API就差不多能動的Redux的核心機制啦。
四、Redux ?API
store
Store 就是保存數據的地方,你可以把它看成一個容器。整個應用只能有一個Store。
Redux提供createStore這個函數,用來生成Store。
下面代碼中,createStore函數接受另一個函數作為參數,返回新生成的Store對象。
import { createStore } from 'redux';
const store = createStore ( fn );
State
Store 對象包含所有的數據,如果想得到某個時點的數據,就要對Store生成快照。這種時點的數據集合,就叫做State。當前此刻的State,可以通過store.getState()拿到。
import { crateStore } from 'redux';
const store = createStore ( fn );
const state = ?store.getState();
Redux 規定,一個State對應一個View。只要State相同,View就相同,你知道State,就知道View是什么樣,反之亦然。
Action
State的變化,會導致VIew 的變化。但是,用戶接觸不到State,只能接觸到View。所以,State的變化必須是VIew導致的。
Action就是View發出的通知,表示state應該要發生變化啦。
Action是一個對象,其中的type屬性是必須的,表示Action的名稱。其他屬性可以自由設置。
const action = {
? ? type : 'ADD_TODO',
? ? payload ; 'learn Redux'
};
上述代碼中,Action的名稱是ADD_TODO,它攜帶的信息是字符串Learn Redux。
可以這樣理解,Action描述當前發生的事情。改變State的唯一辦法,就是使用Action,它會運送數據到Store。
Action Creator
view 要發送多少種信息,就會有多少種Action。如果都手寫,會很麻煩。可以定義一個函數來生成Action,這個函數就叫做ActionCreator。
const ADD_TODO = '添加TODO';
function addTodo (text) {
? ? return {
? ? ? ? type: ADD_TODO,
? ? ? ? text
? ? }
}
const action = addTodo(' Learn Redux');
Store.dispatch()
store.dispatch()是View發出Action的唯一方法。
import { createStore } from 'redux';
const ?store=createStore ( fn );
store.dispatch({
? ? type : 'ADD_TODO';
? ? payload : 'Learn Redux'
});
上面的代碼中,store.diapatch接受了一個Action對象作為參數,將它發出去,結合ActionCreator,這段代碼可以改寫如下。
import { createStore } from 'redux';
const? store=createStore ( fn );
store.dispatch(add Todo('Learn Rudux'));
const ADD_TODO = '添加TODO';
function addTodo (text) {
? ? return {
? ? ? ? type: ADD_TODO,
? ? ? ? text
? }
}
Reducer
Store 收到Action以后,必須給出一個新的State,這樣View才會發生變化。這種State的計算過程就叫做Reducer。Ruducer是一個函數,他接受Action和當前State作為參數,返回一個新的State,下面是一個實際的例子
const defaultState = 0;
const reducer = (state=defaultState,action)=>{
switch (action.type){
? ? case 'ADD';
? ? ? return state +action.payload;
? ? default:
? ? ? return state;
? ? }
};
const state = reducer (1, {
type : 'ADD',
payload: 2
});
上面代碼中,reducer函數收到名為ADD的Action以后,就返回一個新的State,作為加法的計算結果。其他運算的邏輯(比如剪發),也可以根據Action的不同來實現。
實際應用中,Reducer函數不用像上面那樣手動調用,store.dispatch方法會觸發Reudcer自動執行。為此,Store需要知道ReduCer函數,做法就是在生成Store的時候,將Reducer傳入createStore方法。
import {createStore } from 'redux';
const store = createStore(reducer);
store.dispatch(addTodo('Learn Redux'));
const ADD_TODO='添加Todo';
function addTodo(text){
? return {
? ? type:ADD_TODO,
? ? text
? }
}
上面代碼中,createStore接受Reducer作為參數,生成一個新的Store,以后每當store.dispatch發過來一個新的Action,就會自動調用Reducer,得到新的State。
Store.subscribe()
Store允許使用store.subscribe方法設置監聽函數,一旦State發生變化,就自動執行這個函數。
import { createStore } from 'redux';
const store = createStore (reducer);
store.dispatch(addTodo('Learn Redux'));
const ADD_TODO='添加Todo';
function addTodo(text){
? return {
? ? type:ADD_TODO,
? ? text
}
}
store.subscribe(listener);
顯然,只要把View的更新函數(對于React項目,就是組件的render方法和setState方法)放入listen,就會實現view的自動渲染。
store.subscribe方法返回一個函數,調用這個函數就可以解除監聽。
let unsubscribe = store.subscribe( () =>
? ? console.log(store.getState())
};
unsubscribe();
五、中間件與異步操作
一個關鍵的問題沒有解決:異步操作怎么辦?Action發出以后,Reducer立即算出State,這叫同步;Action發出以后,過一段時間再執行Reducer,這叫異步。
怎么才能Reducer在異步操作結束后自動執行呢?這就要用到新的工具:中間件(middleware)
為了理解中間件,然我們站在框架作者的角度思考問題:如果要添加功能,你會在那個環節添加?
(1)Reducer:純函數,只承擔計算State的功能,不適合承擔其他功能,也承擔不了,因為理 ? ? ? ? ?論上,純函數不能進行讀寫操作。
(2)View :與State一一對應,可以看作是State的視覺層,也不合適承擔其他功能。
(3)Action:存放數據的對象,即消息的載體,只能被別人操作,自己不能進行任何操作。
想來想去,只有發送Action的這個步驟,即store.dispatch()方法,可以添加功能
中間件的用法
這里只介紹如何使用中間件
import { applyMIddleware , crateStore } from 'redux';
import ?createLogger from 'redux-logger';
const logger= createLogger();
const store = createStore (
? ? reducer,
? ? applyMiddleware(logger)
);
上面代碼中,redux-logger提供了一個生成器createLogger,可以生出日志中間件logger。然后將它放在applyMiddleware方法之中,傳入createState方法,就完成了store.dispatch()的功能增強。
這里有兩點需要注意:
(1)createStore方法可以接受整個應用的初始狀態作為參數,那樣的話,applyMiddleware就 ? ? ? ? ? ?是第三個參數了。
import { applyMIddleware , crateStore } from 'redux';
import? createLogger from 'redux-logger';
const logger= createLogger();
const store = createStore(
? ? reducer,
? ? initial_state,
? ? applyMiddleware(logger)
);
(2)中間件的次序有講究
const store = createStore(
? ? reducer,
? ? applyMiddleware(thunk,promise,logger)
);
上面代碼中,applyMiddleware方法的三個參數,就是三個中間件。有的中間件有次序要求,使用前要查一下文檔。比如,logger就一定要放到最后,否則輸出結果會不正確。
六、異步操作的基本思路
同步操作只要發出一種Action即可,異步操作的差別是他要發出三種Action。
? ? 1)操作發起時的Action
? ? 2)操作成功時的Action
? ? 3)操作失敗時的Action
以向服務器取出數據為例,三種Action可以有兩種不同的寫法
// 寫法一:名稱相同,參數不同
{ ?type : 'FETCH_POSTS' }
{ type: 'FETCH_POSTS', status: 'error', error: 'Oops' }
{ type: 'FETCH_POSTS', status: 'success', response: { ... } }
// 寫法二:名稱不同
{ type: 'FETCH_POSTS_REQUEST' }
{ type: 'FETCH_POSTS_FAILURE', error: 'Oops' }
{ type: 'FETCH_POSTS_SUCCESS', response: { ... } }
除了Action種類不同,同步操作的State也要進行改造,反映不同的操作狀態,下面是State的一個例子
let state = {
? ? isFetching : true,
? ? didInvalidate : true,
? ? lastUpdated: 'xxxx'
};
上面代碼中,State的屬性isFetching表示是否在抓取數據,didInvalidate表示數據是否過時,lastUpdated表示上一次更新的時間
現在,整個異步操作的思路清楚啦。
1)操作開始時,送出一個Action,觸發State更新為“正在操作”狀態,View重新渲染
2)操作結束后,再送出一個Action,觸發State更新為“操作結束”狀態,View再一次重新渲染
redux-thunk中間件
異步操作至少要送出兩個Action:用戶觸發第一個Action,這個跟同步一樣,沒有問題;如何才能在操作結束時,系統自動送出第二個Action呢?
奧妙就在Action Creator之中。
class AsyncApp extends Component {
? componentDidMount() {
? ? ? ?const { dispatch,selectedPost } =this.props
? ? ? ?dispatch (fetchPosts(selectedPost))
}
}
上面代碼是一個異步組件的例子,加載成功后(componentDidMouth方法),他送出了(dispatch方法)一個Action,向服務器要求數據fetchPosts(selectedSUbredddit).這里的fetchPosts就是Action Creator。
const fetchPosts = postTitle => ( dispatch , getState) => {
? dispatch ( requestPosts( postTitle));
? return fetch ( '/some/API/${postTitle}.json')
? ? ? .then (response = > response.json() )
? ? ? .then( json=> dispatch(receivePosts(postTitle,json)));
};
};
// 使用方法一
store.dispatch(fetchPosts(reactjs'));
// 使用方法二
store.dispatch ( fetchPosts (reactjs')).then(()=>
? ? ? console.log(store.getState())
);
上面代碼中,fetchPosts是一個Action Creator(動作生成器),返回一個函數,這個函數執行后,先發出一個Action(requestPosts(postTitle)),然后進行操作。拿到結果后,先將結果轉換為JSON格式,然后再發出一個Action(recervePosts(postTitle,json)).
上面代碼中,有幾個地方需要注意。
(1)fetchPosts返回一個函數,而普通的Action Creator默認返回一個對象
(2)返回的函數的參數是disPatch和getState這兩個Redux方法,普通的ActionCreator的參數 ? ? ? ? ?是Action的內容。
(3)在返回的函數之中,先發出一個Action(requestPosts(postTitle)),表示操作開始
(4)異步操作結束之后,再發出一個Action(ReceivePosts(postTitle,json)),表示操作結束
這樣的處理,就解決了自動發送第二個Action的問題,但是又帶來一個新的問題,Action是由store.dispatch方法發送的。這時,就要使用中間件redux-thunk
import { createStore, applyMiddware } from 'redux';
import thunk form 'redux-thunk';
import reducer from './reducers';
const store = creareStore(
reducer,
applyMiddleware(thunk)
};
上面代碼使用redux-thunk中間件,改造store.dispatch,使的后者可以接受函數作為參數。因此,異步操作的第一種解決方案就是,寫一個返回函數的Action Creator,然后使用redux-thunk中間件改造store.dispatch.
*七、React-Redux的用法
為了方便使用,Redux的作者封裝了一個React專用的庫React-Redux本文主要介紹它
這個庫可以選用的。實際項目中,你應該權衡一下,是直接使用Redux,還是使用React-Redux。后者雖然提供了便利,但是需要掌握額外的Api,并且要遵循它的組件拆分規范。
React-Redux將所有的組件分為兩大類:UI組件(presentational component)和容器組件(container component)。
UI 組件
UI組件有一下幾個特征。
1)只負責UI的呈現,不帶有任何業務邏輯。
2)沒有狀態(即不使用this.state這個變量)
3)所有數據都由參數(this.props)提供
4)不使用任何Redux的API
下面就是一個UI組件的例子
const Title =
value = > <h1> { value} </h1>;
因為不含有狀態,UI組件又稱為“純組件”,即它純函數一樣,純粹由參數決定他的值
容器組件
容器組件的特征恰恰相反。
1)負責管理數據和業務邏輯,不負責Ui的呈現
2)帶有內部狀態
3)使用Redux的ApI
總之,只要記住一句話就可以了:UI組件負責Ui的呈現,容器組件負責管理數據和邏輯
你可能會問,如果一個組件既有UI又有業務邏輯,怎么辦? 答案是,將它拆分成下面的結構:外面是一個容器組件,里面包含了一個UI組件。前者負責與外部的通信,將數據傳給后者,由后者渲染出視圖。
React-Redux提供connect方法,用于從UI組件生成容器組件。connect的意思,就是將這兩種組件連起來。
connect方法的完整API如下。
import ?{ connect ?} ?from 'react-redux';
const VIsibleTodoList = connect(
mapStateToProps,
mapDisPatchToProps
) (TodoList)
上面代碼中,TodoList是UI組件,VisibleTodoList就是由React-Redux通過connect方法自動生成的容器組件。connect方法接受兩個參數:mapStateToProps和mapDispatchToProps。他們定義了UI組件的業務邏輯,前者負責輸入邏輯,即將state映射到UI組件的參數(props),后者負責輸出邏輯,即將用戶對UI組件的操作映射成Action。
mapStateToProps
mapStateToProps是一個函數,它的作用就像他的名字一樣,建立一個從(外部的)state對象到(UI組件的)props對象的映射關系。
作為函數,mapStateProps執行后應該返回一個對象,里面每個鍵值對就是一個映射,請看下例
const mapStateToProps = (state) =>{
? ? return {
? ? ?todos:getVisibleTodos(state.todos,state.visibilityFilter)
}
}
上面代碼中,mapStateToProps是一個函數,他接受state作為參數,返回一個對象。這個對象有一個todos屬性,代表UI組件的同名參數,后面的getVIsibleTodos也是一個函數,可以從state算出todos的值。
下面就是getVisibleTodos的一個例子,用來算出Todos。
const getVisibleTodos =(todos , ?filter) =>{
? switch (filter){
? ? case 'SHOW_ALL';
? ? ? ? return todos
? ? case 'SHOW_COMPLETED';
? ? ? ? return todos.filter( t => t.completed)
? ? ?case 'SHOW_ACTIVE' ;
? ? ? ? ?return todos.filter ( t=> ! t.completed)
? ? ? default:
? ? ? ? ? throw new Error ( ' Unknown filter: ' +filter)
? ?}
}
mapStateToProps會訂閱Store,每當state更新的時候,就會自動執行,重新計算UI組件的參數,從而觸發UI組件的重新渲染,
mapStateToProps的第一個參數總是state對象,還可以使用第二個參數,代表容器組件的props對象。
// 容器組件的代碼
// <FilterLink filter = "SHOW_ALL">
// ? ? ?ALL
// ?</FilterLink>
const mapStateToProps = (state, ownProps) =>{
return{
active : ownProps.filter===state.visibilityFilter
}
}
使用ownProps作為參數后,如果容器組件的參數發生變化,也會引發UI組件重新渲染。
connect方法可以省略mapStateToProps參數,那樣的話,UI組件就不會訂閱Store,就是說Store的更新不會引起UI組件的更新。
mapDispatchToProps()
mapDispatchToProps是connect函數的第二個參數,用來建立UI組件的參數到store.dispatch方法的映射。也就是說,它定義了哪些用戶的操作應該當做Action,傳給Store,他可以是一個函數,也可以是一個對象。
如果mapDispatchToProps是一個函數,會得到dispatch和ownProps(容器組件的props對象)兩個參數。
const mapDispatchToProps = {
? ? dispatch,
? ? ownprops
} = > {
? return {
? ? ?onClick: () =>{
? ? ?dispatch({
? ? ? ? ?type: ?'SET_VISIBILITY_FILTER',
? ? ? ? ?filter : ownProps.filter
? ? ? ? ? ?});
? ? ? ? }
? ? };
}
從上面代碼可以看出,mapDispatchToProps作為函數,應該返回一個對象,該對象的每一個鍵值對都是一個映射,定義了UI組件的參數怎么發出Action
如果mapDispatchToProps是一個對象,它的每個鍵名也是對應的UI組件的同名參數,鍵值應該是一個函數,會被當做Action Creator,返回的Action會由Redux自動發出。舉例來說,上面的mapDispatchToProps寫成對象就是下面這樣的
const mapDispatchToProps = ?{
? ?onClick : (filter) => {
? ? type:? 'SET_VISIBILITY_FILTER',
? ? filter : filter
};
}
<Provider>組件
connect 方法生成容器組件后,需要讓組件拿到state對象,才能生成UI組件的參數。React-Redux提供的Provider組件,可以讓容器組件拿到state。
import ?{ Provider } from 'react-redux'
import { createStore ?} from 'redux'
import todoApp from './reducers'
import App form ' ./components/App'
let store = createStore(todoApp);
render(
? ? <Provider store={store}>
? ? ? ? <App/>
? ? ?</Provider>
? ? ?document.getElementById('root')
)
上面代碼中,Provider在根組件外面包了一層,這樣一來,App的所有子組件就默認都可以拿到state啦。
React-Router路由庫
使用React-Route的項目,與其他項目沒有不同之處,也是使用Provider在Router外面包了一層,畢竟Provider的唯一功能就是傳入store對象。
const Root =( { store } ) => (
? ? <Provider store ={store}>
? ? ? ? <Router>
? ? ? ? ? ? ?<Route path="/" component= { App } />
? ? ? ? </Router>
? ? </Provider>
);
后記:本文屬于筆記,所有內容出自是云隨風的博客,謝謝大神