From React to Redux.

可有可無的 Redux

舉例:需求是吃飯犹撒,React 是做飯的工具或者吃飽的途徑,也就是解法颓屑。那么 Redux 是餐飲管理辦法斤寂。1個人吃飯的話,可以隨心隨便吃揪惦,也可以事無巨細規(guī)劃的很詳細遍搞。2個人天天一起吃飯就會有一些潛移默化的規(guī)則,如果太隨心的話經常會有一些矛盾器腋。如果10個人20個人溪猿,就一定需要一個行之有效的餐飲管理辦法钩杰。

Redux 就是這樣的一個管理辦法,他不是一定需要的诊县。即便是一個大型的 Web 大型項目讲弄,沒有 Redux 的加持,也有方法非常有效的迭代開發(fā)依痊。所以說避除,大多數(shù)情況,你可以不用它胸嘁,只用 React 就夠了驹饺。

再說了,Redux 也只是 Web 架構的一種解決方案缴渊。

為什么我們不需要 Redux

有人說過:

    "如果你不知道是否需要 Redux赏壹,那就是不需要它。"

任何場景都可以完全不用Redux衔沼,特殊的說有一些場景是完全沒有必要的蝌借,比如:

  1. 用戶的使用方式非常簡單
  2. 用戶之間沒有協(xié)作
  3. 不需要與服務器大量交互,也沒有使用 WebSocket
  4. 視圖層(View)只從單一來源獲取數(shù)據(jù)

為什么我們需要 Redux

Redux 的創(chuàng)造者 Dan Abramov 補充了一句:

    "只有遇到 React 實在解決不了的問題指蚁,你才需要 Redux 菩佑。"

前面說了那么多,但是Redux獨特能優(yōu)雅的解決一些問題:

  1. 某個組件的狀態(tài)凝化,需要共享
  2. 某個狀態(tài)需要在任何地方都可以拿到
  3. 一個組件需要改變全局狀態(tài)
  4. 一個組件需要改變另一個組件的狀態(tài)

是什么

Redux 是一個應用數(shù)據(jù)流框架

Redux 對于 JavaScript 應用而言是一個可預測狀態(tài)的容器稍坯。應用數(shù)據(jù)流框架,而不是傳統(tǒng)的像 underscore.js 或者 AngularJs 那樣的庫或者框架搓劫。

Redux 最主要是用作應用狀態(tài)的管理瞧哟。簡言之,Redux 用一個單獨的常量狀態(tài)樹(對象)保存這一整個應用的狀態(tài)枪向,這個對象不能直接被改變勤揩。當一些數(shù)據(jù)變化了,一個新的對象就會被創(chuàng)建(使用 actions 和 reducers)秘蛔。

如果要用一句話來概括Redux陨亡,那么可以使用官網(wǎng)的這句話:Redux是針對JavaScript應用的可預測狀態(tài)容器。此句話雖然簡單深员,但包含了以下幾個含義:

可預測性(predictable): 因為Redux用了reducer與純函數(shù)(pure function)的概念负蠕,每個新的state都會由舊的state建來一個全新的state。因而所有的狀態(tài)修改都是”可預測的”倦畅。
狀態(tài)容器(state container): state是集中在單一個對象樹狀結構下的單一store遮糖,store即是應用程序領域(app domain)的狀態(tài)集合。
JavaScript應用: 這說明Redux并不是單指設計給React用的滔迈,它是獨立的一個函數(shù)庫止吁,可通用于各種JavaScript應用被辑。

Redux基于簡化版本的Flux框架,F(xiàn)lux是Facebook開發(fā)的一個框架敬惦。在標準的MVC框架中盼理,數(shù)據(jù)可以在UI組件和存儲之間雙向流動,而Redux嚴格限制了數(shù)據(jù)只能在一個方向上流動俄删。

核心思想

  1. Web 應用是一個狀態(tài)機宏怔,視圖與狀態(tài)是一一對應的。
  2. 所有的狀態(tài)畴椰,保存在一個對象里面臊诊。

核心概念 和 API

ReduxMap.jpg

概念1、Store

Store 就是保存數(shù)據(jù)的地方斜脂,你可以把它看成一個容器抓艳。

前面說了,所有的狀態(tài)帚戳,保存在一個對象里面玷或,所以整個應用有且只能有一個 Store。

概念2片任、State

