Redux初探

Redux是什么:Redux是一個(gè)專門用來做狀態(tài)管理的JS庫(不是React插件庫)掏导。它可以用在React资盅,Angular晴股,Vue應(yīng)用中愿伴,但基本和React配合使用。用于集中式管理React應(yīng)用中多個(gè)組件共享的狀態(tài)电湘。

首先我們先介紹一個(gè)概念集中式管理公般,集中式管理要求狀態(tài)在哪里,修改狀態(tài)的行為就定義在那個(gè)組件胡桨。

但是集中式管理有一個(gè)問題。一個(gè)組件下面有很多路由組件瞬雹。如果這些組件的行為全部放在一個(gè)文件下會(huì)造成很大的冗余昧谊。

然而Redux使用集中式管理來管理狀態(tài)。將狀態(tài)以及狀態(tài)管理的方法放在一處酗捌,需要的時(shí)候調(diào)用狀態(tài)管理的方法呢诬。此時(shí)狀態(tài)不在組件的內(nèi)部,而是在Redux中胖缤。



組件的兩大功能:

  • 展現(xiàn)數(shù)據(jù)
  • 與用戶交互更新數(shù)據(jù)

Action Creators尚镰,Store,Reducers都是Redux代碼哪廓。因此整個(gè)過程可以看做是React組件和Redux進(jìn)行交互狗唉。

展現(xiàn)數(shù)據(jù):Store是Redux中的核心對(duì)象。從圖中可以看出React組件是從Store中讀取狀態(tài)涡真。

更新新的狀態(tài)顯示:使用分發(fā)功能時(shí)會(huì)使用到action分俯,其中有兩個(gè)屬性type,data哆料。Action Creators是一個(gè)工廠函數(shù)缸剪,用于修改action對(duì)象。我們需要傳遞一個(gè)type和data东亦。type代表的是事件的類型杏节。Action Creators通過type決定生成什么類型的Action。data是相關(guān)的數(shù)據(jù)典阵。

Reducers相當(dāng)于一個(gè)函數(shù)奋渔,該函數(shù)的形參是previousState和action,返回值為newState萄喳。返回的newState交給store進(jìn)行狀態(tài)的更新卒稳。

React中狀態(tài)無法直接更新,必須調(diào)用this.setState()他巨。

總結(jié):一個(gè)組件重要的功能是兩方面:顯示狀態(tài)和更新狀態(tài)充坑。顯示狀態(tài)可以通過Store來修改狀態(tài)减江。更新狀態(tài)時(shí)我們主要做兩件事,首先是發(fā)送通知捻爷,通過dispatch辈灼。之后是實(shí)現(xiàn)Reducers函數(shù)產(chǎn)生新的狀態(tài)。

什么情況下需要使用Redux

  1. 總體原則:能不用就不用也榄,如果不用比較吃力才考慮使用巡莹。
  2. 某個(gè)組件的狀態(tài),需要共享甜紫。
  3. 某個(gè)狀態(tài)需要在任何地方都可以拿到降宅。
  4. 一個(gè)組件需要改變?nèi)譅顟B(tài)。
  5. 一個(gè)組件需要改變另一個(gè)組件的狀態(tài)囚霸。

使用Redux

首先先下載依賴包

npm install --save redux

Store對(duì)象

作用:Redux庫最核心的管理對(duì)象腰根。
內(nèi)部維護(hù):state,reducer
核心方法:

  • getState()
  • dispatch(action)
  • subscribe(listener)

編碼:

  • store.getState()
  • store.dispatch({type:'INCREMENT', number})
  • store.subscribe(render)

Redux三個(gè)核心概念

action

標(biāo)識(shí)要執(zhí)行行為的對(duì)象
包含兩個(gè)方面的屬性:

  • type:標(biāo)識(shí)屬性拓型,值為字符串额嘿,唯一,必要屬性劣挫。
  • xxx:數(shù)據(jù)屬性册养,值為任意類型,可選屬性压固。
    例子:
const action = {
  type: "INCREMENT",
  data: 2
}

Action.Creator(Action的工廠函數(shù))的創(chuàng)建

const increment = (number) => ({type: "INCREMENT", data: number})

reducer

根據(jù)老的state和action產(chǎn)生新的state的純函數(shù)球拦。
例子:

export default function counter(state=0, action){
  switch(action.type){
    case: "INCREMENT":
      return state+action.data
    case: "DECREMENT":
      return state-action.data
    default:
      return state
  }
}

注意:

  1. 每一次都返回一個(gè)新的狀態(tài)數(shù)據(jù)。
  2. 不要改變以前的狀態(tài)數(shù)據(jù)帐我。

store

將state, action與reducer聯(lián)系到一起的對(duì)象刘莹。
如何得到該對(duì)象:

import {createStore} from 'redux'
import reducer from './reducers'
const store = createStore(reducer)

此對(duì)象的功能:
getStete():得到state
dispatch(action):分發(fā)action,觸發(fā)reducer的調(diào)用焚刚,產(chǎn)生新的state点弯。
subscribe(listener):注冊(cè)監(jiān)聽,當(dāng)產(chǎn)生了新的state對(duì)象時(shí)矿咕,自動(dòng)調(diào)用抢肛。

