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}就可以获得在容器组件中的状态。
这样就达到了在切换菜单时改变面包屑显示的目的。