1-Redux

Redux

[TOC]

知識(shí)點(diǎn)

  • 狀態(tài)管理器
  • state 對象
  • reducer 純函數(shù)
  • store 對象
  • action 對象
  • combineReducers 方法
  • react-redux
  • provider 組件
  • connect 方法
  • Redux DevTools extension 工具
  • 中間件 - middleware
  • redux-chunk

狀態(tài)(數(shù)據(jù))管理

前端應(yīng)用越來越復(fù)雜,要管理和維護(hù)的數(shù)據(jù)也越來越多肪获,為了有效的管理和維護(hù)應(yīng)用中的各種數(shù)據(jù),我們必須有一種強(qiáng)大而有效的數(shù)據(jù)管理機(jī)制高诺,也稱為狀態(tài)管理機(jī)制趴樱,<u>Redux</u> 就是解決該問題的

Redux

<u>Redux</u> 是一個(gè)獨(dú)立的 <u>JavaScript</u> 狀態(tài)管理庫狮斗,與非 <u>React</u> 內(nèi)容之一

https://www.bootcdn.cn/redux/

核心概念

理解 <u>Redux</u> 核心幾個(gè)概念與它們之間的關(guān)系

  • state
  • reducer
  • store
  • action

<u>state</u> 對象

通常我們會(huì)把應(yīng)用中的數(shù)據(jù)存儲(chǔ)到一個(gè)對象樹(<u>Object Tree</u>) 中進(jìn)行統(tǒng)一管理牧抵,我們把這個(gè)對象樹稱為:<u>state</u>

<u>state</u> 是只讀的

這里需要注意的是灭袁,為了保證數(shù)據(jù)狀態(tài)的可維護(hù)和測試实昨,不推薦直接修改 <u>state</u> 中的原數(shù)據(jù)

通過純函數(shù)修改 <u>state</u>

什么是純函數(shù)洞豁?

純函數(shù)
  1. 相同的輸入永遠(yuǎn)返回相同的輸出
  2. 不修改函數(shù)的輸入值
  3. 不依賴外部環(huán)境狀態(tài)
  4. 無任何副作用

使用純函數(shù)的好處

  1. 便于測試
  2. 有利重構(gòu)

Reducer 函數(shù)

function todo(state, action) {
  switch(action.type) {
    case 'ADD':
      return [...state, action.payload];
      break;
    case 'REMOVE':
      return state.filter(v=>v!==action.payload);
      break;
    default:
      return state;
    }
}

上面的 <u>todo</u> 函數(shù)就是 <u>Reducer</u> 函數(shù)

  1. 第一個(gè)參數(shù)是原 <u>state</u> 對象
  2. <u>Reducer</u> 函數(shù)不能修改原 <u>state</u>,而應(yīng)該返回一個(gè)新的 <u>state</u>
  3. 第二參數(shù)是一個(gè) <u>action</u> 對象荒给,包含要執(zhí)行的操作和數(shù)據(jù)
  4. 如果沒有操作匹配丈挟,則返回原 <u>state</u> 對象

action 對象

我們對 <u>state</u> 的修改是通過 <u>reducer</u> 純函數(shù)來進(jìn)行的,同時(shí)通過傳入的 <u>action</u> 來執(zhí)行具體的操作志电,<u>action</u> 是一個(gè)對象

  • type 屬性 : 表示要進(jìn)行操作的動(dòng)作類型曙咽,增刪改查……
  • payload屬性 : 操作 <u>state</u> 的同時(shí)傳入的數(shù)據(jù)

但是這里需要注意的是,我們不直接去調(diào)用 <u>Reducer</u> 函數(shù)挑辆,而是通過 <u>Store</u> 對象提供的 <u>dispatch</u> 方法來調(diào)用

Store 對象

為了對 <u>state</u>例朱,<u>Reducer</u>,<u>action</u> 進(jìn)行統(tǒng)一管理和維護(hù)鱼蝉,我們需要?jiǎng)?chuàng)建一個(gè) <u>Store</u> 對象

