問題引入
問題描述
現(xiàn)在需要實(shí)現(xiàn)以下功能:
點(diǎn)擊左邊的導(dǎo)航欄(1)按鈕梗肝,右邊頭部的文字(2)會(huì)根據(jù)所選的按鈕改變成對應(yīng)的文字。
問題分析
左邊的導(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(從零實(shí)現(xiàn)一個(gè) redux)
React-redux框架之connect()與Provider組件 用法講解
通讀以上的文章之后凶伙,對Redux也有一個(gè)較全面的理解了。React的工作原理可以用下圖表示
實(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ò)如下
這是由于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更佳