Redux原理分析

Redux原理分析

Redux是什麼javascript

不少人認爲redux必需要結合React使用,其實並非的,Redux 是 JavaScript 狀態容器,只要你的項目中使用到了狀態,而且狀態十分複雜,那麼你就可使用Redux管理你的項目狀態,它可使用在react中,也可使用中在Vue中,固然也適用其餘的框架。

一.redux的工做原理

先上圖(圖片源於網絡)
reduxjava

  1. 首先咱們找到最上面的state
  2. 在react中state決定了視圖(ui),state的變化就會調用React的render()方法,從而改變視圖
  3. 用戶經過一些事件(如點擊按鈕,移動鼠標)就會像reducer派發一個action
  4. reducer接收到action後就會去更新state
  5. store是包含了全部了state,能夠把他看作全部狀態的集合

固然,如今可能看不懂這在瞎說啥,可是等把這篇文章看完再來這個圖,和這段話,就會有恍然大明白的感受react

1.action

action本質上就是一個對象,它必定有一個名爲type的key 如{type: 'add'},{type: 'add'}就是一個action
可是咱們只實際工做中並非直接用action ,而是使用action建立函數,(千萬別弄混淆),
顧名思義action建立函數就是一個函數,它的做用就是返回一個action,如:es6

function add() {
    return {type: 'add'}
}

2.reducer

reducer其實就是一個函數,它接收兩個參數,第一個參數是須要管理的狀態state,第二個是action。reducer會根據傳入的action的type值對state進行不一樣的操做,而後返回一個新的state,而不是在原有state的基礎上進行修改,可是若是遇到了未知的(不匹配的)action,就會返回原有的state,不進行任何改變redux

function reducer(state = {money: 0}, action) {
    //返回一個新的state可使用es6提供的Object.assign()方法,或擴展運算符(此方法須要babel-preset-state-3支持)
    switch (action.type) {
        case '+':
            return Object.assign({}, state, {money: state.money + 1});
        case '-':
            return {...state, ...{money: state.money - 1}};
        default:
            return state;
    }
}

3.store

你能夠把store想成一個狀態樹,它包含了整個redeux應用的全部狀態。
咱們使用redux提供的createStore方法生成storebabel

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

store提供了幾個方法供咱們使用,下面是咱們經常使用的3個:網絡

store.getState();//獲取整個狀態樹
store.dispatch();//改變狀態,改變state的惟一方法
store.subscribe();//訂閱一個函數,每當state改變時,都會去調用這個函數

接下來演示一個redux的完整應用,而且說明這三個方法該怎麼用app

import {createStore} from 'redux';

//給初始狀態一個默認值:{money: 0}
function reducer(state = {money: 0}, action) {
    //返回一個新的state可使用es6提供的Object.assign()方法,或擴展運算符(此方法須要babel-preset-state-3支持)
    switch (action.type) {
        case '+':
            return Object.assign({}, state, {money: state.money + 1});
        case '-':
            return {...state, ...{money: state.money - 1}};
        default:
            return state;
    }
}

//action建立函數,返回了一個action
function add() {
    return {type: '+'}
}

function subtraction() {
    return {type: '-'}
}

//建立單一狀態樹
const store = createStore(reducer);

console.log(store.getState());//{money: 0},初始的狀態,沒有任何改變(經過getState來獲取目前的狀態)

//store經過dispatch這個方法,而且傳入action做爲參數,對store進行了改變
store.dispatch(add());
console.log(store.getState());//{money: 1},reducer接受到了 '+' 這個命令,就撿到了一塊錢

store.dispatch(subtraction());
console.log(store.getState());//{money: 0},reducer接受到了 '-' 這個命令,又掉了一塊錢

store.dispatch({type:'我是來搗亂的'});
console.log(store.getState());//{money: 0},reducer接受到了一個不識別命令,返回原有的state

這個時候咱們就會發現幾個問題:框架

  1. 每次狀態改變的時候咱們都要console.log()才能知道改變後的狀態,
  2. action的type實際上就是一個字符串,若是咱們須要進行項目維護,更改type的值,就須要在多處進行修改,變得十分麻煩。

這個時候咱們就可使用store.subscribe()來訂閱一個事件,代替咱們在每次dispatch後都要console.log()後才能知道改變後的狀態dom