Redux實(shí)例

我們需要實(shí)現(xiàn)以下的效果:



點(diǎn)擊“加號(hào)”,“減號(hào)”按鈕碳柱,上面的數(shù)字分別增加或減少顯示的數(shù)字捡絮。點(diǎn)擊“increment if odd”如果顯示的數(shù)字為奇數(shù)則增加顯示的數(shù)據(jù)奋隶。點(diǎn)擊“increment async”隔一段時(shí)間后增加顯示的數(shù)據(jù)接谨。

首先在index.js中引入Store對(duì)象

import {createStore} from 'redux'

之后是創(chuàng)建Store對(duì)象

const store = createStore()

之后是編寫Action Creators(reducers.js)并將其傳給Store對(duì)象撮抓。和前面說的一樣Action Creators相當(dāng)于一個(gè)工廠方法根據(jù)傳遞的type決定產(chǎn)生什么樣的Action糠雨。

export function counter(state = 0, action) {
    switch (action.type){
        case "INCREMENT":
            return state + action.data
        case "DECREMENT":
            return state - action.data
        default:
            return state
    }
}

這里做一些改進(jìn)。由于我們?cè)趥鬟ftype的時(shí)候可能會(huì)將type的類型寫錯(cuò)标沪。所以這里我們最好寫一個(gè)類(action-types.js)來統(tǒng)一管理這些字符贮配。

export const INCREMENT = 'INCREMENT'
export const DECREMENT = 'DECREMENT'

之后在reducers.js中引入之前寫的action-types.js

import {INCREMENT, DECREMENT} from './action-types'

export function counter(state = 0, action) {
    switch (action.type){
        case INCREMENT:
            return state + action.data
        case DECREMENT:
            return state - action.data
        default:
            return state
    }
}

這個(gè)時(shí)候我們就不需要擔(dān)心type寫錯(cuò)的問題了囊卜。

之后回到index.js中,引入之前寫的reducers.js越妈。并將Action Creators傳遞給Store對(duì)象季俩。并將Store對(duì)象傳遞給組件類。

import {counter} from './redux/reducers'

const store = createStore(counter)

function new_render(){
    render(<App store={store}/>, document.getElementById('root'))
}

new_render()

store.subscribe(new_render)

讓我們來到組件類app.jsx梅掠。由于狀態(tài)是被Redux管理的酌住。因此在組件類中我們無需定義state了。狀態(tài)值的獲取與更新也要經(jīng)過Redux阎抒。不需要自己寫方法了酪我。例如:

//獲取狀態(tài)值
const count = this.props.store.getState()
//更新狀態(tài)值
this.props.store.dispatch({type: INCREMENT, data: number})

整個(gè)app.jsx的代碼如下:

import React, {Component} from 'react'
import {INCREMENT, DECREMENT} from '../redux/action-types'

class App extends Component{
    increment = () => {
        const number = this.select.value*1
        this.props.store.dispatch({type: INCREMENT, data: number})
    }

    decrement = () => {
        const number = this.select.value*1
        this.props.store.dispatch({type: DECREMENT, data: number})
    }

    incrementIfOdd = () => {
        const number = this.select.value*1
        const count = this.props.store.getState()
        if(count %2 === 1)
            // this.setState({count: count + number})
            this.props.store.dispatch({type: INCREMENT, data: number})
    }

    incrementAsync = () => {
        const number = this.select.value*1
        setTimeout(() => {
            this.props.store.dispatch({type: INCREMENT, data: number})
        }, 1000)
    }

    render(){
        const count = this.props.store.getState()
        return (
            <div>
                <p>Click {count} times</p>
                <div>
                    <select ref={select => this.select = select}>
                        <option value="1">1</option>
                        <option value="2">2</option>
                        <option value="3">3</option>
                    </select>&nbsp;
                    <button onClick={this.increment}>+</button>&nbsp;
                    <button onClick={this.decrement}>-</button>&nbsp;
                    <button onClick={this.incrementIfOdd}>increment if odd</button>&nbsp;
                    <button onClick={this.incrementAsync}>increment async</button>&nbsp;
                </div>
            </div>
        )
    }
}

export default App

使用react-redux進(jìn)行簡(jiǎn)化

react-redux是一個(gè)react插件庫。專門用于簡(jiǎn)化在react應(yīng)用中使用redux且叁。

export default connect(
    state => ({count: state}),
    {increment, decrement}
)(App)

在此示例中祭示。state和{increment, decrement}會(huì)被結(jié)構(gòu)傳遞給App組件。狀態(tài)放到了Redux中谴古,我們需要這些狀態(tài)就需要屬性來接收。connect起到了傳遞屬性的作用稠歉。注意:connect中的屬性名必須和組件中的屬性名一致掰担。

React-Redux將所有的組件分為兩大類:

  • UI組件
    不使用Redux的API
    一般放在components文件夾下
  • 容器組件
    使用Redux的API
    一般保存在containers文件夾下
