参考自阮一峰React教程
react-redux的用法
一、UI组件
react-redux将所有组件分成两大类:UI组件(presentational component)和容器组件(container component)。
UI组件有以下几个特征:
1. 只负责UI的呈现,不带有任何业务逻辑
2. 没有状态(即不使用this.state这个变量)
3. 所有数据都由参数(this.props)提供
4. 不使用任何Redux的API
二、容器组件
容器组件的特征如下:
1. 负责管理数据和业务逻辑,不负责UI的呈现
2.带有内部逻辑
3. 使用redux的API
若一个组件既有UI又有业务逻辑,那就将它拆分成下面的结构:外边是一个容器组件,里面包含了一个UI组件,前者负责与外传给部的通信,将数据传给后者,由后者渲染出视图。
三、connect()
react-redux提供connect方法,用于从UI组件生成容器组件。
import { connect } from 'react-redux'
const VisibleTodoList = connect()(TodoList);
上面代码中TodoList是UI组件,VisibleTodoList就是由react-redux通过connect方法自动生成的容器组件。
为了定义业务逻辑,需给出以下两方面的信息:
1. 输入逻辑:外部的数据(即state对象)如何转换为UI组件的参数。
2. 输出逻辑:用户发出的动作如何变为Action对象,从UI组件传出去。
//connect方法的完整API
import { connect } from 'react-redux'
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
注:connect方法接受两个参数:
mapStateToProps
和mapDispatchToProps
,前者负责输入输入逻辑,即将state映射到UI组件的参数props,后者负责输出逻辑,即将用户对UI组件的操作映射为Action.
四、mapStateToProps()
mapStateToProps是一个函数,用于建立一个从state对象到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组件的重新渲染。
五、mapDispatchToProps()
mapDispatchToProps是connect函数的第二个参数,用来建立UI组件的参数到store.dispatch方法的映射,它可以是一个函数,也可以是一个对象。
1. mapDispatchToProps是一个函数,会接受dispatch和ownProps两个参数。
const mapDispatchToProps = (
dispatch,
ownProps
) => {
return {
onClick: () => {
dispatch({
type: 'SET_VISIBILITY_FILTER',
filter: ownProps.filter
});
}
};
}
2. mapDispatchToProps是一个对象,它的每个键名也是对应UI组件的同名参数,键值是一个函数,被当做Action creator,返回的Action会由Redux自动发出。
const mapDispatchToProps = {
onClick: (filter) => {
type: 'SET_VISIBILITY_FILTER',
filter: filter
};
}
六、组件
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了。
它的原理就是react组件的context属性:
class Provider extends Component {
getChildContext() {
return {
store: this.props.store
};
}
render() {
return this.props.children;
}
}
Provider.childContextTypes = {
store: React.PropTypes.object
}
上面代码中,store放在了上下文对象context上面,然后子组件就可以从context拿到store.
class VisibleTodoList extends Component {
componentDidMount() {
const { store } = this.context;
this.unsubscribe = store.subscribe(() =>
this.forceUpdate()
);
}
render() {
const props = this.props;
const { store } = this.context;
const state = store.getState();
// ...
}
}
VisibleTodoList.contextTypes = {
store: React.PropTypes.object
}
七、实例:计数器
1. 纯的计数器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.
2. 定义state到value的映射,以及onIncreaseClick到dispatch的映射。
function mapStateToProps(state) {
return {
value: state.count
}
}
function mapDispatchToProps(dispatch) {
return {
onIncreaseClick: () => dispatch(increaseAction)
}
}
// Action Creator
const increaseAction = { type: 'increase' }
3. 使用connect方法生成容器组件。
const App = connect(
mapStateToProps,
mapDispatchToProps
)(Counter)
4. 定义这个组件的Reducer
// Reducer
function counter(state = { count: 0 }, action) {
const count = state.count
switch (action.type) {
case 'increase':
return { count: count + 1 }
default:
return state
}
}
5. 生成store对象,并使用Provider在根组件外面包一层。
import { loadState, saveState } from './localStorage';
const persistedState = loadState();
const store = createStore(
counter,
persistedState
);
store.subscribe(throttle(() => {
saveState({
count: store.getState().count,
})
}, 1000))
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
八、react-router路由库
const Root = ({ store }) => (
<Provider store={store}>
<Router>
<Route path="/" component={App} />
</Router>
</Provider>
);