Redux的核心概念,實現代碼與應用示例

Redux是一種JavaScript的狀態管理容器,是一個獨立的狀態管理庫,可配合其它框架使用,好比React。引入Redux主要爲了使JavaScript中數據管理的方便,易追蹤,避免在大型的JavaScript應用中數據狀態的使用混亂狀況。Redux 試圖讓 state 的變化變得可預測,爲此作了一些行爲限制約定,這些限制條件反映在 Redux 的三大原則中。html

本文會介紹Redux的幾個基本概念和堅持的三大原則,以及完整的迴路一下Redux中的數據流。在瞭解以上這些概念以後,用本身的代碼來實現一個簡版的Redux,而且用本身實現的Redux結合React框架,作一個簡單的TodoList應用示例。但願本文對於初識Redux的同窗有一個清晰,全面的認識。node

Redux的幾個基本概念

1、數據存儲 - state

Redux就是用來管理狀態數據,因此第一個概念就是狀態數據,state就是存放數據的地方,根據應用須要,通常定義成一個對象,好比:react

{
    todos: [],
    showType: 'ALL',
    lastUpdate: '2019-10-30 11:56:11'
}

 

2、行爲觸發 - action

web應用,全部的數據狀態變動,都是由一個行爲觸發的,好比用戶點擊,網絡加載完成,或者定時事件。在簡單應用裏面,咱們通常都是在行爲觸發的時候,直接修改對應的數據狀態,可是在大型複雜的應用裏面,修改同一數據的地方可能不少,每一個地方直接修改,會形成數據狀態不可維護。web

Redux引入了action的概念,每一個要改變數據狀態的行爲,都定義成一個action對象,用一個type來標誌是什麼行爲,行爲附帶的數據,也都直接放在action對象,好比一個用戶輸入的行爲:npm

{
    type: 'INPUT_TEXT',
    text: '今天下午6點活動碰頭會議'
}

而後經過dispatch觸發這個action,dispatch(action)redux

3、行爲響應 - reducer

狀態,action的概念瞭解了,當action觸發的時候,確定要修改state數據,在講解action的時候有說過,不能直接修改state,咱們須要定義一個reducer來修改數據,這個reducer就是一個行爲響應函數,他接收當前state,和對應的action對象,根據不一樣的action,作相應的邏輯判斷和數據處理,而後返回一個新的state。網絡

注意,必定是返回一個新的state,不能直接修改參數傳入的原state,這是redux的原則之一,後面會講到。數據結構

function reducer ( state = [], action ) {
    switch ( action.type ) {
        case 'INPUT_TEXT':
            return [...state, {text: action.text, id: Math.random() }]
        default:
            return state;
    }
}

 

4、數據監聽 - subscribe

數據的更新已經在reducer中完成了,在一些響應式的web應用中,咱們每每須要監聽數據狀態的變化,這個時候就能夠用subscribe了app

redux內部保存一個監聽隊列,listeners,能夠調用subscribe來往listeners裏面增長新的監聽函數,每次reducer修改完state以後,會逐個執行監聽函數,而監聽函數能夠獲取已經更新過的state數據了框架

listeners = [];
subscrible( listener ) {
    listeners.push( listener );
    return function () {
        let index = listeners.index( listener );
        listeners.splice( index, 1 );
    }
}
dispatch( action ) // 觸發 action
reducer(state, action)

listeners.map( ( listener ) => {
    listener()
} )

 

Redux的幾大原則

1、單一數據原則

整個應用的數據都在state,而且只有這一個state,這麼作的目的是方便管理,整個應用的數據就這一份,調試方便,開發也方便,能夠在開發的時候用本地的數據。並且開發同構應用也很方便,好比服務端渲染,把服務端的數據所有放在state,做爲web端初始化時候的數據

2、state只讀

state的數據對外只讀,不能直接修改state,惟一能夠修改的方式是觸發action,而後經過reducer來處理。

由於全部的修改都被集中化處理,且嚴格按照一個接一個的順序執行,所以不用擔憂競態條件(race condition)的出現。 Action 就是普通對象而已,所以它們能夠被日誌打印、序列化、儲存、後期調試或測試時回放出來。

3、使用純函數