function listen() {
    console.log(store.getState());
}

store.subscribe(listen);

將type維護成常量,這樣咱們在往後的維護過程當中只須要對常量進行維護就能夠了,咱們目前這個demo使用到type的地方太少可能感受不到,但是在實際項目中這個方法卻很是的實用

const ADD = '+', SUBTRACTION = '-';

咱們優化後的代碼以下:

import {createStore} from 'redux';

//定義常量方便維護
const ADD = '+', SUBTRACTION = '-';

//給初始狀態一個默認值:{money: 0}
function reducer(state = {money: 0}, action) {
    //返回一個新的state可使用es6提供的Object.assign()方法,或擴展運算符(此方法須要babel-preset-state-3支持)
    switch (action.type) {
        case ADD:
            return Object.assign({}, state, {money: state.money + 1});
        case SUBTRACTION:
            return {...state, ...{money: state.money - 1}};
        default:
            return state;
    }
}

//action建立函數,返回了一個action
function add() {
    return {type: ADD}
}

function subtraction() {
    return {type: SUBTRACTION}
}

//打印改變後的狀態
function listen() {
    console.log(store.getState());
}

//建立單一狀態樹
const store = createStore(reducer);

//訂閱listen,每次dispatch後都會執行listen,從而打印狀態(只有在執行dispatch後纔會執行,狀態初始化的時候並不會執行)
store.subscribe(listen);

console.log(store.getState());//初始的狀態,沒有任何改變

//store經過dispatch這個方法,而且傳入action做爲參數,對store進行了改變
store.dispatch(add());
store.dispatch(subtraction());
store.dispatch({type: '我是來搗亂的'});

/*控制檯的打印結果以下:
{money: 0}
{money: 1}
{money: 0}
{money: 0}*/

補充:
一個應用只能有一個store,這個時候就會有一個問題 ,若是有多個reducer分別來處理不一樣的狀態,而createStore是能接受一個reducer,這個時候咱們就須要redux提供的combineReducers方法來將多個reducer結合成一個reducer

import {combineReducers} from 'redux';

const reducerFamily=combineReducers({
    reduceSon,
    reduceDaughter,
    reducerFather,
    reducerMother
})
const store = createStore(reducerFamily);

二.在React中使用redux

若是會react,那麼也必定知道creact-react-app這個官方腳手架工具,首先使用creact-react-app建立一個項目,而後刪除src目錄下全部文件,接下來就能夠愉快的敲代碼了。

在src下建立三個文件
index.js

import React from 'react'
import ReactDOM from 'react-dom'
import {createStore} from 'redux'
//引入咱們的reducer和action建立函數
import {reducer, add, subtraction} from './index.redux'
import App from './App'

//建立store
const store = createStore(reducer);

//store.subscribe方法接受的參數是一個函數,
// 因此將ReactDOM.render方法寫在一個函數內
function listen() {
    //將store,action建立函數分別以屬性的方式傳遞給子組件App
    ReactDOM.render(<App store={store} add={add} subtraction={subtraction}/>,
        document.querySelector('#root'));
}

//由於剛進入頁面沒有dispatch操做改變store,
// 因此listen不會執行,咱們須要手動調用一次
listen();

//重點,改變了store,頁面就會從新渲染,
// 能夠試試不寫這行代碼會是怎樣的效果
store.subscribe(listen);

App.js

import React from 'react'

export default class App extends React.Component {
    render() {
        //從屬性中獲取store,action建立函數
        const {store, add, subtraction} = this.props;
        //獲取state
        let state = store.getState();
        return <div>
            <h1>我有{state.money}元</h1>

            {/*經過store.dispatch方法改變store,從而頁面也會改變*/}
            <button onClick={() => {store.dispatch(add())}}>
                撿了一塊錢
            </button>

            <button onClick={() => {store.dispatch(subtraction())}}>
                掉了一塊錢
            </button>
        </div>
    }
}

index.redux.js

//定義常量方便維護
const ADD = '+', SUBTRACTION = '-';