文件結(jié)構(gòu)

action-types.js:包含所有action類型的名稱常量。
actions.js:包含了所有的action怒炸,工廠函數(shù)带饱。
reducers.js:包含多個(gè)reducer函數(shù)。根據(jù)老的state和action阅羹,返回一個(gè)新的state勺疼。
store.js:redux最核心的管理對(duì)象。
components下存放著UI組件(counter.jsx)

import React, {Component} from 'react'
import PropTypes from 'prop-types'

export default class Counter extends Component{

    static propTypes = {
        count: PropTypes.number.isRequired,
        increment: PropTypes.func.isRequired,
        decrement: PropTypes.func.isRequired
    }

    increment = () => {
        const number = this.select.value*1
        this.props.increment(number)
    }

    decrement = () => {
        const number = this.select.value*1
        this.props.decrement(number)
    }

    incrementIfOdd = () => {
        const number = this.select.value*1
        const {count} = this.props
        if(count %2 === 1)
            // this.setState({count: count + number})
            this.props.increment(number)
    }

    incrementAsync = () => {
        const number = this.select.value*1
        setTimeout(() => {
            this.props.increment(number)
        }, 1000)
    }

    render(){
        const {count} = this.props
        return (
            <div>
                <p>Click {count} times</p>
                <div>
                    <select ref={select => this.select = select}>
                        <option value="1">1</option>
                        <option value="2">2</option>
                        <option value="3">3</option>
                    </select>&nbsp;
                    <button onClick={this.increment}>+</button>&nbsp;
                    <button onClick={this.decrement}>-</button>&nbsp;
                    <button onClick={this.incrementIfOdd}>increment if odd</button>&nbsp;
                    <button onClick={this.incrementAsync}>increment async</button>&nbsp;
                </div>
            </div>
        )
    }
}

containers下放容器組件(app.jsx)

import {decrement, increment} from "../redux/actions";
import {connect} from "react-redux";
import Counter from "../components/counter";

export default connect(
    state => ({count: state}),
    {increment, decrement}
)(Counter)

Provider組件:
讓所有組件都可以得到state數(shù)據(jù)

<Provider store={store}>
  <App />
</Provider>

connect組件:
用于包裝UI組件形成容器組件捏鱼。將組件和Redux關(guān)聯(lián)起來执庐。

import {connect} from 'react-redux'
connect(
  mapStateToProps,
  mapDispatchToProps
)(Counter)

mapStateToProps():
將外部數(shù)據(jù)(即state對(duì)象)轉(zhuǎn)換為UI組件的標(biāo)簽屬性。

const mapStateToProps = function(state){
  return (
    {value: state}
  )
}

mapDispatchToProps():
將分發(fā)action的函數(shù)轉(zhuǎn)換為UI組件的標(biāo)簽屬性导梆。
簡(jiǎn)潔語法可以直接指定為actions對(duì)象或包含多個(gè)action方法的對(duì)象轨淌。

管理多個(gè)Reducer

import {combineReducers} from 'react'

合并多個(gè)reducer放在一起管理。

假設(shè)此時(shí)有兩個(gè)reducer看尼。我們不應(yīng)將它們分別暴露递鹉。而是統(tǒng)一放到combineReducers方法中進(jìn)行統(tǒng)一暴露。同時(shí)之前暴露的方法也要統(tǒng)一改成統(tǒng)一管理的方法藏斩。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末躏结,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子狰域,更是在濱河造成了極大的恐慌媳拴,老刑警劉巖黄橘,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異禀挫,居然都是意外死亡旬陡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門语婴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來描孟,“玉大人,你說我怎么就攤上這事砰左∧湫眩” “怎么了?”我有些...
    開封第一講書人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵缠导,是天一觀的道長(zhǎng)廉羔。 經(jīng)常有香客問我,道長(zhǎng)僻造,這世上最難降的妖魔是什么憋他? 我笑而不...
    開封第一講書人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮髓削,結(jié)果婚禮上竹挡,老公的妹妹穿的比我還像新娘。我一直安慰自己立膛,他們只是感情好揪罕,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著宝泵,像睡著了一般好啰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上儿奶,一...
    開封第一講書人閱讀 49,929評(píng)論 1 290
  • 那天框往,我揣著相機(jī)與錄音,去河邊找鬼闯捎。 笑死搅窿,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的隙券。 我是一名探鬼主播男应,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼娱仔!你這毒婦竟也來了沐飘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎耐朴,沒想到半個(gè)月后借卧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡筛峭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年铐刘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片影晓。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡镰吵,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出挂签,到底是詐尸還是另有隱情疤祭,我是刑警寧澤,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布饵婆,位于F島的核電站勺馆,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏侨核。R本人自食惡果不足惜草穆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望搓译。 院中可真熱鬧悲柱,春花似錦、人聲如沸侥衬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽轴总。三九已至,卻和暖如春博个,著一層夾襖步出監(jiān)牢的瞬間怀樟,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工盆佣, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留往堡,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓共耍,卻偏偏與公主長(zhǎng)得像虑灰,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子痹兜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350