先說明下什麼是純函數,純函數指的是函數內部不修改傳入的參數,無反作用,在傳參必定的狀況下,返回的結果也是必定的。Redux中的Reducer須要設計成存函數,不能直接操做傳入的state,須要把改變的數據以一個新的state方式返回。

Redux中的數據流

其實上面講Redux基本概念的時候已經大概的說了下數據流向方式了,就是: view->action->reducer->state->view,用文字來表述就是,首先因爲頁面上的某些事件會觸發action,經過dispatch(action)來實現,而後經過reducer處理,reducer(state, action)返回一個新的state,完成state的更新,固然對於響應式的應用,會觸發listener(),在listener裏面獲取最新的state狀態,完成對應視圖(view)的更新。這就是整個redux中的數據流描述,以下圖所示:

image.png

Redux的實現代碼(非官方)

在對Redux的基本概念和幾大原則熟悉了以後,能夠實現一個本身的Redux了,固然咱們通常都直接用官方的npm包,這裏本身實現的比較簡單,沒有作什麼入參驗證,異常處理之類的,主要是加深下對Redux的理解。下面直接貼代碼了,對應的概念都有註釋。

// redux.js
// 建立state的函數
// 傳入reducer 和初始化的state
function createStore( reducer, initState ) {
    let ref = {};
    let listeners = [];
    let currentState = initState;

    // dispath函數,用來觸發action
    function dispatch ( action ) {
        // 觸發的action,經過reducer處理
        currentState = reducer( currentState, action )

        // 處理完成後,通知listeners
        for ( let i in listeners ) {
            let listener = listener[ i ];
            listener();
        }
        return action;
    }

    // 返回當前的state
    function getState () {
        return currentState;
    }

    // 訂閱state變化, 傳入listener,返回取消訂閱的function
    function subscribe ( listener ) {
        listeners.push( listener );
        return function () {
            let index = listeners.indexOf( listener );
            if ( index > -1 ) {
                listeners.splice( index, 1 );
            }
        }
    }
    
    ref = {
        dispatch: dispatch,
        subscribe: subscribe,
        getState: getState
    };
    return ref;
}

function combineReducers( reducers ) {
    return function ( state, action ) {
        let finalState = {};
        let hasChanged = false;
        for ( let key in reducers ) {
            let reducer = reducers[ key ]
            if ( typeof reducer === 'function' ) {
                let keyState = reducer( state && state[ key ], action );
                hasChanged = hasChanged || keyState !== state[ key ];
                finalState[ key ] = keyState;
            }
        }
        return hasChanged ? finalState : state;
    }
}

export { createStore, combineReducers }

是否是以爲怎麼才這麼點代碼,就是這麼點代碼,並且還包含了一個combineReducers輔助函數,下面再貼一點使用示例代碼

// reducer函數,用於處理action
function reducer( state = [], action ) {
    switch( action.type ) {
        case 'INPUT_TEXT':
            return [ ...state, { text: action.text, key: Math.random(), isDo: false }];
        case 'TOGGLE_TODO':
            return state.map( ( item ) => {
                if ( item.key === action.id ) {
                    return {...item, isDo: !item.isDo };
                }
            } );
        default:
            return state;
    }
}

let store = createStore( reducer );

// 在用戶輸入一條Todo時候
console.log(store.getState());
store.dispatch( { type: 'INPUT_TEXT', text: '這裏是一條待辦事項' } );
console.log(store.getState());

//在用戶點擊一條Todo Item的時候,切換完成狀態
console.log(store.getState());
store.dispatch( { type: 'TOGGLE_TODO', id: item.key } )
console.log(store.getState());

 

Redux與React的結合應用示例

下面,利用Redux結合React開發一個簡單的Todo工具,頁面主要功能點

一、能夠添加Todo事項

二、點擊事項會切換事項的完成狀態

三、能夠切換展現所有/已完成/待完成事項

這個實例是基於react,react-redux完成的,項目搭建用的是create-react-app,利用react-redux提供的接口,將redux中的state和action集成到組件中,須要讀者熟悉create-react-app的使用,以及react-redux的主要接口功能,如下貼出主要代碼,感興趣的同窗能夠本身搭建實現

首先定義好state數據結構和action以及對應的reducer

state包含兩部分,一是todos,待辦事項列表,二是showType,展現類型

