Actions
Actions是用于存放數(shù)據(jù)的載體,通過(guò)store.dispatch()
函數(shù)來(lái)將數(shù)據(jù)從app發(fā)送到store
Actions 是一個(gè)Js對(duì)象霹琼,它有一個(gè)默認(rèn)的屬性:type
,它用于表明action的類型做盅,它的值是一個(gè)字符串常量
定義一個(gè)Actions:
addTodo = {
type: 'ADD_TODO',
text: 'Build my first Redux app'
}
Action Creators
上面我們定義了一個(gè)actions轿偎,現(xiàn)在需要一個(gè)Action Creators,用于創(chuàng)造actions
在Redux中creators 僅僅是返回一個(gè)action罚攀, 如下:
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
在傳統(tǒng)的 Flux 中,當(dāng)action cretors被調(diào)用時(shí) 常常用于觸發(fā)一個(gè)dispatch雌澄,如下:
function addTodoWithDispatch(text) {
const action = {
type: ADD_TODO,
text
}
dispatch(action)
}
在Redux中不是這樣的斋泄,它并不與dispatch綁定,如下:
dispatch(addTodo(text))
dispatch(completeTodo(index))
另外镐牺,我們可以將其封裝一下炫掐,如下:
const boundAddTodo = text => dispatch(addTodo(text))
const boundCompleteTodo = index => dispatch(completeTodo(index))
這樣,我們就可以直接調(diào)用他們睬涧,如下:
boundAddTodo(text)
boundCompleteTodo(index)
actions.js部分
/*
* action types
*/
export const ADD_TODO = 'ADD_TODO'
export const TOGGLE_TODO = 'TOGGLE_TODO'
export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER'
/*
* other constants
*/
export const VisibilityFilters = {
SHOW_ALL: 'SHOW_ALL',
SHOW_COMPLETED: 'SHOW_COMPLETED',
SHOW_ACTIVE: 'SHOW_ACTIVE'
}
/*
* action creators
*/
export function addTodo(text) {
return { type: ADD_TODO, text }
}
export function toggleTodo(index) {
return { type: TOGGLE_TODO, index }
}
export function setVisibilityFilter(filter) {
return { type: SET_VISIBILITY_FILTER, filter }
}
Reducers
Actions描述了有事情發(fā)生募胃,但是沒(méi)有指定在響應(yīng)中,app的state應(yīng)該如何改變畦浓,這個(gè)工作由 Reducers來(lái)完成
設(shè)計(jì)state
在Redux中痹束,所有的state都被存儲(chǔ)到一個(gè)單一的對(duì)象上,因此在寫代碼之前讶请,先思考狀態(tài)的設(shè)計(jì)祷嘶,是一個(gè)良好的習(xí)慣,
在實(shí)際應(yīng)用中夺溢,我們經(jīng)常需要存儲(chǔ)一些數(shù)據(jù)论巍,以及一些UI state在state tree上,最好做到:保持?jǐn)?shù)據(jù)與UI狀態(tài)分離
設(shè)state時(shí)风响,盡量要保持標(biāo)準(zhǔn)化嘉汰,不要有任何嵌套,參考JSON數(shù)據(jù)標(biāo)準(zhǔn)化
處理狀態(tài)
reducer函數(shù)是一個(gè)純函數(shù)钞诡,它接收兩個(gè)參數(shù):prestate和一個(gè)action郑现,然后返回下一個(gè)state,如下:
(previousState, action) => newState
純函數(shù):就是傳入什么荧降,輸出什么接箫,不會(huì)改變輸入,沒(méi)有副作用
注意:一定要保證reducer是一個(gè)純函數(shù)朵诫,有以下幾點(diǎn)要注意:
- 不要修改它的傳入?yún)?shù)
- 不要執(zhí)行API的調(diào)用和路由轉(zhuǎn)換等有副作用的操作
- 不要調(diào)用不純的函數(shù)辛友,如:Date.now() 、Math.random()
也就是:給reducer參數(shù)剪返,它應(yīng)該計(jì)算出next state废累,然后返回next state,不能有副作用脱盲、不能有API調(diào)用邑滨,僅僅只是計(jì)算
接下來(lái)定義一個(gè)reducer,并設(shè)置它的初始狀態(tài), 如下:
import { VisibilityFilters } from './actions'
const initialState = {
visibilityFilter: VisibilityFilters.SHOW_ALL,
todos: []
}
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
})
default:
return state
}
}
最終分割reducer之后的代碼钱反,如下:
reducers.js
import { combineReducers } from 'redux'
import {
ADD_TODO,
TOGGLE_TODO,
SET_VISIBILITY_FILTER,
VisibilityFilters
} from './actions'
const { SHOW_ALL } = VisibilityFilters
function visibilityFilter(state = SHOW_ALL, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return action.filter
default:
return state
}
}
function todos(state = [], action) {
switch (action.type) {
case ADD_TODO:
return [
...state,
{
text: action.text,
completed: false
}
]
case TOGGLE_TODO:
return state.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {
completed: !todo.completed
})
}
return todo
})
default:
return state
}
}
const todoApp = combineReducers({
visibilityFilter,
todos
})
export default todoApp
Store
之前講過(guò)Actions表示發(fā)生了什么掖看,Reducers就是根據(jù)Actions來(lái)更新state,
而Store是將它們組合在一起面哥,
Store有以下職責(zé):
- Holds application state;
- Allows access to state via getState();
- Allows state to be updated via dispatch(action);
- Registers listeners via subscribe(listener);
Handles unregistering of listeners via the function returned by subscribe(listener).
需要注意的是在一個(gè)Redux應(yīng)用中哎壳,只有一個(gè)Store,如果需要分割應(yīng)用的數(shù)據(jù)邏輯尚卫,可以使用reducer composition 來(lái)實(shí)現(xiàn)
使用createStore归榕,如下:
import { createStore } from 'redux'
import todoApp from './reducers'
let store = createStore(todoApp)
Data Flow 數(shù)據(jù)流向
Redux遵循嚴(yán)格的單向數(shù)據(jù)流,這意味著所有的數(shù)據(jù)在APP中的流向具有相同的生命周期吱涉,且它們是可預(yù)測(cè)的刹泄,一個(gè)數(shù)據(jù)的生命周期主要包括以下四個(gè)階段:
- 調(diào)用
store.dispatch(action)
我們可以在任何地方調(diào)用該函數(shù); - Redux的store調(diào)用我們提供的reducer函數(shù)
store將傳遞兩個(gè)參數(shù)給reducer:當(dāng)前state tree和action, reducer函數(shù)會(huì)返回下一個(gè)start邑飒,它是一個(gè)純函數(shù)
例如循签,上面的todo代碼
// The current application state (list of todos and chosen filter)
let previousState = {
visibleTodoFilter: 'SHOW_ALL',
todos: [
{
text: 'Read the docs.',
complete: false
}
]
}
// The action being performed (adding a todo)
let action = {
type: 'ADD_TODO',
text: 'Understand the flow.'
}
// Your reducer returns the next application state
let nextState = todoApp(previousState, action)
注意: reducer函數(shù)是可預(yù)測(cè)的,當(dāng)輸入相同的參數(shù)疙咸,調(diào)用它很多次县匠,它應(yīng)該輸出相同的值
- root reducer 可以將多個(gè)reducer輸出組合到一個(gè)state tree中
root reducer的結(jié)構(gòu)完全由我們自己來(lái)決定,Redux提供了一個(gè)combineReducers()
函數(shù)來(lái)幫助我們將多個(gè)reducer合并為一個(gè)撒轮,如下所示:
function todos(state = [], action) {
// Somehow calculate it...
return nextState
}
function visibleTodoFilter(state = 'SHOW_ALL', action) {
// Somehow calculate it...
return nextState
}
let todoApp = combineReducers({
todos,
visibleTodoFilter
})
當(dāng)觸發(fā)一個(gè)action時(shí)乞旦,todoApp通過(guò)combineReducers將會(huì)返回下面兩個(gè)reducers:
let nextTodos = todos(state.todos, action)
let nextVisibleTodoFilter = visibleTodoFilter(state.visibleTodoFilter, action)
然后它將所有的結(jié)果合并到一個(gè)的單一的state tree中:
return {
todos: nextTodos,
visibleTodoFilter: nextVisibleTodoFilter
}
combineReducers()
是一個(gè)很方便的函數(shù),可以充分利用它
- Redux store 將root reducer返回的state tree保存起來(lái)
這個(gè)新的state tree 就是app的下一個(gè) state tree题山,接下來(lái)兰粉,每一個(gè)通過(guò)store.subscribe(listener)
注冊(cè)的listener都將被調(diào)用,listener可以通過(guò)調(diào)用store.getstate()
去獲取最新的state tree
現(xiàn)在顶瞳,如果使用了react玖姑,那么可以通過(guò)component.setState(newState)
來(lái)更新UI
與React配合使用
展示和容器組件(Presentational and Container Component)
Presentational Component | Container Component | |
---|---|---|
Purpose | How thing look(markup, styles) | How thing work(data fetching,data updates |
Awre of Redux | No | Yes |
To read data | Read data from pops | Subscribe to Redux data |
To change data | Invoke callbacks from props | Dispatch Redux actions |
Are written | By hand | Usually generated by React Redux |
注意一點(diǎn):Redux配合React使用時(shí)愕秫,加強(qiáng)了展示與容器組件分離這一原則
關(guān)于兩者的對(duì)比如下:
Presentational Component | Container Component | |
---|---|---|
Purpose | How thing look(markup, styles) | How thing work(data fetching,data updates |
Awre of Redux | No | Yes |
To read data | Read data from pops | Subscribe to Redux data |
To change data | Invoke callbacks from props | Dispatch Redux actions |
Are written | By hand | Usually generated by React Redux |
大多數(shù)情況下,我們寫的都是Presentation Component焰络,而展示組件本身并不存儲(chǔ)數(shù)據(jù)戴甩,它通過(guò)Props來(lái)獲得數(shù)據(jù),因此闪彼,還需要編寫一些Container Component甜孤,通過(guò)它從Redux獲取數(shù)據(jù),并傳給Presentation Component畏腕;注意:雖然我們可以將store.subscribe()
寫入Container component來(lái)獲取數(shù)據(jù)缴川,但是不推薦這樣做,因?yàn)镽edux在內(nèi)部做了很多性能優(yōu)化描馅,而這些優(yōu)化是我們手動(dòng)無(wú)法達(dá)成的把夸,最好的辦法是:使用Redux提供的connect()
方法來(lái)生成,而不是寫入Container component
Designing Component Hierarchy
設(shè)計(jì)組件的層級(jí)時(shí)铭污,我們要將其匹配root state object扎即,在設(shè)計(jì)之前,對(duì)功能做一個(gè)簡(jiǎn)短的陳述很有必要
設(shè)計(jì)Presentation component
幾個(gè)原則:
- 當(dāng)在設(shè)計(jì)展示組件時(shí)况凉,不要考慮它如何獲取數(shù)據(jù)谚鄙,例如它是如何綁定Redux,先不需要考慮這些j刁绒,這樣便可以將其與Container component 分離開來(lái)岳颇,這些Presentation component就成為一個(gè)獨(dú)立的組件皮仁,沒(méi)有任何牽連赵誓,提供了它的復(fù)用性
- 一般來(lái)說(shuō)活孩,Presentation component 本身是不保持狀態(tài)的(stateless component)或者說(shuō)是不存儲(chǔ)數(shù)據(jù),所以嫂丙,我們?cè)谧铋_始可以將它們?cè)O(shè)計(jì)為一個(gè)function而不是class娘赴,當(dāng)隨著程序的擴(kuò)展,它們需要local state 或者使用 lifecycle methods跟啤,我們可以將它們轉(zhuǎn)換為class
Implementing Container Components
一般說(shuō)來(lái)诽表,Container componennt是一個(gè)React組件,它主要通過(guò)調(diào)用store.subscribe()
來(lái)從Redux中獲取state隅肥,并通過(guò)props將state傳入到它負(fù)責(zé)渲染的Presentation component中竿奏,然后Presentation component 根據(jù)傳入的props進(jìn)行渲染;雖然腥放,我們可以自己編寫Container component泛啸,但是推薦使用Redux提供的connetc()
來(lái)進(jìn)行編寫。
對(duì)于Container component 來(lái)說(shuō)秃症,它與Redux進(jìn)行通信候址,無(wú)非就是兩種操作:
- 從Redux獲取state吕粹,然后將state 傳輸?shù)?Presentation component中
- 因?yàn)镻resentation component是最接近用戶的,因此肯定需要處理一些用戶的操作岗仑,當(dāng)用戶操作了昂芜,便需要對(duì)Redux中的state 進(jìn)行更新,而Presentation component不直接對(duì)state進(jìn)行更新赔蒲,而是通過(guò)調(diào)用dispatch來(lái)分發(fā)事件來(lái)對(duì)state進(jìn)行更新,而這個(gè)事件也是通過(guò)Container來(lái)定義良漱,并通過(guò)props傳遞個(gè)Presentation component
React是基于MVC架構(gòu)的舞虱,所以可以通過(guò)MVC模型來(lái)理解這個(gè)過(guò)程:
Presentation component: 是View層
Container component:是Controller層
Redux:是Model層
View層不能直接與Model層進(jìn)行交互,只能通過(guò)Controller來(lái)連接View層和Model層
明確這點(diǎn)之后母市,再看我們這個(gè)例子矾兜,我們?cè)贑ontainer component 中定義了一個(gè)方法來(lái)從Redux中獲取數(shù)據(jù)(Controller 從 Model中取數(shù)據(jù)),然后,我們需要將這個(gè)數(shù)據(jù)傳輸?shù)絇resentation component(Controller 傳輸數(shù)據(jù)到 View層)患久,Redux提供了一個(gè)接口mapStateToProps
椅寺,來(lái)便于我們高效的進(jìn)行傳輸,這是第一種操作蒋失。
Presentation component(View層)是最接近用戶的返帕,因此,它需要處理用戶的操作篙挽;而我們知道Presentation component是不保存數(shù)據(jù)荆萤,而用戶的操作可能需要更改數(shù)據(jù),因此它需要通過(guò)Container component來(lái)處理Presentation component的操作铣卡,對(duì)Redux中的數(shù)據(jù)進(jìn)行更改链韭。
對(duì)應(yīng)到這個(gè)例子中就是:我們?cè)贑ontainer component中定義一些事件處理函數(shù),并將其綁定到Presentation component中(通過(guò)props傳遞)煮落,在Redux中敞峭,對(duì)數(shù)據(jù)更新是通過(guò)store.dispatch()
來(lái)分發(fā)action處理的, 所以我們的事件處理函數(shù)就是要通過(guò)store.dispatch()
來(lái)分發(fā)action蝉仇;Redux提供了一個(gè)接口mapDispacthToProps()
旋讹,來(lái)便于我們將dispatch通過(guò)props傳輸?shù)絇resentation component中
View層要處理用戶的操作,這個(gè)過(guò)程就是View 通知 Controller 有事件發(fā)生了轿衔,Controller再通知Redux進(jìn)行數(shù)據(jù)更新骗村,
從Redux獲取數(shù)據(jù)
通過(guò) mapStateToProps 將數(shù)據(jù)傳入Presentation component中
為了使用connect()
,我們需要定義一個(gè)叫做mapStateToProps
的函數(shù)呀枢,該函數(shù)會(huì)告訴我們?nèi)绾螌?dāng)前的Redux store state 傳遞到該Container component 負(fù)責(zé)渲染的 presentation component的props中
例如:VisibleTodoList組件(它是Container component)需要根據(jù)傳入的TodoList計(jì)算出todos胚股,因此,我們定義一個(gè)函數(shù)裙秋,它根據(jù)state.visibilityFilter
來(lái)過(guò)濾出state.todos
琅拌,然后在它的mapStateToProps
中調(diào)用它缨伊,如下:
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)
}
}
const mapStateToProps = state => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
}
分發(fā)事件來(lái)更新Redux
const mapDispatchToProps = dispatch => {
return {
onTodoClick: id => {
dispatch(toggleTodo(id))
}
}
}
通過(guò) connnet()
來(lái)傳輸mapStateToProps
和mapDispatchToProps
import { connect } from 'react-redux'
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
export default VisibleTodoList
這個(gè)便是我們的一個(gè) Container component,也就是Controller 層
containers/VisibleTodoList.js
import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'
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)
}
}
const mapStateToProps = state => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
}
const mapDispatchToProps = dispatch => {
return {
onTodoClick: id => {
dispatch(toggleTodo(id))
}
}
}
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
export default VisibleTodoList
關(guān)于connect()方法的使用
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
作用:將React 組件與Redux store 連接起來(lái)
它不會(huì)更改傳入的組件进宝,而是將一個(gè)與Redux綁定的新的組件返回給你
Arguments:
[mapStateToProps(state, [ownProps]):stateProps](function)
:如果這個(gè)參數(shù)被指定刻坊,那么返回的新的組件將會(huì)subscribe(訂閱) Redux store的更新,也就是說(shuō)不管Redux store什么時(shí)候被更新党晋,mapStateToProps都將會(huì)被調(diào)用谭胚,它是一個(gè)純對(duì)象,會(huì)被合并新組件的props上未玻,如果你不想要訂閱 Redux store 的更新灾而,那么就在該參數(shù)位置填寫:null
或undefined
[mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or Function):
: