redux

redux重記

入門

redux入門先要使用兩個東西鞍陨,一個是 reducer洪燥,一個是 store霉祸。
先說 reducer轻姿,是一個會被 redux store 調(diào)用的一個函數(shù)犁珠,會接到兩個參數(shù):state 和 action。
然后根據(jù) action 的類型和 action 攜帶的參數(shù)來執(zhí)行一些修改state的操作互亮,我們先不管 action

const reducer = (state, action) => {
    switch (action.type) {
        case '???':
            return '本次reducer處理完的數(shù)據(jù)'
        default:
            return 'reducer默認數(shù)據(jù)'
    }
}
export default reducer

store 的建立必須依賴一個 reducer犁享,使用 createStore 這個來自 redux 庫的 api,然后傳入一個 reducer 豹休,就會生成一個 store炊昆,這個store,就是給其他組件用的大倉庫慕爬。

import { legacy_createStore as createStore } from "redux";
import reducer from './reducer'
const store = createStore(reducer)
export default store

我們找個地方看一下 store 長啥樣窑眯,控制臺輸出可以看到,store 帶有dispatch医窿,getState等方法
[圖片上傳失敗...(image-4d903-1658743707093)]
調(diào)用一下 getState 磅甩,就會發(fā)現(xiàn)得到 reducer 的默認值

import store from '../../store'
console.log(store.getState())       //輸出    'reducer默認數(shù)據(jù)'

此時意識到 reducer 那個 default 的 return 就是 store 的初始值,我們改寫一下 store

const defaultState = {
    count: 0
}
const reducer = (state = defaultState, action) => {
    switch (action.type) {
        case '???':
            return '本次reducer處理完的數(shù)據(jù)'
        default:
            return state
    }
}
export default reducer

?補充知識:第一次 createStore 調(diào)用 reducer的時候姥卢,state 是 undefined 卷要,action 是名為 {
type: "@@redux/INITk.0.v.x.n.o"} 的一個對象,因為這個 state 實際上是 preState即 前一個狀態(tài)

此時在組件中調(diào)用 store.getState() 就可以讀到 store 里面的數(shù)據(jù)了独榴。

import store from '../../store'
...
<h1>當(dāng)前求和為:{store.getState().count}</h1>

入門階段先告一段落僧叉,我們可以知道,store貌似什么都沒有棺榔,實際數(shù)據(jù)都在 reducer 上瓶堕,這點很重要。

上手

此時我們知道了 reducer 才是賣力工作那個症歇,我們看回來那個入門階段沒管的 action 與 store 的 dispatch
action 是一個我們約定好的對象郎笆,包含 type 與 data 給 reducer 處理谭梗。怎么給?在組件調(diào)用 store.dispatch(action) 這種形式給

import store from '../../store'
某個方法 = ()=>{
    store.dispatch({
        type: '派發(fā)的action名',
        data: reducer 可以用的數(shù)據(jù)
    })
}
實例:
increment = () => {
    let data = this.select.value
    store.dispatch({
        type: 'increment',
        data
    })
}

此時我們回到 reducer 中處理宛蚓,注意 return 的值會修改整個原始 state 對象激捏,我們用合并方式處理

const defaultState = {
    count: 0
}
const reducer = (state = defaultState, action) => {
    switch (action.type) {
        case 'increment':
            console.log(state);
            return { ...state, count: state.count + action.data * 1 }
        default:
            return state
    }
}
export default reducer

此時又可以看到state 確實變了,但頁面沒有更新凄吏,那是因為想要頁面響應(yīng)式远舅,必須調(diào)用 setState,forceUpdate 等方法痕钢,如何檢測到 store 里的變化后就調(diào)用頁面相應(yīng)呢图柏?
store 提供了 subscribe 這個 api,這個 api 會接受一個回調(diào)函數(shù)盖喷,這個回調(diào)函數(shù)會在store里的數(shù)據(jù)變化時調(diào)用爆办。我們現(xiàn)在可以讓組件在掛載時就讓store檢測到變化就調(diào)用 setState({}) 假動作演一下組件

componentDidMount() {
    store.subscribe(() => {
        this.setState({})
    })
}

當(dāng)然可以狠一點直接放去 index.js 的 ReactDOM.render 外面

上手完善

我們已經(jīng)了解了 reducer 與 store 與 action 了难咕,實際工作上课梳,我們還會把 action 作為一個單獨文件提取出來,并把 action 的 type 也提取出來用變量來代替字符串

//type 提取 constant.js 文件
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
//action 提取 action.js 文件
import * as actionType from './constant'
export const createIncrementAction = (data) => ({
    type: actionType.INCREMENT,
    data
})
export const createDecrementAction = (data) => ({
    type: actionType.DECREMENT,
    data
})

組件使用的時候

import * as actionCreators from '../../store/action'
increment = () => {
    let data = this.select.value
    store.dispatch(actionCreators.createIncrementAction(data))
}

進階 異步action

action 除了可以是對象類型余佃,還可以是函數(shù)類型暮刃,一般對象類型的是同步 action ,函數(shù)類型就是異步 action爆土。
我們創(chuàng)建一個異步 action

import * as actionType from './constant'
import store from './index'
export const createIncrementAction = (data) => ({
    type: actionType.INCREMENT,
    data
})
export const createIncrementAsyncAction = (data, time) => {
    return () => {
        setTimeout(() => {
            store.dispatch(createIncrementAction(data))
        }, time)
    }
}

然后給組件派發(fā)

asyncIncrement = () => {
    const { value } = this.select
    store.dispatch(actionCreators.createIncrementAsyncAction(value, 500))
}

然后會發(fā)現(xiàn)報錯椭懊,說如果想使用非對象類型的 action ,需要使用中間件比如 redux-thunk
npm i redux-thunk 接下來就是死記硬背了步势,在 createStore 時傳入第二個參數(shù)氧猬,該參數(shù)需要從 redux 引入 applyMiddleware 和從 redux-thunk 引入 thunk 最后組合成 applyMiddleware(thunk)

import { legacy_createStore as createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import reducer from './reducer'
const store = createStore(reducer, applyMiddleware(thunk))
export default store

然后我們仔細看一下組件和action的代碼會發(fā)現(xiàn)調(diào)用了兩次 store.dispatch,實際上在 action 返回的函數(shù)中我們可以接到一個 dispatch坏瘩,就不用引入 store 再 dispatch 了

import * as actionType from './constant'
export const createIncrementAction = (data) => ({
    type: actionType.INCREMENT,
    data
})
export const createIncrementAsyncAction = (data, time) => {
    return (dispatch) => {
        setTimeout(() => {
            dispatch(createIncrementAction(data))
        }, time)
    }
}

react-redux

react-redux 將使用 redux 的組件分離成容器組件和 UI 組件盅抚,UI 組件不處理任何與 store 相關(guān)的操作。全部交給容器組件處理倔矾。容器通過 props 把數(shù)據(jù)傳給 UI 組件妄均。創(chuàng)建容器組件需要借助 react-redux 上的 connect,在第二個括號放入 UI 組件

class Count extends Component{
    ...
}
export default connect()(Count)

此時我們使用容器組件哪自,會發(fā)現(xiàn)報錯說找不到 store丰包,我們需要在使用容器組件時通過props傳入store

import Count from './component/Count'
import store from './store'
export default class App extends Component {
    render() {
        return (
            <div>
                <Count store={store} />
            </div>
        )
    }
}

容器通過 connect 的第一個參數(shù)把 store 的 state 傳給 UI 組件,第一個參數(shù)叫 mapStateToProps 壤巷,是一個接收到 store 的state為形參的函數(shù)邑彪。返回值的對象就是作為 props 傳給 UI 組件的對象

const mapStateToProps = (state)=>{
    return {
        count: state.count
    }
}

容器通過 connect 的第二個參數(shù)把 store 的 dispatch 傳遞給 UI 組件,第二個參數(shù)叫 mapDispatchToProps胧华,是一個接收 store 的 dispatch 為形參的函數(shù)寄症,返回值的對象就是作為 props 傳給 UI 組件的對象

const mapDispatchToProps = (dispatch) => {
    return {
        increment: value => { dispatch(actionCreators.createIncrementAction(value)) },
        decrement: value => { dispatch(actionCreators.createDecrementAction(value)) }
    }
}

此時只需要 export default connect(mapStateToProps, mapDispatchToProps)(index) 就好了

簡寫優(yōu)化

可以看到 mapStateToProps 和 mapDispatchToProps 最終返回的都是一個對象升筏,可以觸發(fā)直接返回對象的簡寫形式,但針對 mapDispatchToProps 還有一項優(yōu)化瘸爽,那就是 mapDispatchToProps 不僅可以寫成方法您访,還可以寫成對象。
那么寫成對象 dispatch去哪了剪决?參數(shù)去哪傳灵汪?dispatch 的話 react-redux 會自動幫你調(diào)用,參數(shù)直接往 actionCreate 里傳

const mapStateToProps = (state) => ({
    count: state.count
})
const mapDispatchToProps = {
    increment: actionCreators.createIncrementAction,
    decrement: actionCreators.createDecrementAction
}
export default connect(mapStateToProps, mapDispatchToProps)(index)

省略 store.subscribe

我們在 redux 中還需要手動調(diào)用 store.subscribe 柑潦,在 react-redux 中發(fā)現(xiàn)不使用也會自動更新視圖享言,實際上是因為 react-redux 之所以分離 容器組件和 UI組件,就是因為 connect 會自動更新 UI 組件

Provider

我們在使用容器組建的時候會傳入一個store={store}渗鬼,如果組件一多览露,豈不是每個組件都要傳一次?
此時我們需要使用 Provider 譬胎,外面包一層 <Provider store={store}> 即可差牛,這個組件是從 react-redux 中引入的,我們可以用它直接包住 App 組件

import store from "./store";
import { Provider } from "react-redux";
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
    <React.StrictMode>
        <Provider store={store}>
            <App />
        </Provider>
    </React.StrictMode>
)

