React學習(三):Redux入門教程

Redux入門教程

三大原則

  1. 單一數據源

    整個應用的state被儲存在一棵object tree中,並且這個object tree只存在於唯一一個store中。

    每個應用只有一個store

  2. State是隻讀的

    唯一改變state 的方法就是觸發action,action是一個用於描述已發生事件的普通對象。

  3. 使用純函數來執行修改

    爲了描述action如何改變state tree,需要編寫reducer

基礎概念

Action

Action是把數據從應用傳到store的有效載荷,它是store數據的唯一途徑。

Reducer

Reducers指定了應用狀態的變化如何響應actions併發到store。

Store

Store是把它們聯繫到一起的對象,它有以下職責:

  • 維持應用的state;
  • 提供getState()方法獲取state;
  • 提供dispath(action)方法更新state;
  • 通過subscribe(listener)註冊監聽器;subscribe(listener)方法返回一個函數,調用這個函數就可以解除監聽。

工作流程

img

假設React組件上綁定了一個Action - Click事件:

  • 當點擊事件時,dispatch會分發對應的action
  • action通過reducers對狀態進行更新並返回到store當中
  • store獲得新狀態之後又重新渲染到視圖上
  • 如此反覆…

手把手實例

首先,我們通過create-react-app demo03命令,創建一個React腳手架應用。

npm i redux --savenpm i react-redux --save 分別安裝Redux和React-Redux

PS:

我在剛開始接觸Redux時,一直都沒理清楚這兩者的關係,實際上這是兩個獨立的存在。這裏摘錄網上的資料進行解釋。

Redux:數據處理中心

React-Redux:連接組件和數據中心,也就是把React和Redux聯繫起來

npm i node-sass --save安裝Sass,方便樣式編寫。

PS:

create-react-app 腳手架中已經添加了 sass-loader 的支持,所以只需要安裝 node-sass 插件即可;

但如何是配置less則沒有這麼方便了,因爲腳手架本身並沒有配置關於less文件的解析。添加Less相關步驟如下:

  • 運行npm run eject命令,該命令會將腳手架中隱藏的配置都展示出來,此過程不可逆。

  • 之後,打開config目錄下的webpack.config.js文件,找到// style files regexes註釋位置,添加以下兩行代碼

    // 添加 less 解析規則
    const lessRegex = /\.less$/;
    const lessModuleRegex = /\/module\.less$/;
    
  • 找到rules屬性配置,添加less解析配置

    // Less 解析配置
    {
        test: lessRegex,
        exclude: lessModuleRegex,
        use: getStyleLoaders(
            {
                importLoaders: 2,
                sourceMap: isEnvProduction && shouldUseSourceMap,
            },
            'less-loader'
        ),
        sideEffects: true,
    },
    {
        test: lessModuleRegex,
        use: getStyleLoaders(
            {
                importLoaders: 2,
                sourceMap: isEnvProduction && shouldUseSourceMap,
                modules: true,
                getLocalIdent: getCSSModuleLocalIdent,
            },
            'less-loader'
        )
    }
    
  • 配置完成後,安裝less和less-loader插件即可

    npm i less less-loader --save
    

此時,即可使用Less了

接着,我們將目錄中多餘的文件刪除,src文件夾下僅留index.js文件即可。下面是我當前src文件夾下的目錄結構:

- src
	- contaienrs // 用於使用react-redux,連接視圖層和store
	- css // 存放各個視圖層用到的css文件,通過index.scss文件進入導入
	- reducers // 用於存儲reducers文件
		- actionCreator 用於存儲action操作,便於管理
		- actionType 用於存儲action type的變量,便於管理
	- views // 用於存儲視圖
	index.js
	index.scss // 用於導入所有scss文件

將目錄結構整理好之後,就可以開始實例了,例子較爲簡單,主要是想通過例子學習和鞏固React-redux的使用。


首先,創建store。在Redux當中,store是用來存儲狀態的,同時它也是唯一的,整個項目當中有且只有一個store。

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.scss';
import ToDoList from './containers/ToDoList';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import reducer from './reducers/common';

const store = createStore(reducer)
ReactDOM.render(
    <Provider store={store}>
        <ToDoList />
    </Provider>,
    document.getElementById('root')
);

PS:

<Provider store>使組件層級中的connect()方法能夠獲得Redux store。這是React redux中的一個重要的組成部分。在還沒有使用React redux之前,我們需要通過store.subscirbe對數據進行監控,但使用React redux則通過Providerconnect()將React和Redux聯繫起來。在我看來,它就像一座橋樑,更加的方便了我們使用Redux。

