React:Redux入門

問題引入

問題描述

現(xiàn)在需要實(shí)現(xiàn)以下功能:
點(diǎn)擊左邊的導(dǎo)航欄(1)按鈕梗肝,右邊頭部的文字(2)會(huì)根據(jù)所選的按鈕改變成對應(yīng)的文字。

problem.PNG

問題分析

左邊的導(dǎo)航欄屬于組件NaviLeft,右邊的頭部屬于組件Header杈抢。而這兩個(gè)組件都被引用到父組件Admin中。因此頭部的文字隨導(dǎo)航欄選中的內(nèi)容改變的問題可轉(zhuǎn)變?yōu)樾值芙M件通信的問題。

傳統(tǒng)解決方案

狀態(tài)的單向傳遞

在React中粥喜,狀態(tài)傳遞的方向是單向的畏腕。

父組件向子組件傳遞狀態(tài)是通過在父組件中定義一個(gè)state缴川,然后子組件通過props接受父組件的state屬性。當(dāng)父組件的state屬性改變時(shí)描馅,子組件的props通過接受父組件的state把夸,也會(huì)隨之改變。進(jìn)而達(dá)到渲染子組件UI的目的铭污。通過狀態(tài)提升恋日,父組件可以控制多個(gè)子組件共享該state屬性。

子組件向父組件傳遞狀態(tài)通常是通過回調(diào)函數(shù)(callback)實(shí)現(xiàn)的嘹狞。在子組件中定義一個(gè)事件方法(假設(shè)為點(diǎn)擊事件)this.props.handleClick岂膳,并把自身的狀態(tài)作為參數(shù)傳遞過去。父組件通過給子組件的props.handleClick賦值一個(gè)函數(shù)clickOperation(),當(dāng)子組件點(diǎn)擊時(shí)磅网,父組件通過clickOPeration回調(diào)子組件的handleClick谈截,并接受子組件傳遞過來的參數(shù),通過this.setState來改變父組件的狀態(tài)涧偷。

結(jié)合以上兩點(diǎn)簸喂,兄弟組件間要實(shí)現(xiàn)通信。首先得通過子組件向父組件傳遞狀態(tài)燎潮,將該狀態(tài)傳遞到父組價(jià)的state中喻鳄。然后通過父組件向子組件傳遞狀態(tài),另一個(gè)子組件(兄弟組件)通過props接受父組件的state值确封,最終達(dá)到兄弟組件通信的目的诽表。

兄弟組件通信的實(shí)現(xiàn)

首先在NaviLeft組件(該組件直接使用的Ant Design中的Menu組件)中定義一個(gè)onClick事件,查看Ant Design文檔,改時(shí)間會(huì)傳遞參數(shù){item},該參數(shù)包含點(diǎn)擊菜單按鈕的文字內(nèi)容

render() {
    return(  
        <div>
            <div className="logo">
                <img src="favicon.ico" alt=""/>
                <h1>后臺(tái)管理系統(tǒng)</h1>
            </div>
            <Menu onClick={this.props.handleClick} mode="vertical" theme="dark">
                {this.state.menuTree}
            </Menu>
        </div>
    )
}

然后在父組件Admin中,給子組件的props.handelClick賦值一個(gè)函數(shù)getMenuItem(),然后在getMenuItem中接受參數(shù)item并改變父組件的state值info

render(){
    return(
        <Row className='container'>
            <Col span='4' className='navi-left'>
                <NaviLeft handleClick={this.getMenuItem}/>
            </Col>
            <Col span='20' className='main'>
                <Header info={this.state.info}/>
                <Row className='content'>
                    {this.props.children}
                </Row>
                <Footer />
            </Col>
        </Row>
    )
}

getMenuItem = ({item}) => {
    this.setState({
        info:item.props.title
    })
}

最后兄弟組件Header中通過this.props.info接受父組件的state值隅肥,最終實(shí)現(xiàn)了該需求竿奏。

 return(
    <div className="header">
        // 其他代碼
        <Row className="breadcrumb">
            <Col span="2">
                {this.props.info}
            </Col>
            <Col span="22">
                <span className="current-time">{this.state.time}</span>
                <span className="weather-img">
                    <img src={this.state.dayPictureUrl} alt='天氣' />
                </span>
                <span className="weather-detail">
                    {this.state.weather}
                </span>
            </Col>
        </Row>
    </div>
);
}

方案評價(jià)

傳統(tǒng)方法暴露除了React狀態(tài)單向傳遞的不足。狀態(tài)只能自頂向下或者自下向上腥放。兄弟組件之間不能之間通信泛啸。必須通過父組件來實(shí)現(xiàn)中轉(zhuǎn),中間會(huì)經(jīng)過很多無關(guān)的子組件秃症。當(dāng)組件層級復(fù)雜時(shí)候址。傳統(tǒng)方法將會(huì)變得異常麻煩且難以維護(hù)吕粹。

Redux入門

