问题引入
问题描述
现在需要实现以下功能:
点击左边的导航栏(1)按钮,右边头部的文字(2)会根据所选的按钮改变成对应的文字。
问题分析
左边的导航栏属于组件NaviLeft,右边的头部属于组件Header。而这两个组件都被引用到父组件Admin中。因此头部的文字随导航栏选中的内容改变的问题可转变为兄弟组件通信的问题。
传统解决方案
状态的单向传递
在React中,状态传递的方向是单向的。
父组件向子组件传递状态是通过在父组件中定义一个state,然后子组件通过props接受父组件的state属性。当父组件的state属性改变时,子组件的props通过接受父组件的state,也会随之改变。进而达到渲染子组件UI的目的。通过状态提升,父组件可以控制多个子组件共享该state属性。
子组件向父组件传递状态通常是通过回调函数(callback)实现的。在子组件中定义一个事件方法(假设为点击事件)this.props.handleClick,并把自身的状态作为参数传递过去。父组件通过给子组件的props.handleClick赋值一个函数clickOperation(),当子组件点击时,父组件通过clickOPeration回调子组件的handleClick,并接受子组件传递过来的参数,通过this.setState来改变父组件的状态。
结合以上两点,兄弟组件间要实现通信。首先得通过子组件向父组件传递状态,将该状态传递到父组价的state中。然后通过父组件向子组件传递状态,另一个子组件(兄弟组件)通过props接受父组件的state值,最终达到兄弟组件通信的目的。
兄弟组件通信的实现
首先在NaviLeft组件(该组件直接使用的Ant Design中的Menu组件)中定义一个onClick事件,查看Ant Design文档,改时间会传递参数{item},该参数包含点击菜单按钮的文字内容
render() {
return(
<div>
<div className="logo">
<img src="favicon.ico" alt=""/>
<h1>后台管理系统</h1>
</div>
<Menu onClick={this.props.handleClick} mode="vertical" theme="dark">
{this.state.menuTree}
</Menu>
</div>
)
}
然后在父组件Admin中,给子组件的props.handelClick赋值一个函数getMenuItem(),然后在getMenuItem中接受参数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值,最终实现了该需求。
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>
);
}
方案评价
传统方法暴露除了React状态单向传递的不足。状态只能自顶向下或者自下向上。兄弟组件之间不能之间通信。必须通过父组件来实现中转,中间会经过很多无关的子组件。当组件层级复杂时。传统方法将会变得异常麻烦且难以维护。
Redux入门
Redux的出现就是为了解决传统方法解决状态传递时的痛点。它是将状态存放在一个统一的store(仓库)中,统一管理各组件状态的改变,并直接分发给各级组件。除了store没有中间商。
相关概念
在Redux中有三个关键的概念:Action、Reducer和Store。简单来说:
- action就是动作,也就是通过动作来修改state的值。也是修改store的唯一途径。
- Action 只是描述了有事情发生了这件事实,但并没有说明要做哪些改变,这正是reducer需要做的事情。
- store是redux应用的唯一数据源,我们调用createStore Api创建store。
具体的概念理解只能多看多试,反复揣摩。学习过程不是一蹴而就的
React-redux框架之connect()与Provider组件 用法讲解
通读以上的文章之后,对Redux也有一个较全面的理解了。React的工作原理可以用下图表示
Redux.PNG实战初体验
回到一开始的那个问题,现在用Redux重新实现一遍。
首先编写action、reducer和store部分。其中在store中使用了Redux调试工具redux-devtools-extension。同时还需要在Chrome浏览器中安装Redux DevTools插件,才能使该调试工具生效。
//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这个最上层的组件中添加Provider将所有子组件包裹起来,这样所有的子组件就可以与store联系起来了。再通过其它的后续操作可以获取和修改store中的状态值(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();
然后在需要相互通信的两个兄弟组件NaviLeft和Header中添加connect就可以使这两个组件与store相连,从而做到改变和获取store中的state。这个connect就是之前所说的后续操作
其中NaviLeft的职责是改变store中的state。定义一个mapDispatchToProps函数(将组件的事件作用到store来改变state的值),该函数的作用实现Menu的点击事件onClick=this.props.handleClick,在handleClick中通过将电机的导航按钮的文字传给action来改变store中的state值。然后将mapDispatchToProps与NaviLeft通过connect绑定起来,达到作用于store的目的
class NaviLeft extends React.Component{
render() {
return(
<div>
<div className="logo">
<img src="favicon.ico" alt=""/>
<h1>后台管理系统</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的职责是接受store中的对应state,从而达到渲染UI的目的(更改头部文字)。通过 mapStateToProps函数将组件中的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在组件内部通过handleClick函数分发(dispatch)给了store。因此,在connect中就无需传递参数mapDispatchToProps函数了
class NaviLeft extends React.Component{
handleClick = ({ item }) => {
// 事件派发,自动调用reducer,通过reducer保存到store对象中
const { dispatch } = this.props;
dispatch(changeMenu(item.props.title));
};
render() {
return(
<div>
<div className="logo">
<img src="favicon.ico" alt=""/>
<h1>后台管理系统</h1>
</div>
<Menu onClick={ this.handleClick } mode="vertical" theme="dark">
{this.state.menuTree}
</Menu>
</div>
)
}
}
export default connect()(NaviLeft)
在实际的编写NaviLeft中采用mapDispatchToProps时,第一次写的时候connect写成了如下形式
export default connect(mapDispatchToProps)(NaviLeft)
结果运行报错如下
error.PNG这是由于connect严格按照如下代码形式传参,第一个参数必须是mapStateToProps函数,如果把mapDispatchToProps放在第一个参数的位置,Redux会按照mapStateToProps中将state传给组件props的形式解析。因为mapStateToProps中没有dispatch这个分发函数,因此会报错:dispatch is not a function!该错误查遍了CSDN、简书等国内网站都未找到结果。最后还是在Stack Overflow中找到答案的。
connect(mapStateToProps,mapDispatchToProps)({组件})
总结
在该案例中,使用传统方案和Redux并未多大区别,甚至Redux实现起来更复杂些(写的代码更多)。
- 因此,Redux并不是一个必须要使用的技术栈,能用传统方案轻松解决的问题没必要使用Redux。
- 但是,当web app中,需要共享的状态特别多、并且组件的层级结果非常复杂时,往往使用Redux更佳
网友评论