action包含這麼三種,一是添加新的Todo,二是切換事項完成狀態,三是切換展現類型,分別定義好

actions.js

// actions.js
let nextTodoId = 0

export const addTodo = text => {
    return {
        type: 'ADD_TODO',
        id: nextTodoId++,
        text
    };
};

export const setShowType = showType => {
    return {
        type: "SET_SHOW_TYPE",
        showType
    };
};

export const toggleTodo = id => {
    return {
        type: 'TOGGLE_TODO',
        id
    };
};

reducers.js

const todos = ( state = [], action ) => {
    switch ( action.type ) {
        case 'ADD_TODO':
            return [
                ...state,
                {
                    id: action.id,
                    text: action.text,
                    isDo: false
                }
            ];
        case 'TOGGLE_TODO':
            return state.map( todo => {
                return todo.id === action.id ? {...todo, isDo: !todo.isDo } : todo;
            } );
        default:
            return state;
    }
}

const showType = ( state = 'SHOW_ALL', action ) => {
    switch ( action.type ) {
        case 'SET_SHOW_TYPE':
            return action.showType;
        default:
            return state;
    }
}

const todoList = combineReducers({
    todos,
    showType
})
export { todoList }

 

至此,數據狀態redux部分算完成了,接下來實現對應的Component和入口文件了,準備分這麼幾個組件

一、待辦事項Todo

二、輸入框 AddTodo

三、待辦事項列表TodoList

四、底部展現類型切換Tab

// component.js
import { connnect } from 'react-redux';
import { addTodo, setShowType, toggleTodo } from './actions'

const Todo = ( { onClick, completed, text } ) => (
    <li onClick={onClick} style={{ textDecoration: completed ? 'line-through' : 'none' }}>
        {text}
    </li>
)

const AddTodo = ( { dispatch } ) => {
    let input;
    return (
        <div>
            <form
                onSubmit={ e => {
                    e.preventDefault()
                    if ( !input.value.trim() ) {
                        return;
                    }
                    dispatch( addTodo( input.value ) )
                    input.value = ''
                }}
            >
                <input ref={ node => {input = node } } />
                <button type='submit'>Add Todo</button>
            </form>
        </div>
    )
}
AddTodo = connect()( AddTodo );

const TodoList =  ( { todos, onTodoClick } ) => {
    return (
        <ul>
            {todos.map( todo => (
                <Todo key={todo.id} {...todo} onClick={ () => onTodoClick( todo.id ) } />
            ) )}
        </ul>
    ) };
    
const getShowTodoList = ( todos, showType ) => {
    switch( showType ) {
        case 'SHOW_ISDO':
            return todos.filter( item => item.isDo );
        case 'SHOW_ACTIVE':
            return todos.filter( item => !item.isDo );
        case 'SHOW_ALL':
        default :
            return todos;
    }
}

const mapStateToProps = state => {
    return {
        todos: getShowTodoList ( state.todos, state.showType)
    };
};

const mapDispatchToProps = dispatch => {
    return {
        onTodoClick: id => {
            dispatch( toggleTodo( id ) );
        }
    };
}

const ShowTodoList = connect(
    mapStateToProps,
    mapDispatchToProps
)( TodoList );
   
 const Tab = () => (
    <p>
        Show: { ' ' }
        <FilterLink filter='SHOW_ALL'>ALL</FilterLink>
        { ', ' }
        <FilterLink filter='SHOW_ACTIVE'>ACTIVE</FilterLink>
        { ', ' }
        <FilterLink filter='SHOW_ISDO'>ISDO</FilterLink>
    </p>
)

export { AddTodo, ShowTodoList, Tab }

 

入口文件 index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore } from './redux';
import todoList from './reducers'
import {AddTodo, ShowTodoList, Tab } from './component'

let store = createStore( todoApp );

ReactDOM.render(
    <Provider store={store}>
        <div>
            <AddTodo />
            <ShowTodoList />
            <Tab />
        </div>
    </Provider>
    , document.getElementById('root'));

 

主要代碼完成,npm start 運行,功能截圖以下

image.png

文章同步發佈: https://www.geek-share.com/detail/2783420870.html

參考文章:

原生實現一個react-redux的代碼示例

用React實現一個完整的TodoList的示例代碼

相關文章
相關標籤/搜索