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)容之一
核心概念
理解 <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ù)
- 相同的輸入永遠(yuǎn)返回相同的輸出
- 不修改函數(shù)的輸入值
- 不依賴外部環(huán)境狀態(tài)
- 無任何副作用
使用純函數(shù)的好處
- 便于測試
- 有利重構(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ù)
- 第一個(gè)參數(shù)是原 <u>state</u> 對象
- <u>Reducer</u> 函數(shù)不能修改原 <u>state</u>,而應(yīng)該返回一個(gè)新的 <u>state</u>
- 第二參數(shù)是一個(gè) <u>action</u> 對象荒给,包含要執(zhí)行的操作和數(shù)據(jù)
- 如果沒有操作匹配丈挟,則返回原 <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
))
);