Redux的出現(xiàn)就是為了解決傳統(tǒng)方法解決狀態(tài)傳遞時(shí)的痛點(diǎn)。它是將狀態(tài)存放在一個(gè)統(tǒng)一的store(倉庫)中岗仑,統(tǒng)一管理各組件狀態(tài)的改變匹耕,并直接分發(fā)給各級組件。除了store沒有中間商荠雕。

相關(guān)概念

在Redux中有三個(gè)關(guān)鍵的概念:Action稳其、Reducer和Store。簡單來說:

  • action就是動(dòng)作炸卑,也就是通過動(dòng)作來修改state的值既鞠。也是修改store的唯一途徑。
  • Action 只是描述了有事情發(fā)生了這件事實(shí)盖文,但并沒有說明要做哪些改變嘱蛋,這正是reducer需要做的事情。
  • store是redux應(yīng)用的唯一數(shù)據(jù)源五续,我們調(diào)用createStore Api創(chuàng)建store洒敏。

具體的概念理解只能多看多試,反復(fù)揣摩疙驾。學(xué)習(xí)過程不是一蹴而就的

Redux 中文文檔

Redux入門教程(快速上手)

完全理解 redux(從零實(shí)現(xiàn)一個(gè) redux)

React-redux框架之connect()與Provider組件 用法講解

Redux 入門教程(阮一峰)

通讀以上的文章之后凶伙,對Redux也有一個(gè)較全面的理解了。React的工作原理可以用下圖表示

Redux.PNG

實(shí)戰(zhàn)初體驗(yàn)

回到一開始的那個(gè)問題荆萤,現(xiàn)在用Redux重新實(shí)現(xiàn)一遍镊靴。

首先編寫action、reducer和store部分链韭。其中在store中使用了Redux調(diào)試工具redux-devtools-extension偏竟。同時(shí)還需要在Chrome瀏覽器中安裝Redux DevTools插件,才能使該調(diào)試工具生效敞峭。

//action
export default function changeMenu(menuName){
    return {
        type: 'CHANGE_MENU',
        menuName: menuName,
    }
}

//reducer
const initState = {
    menuName: ''
}

export default function menuReducer(state = initState,action){
    switch (action.type) {
        case 'CHANGE_MENU':
            return {
                ...state,
                menuName: action.menuName,
            }
        default:
            return {
                ...state
            }
    }

}

//store
import menuReducer from './../reducer/index'
import { createStore } from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension'

const store = createStore(menuReducer,composeWithDevTools());

export default store;

然后在index.js這個(gè)最上層的組件中添加Provider將所有子組件包裹起來踊谋,這樣所有的子組件就可以與store聯(lián)系起來了。再通過其它的后續(xù)操作可以獲取和修改store中的狀態(tài)值(state)

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import * as serviceWorker from './serviceWorker';
import ERouter from './router'
import { Provider } from 'react-redux'
import store from './redux/store/index'

ReactDOM.render(
    <Provider store={store}>
        <ERouter />
    </Provider>,
    document.getElementById('root')
);

serviceWorker.unregister();

然后在需要相互通信的兩個(gè)兄弟組件NaviLeft和Header中添加connect就可以使這兩個(gè)組件與store相連旋讹,從而做到改變和獲取store中的state殖蚕。這個(gè)connect就是之前所說的后續(xù)操作

其中NaviLeft的職責(zé)是改變store中的state。定義一個(gè)mapDispatchToProps函數(shù)(將組件的事件作用到store來改變state的值)沉迹,該函數(shù)的作用實(shí)現(xiàn)Menu的點(diǎn)擊事件onClick=this.props.handleClick,在handleClick中通過將電機(jī)的導(dǎo)航按鈕的文字傳給action來改變store中的state值睦疫。然后將mapDispatchToProps與NaviLeft通過connect綁定起來,達(dá)到作用于store的目的

class NaviLeft extends React.Component{
    render() {
        return(  
            <div>
                <div className="logo">
                    <img src="favicon.ico" alt=""/>
                    <h1>后臺(tái)管理系統(tǒng)</h1>
                </div>
                <Menu onClick={ this.props.handleClick } mode="vertical" theme="dark">
                    {this.state.menuTree}
                </Menu>
            </div>
        )
    }
}

const mapDispatchToProps = (dispatch) => { 
    return{
        handleClick: ({item}) => { 
            dispatch(changeMenu(item.props.title));
        }
    }
}
  
export default connect(null,mapDispatchToProps)(NaviLeft)

組件Header的職責(zé)是接受store中的對應(yīng)state鞭呕,從而達(dá)到渲染UI的目的(更改頭部文字)蛤育。通過 mapStateToProps函數(shù)將組件中的state值傳遞到組件Header的props中。然后直接通過this.props.menuName即可渲染UI。其中connect綁定mapStateToProps和Header的作用和之前敘述的一樣

import React from 'react'
import { Row, Col } from 'antd';
import './index.less'
import Axios from '../../axios'
import { connect } from 'react-redux'

