[React]React-Redux用法
React-Redux是Redux对于React的封装
UI组件
React-Redux将所有的组件分为两大类,UI组件和容器组件。
UI组件将不带有任何逻辑,无状态,所有的参数由this.props提供且不使用Redux的API
例如:
const Title= value=><h1>{value}</h1>;
UI组件不含有状态,因此也被称为纯组件,与纯函数相同,也由参数决定其值。
容器组件
容器组件负责管理数据和状态,不呈现UI,且使用Redux‘s API
在大多数情况下要将组件进行拆分,使其由外面包裹的容器组件和内部的UI组件构成。外部的容器组件负责与外部的通信,将数据传给UI组件,UI组件负责渲染出视图。
React-Redux规定,所有的UI组件都由用户提供,容器组件是由React-Redux自动生成的。
connect()
React-Redux提供connect()方法,用于从UI组件生成容器组件。
import {connect} from ‘react-redux‘ const VisibleTodoList=connect()(TodoList);
上面的代码将UI组件TodoList通过connect()生成VisibleTodoList容器组件。
需要为了容器组件定义业务逻辑:
- 输入逻辑:外部的数据转换为UI组件的参数
- 输出逻辑:用户的动作变为Action对象,从UI组件传出。
connect的完整API如下:
import {connect} from ‘react-redux‘ const VisibleTodoList=connect( mapStateToProps, mapDispatchToProps )(TodoList)
在上面的代码中,connect()接收了两个参数:mapStateToprops和mapDispatchToProps。它们定义了UI组件的业务逻辑。
mapStateToProps()
mapStateToProps是一个函数。它的作用是建立一个从外部state对象到UI组件的props对象的映射关系。
它执行后返回一个对象,里面的一个键值对就是一个映射。
const mapStateToProps=(state)=>{ return{ todos:getVisibleTodos(state.todos.state.visibilityFilter) } }
在上面的代码中,mapStateToProps函数接收了state作为参数,返回一个对象。这个对象有一个todos属性,代表UI组件的同名函数,后面的getVisibleTodos也是一个函数,将会从state种获得todos的值。
例如:
const getVisibleTodos = (todos, filter) => { switch (filter) { case ‘SHOW_ALL‘: return todos case ‘SHOW_COMPLETED‘: return todos.filter(t => t.completed) case ‘SHOW_ACTIVE‘: return todos.filter(t => !t.completed) default: throw new Error(‘Unknown filter: ‘ + filter) } }
mapStateToProps会订阅store,在state更新的时候就会自动地执行,重新计算UI组件的参数从而触发UI组件的重新渲染。
mapStateToProps的第一个参数是state对象,第二个参数代表容器组件的props对象。
在使用第二个参数之后,如果容器组件的参数发生变化,将会引发UI组件重新渲染。
connect方法可以忽略mapStateToProps参数,这时Store的更新不会引起UI组件的更新。
mapDispatchToProps()
mapDispatchToProps()是connect()函数的第二个参数,用来建立UI组件的参数到store.dispatch()方法的映射。它定义了哪些用户的操作应该作为Action传给Store进行处理。
它可以是函数或者对象。
如果它是函数,它的入参为dispatch和ownProps(容器组件的props对象)两个参数。
const mapDispatchToProps = ( dispatch, ownProps ) => { return { onClick: () => { dispatch({ type: ‘SET_VISIBILITY_FILTER‘, filter: ownProps.filter }); } }; }
mapDispatchToProps作为函数返回了一个对象,该对象的每个键值对定义了UI组件的参数如何发出Action。
mapDispatchToProps是一个对象时,它的每个键名是对应的UI组件的同名参数,键值为一个函数(Action creator),返回的Action将会由Redux自动发出。
const mapDispatchToProps = { onClick: (filter) => { type: ‘SET_VISIBILITY_FILTER‘, filter: filter }; }
组件
connect()方法生成容器组件后,需要使容器组件得到state才能生成UI组件的参数。
为了避免一级级地传递state导致的不必要的麻烦,使用Provider组件可以使得容器组件得到state;
import { Provider } from ‘react-redux‘ import { createStore } from ‘redux‘ import todoApp from ‘./reducers‘ import App from ‘./components/App‘ let store = createStore(todoApp); render( <Provider store={store}> <App /> </Provider>, document.getElementById(‘root‘)
在上面的例子中,App组件的所有子组件默认可以得到state。
原理是React组件的context机制。
计数器实例
import React, { Component } from ‘react‘ import PropTypes from ‘prop-types‘ import ReactDOM from ‘react-dom‘ import { createStore } from ‘redux‘ import { Provider, connect } from ‘react-redux‘ // React component--UI组件 class Counter extends Component { render() { const { value, onIncreaseClick } = this.props return ( <div> <span>{value}</span> <button onClick={onIncreaseClick}>Increase</button> </div> ) } } Counter.propTypes = { value: PropTypes.number.isRequired, onIncreaseClick: PropTypes.func.isRequired } // Action const increaseAction = { type: ‘increase‘ } // Reducer function counter(state = { count: 0 }, action) { const count = state.count switch (action.type) { case ‘increase‘: return { count: count + 1 } default: return state } } // Store const store = createStore(counter) // Map Redux state to component props function mapStateToProps(state) { return { value: state.count } } // Map Redux actions to component props function mapDispatchToProps(dispatch) { return { onIncreaseClick: () => dispatch(increaseAction) } } // Connected Component--容器组件 const App = connect( mapStateToProps, mapDispatchToProps )(Counter) ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById(‘root‘) )
另一个例子
将之前提过的面包屑实例进行完善:
在这个例子中,需要在切换路由的时候,将state中的当前菜单名字状态赋给面包屑UI组件的props,进一步重新渲染UI;
在切换路由的子组件中,需要在路由变化时发送Action改变Store;
import {switchMenu} from ‘./../../redux/action‘ import {connect} from ‘react-redux‘ //... class Navleft extends React.Component{ //... handleOnClick=({item}){ const {dispatch}=this.props; //产生Action改变Store dispatch(switchMenu(item.props.title)); } render(){ return ( //... <Menu onClick={this.handleOnClick} {...} </Menu> ); } } //连接 export default connect()(Navleft);
以上的代码在路由菜单切换时产生Action改变Store。
接下来要在Store改变时重新渲染面包屑UI
import {connect} from ‘react-redux‘ class Header extends React.Component{ render() { return ( //... ); } } //connect参数,通过state影响props const mapStateToProps=state=>{ return{ menuName:state.menuName } } //连接 export default connect(mapStateToProps)(Header)
在以上的例子中,在UI组件中通过{this.props.menuName}就可以获得在容器组件中的状态。
这样就达到了在切换菜单时改变面包屑显示的目的。