Redux.createStore 方法
let store = Redux.createStore((state, action) => {
  // ...
}, []);

todo

用戶操作數(shù)據(jù)的 <u>reducer</u> 函數(shù)

[]

初始化的 <u>state</u>

我們也可以使用 <u>es6</u> 的函數(shù)參數(shù)默認(rèn)值來對 <u>state</u> 進(jìn)行初始化

let store = Redux.createStore( (state = [], action) => {
  // ...
} )
getState() 方法

通過 <u>getState</u> 方法洒嗤,可以獲取 <u>Store</u> 中的 <u>state</u>

store.getState();
dispatch() 方法

通過 <u>dispatch</u> 方法,可以提交更改

store.dispatch({
  type: 'ADD',
  payload: 'MT'
})
<u>action</u> 創(chuàng)建函數(shù)

<u>action</u> 是一個(gè)對象魁亦,用來在 <u>dispatch</u> 的時(shí)候傳遞動(dòng)作和數(shù)據(jù)渔隶,我們在實(shí)際業(yè)務(wù)中可能會(huì)中許多不同的地方進(jìn)行同樣的操作,這個(gè)時(shí)候洁奈,我們可以創(chuàng)建一個(gè)函數(shù)用來生成(返回)<u>action</u>

function add(payload) {
  return {
    type: 'ADD',
    payload
  }
}

store.dispatch(add('MT'));
store.dispatch(add('Reci'));
...
subscribe() 方法

可以通過 <u>subscribe</u> 方法注冊監(jiān)聽器(類似事件)间唉,每次 <u>dispatch</u> <u>action</u> 的時(shí)候都會(huì)執(zhí)行監(jiān)聽函數(shù),該方法返回一個(gè)函數(shù)利术,通過該函數(shù)可以取消當(dāng)前監(jiān)聽器

let unsubscribe = sotre.subscribe(function() {
  console.log(store.getState());
});
unsubscribe();

Redux 工作流

[圖片上傳失敗...(image-bba1ee-1614260835998)]

Reducers 分拆與融合

當(dāng)一個(gè)應(yīng)用比較復(fù)雜的時(shí)候呈野,狀態(tài)數(shù)據(jù)也會(huì)比較多,如果所有狀態(tài)都是通過一個(gè) <u>Reducer</u> 來進(jìn)行修改的話印叁,那么這個(gè) <u>Reducer</u> 就會(huì)變得特別復(fù)雜际跪。這個(gè)時(shí)候,我們就會(huì)對這個(gè) <u>Reducer</u> 進(jìn)行必要的拆分

let datas = {
  user: {},
  items: []
  cart: []
}

我們把上面的 <u>users</u>喉钢、<u>items</u>姆打、<u>cart</u> 進(jìn)行分拆

// user reducer
function user(state = {}, action) {
  // ...
}
// items reducer
function items(state = [], action) {
  // ...
}
// cart reducer
function cart(state = [], action) {
  // ...
}

<u>combineReducers</u> 方法

該方法的作用是可以把多個(gè) <u>reducer</u> 函數(shù)合并成一個(gè) <u>reducer</u>

let reducers = Redux.combineReducers({
  user,
  items,
  cart
});

let store = createStore(reducers);

react-redux

再次強(qiáng)調(diào)的是,<u>redux</u> 與 <u>react</u> 并沒有直接關(guān)系肠虽,它是一個(gè)獨(dú)立的 <u>JavaScript</u> 狀態(tài)管理庫幔戏,如果我們希望中 <u>React</u> 中使用 <u>Redux</u>,需要先安裝 <u>react-redux</u>

<u>react-redux</u>

安裝

npm i -S redux react-redux
// ./store/reducer/user.js
let user = {
    id: 0,
    username: ''
};