class Header extends React.Component{

    render(){
        return(
            <div className="header">
                // 其他代碼
                <Row className="breadcrumb">
                    <Col span="2">
                        {this.props.menuName}
                    </Col>
                    <Col span="22">
                        <span className="current-time">{this.state.time}</span>
                        <span className="weather-img">
                            <img src={this.state.dayPictureUrl} alt='天氣' />
                        </span>
                        <span className="weather-detail">
                            {this.state.weather}
                        </span>
                    </Col>
                </Row>
            </div>
        );
    }
}

const mapStateToProps = (state) => {
    return{
        menuName: state.menuName,
    }
}

export default connect(mapStateToProps)(Header)

踩坑指南

NaviLeft組件中更改store中的state還有如下的一種方案,該方案直接將action在組件內(nèi)部通過handleClick函數(shù)分發(fā)(dispatch)給了store瓦糕。因此底洗,在connect中就無需傳遞參數(shù)mapDispatchToProps函數(shù)了

class NaviLeft extends React.Component{
    handleClick = ({ item }) => {
        // 事件派發(fā),自動(dòng)調(diào)用reducer咕娄,通過reducer保存到store對象中
        const { dispatch } = this.props;
        dispatch(changeMenu(item.props.title));
    };

    render() {
        return(  
            <div>
                <div className="logo">
                    <img src="favicon.ico" alt=""/>
                    <h1>后臺(tái)管理系統(tǒng)</h1>
                </div>
                <Menu onClick={ this.handleClick } mode="vertical" theme="dark">
                    {this.state.menuTree}
                </Menu>
            </div>
        )
    }
}

export default connect()(NaviLeft)

在實(shí)際的編寫NaviLeft中采用mapDispatchToProps時(shí)亥揖,第一次寫的時(shí)候connect寫成了如下形式

export default connect(mapDispatchToProps)(NaviLeft)

結(jié)果運(yùn)行報(bào)錯(cuò)如下

error.PNG

這是由于connect嚴(yán)格按照如下代碼形式傳參,第一個(gè)參數(shù)必須是mapStateToProps函數(shù)圣勒,如果把mapDispatchToProps放在第一個(gè)參數(shù)的位置费变,Redux會(huì)按照mapStateToProps中將state傳給組件props的形式解析。因?yàn)閙apStateToProps中沒有dispatch這個(gè)分發(fā)函數(shù)灾而,因此會(huì)報(bào)錯(cuò):dispatch is not a function胡控!該錯(cuò)誤查遍了CSDN扳剿、簡書等國內(nèi)網(wǎng)站都未找到結(jié)果旁趟。最后還是在Stack Overflow中找到答案的。

connect(mapStateToProps,mapDispatchToProps)({組件})

總結(jié)

在該案例中庇绽,使用傳統(tǒng)方案和Redux并未多大區(qū)別锡搜,甚至Redux實(shí)現(xiàn)起來更復(fù)雜些(寫的代碼更多)。

  • 因此瞧掺,Redux并不是一個(gè)必須要使用的技術(shù)棧耕餐,能用傳統(tǒng)方案輕松解決的問題沒必要使用Redux。
  • 但是辟狈,當(dāng)web app中肠缔,需要共享的狀態(tài)特別多、并且組件的層級結(jié)果非常復(fù)雜時(shí)哼转,往往使用Redux更佳
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末明未,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子壹蔓,更是在濱河造成了極大的恐慌趟妥,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件佣蓉,死亡現(xiàn)場離奇詭異披摄,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)勇凭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進(jìn)店門疚膊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人虾标,你說我怎么就攤上這事寓盗。” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵贞让,是天一觀的道長周崭。 經(jīng)常有香客問我,道長喳张,這世上最難降的妖魔是什么续镇? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮销部,結(jié)果婚禮上摸航,老公的妹妹穿的比我還像新娘。我一直安慰自己舅桩,他們只是感情好酱虎,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著擂涛,像睡著了一般读串。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上撒妈,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天恢暖,我揣著相機(jī)與錄音,去河邊找鬼狰右。 笑死杰捂,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的棋蚌。 我是一名探鬼主播嫁佳,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼谷暮!你這毒婦竟也來了蒿往?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤坷备,失蹤者是張志新(化名)和其女友劉穎熄浓,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體省撑,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡赌蔑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了竟秫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片娃惯。...
    茶點(diǎn)故事閱讀 38,622評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖肥败,靈堂內(nèi)的尸體忽然破棺而出趾浅,到底是詐尸還是另有隱情愕提,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布皿哨,位于F島的核電站浅侨,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏证膨。R本人自食惡果不足惜如输,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望央勒。 院中可真熱鬧不见,春花似錦、人聲如沸崔步。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽井濒。三九已至灶似,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間眼虱,已是汗流浹背喻奥。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工席纽, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留捏悬,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓润梯,卻偏偏與公主長得像过牙,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子纺铭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評論 2 348

推薦閱讀更多精彩內(nèi)容