上一篇中簡(jiǎn)單的描述了Redux的主要使用流程,本篇針對(duì)上一篇做一些補(bǔ)充。
1、Action 與 Reducer的詳細(xì)理解
先回顧一下不使用Redux的情況下,React的Class該怎么寫(xiě)。
import React, { Component } from 'react';
class Test extends Component{
constructor(props){
super(props);
state={
name:''
}
}
handlerFunc = () =>{
const inputName = this.refs.inputValueTest.value;
this.setState({
name:inputName
})
}
render() {
const {name} = this.state;
return (
<div>
<label> {name} </label><br/>
<input ref="inputValueTest" /><br/>
<button onClick={this.handlerFunc}>confirm</button><br/>
</div>
);
}
}
嗯,主要就是這樣:constructor 里初始化狀態(tài),然后渲染組件,onClick里綁定點(diǎn)擊事件,事件函數(shù)中setState()一下,此時(shí)得到了新的狀態(tài),并使虛擬DOM重新渲染,最終Label顯示我們輸入的值。
這個(gè)過(guò)程理解了的話(huà),也就能夠和Redux進(jìn)行對(duì)照理解。
然后看看使用Redux 后,組件是怎么寫(xiě)的:
import React, { Component } from 'react';
import PropTypes from 'prop-types'
class Test extends Component{
static propTypes = {
firstname:PropTypes.string.isRequired,
addName:PropTypes.func.isRequired
}
handlerFunc = () =>{
const inputName = this.refs.inputValueTest.value;
this.props.addName(inputName);
}
render() {
const {firstname} = this.props;
return (
<div>
<label> {firstname} </label><br/>
<input ref="inputValueTest" /><br/>
<button onClick={this.handlerFunc}>confirm</button><br/>
</div>
);
}
}
觀察兩種寫(xiě)法,會(huì)發(fā)現(xiàn):
(1)初始化狀態(tài)沒(méi)有了,
(2)setState()也移除了,然后通過(guò)屬性結(jié)構(gòu)賦值。
總之,狀態(tài)管理方面的東西都被移除了。因?yàn)椴捎肦edux框架,本身就是要讓他接管狀態(tài)的,所以,redux框架下的React的代碼就很好理解了。就是react組件里面是沒(méi)有State相關(guān)的東西(當(dāng)然,特殊情況下還是需要用的組件自己的狀態(tài)管理的)。它所要做的就是,傳遞動(dòng)作(按鈕點(diǎn)擊)或者請(qǐng)求(請(qǐng)求數(shù)據(jù)),并根據(jù)自身屬性向HTML中填入數(shù)據(jù)。這里用到了prop-types這個(gè)庫(kù)。來(lái)進(jìn)行屬性的校驗(yàn)。
之后可以這樣理解,setState()方法在Redux那邊被拆分成兩步,現(xiàn)在Action中,把獲取到的值,弄成一個(gè)Object,代碼如下:
import { ADDNAME} from "./action-type";
//包含所有的action creator
export const addName = (name) =>({type:ADDNAME,data:name})
這里面的addName 就是React代碼里按鈕觸發(fā)的方法中要執(zhí)行的函數(shù),name 就是我們或得到的輸入框里的值。
接著redux幫我們做的就是用Store中提供的dispatch方法,把這個(gè)動(dòng)作傳到reducer那去,然后來(lái)看看Reducer做了什么:
import {ADDNAME} from './action-type'
const initialState = {
name:'Peter'
}
const name = (state=initialState,action) => { //形參默認(rèn)值
switch(action.type){
case ADDNAME:
return action.data
default:
return state
}
}
由于我們把React中的狀態(tài)讓redux單獨(dú)拎出來(lái),所以最終會(huì)生成一棵大的狀態(tài)樹(shù),也就是一個(gè)Object。所以這里的name,最終會(huì)成為這個(gè)狀態(tài)樹(shù)的一個(gè)屬性,值就是返回值,action.data ,對(duì)應(yīng)action里傳入的值??梢钥匆幌?狀態(tài)樹(shù)可以通過(guò)Store.getState獲得:

Reducer 可以類(lèi)別理解為,充當(dāng)了setState的角色。并且也負(fù)責(zé)了初始化state的任務(wù)。
所以,reducer在case到ADDNAME 這個(gè)動(dòng)作之后,就把從action那傳來(lái)的值,做了一個(gè)相當(dāng)于setState()的操作。要是一開(kāi)始沒(méi)有觸發(fā)任何動(dòng)作,則Store會(huì)幫我們?cè)诔绦蜻\(yùn)行之初執(zhí)行一遍Reducer,使用初始值,initialState
this.setState({
name:action.data
})
2、展示組件與容器組件
redux寫(xiě)的代碼單拎出來(lái)與react代碼是沒(méi)啥聯(lián)系的。寫(xiě)完上面的東西后,一運(yùn)行就會(huì)報(bào)錯(cuò),一大堆undefined。這個(gè)時(shí)候就需要一個(gè)connect來(lái)連接Redux和React?,F(xiàn)在有這么一個(gè)庫(kù)'react-redux'. 使用里面的connect 即可。如下:
import React from 'react'
import {connect} from 'react-redux'
import {addName} from '../redux/actions'
import Test from '../component/Test'
export default connect(
state => ({
firstname :state.name
}),
{addName}
)(Test)
(1)容器組件
這樣就寫(xiě)好了一個(gè)組件,官方稱(chēng)之為容器組件,我個(gè)人把更愿意把它稱(chēng)之為封裝組件,就是引入之前寫(xiě)好的React組件Test,再引入之前action和reducer 里的動(dòng)作、狀態(tài),進(jìn)行一個(gè)封裝,這樣就把Test里事件函數(shù)與屬性匹配到了一起。
其中 firstname 對(duì)應(yīng)Test 里面的firstname 屬性 ,state.name 就是reducer 里定義的那個(gè)name.這樣的話(huà),到時(shí)候Test組件里用的firstname的值,就是state.name給賦上去的。屬性狀態(tài)匹配完之后要匹配動(dòng)作,Test中點(diǎn)擊事件中執(zhí)行的addName,就匹配action里的addName。因?yàn)槲疫@里兩邊都命名為addName,根據(jù)es6規(guī)則,屬性名和屬性值相同的話(huà),可以省略不寫(xiě),這樣就單寫(xiě)一個(gè)就可以。這樣,點(diǎn)擊按鈕之后,數(shù)據(jù)就會(huì)通過(guò)action傳到Reducer那去做類(lèi)似于setState()這樣的操作。
最終拿出去用的,也都是這個(gè)組件,可以把它命名為T(mén)estContainer.jsx 用于路由或者直接放到哪都可以。
(2)展示組件 這個(gè)就是Test那個(gè)組件。里面一般不包含任何Redux相關(guān)的東西。純凈的React組件。稱(chēng)之為展示組件??梢岳斫鉃槿萜鹘M件(封裝組件)的一部分
3、異步
一個(gè)web應(yīng)用,大多數(shù)都需要做請(qǐng)求操作的。而在React + Redux這樣的一個(gè)結(jié)構(gòu)中,異步請(qǐng)求操作最合適的位置就是放到Redux中的action中。首先,展示組件,即Test組件,這個(gè)是view層。view層顯然不適合摻雜異步請(qǐng)求的。然后reducer其實(shí)是實(shí)現(xiàn)的類(lèi)似于setState()的作用,并且這個(gè)時(shí)候才請(qǐng)求數(shù)據(jù)就有點(diǎn)晚了。所以在Redux中的Action中做異步請(qǐng)求是比較合適的。action其實(shí)也是個(gè)傳接數(shù)據(jù)的載體。
既然選擇在Redux中進(jìn)行異步請(qǐng)求,就需要引入一個(gè)庫(kù) react-thunk,來(lái)幫助我們進(jìn)行異步請(qǐng)求.
首先進(jìn)行配置。
import {createStore,applyMiddleware} from 'redux'
import {finalReducer } from './reducers'
import thunk from 'redux-thunk'
//生成store對(duì)象
const store = createStore(finalReducer,applyMiddleware(thunk));//內(nèi)部會(huì)第一次調(diào)用reducer函數(shù),得到初始state
export default store
第一步 npm i redux-thunk;
第二步 引入 redux-thunk的thunk 以及Redux的applyMiddleware
第三步,在createStore中加入這個(gè)庫(kù)
很簡(jiǎn)單。
然后就是使用了
正常來(lái)說(shuō),一般我們是觸發(fā)一個(gè)動(dòng)作,讓他進(jìn)行請(qǐng)求,得到數(shù)據(jù)后,再去setState.在Action中也是一樣。如下:
export const addNameAsync = (name) =>{
return dispatch =>{
setTimeout(()=>{
dispatch(addNameCreater(name))
},2000);
}
}
這里用setTimeout模擬請(qǐng)求過(guò)程。兩秒后,數(shù)據(jù)請(qǐng)求到了,再把數(shù)據(jù)給Reducer進(jìn)行狀態(tài)替換。這里的話(huà),我們需要手動(dòng)調(diào)用dispatch這個(gè)函數(shù),向Reducer去分發(fā)動(dòng)作。
總結(jié)
Redux這塊一開(kāi)始接受很多概念確實(shí)很難理解。學(xué)習(xí)就是如此,一下子鋪天蓋地的拋出很多你從來(lái)沒(méi)聽(tīng)過(guò)的概念,無(wú)論是誰(shuí)都接受的很困難。作為學(xué)習(xí)者,就需要類(lèi)比,理解,然后追根溯源,看看底層如何實(shí)現(xiàn)。
就本篇與前一篇的例子而言,原本不到一百行的東西,在使用了Redux之后,分出了這么多個(gè)文件。確實(shí),折中小來(lái)小去的玩意兒,放在一個(gè)文件里寫(xiě)就完事兒了。但是如果沒(méi)有狀態(tài)管理的這個(gè)意識(shí)。在進(jìn)行大型項(xiàng)目的構(gòu)建時(shí),就很麻煩,一個(gè)請(qǐng)求過(guò)來(lái)的數(shù)據(jù),可能要在多個(gè)組件用。這個(gè)時(shí)候,無(wú)論MobX 還是Redux這類(lèi)的狀態(tài)管理庫(kù),會(huì)節(jié)省很多開(kāi)發(fā)時(shí)間,提高開(kāi)發(fā)效率。
另外就是封裝性很好,清晰的文件結(jié)構(gòu)和代碼結(jié)構(gòu),能夠大大提高項(xiàng)目的可擴(kuò)展行、可維護(hù)性以及降低耦合度。而對(duì)于一些把任何東西寫(xiě)到一起的那種項(xiàng)目,一個(gè)頁(yè)面上千行甚至幾千行,擴(kuò)展維護(hù)起來(lái)的體驗(yàn)可想而知。
作為開(kāi)發(fā)者,還是要對(duì)自己寫(xiě)的東西負(fù)責(zé)。
=========================
多少要有點(diǎn)基本的素養(yǎng)。不要?jiǎng)硬粍?dòng)就把請(qǐng)求寫(xiě)在componentWillMount里!!!