一、前言
React-Redux
将所有组件分成两大类:UI 组件(presentational component
)和容器组件(container component
)。
二、UI 组件
三、容器组件
负责管理数据和业务逻辑,不负责 UI
呈现;
带有内部状态;
使用 Redux
的 API
;
UI 组件负责 UI 的呈现,容器组件负责管理数据和逻辑。
如果一个组件既有 UI
又有业务逻辑,那怎么办?回答是,将它拆分成下面的结构:外面是一个容器组件,里面包了一个UI
组件。前者负责与外部的通信,将数据传给后者,由后者渲染出视图。
React-Redux
规定,所有的 UI
组件都由用户提供,容器组件则是由 React-Redux
自动生成。也就是说,用户负责视觉层,状态管理则是全部交给它。
3.1 connect()
React-Redux
提供connect
方法,用于从 UI
组件生成容器组件。connect
的意思就是将这两种组件连起来。
import { connect } from 'react-redux'
const VisibleTodoList = connect()(TodoList);
复制代码
上面 VisibleTodoList 便是通过connect
方法自动生成的容器组件。但需要定义业务逻辑,组件才有意义。
import { connect } from 'react-redux'
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
复制代码
connect
方法接受两个参数:mapStateToProps
和mapDispatchToProps
。它们定义了 UI
组件的业务逻辑。前者负责输入逻辑,即将state
映射到 UI
组件的参数(props
),后者负责输出逻辑,即将用户对 UI
组件的操作映射成 Action
。
mapStateToProps
()
它是一个函数,建立一个从(外部的)state
对象到(UI
组件的)props
对象的映射关系。
mapStateToProps
执行后应该返回一个对象,里面的每一个键值对就是一个映射。
const mapStateToProps = (state) => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
}
复制代码
正如前面所说,mapStateToProps
是一个函数,它接受state
作为参数,返回一个对象。这个对象有一个todos
属性,代表 UI
组件的同名参数,里面的 getVisibleTodos 也是一个函数,可以从 state 算出 todos 的值。
mapDispatchToProps()
mapDispatchToProps
是connect
函数的第二个参数,用来建立 UI
组件的参数到store.dispatch
方法的映射。它定义了哪些用户的操作应该当作 Action
,传给 Store
。它可以是一个函数,也可以是一个对象。
当mapDispatchToProps
为函数时,则会得到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
};
}
复制代码
3.2 Provider 组件
connect
方法生成容器组件以后,需要让容器组件拿到state
对象,才能生成 UI
组件的参数。
React-Redux
提供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')
)
复制代码
上面代码中,Provider
在根组件外面包了一层,这样一来,App 的所有子组件就默认都可以拿到state
了。
四、Demo:简单计数器
下面是一个计数器组件,它是一个纯的 UI
组件。
class Counter extends Component {
render() {
const { value, onIncreaseClick } = this.props
return (
<div>
<span>{value}</span>
<button onClick={onIncreaseClick}>Increase</button>
</div>
)
}
}
复制代码
上面代码中,这个 UI 组件有两个参数:value
和onIncreaseClick
。前者需要从state
计算得到,后者需要向外发出 Action
。
接着,定义value
到state
的映射,以及onIncreaseClick
到dispatch
的映射。
function mapStateToProps(state) {
return {
value: state.count
}
}
function mapDispatchToProps(dispatch) {
return {
onIncreaseClick: () => dispatch(increaseAction)
}
}
// Action Creator
const increaseAction = { type: 'increase' }
复制代码
然后,使用connect
方法生成容器组件。
const App = connect(
mapStateToProps,
mapDispatchToProps
)(Counter)
复制代码
然后,定义这个组件的 Reducer
。
// Reducer
function counter(state = { count: 0 }, action) {
const count = state.count
switch (action.type) {
case 'increase':
return { count: count + 1 }
default:
return state
}
}
复制代码
最后,生成store
对象,并使用Provider
在根组件外面包一层。
import { loadState, saveState } from './localStorage';
const persistedState = loadState();
const store = createStore(
todoApp,
persistedState
);
store.subscribe(throttle(() => {
saveState({
todos: store.getState().todos,
})
}, 1000))
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
复制代码
完整代码如下:
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'
// 定义counter组件
class Counter extends Component {
render() {
const { value, onIncreaseClick } = this.props
// const value = this.props.value
return (
<div>
<span>{value}</span>
<button onClick={onIncreaseClick}> +1</button>
</div>
)
}
}
//对Counter组件接受的props进行类型检查
Counter.propTypes = {
value: PropTypes.number.isRequired, //要求数字类型,没有提供会警告
onIncreaseClick: PropTypes.func.isRequired //要求函数类型
}
// Action
const increaseAction = { type: 'increase' }
// Reducer基于原有state,根据action得到新的state
function counter(state = { count: 0 }, action) {
const count = state.count
switch (action.type) {
case 'increase':
return { count: count + 1 }
default:
return state
}
}
// 根据reducer函数通过createStore()创建store
const store = createStore(counter)
// 将state映射到Counter组件的props
function mapStateToProps(state) {
return {
value: state.count
}
}
// 将action映射到Counter组件的props
function mapDispatchToProps(dispatch) {
return {
onIncreaseClick: () => dispatch(increaseAction)
}
}
// 传入上面两个函数参数,将Counter组件变为App组件
const App = connect(
mapStateToProps,
mapDispatchToProps
)(Counter)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
复制代码
五、拓展阅读
评论