Store 之中有所有的數(shù)據(jù)偏友。那么 Store 的快照就是 State。當前的 State 通過store.getState()拿到对供。

Redux 規(guī)定位他, 一個 State 對應一個 View。只要 State 相同产场,View 就相同鹅髓。你知道 State,就知道 View 是什么樣涝动。

概念3迈勋、Action

State 的變化炬灭,會導致 View 的變化醋粟。但是,用戶接觸不到 State重归,只能接觸到 View米愿。所以,State 的變化必須是 View 導致的鼻吮。

Action 就是 View 發(fā)出的通知育苟,表示 State 應該要發(fā)生變化了。

Action 是一個對象椎木。其中的type屬性是必須的违柏,表示 Action 的名稱博烂。其他屬性可以自由設置,社區(qū)有一個規(guī)范可以參考漱竖。

const action = {
  type: 'ADD_TODO',
  payload: 'Learn Redux'
};

概念4禽篱、Action Creator

View 要發(fā)送多少種消息,就會有多少種 Action馍惹。如果都手寫躺率,會很麻煩⊥蚍可以定義一個函數(shù)來生成 Action悼吱,這個函數(shù)就叫 Action Creator。

const ADD_TODO = '添加 TODO';

function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}

const action = addTodo('Learn Redux');
上面代碼中良狈,addTodo函數(shù)就是一個 Action Creator后添。

概念5、store.dispatch()

store.dispatch()是 View 發(fā)出 Action 的唯一方法薪丁。

import { createStore } from 'redux';
const store = createStore(fn);

store.dispatch({
  type: 'ADD_TODO',
  payload: 'Learn Redux'
});

上面代碼中吕朵,store.dispatch接受一個 Action 對象作為參數(shù),將它發(fā)送出去窥突。
結合 Action Creator努溃,這段代碼可以改寫如下。

store.dispatch(addTodo('Learn Redux'));

概念6阻问、Reducer梧税,計算過程

Store 收到 Action 以后,必須給出一個新的 State称近,這樣 View 才會發(fā)生變化第队。這種 State 的計算過程就叫做 Reducer。
Reducer 是一個函數(shù)刨秆,它接受 Action 和當前 State 作為參數(shù)凳谦,返回一個新的 State。

const reducer = function (state, action) {
  // ...
  return new_state;
};

整個應用的初始狀態(tài)衡未,可以作為 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函數(shù)收到名為ADD的 Action 以后如失,就返回一個新的 State,作為加法的計算結果送粱。其他運算的邏輯(比如減法)褪贵,也可以根據(jù) Action 的不同來實現(xiàn)。

實際應用中,Reducer 函數(shù)不用像上面這樣手動調用脆丁,store.dispatch方法會觸發(fā) Reducer 的自動執(zhí)行世舰。為此,Store 需要知道 Reducer 函數(shù)槽卫,做法就是在生成 Store 的時候冯乘,將 Reducer 傳入createStore方法。

import { createStore } from 'redux';
const store = createStore(reducer);

上面代碼中晒夹,createStore接受 Reducer 作為參數(shù)裆馒,生成一個新的 Store。以后每當store.dispatch發(fā)送過來一個新的 Action丐怯,就會自動調用 Reducer喷好,得到新的 State。

為什么這個函數(shù)叫做 Reducer 呢读跷?因為它可以作為數(shù)組的reduce方法的參數(shù)梗搅。請看下面的例子,一系列 Action 對象按照順序作為一個數(shù)組效览。

const actions = [
  { type: 'ADD', payload: 0 },
  { type: 'ADD', payload: 1 },
  { type: 'ADD', payload: 2 }
];

const total = actions.reduce(reducer, 0); // 3

上面代碼中无切,數(shù)組actions表示依次有三個 Action,分別是加0丐枉、加1和加2哆键。數(shù)組的reduce方法接受 Reducer 函數(shù)作為參數(shù),就可以直接得到最終的狀態(tài)3瘦锹。

概念7籍嘹、純函數(shù)

Reducer 函數(shù)最重要的特征是,它是一個純函數(shù)弯院。也就是說辱士,只要是同樣的輸入,必定得到同樣的輸出听绳。

純函數(shù)是函數(shù)式編程的概念颂碘,必須遵守以下一些約束。

  1. 不得改寫參數(shù)
  2. 不能調用系統(tǒng) I/O 的API
  3. 不能調用Date.now()或者Math.random()等不純的方法椅挣,因為每次會得到不一樣的結果