在剛開始接觸的時候,一直不理解爲什麼單獨使用Redux也可以,可是大部分人都會選擇React redux呢?之後在實踐的過程中才慢慢理解了這點。

此處寫demo的時候還遇到一個坑,就是Provider包裹的地方,應與container的文件產生聯繫,因爲container就是通過connetc將redeux和react聯繫起來。一開始我錯誤的將view包裹在裏面,卻發現似乎connect的文件一直沒有生效。。。思考了很久之後纔想明白了這件事。


接下來,我開始了編寫了views文件夾裏的ToDoList.js文件

// views ToDoList.js
import React, { useState, useEffect } from 'react';

function ToDoList(props) {
    const { inputValue, getInputVal, addItem, list } = props;
    const [toDoList, setToDoList] = useState([]);

    useEffect(() => {
        setToDoList(list);
    }, [list]);

    return (
        <div className="container">
            <h3>To Do List</h3>
            <div className="input">
                <input
                    type="textarea"
                    placeholder={inputValue}
                    onChange={getInputVal}
                    value={inputValue}
                />
                <button onClick={addItem}>添加</button>
            </div>
            <div className="list">
                <ul>
                    {toDoList &&
                        toDoList.map((item, index) => {
                            return <li key={index}>{item}</li>;
                        })}
                </ul>
            </div>
        </div>
    );
}

export default ToDoList;

這裏是demo的所有代碼,下面將會對裏面的一些函數和變量進行講解。

props在平常的父子組件當中,主要是用做傳遞父組件的數據。而在這裏,則是和container容器中對應的文件聯繫起來。

// containers ToDoList.js
import { connect } from 'react-redux';
import ToDoList from '../views/ToDoList'
import { getInputAction, addItemAction } from '../reducers/actionCreator/toDoList'

const mapStateToProps = state => {
    console.log(state)
    return {
        inputValue: state.inputValue,
        list: state.list
    };
}

const mapDispatchToPros = dispatch => {
    return {
        getInputVal(e) {
            console.log(e.target.value);
            const action = getInputAction(e.target.value)
            dispatch(action)
        },
        addItem() {
            const action = addItemAction()
            dispatch(action)
        }
    }
}

export default connect(
    mapStateToProps,
    mapDispatchToPros
)(ToDoList)

這個容器裏面,就是通過connect將視圖層和redux聯繫起來。

connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])

mapStateToProps(state, ownProps) : stateProps

這個函數允許我們將store中的數據作爲props綁定到組件上, state則是可以獲取store當中的變量。

mapDispatchToProps(dispatch, ownProps): dispatchProps

這個函數可以將action作爲props綁定到組件上,而需要出發action則是通過dispatch進行分發。


最後,看看reducers裏面的文件

// actionType toDoList.js
export const GET_INPUTVAL = 'getInputVal'
export const ADD_ITEM = 'addItem'
// actionCreator toDoList.js
import { GET_INPUTVAL, ADD_ITEM } from '../actionType/toDoList'

export const getInputAction = (value) => ({
    type: GET_INPUTVAL,
    value
})

export const addItemAction = () => ({
    type: ADD_ITEM
})
// reducers common.js
import { GET_INPUTVAL, ADD_ITEM } from './actionType/toDoList'

const defaultState = {
    inputValue: 'input something...',
    list: []
}

export default (state = defaultState, action) => {
    switch (action.type) {
        case GET_INPUTVAL:
            // let newState = JSON.parse(JSON.stringify(state))
            // newState.inputValue = action.value
            // console.log(newState)
            // return newState
            return Object.assign({}, state, {
                inputValue: action.value
            })
            break
        case ADD_ITEM:
            console.log('object')
            let newState = JSON.parse(JSON.stringify(state))
            newState.list.push(newState.inputValue)
            newState.inputValue = ''
            return newState
            break
        default:
            break;
    }
    return state
}

在Redux中,有一個重要的原則就是不能直接對state進行改變,state狀態是不可變的,需要改變state則只能通過dispatch分發action,觸發對應的reducer進行改變。

PS:

這裏說個題外話,在編寫reducer的時候,因爲意識到state是不可變的,所以在返回新狀態的時候需要對其進行深拷貝。也因此對深拷貝和淺拷貝產生了疑惑…(之後需要好好學學😢)


通過這個簡單的實例,我對Redux和React-redux有了初步的認識,而在真正的項目當中,往往都不會只有一個reducer這麼簡單,因此學習如何合併多個reducer至關重要。在這裏則需要學習combineReducers等其它API的進階使用了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章