也可以在項目中看到在 App 組件中用 Provider 包住 children

render() {
    return (
        <Provider store={store}>
            <React.Fragment>
                {this.state.env && this.props.children}
            </React.Fragment>
        </Provider>
    );
}

處理多個 store 堰乔,使用 combineReducers

使用完 combineReducers 后偏化,我們來看一下 store 總文件。

import { legacy_createStore as createStore, applyMiddleware, combineReducers } from "redux";
import thunk from "redux-thunk";
import countReducer from '../component/Count/store/reducer'
import personReducer from '../component/Person/store/reducer'
const reducer = combineReducers({
    countReducer,
    personReducer
})
const store = createStore(reducer, applyMiddleware(thunk))
export default store

在其他容器組件中镐侯,讀取 store 的內(nèi)容就變成了

const mapStateToProps = (state) => ({
    persons: state.personReducer.persons,
    count: state.countReducer.count
})

mapDispatchToProps 沒變侦讨,因為 action 的生成不依賴 store。
工作項目中還能看到 immutable 配合 redux-immutable 的苟翻,combineReducers 從 redux-immutable中引入韵卤。
實際上還能把 reducer 單獨提成一個文件,在 store 引入就行崇猫。

import { combineReducers } from 'redux'
import countReducer from '../component/Count/store/reducer'
import personReducer from '../component/Person/store/reducer'
export default combineReducers({
    countReducer,
    personReducer
})

配置 redux 開發(fā)者工具

store 中

import {composeWithDevTools} from 'redux-devtools-extension'
export default createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))

純函數(shù)

只要是同樣的輸入沈条,必定得到同樣的輸出。遵循以下約束

  1. 不得改寫原參數(shù)數(shù)據(jù)
  2. 不會產(chǎn)生任何副作用邓尤,例如網(wǎng)絡(luò)請求拍鲤,輸入輸出設(shè)備
  3. 不能調(diào)用 Date.now() 或 Math.random() 等不純的方法

redux 中的 reducer 必須是一個純函數(shù)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市汞扎,隨后出現(xiàn)的幾起案子季稳,更是在濱河造成了極大的恐慌,老刑警劉巖澈魄,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件景鼠,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機铛漓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門溯香,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人浓恶,你說我怎么就攤上這事玫坛。” “怎么了包晰?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵湿镀,是天一觀的道長。 經(jīng)常有香客問我伐憾,道長勉痴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任树肃,我火速辦了婚禮蒸矛,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘胸嘴。我一直安慰自己雏掠,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布筛谚。 她就那樣靜靜地躺著磁玉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪驾讲。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天席赂,我揣著相機與錄音吮铭,去河邊找鬼。 笑死颅停,一個胖子當(dāng)著我的面吹牛谓晌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播癞揉,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼纸肉,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了喊熟?” 一聲冷哼從身側(cè)響起柏肪,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎芥牌,沒想到半個月后烦味,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡壁拉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年谬俄,在試婚紗的時候發(fā)現(xiàn)自己被綠了柏靶。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡溃论,死狀恐怖屎蜓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情钥勋,我是刑警寧澤梆靖,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站笔诵,受9級特大地震影響返吻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜乎婿,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一测僵、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧谢翎,春花似錦捍靠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至褒侧,卻和暖如春良风,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背闷供。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工烟央, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人歪脏。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓疑俭,卻偏偏與公主長得像,于是被迫代替她去往敵國和親婿失。 傳聞我的和親對象是個殘疾皇子钞艇,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,914評論 2 355

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