//給初始狀態一個默認值:{money: 0}
export function reducer(state = {money: 0}, action) {
    //返回一個新的state可使用es6提供的Object.assign()方法,或擴展運算符(此方法須要babel-preset-state-3支持)
    switch (action.type) {
        case ADD:
            return Object.assign({}, state, {money: state.money + 1});
        case SUBTRACTION:
            return {...state, ...{money: state.money - 1}};
        default:
            return state;
    }
}

//action建立函數,返回了一個action
export function add() {
    return {type: ADD}
}

export function subtraction() {
    return {type: SUBTRACTION}
}

效果圖
效果圖

這樣咱們就將redux和react結合了起來可是這樣咱們可能會以爲麻煩,由於咱們要將store和action建立函數傳給子組件,當咱們的action比較多時,子組件比較多時,就須要將store和大量的action建立函數一層層的屢次傳遞下去。這樣就會十分麻煩,所以咱們就可使用react-redux這個庫來幫助咱們實現這個麻煩的過程

三.react-redux的使用

1.Provider

react-redux給咱們提供了一個Provider組件,咱們能夠把這個組件寫在最外層,這樣被Provider包裹的全部組件均可以經過props來獲取state,不管組個組件藏得多麼深。
Provider組件只接受一個屬性,那就是store

那麼咱們index.js的代碼就變成下面這樣了:

import React from 'react'
import ReactDOM from 'react-dom'
import {createStore} from 'redux'
import {Provider} from 'react-redux'
import {reducer} from './index.redux'
import App from './App'

//建立store
const store = createStore(reducer);

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

2.connect

固然,只有Provider組件是不夠的,咱們還須要connect來幫助咱們獲取state和action,沒錯,connect就是幫助咱們獲取state和action的

那麼問題就來了,咱們的組件可不是須要項目中全部的state和action,只須要其中的一部分就能夠了,因此connect會接受兩個參數,第一個參數它能夠幫咱們篩選state,第二個參數能夠幫咱們篩選action。
咱們能夠把這兩個參數寫成函數的形式,
參數1,

function mapStateToProps(state) {
    return {
        money: state.money
    }
}

參數2,

function actionCreators() {
    return {
        subtraction,
        add
    }
}

咱們能夠發現這兩個函數都是返回了一個對象,第一個函數返回了咱們須要的state,第二個函數返回了咱們須要的action建立函數

那麼app.js 的代碼就變成這樣了:

import React from 'react'
import {connect} from 'react-redux'
import {add, subtraction} from './index.redux'

class App extends React.Component {
    render() {
        //由於connect的緣由,state和action咱們已經能夠從屬性中獲取了
        const {money, add, subtraction} = this.props;

        return <div>
            <h1>我有{money}元</h1>

            {/*這個時候不須要咱們dispatch了*/}
            <button onClick={add}>
                撿了一塊錢
            </button>

            <button onClick={subtraction}>
                掉了一塊錢
            </button>
        </div>
    }
}

//connect所須要的參數
//函數返回的咱們須要的狀態,咱們須要money,就從state中取出money
//假如咱們還須要house,就增長一個house:state.house
function mapStateToProps(state) {
    return {
        money: state.money
    }
}

//connect須要的第二參數
//返回咱們須要的action建立函數
function actionCreators() {
    return {
        subtraction,
        add
    }
}

//上面兩個函數返回的都是對象

//經過connect將state和action建立函數當作屬性傳遞給組件
export default App = connect(mapStateToProps, actionCreators())(App);

若是熟悉es6裝飾器的語法那就更好了,可使咱們的代碼變得更優雅
app.js

import React from 'react'
import {connect} from 'react-redux'
import {add, subtraction} from './index.redux'

@connect(
    state => ({money: state.money}),
    {
        subtraction,
        add
    })
export default class App extends React.Component {
    render() {
        //由於connect的緣由,state和action咱們已經能夠從屬性中獲取了
        const {money, add, subtraction} = this.props;

        return <div>
            <h1>我有{money}元</h1>

            {/*這個時候不須要咱們dispatch了*/}
            <button onClick={add}>
                撿了一塊錢
            </button>

            <button onClick={subtraction}>
                掉了一塊錢
            </button>
        </div>
    }
}

看到這裏再回頭看看最開始圖片,就能搞清楚redux的工做流程到底是怎樣的。

相關文章
相關標籤/搜索