由于 Reducer 是純函數(shù)头岔,就可以保證同樣的State,必定得到同樣的 View贴妻。但也正因為這一點切油,Reducer 函數(shù)里面不能改變 State,必須返回一個全新的對象名惩,請參考下面的寫法。

// State 是一個對象
function reducer(state, action) {
  return Object.assign({}, state, { thingToChange });
  // 或者
  return { ...state, ...newState };
}

// State 是一個數(shù)組
function reducer(state, action) {
  return [...state, newItem];
}

最好把 State 對象設成只讀孕荠。你沒法改變它娩鹉,要得到新的 State攻谁,唯一辦法就是生成一個新對象。這樣的好處是弯予,任何時候戚宦,與某個 View 對應的 State 總是一個不變的對象。

概念8锈嫩、store.subscribe()

Store 允許使用store.subscribe方法設置監(jiān)聽函數(shù)受楼,一旦 State 發(fā)生變化,就自動執(zhí)行這個函數(shù)呼寸。

import { createStore } from 'redux';
const store = createStore(reducer);

store.subscribe(listener);

顯然艳汽,只要把 View 的更新函數(shù)(對于 React 項目,就是組件的render方法或setState方法)放入listen对雪,就會實現(xiàn) View 的自動渲染河狐。

store.subscribe方法返回一個函數(shù),調用這個函數(shù)就可以解除監(jiān)聽蓉坎。

let unsubscribe = store.subscribe(() =>
  console.log(store.getState())
);

unsubscribe();

中間件是什么

異步操作怎么辦劫狠?Action 發(fā)出以后勃救,Reducer 立即算出 State,這叫做同步捐祠;Action 發(fā)出以后,過一段時間再執(zhí)行 Reducer桑李,這就是異步雏赦。

怎么才能 Reducer 在異步操作結束后自動執(zhí)行呢?這就要用到新的工具:中間件(middleware)芙扎。

目前的情形是這樣的:

  1. Reducer:純函數(shù)星岗,只承擔計算 State 的功能,不合適承擔其他功能戒洼,也承擔不了俏橘,因為理論上,純函數(shù)不能進行讀寫操作圈浇。
  2. View:與 State 一一對應寥掐,可以看作 State 的視覺層,也不合適承擔其他功能磷蜀。
  3. Action:存放數(shù)據(jù)的對象召耘,即消息的載體,只能被別人操作褐隆,自己不能進行任何操作污它。

中間件就是一個函數(shù),對store.dispatch方法進行了改造,在發(fā)出 Action 和執(zhí)行 Reducer 這兩步之間衫贬,添加了其他功能德澈。

在redux中,thunk是redux作者給出的中間件固惯,實現(xiàn)極為簡單梆造,10多行代碼:

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

//這幾行代碼做的事情也很簡單,判別action的類型葬毫,如果action是函數(shù)镇辉,就調用這個函數(shù),調用的步驟為:
//action(dispatch, getState, extraArgument);
//發(fā)現(xiàn)實參為dispatch和getState贴捡,因此我們在定義action為thunk函數(shù)是忽肛,一般形參為dispatch和getState。

redux-saga

redux-saga:action統(tǒng)一栈暇、可以集中處理異步操作等優(yōu)點外麻裁,redux-saga中使用聲明式的Effect以及提供了更加細膩的控制流。

Umi

項目使用Umi源祈,Umi的背后是DvaJS煎源,dva 首先是一個基于 redux 和 redux-saga 的數(shù)據(jù)流方案,然后為了簡化開發(fā)體驗香缺,dva 還額外內置了 react-router 和 fetch手销,所以也可以理解為一個輕量級的應用框架。

配合 umi 使用后图张,0 API

Redux for React

為了方便使用锋拖,Redux 的作者封裝了一個 React 專用的庫 React-Redux,本文主要介紹它祸轮。

這個庫是可以選用的兽埃。實際項目中,你應該權衡一下适袜,是直接使用 Redux柄错,還是使用 React-Redux。后者雖然提供了便利苦酱,但是需要掌握額外的 API售貌,并且要遵守它的組件拆分規(guī)范。

UI 組件 容器組件

React-Redux 將所有組件分成兩大類:UI 組件(presentational component)和容器組件(container component)疫萤。

UI 組件有以下幾個特征:

1. 只負責 UI 的呈現(xiàn)颂跨,不帶有任何業(yè)務邏輯
2. 沒有狀態(tài)(即不使用this.state這個變量)
3. 所有數(shù)據(jù)都由參數(shù)(this.props)提供
4. 不使用任何 Redux 的 API

因為不含有狀態(tài),UI 組件又稱為"純組件"扯饶,即它純函數(shù)一樣恒削,純粹由參數(shù)決定它的值池颈。

容器組件,恰好相反:

1. 負責管理數(shù)據(jù)和業(yè)務邏輯蔓同,不負責 UI 的呈現(xiàn)
2. 帶有內部狀態(tài)
3. 使用 Redux 的 API

如果一個組件既有 UI 又有業(yè)務邏輯饶辙,那怎么辦蹲诀?回答是斑粱,將它拆分成下面的結構:外面是一個容器組件,里面包了一個UI 組件脯爪。前者負責與外部的通信则北,將數(shù)據(jù)傳給后者,由后者渲染出視圖痕慢。

React-Redux 規(guī)定尚揣,所有的 UI 組件都由用戶提供,容器組件則是由 React-Redux 自動生成掖举。也就是說快骗,用戶負責視覺層,狀態(tài)管理則是全部交給它塔次。

@connect

React-Redux 提供connect方法方篮,用于從 UI 組件生成容器組件。connect的意思励负,就是將這兩種組件連起來藕溅。

import { connect } from 'react-redux'
const VisibleTodoList = connect()(TodoList);

使用Redux工具進行調試

Redux擁有很多第三方的調試工具,可用于分析代碼和修復bug继榆。最受歡迎的是time-travelling tool巾表,即redux-devtools-extension。設置它只需要三個步驟略吨。

Redux Devtools很強大集币。你可以在action, state和diff(方法差異)之間切換。選擇左側面板上的不同action翠忠,觀察狀態(tài)樹的變化鞠苟,你還可以通過進度條來播放actions序列。

Redux-saga

redux-saga是一個用于管理redux應用異步操作的中間件负间,redux-saga通過創(chuàng)建sagas將所有異步操作邏輯收集在一個地方集中處理偶妖,可以用來代替redux-thunk中間件。

這意味著應用的邏輯會存在兩個地方:

  1. reducer負責處理action的stage更新
  2. sagas負責協(xié)調那些復雜或者異步的操作

參考文章

引用

  1. 阮一峰 的 《React 入門實例教程》
  2. 阮一峰 的 《Redux 入門教程》
  3. redux原來如此簡單
  4. Redux 快速上手指南
  5. 為何要使用Redux
  6. Redux入門教程(快速上手)
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末政溃,一起剝皮案震驚了整個濱河市趾访,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌董虱,老刑警劉巖扼鞋,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件申鱼,死亡現(xiàn)場離奇詭異,居然都是意外死亡云头,警方通過查閱死者的電腦和手機捐友,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來溃槐,“玉大人匣砖,你說我怎么就攤上這事』璧危” “怎么了猴鲫?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長谣殊。 經常有香客問我拂共,道長,這世上最難降的妖魔是什么姻几? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任宜狐,我火速辦了婚禮,結果婚禮上蛇捌,老公的妹妹穿的比我還像新娘抚恒。我一直安慰自己,他們只是感情好豁陆,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布柑爸。 她就那樣靜靜地躺著,像睡著了一般盒音。 火紅的嫁衣襯著肌膚如雪表鳍。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天祥诽,我揣著相機與錄音譬圣,去河邊找鬼。 笑死雄坪,一個胖子當著我的面吹牛厘熟,可吹牛的內容都是我干的。 我是一名探鬼主播维哈,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼绳姨,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了阔挠?” 一聲冷哼從身側響起飘庄,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎购撼,沒想到半個月后跪削,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谴仙,經...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年碾盐,在試婚紗的時候發(fā)現(xiàn)自己被綠了晃跺。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡毫玖,死狀恐怖掀虎,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情孕豹,我是刑警寧澤涩盾,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布十气,位于F島的核電站励背,受9級特大地震影響,放射性物質發(fā)生泄漏砸西。R本人自食惡果不足惜叶眉,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望芹枷。 院中可真熱鬧衅疙,春花似錦、人聲如沸鸳慈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽走芋。三九已至绩郎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間翁逞,已是汗流浹背肋杖。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留挖函,地道東北人状植。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像怨喘,于是被迫代替她去往敵國和親津畸。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353