Redux基礎(chǔ)

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è)階段:

  1. 調(diào)用 store.dispatch(action)
    我們可以在任何地方調(diào)用該函數(shù);
  2. Redux的store調(diào)用我們提供的reducer函數(shù)
    store將傳遞兩個(gè)參數(shù)給reducer:當(dāng)前state treeaction, 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)該輸出相同的值

  1. 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ù),可以充分利用它

  1. 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ú)非就是兩種操作:

  1. 從Redux獲取state吕粹,然后將state 傳輸?shù)?Presentation component中
  2. 因?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)傳輸mapStateToPropsmapDispatchToProps

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ù)位置填寫: nullundefined

  • [mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or Function):

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市扳剿,隨后出現(xiàn)的幾起案子旁趟,更是在濱河造成了極大的恐慌,老刑警劉巖庇绽,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锡搜,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡瞧掺,警方通過(guò)查閱死者的電腦和手機(jī)耕餐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)辟狈,“玉大人蛾方,你說(shuō)我怎么就攤上這事∩仙拢” “怎么了桩砰?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)释簿。 經(jīng)常有香客問(wèn)我亚隅,道長(zhǎng),這世上最難降的妖魔是什么庶溶? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任煮纵,我火速辦了婚禮,結(jié)果婚禮上偏螺,老公的妹妹穿的比我還像新娘行疏。我一直安慰自己,他們只是感情好套像,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布酿联。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪贞让。 梳的紋絲不亂的頭發(fā)上周崭,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音喳张,去河邊找鬼续镇。 笑死,一個(gè)胖子當(dāng)著我的面吹牛销部,可吹牛的內(nèi)容都是我干的摸航。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼舅桩,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼酱虎!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起江咳,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎哥放,沒(méi)想到半個(gè)月后歼指,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡甥雕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年踩身,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片社露。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡挟阻,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出峭弟,到底是詐尸還是另有隱情附鸽,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布瞒瘸,位于F島的核電站坷备,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏情臭。R本人自食惡果不足惜省撑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望俯在。 院中可真熱鬧竟秫,春花似錦、人聲如沸跷乐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至拙吉,卻和暖如春潮孽,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背筷黔。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工往史, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人佛舱。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓椎例,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親请祖。 傳聞我的和親對(duì)象是個(gè)殘疾皇子订歪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容