export default (state = user, action) => {
    switch (action.type) {
        default:
            return state;
    }
}
// ./store/reducer/users.js
let users = [{
    id: 1,
    username: 'baoge',
    password: '123'
},
{
    id: 2,
    username: 'MT',
    password: '123'
},
{
    id: 3,
    username: 'dahai',
    password: '123'
},
{
    id: 4,
    username: 'zMouse',
    password: '123'
}];

export default (state = users, action) => {
    switch (action.type) {
        default:
            return state;
    }
}
// ./store/reducer/items.js
let items = [
    {
        id: 1,
        name: 'iPhone XR',
        price: 542500
    },
    {
        id: 2,
        name: 'Apple iPad Air 3',
        price: 377700
    },
    {
        id: 3,
        name: 'Macbook Pro 15.4',
        price: 1949900
    },
    {
        id: 4,
        name: 'Apple iMac',
        price: 1629900
    },
    {
        id: 5,
        name: 'Apple Magic Mouse',
        price: 72900
    },
    {
        id: 6,
        name: 'Apple Watch Series 4',
        price: 599900
    }
];

export default (state = items, action) => {
    switch (action.type) {
        default:
            return state;
    }
}
// ./store/reducer/cart.js
export default (state = [], action) => {
    switch (action.type) {
        default:
            return state;
    }
}
// ./store/index.js
import {createStore, combineReducers} from 'redux';

import user from './reducer/user';
import users from './reducer/users';
import items from './reducer/items';
import cart from './reducer/cart';

let reducers = combineReducers({
    user,
    users,
    items,
    cart
});

const store = createStore(reducers);

export default store;

<u>Provider</u> 組件

想在 <u>React</u> 中使用 <u>Redux</u> 税课,還需要通過 <u>react-redux</u> 提供的 <u>Provider</u> 容器組件把 <u>store</u> 注入到應(yīng)用中

// index.js
import {Provider} from 'react-redux';
import store from './store';

ReactDOM.render(
    <Provider store={store}>
        <Router>
            <App />
        </Router>
    </Provider>, 
    document.getElementById('root')
);

<u>connect</u> 方法

有了 <u>connect</u> 方法闲延,我們不需要通過 <u>props</u> 一層層的進(jìn)行傳遞痊剖, 類似路由中的 <u>withRouter</u> ,我們只需要在用到 <u> store</u> 的組件中垒玲,通過 <u>react-redux</u> 提供的 <u>connect</u> 方法陆馁,把 <u>store</u> 注入到組件的 <u>props</u> 中就可以使用了

import {connect} from 'react-redux';

class Main extends React.Component {
  render() {
    console.log(this.props);
  }
}

export default connect()(Main);

默認(rèn)情況下,<u>connect</u> 會(huì)自動(dòng)注入 <u>dispatch</u> 方法

注入 <u>state</u> 到 <u>props</u>
export default connect( state => {
  return {
    items: state.items
  }
} )(Main);

<u>connect</u> 方法的第一個(gè)參數(shù)是一個(gè)函數(shù)

  • 該函數(shù)的第一個(gè)參數(shù)就是 <u>store</u> 中的 <u>state</u> : store.getState()
  • 該函數(shù)的返回值將被解構(gòu)賦值給 <u>props</u> : this.props.items

Redux DevTools extension

為了能夠更加方便的對 <u>redux</u> 數(shù)據(jù)進(jìn)行觀測和管理合愈,我們可以使用 <u>Redux DevTools extension</u> 這個(gè)瀏覽器擴(kuò)展插件

https://github.com/zalmoxisus/redux-devtools-extension

const store = createStore(
    reducers,
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);

打開瀏覽器開發(fā)者工具面板 -> redux

異步 action

許多時(shí)候叮贩,我們的數(shù)據(jù)是需要和后端進(jìn)行通信的,而且中開發(fā)模式下佛析,很容易就會(huì)出現(xiàn)跨域請求的問題益老,好在 <u>create-react-app</u> 中內(nèi)置了一個(gè)基于 <u>node</u> 的后端代理服務(wù),我們只需要少量的配置就可以實(shí)現(xiàn)跨域

package.json 配置

相對比較的簡單的后端 <u>URL</u> 接口寸莫,我們可以直接中 <u>package.json</u> 文件中進(jìn)行配置

// 后端接口
http://localhost:7777/api/items
// http://localhost:3000
{
  ...
  //
  "proxy": "http://localhost:7777"
}
axios({
  url: '/api/items'
});

./src/setupProxy.js 配置

針對相對復(fù)雜的情況捺萌,可以有更多的配置

npm i -S http-proxy-middleware
// setupProxy.js
const proxy = require('http-proxy-middleware');

module.exports = function(app) {
    app.use(
        proxy('/api', {
            target: 'http://localhost:7777/',
            pathRewrite: {
                '^/api': ''
            }
        })
    );
};

其它代碼同上

Middleware

默認(rèn)情況下,<u>dispatch</u> 是同步的膘茎,我們需要用到一些中間件來處理

const logger = store => next => action => {
  console.group(action.type)
  console.info('dispatching', action)
  let result = next(action)
  console.log('next state', store.getState())
  console.groupEnd(action.type)
  return result
}

redux.applyMiddleware

通過 <u>applyMiddleware</u> 方法桃纯,我們可以給 <u>store</u> 注冊多個(gè)中間件

注意:<u>devTools</u> 的使用需要修改一下配置

npm i -D redux-devtools-extension
...
import { composeWithDevTools } from 'redux-devtools-extension';
...
const store = createStore( 
  reducres, 
  composeWithDevTools(
    applyMiddleware( logger )
  )
)

redux-thunk

這是一個(gè)把同步 <u>dispatch</u> 變成異步 <u>dispatch</u> 的中間件

安裝

npm i -S redux-thunk
import {createStore, combineReducers, applyMiddleware} from 'redux';
import thunk from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension';

import user from './reducer/user';
import items from './reducer/items';
import cart from './reducer/cart';

let reducers = combineReducers({
    user,
    items,
    cart
});

const store = createStore(
    reducers,
    composeWithDevTools(applyMiddleware(
        thunk
    ))
);
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市披坏,隨后出現(xiàn)的幾起案子态坦,更是在濱河造成了極大的恐慌,老刑警劉巖刮萌,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件驮配,死亡現(xiàn)場離奇詭異娘扩,居然都是意外死亡着茸,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門琐旁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來涮阔,“玉大人,你說我怎么就攤上這事灰殴【刺兀” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵牺陶,是天一觀的道長伟阔。 經(jīng)常有香客問我,道長掰伸,這世上最難降的妖魔是什么皱炉? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮狮鸭,結(jié)果婚禮上合搅,老公的妹妹穿的比我還像新娘多搀。我一直安慰自己,他們只是感情好灾部,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布康铭。 她就那樣靜靜地躺著,像睡著了一般赌髓。 火紅的嫁衣襯著肌膚如雪从藤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天春弥,我揣著相機(jī)與錄音呛哟,去河邊找鬼。 笑死匿沛,一個(gè)胖子當(dāng)著我的面吹牛扫责,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播逃呼,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼鳖孤,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了抡笼?” 一聲冷哼從身側(cè)響起苏揣,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎推姻,沒想到半個(gè)月后平匈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡藏古,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年增炭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拧晕。...
    茶點(diǎn)故事閱讀 39,965評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡隙姿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出厂捞,到底是詐尸還是另有隱情输玷,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布靡馁,位于F島的核電站欲鹏,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏臭墨。R本人自食惡果不足惜赔嚎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望裙犹。 院中可真熱鬧尽狠,春花似錦衔憨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至沉馆,卻和暖如春码党,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背斥黑。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工揖盘, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人锌奴。 一個(gè)月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓兽狭,卻偏偏與公主長得像,于是被迫代替她去往敵國和親鹿蜀。 傳聞我的和親對象是個(gè)殘疾皇子箕慧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評論 2 355

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