redux并不是 React 独有的一个插件,它是顺应前端组件化开发潮流而诞生的一种状态管理模型
1、先定义全局一个state的应用状态,想要修改这个状态必须使用dispatch方法(便于打断点调试,到底哪里修改了这个数据,也避免了误操作):
let appState = {
title: {
text: 'React.js 小书',
color: 'red',
},
content: {
text: 'React.js 小书内容',
color: 'blue'
}
}
function dispatch (action) {
switch (action.type) {
case 'UPDATE_TITLE_TEXT':
appState.title.text = action.text
break
case 'UPDATE_TITLE_COLOR':
appState.title.color = action.color
break
default:
break
}
}
2、将这两个东西集合到一个地方,命名为store,然后构建一个函数 createStore,用来专门生产这种 state 和 dispatch 的集合,这样别的 App 也可以用这种模式了:
function createStore (state, stateChanger) {
const getState = () => state
const dispatch = (action) => stateChanger(state, action)
return { getState, dispatch }
}
function stateChanger (state, action) {
switch (action.type) {
case 'UPDATE_TITLE_TEXT':
state.title.text = action.text
break
case 'UPDATE_TITLE_COLOR':
state.title.color = action.color
break
default:
break
}
}
const store = createStore(appState, stateChanger)
renderApp(store.getState()) // 首次渲染页面
store.dispatch({ type: 'UPDATE_TITLE_TEXT', text: '《React.js 小书》' }) // 修改标题文本
store.dispatch({ type: 'UPDATE_TITLE_COLOR', color: 'blue' }) // 修改标题颜色
renderApp(store.getState()) // 把新的数据渲染到页面上
后面经过很多优化可以写成下面这样(把stateChanger名字改成reducer)
function createStore (reducer) {
let state = null
const listeners = []
const subscribe = (listener) => listeners.push(listener)
const getState = () => state
const dispatch = (action) => {
state = reducer(state, action)
listeners.forEach((listener) => listener())
}
dispatch({}) // 初始化 state
return { getState, dispatch, subscribe }
}
function reducer(state, action) {
if (!state) { //初始化数据合并到reducer里
return {
title: {
text: 'React.js 小书',
color: 'red',
},
content: {
text: 'React.js 小书内容',
color: 'blue'
}
}
}
switch (action.type) {
case 'UPDATE_TITLE_TEXT':
return {
...state,
title: {
...state.title,
text: action.text
}
}
case 'UPDATE_TITLE_COLOR':
return {
...state,
title: {
...state.title,
color: action.color
}
}
default:
return state
}
}
那么具体在项目中,redux是如何使用的呢?
使用createStore方法初始一个store,在reducer里面初始化state和action
import { createStore } from 'redux'
function todoReducer(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return state.concat([action.text])
default:
return state
}
}
let store = createStore(todoReducer, ['Use Redux'])
store.dispatch({ //这里就改变store里的数据
type: 'ADD_TODO',
text: 'Read the docs'
}) //dispatch里的就是action,有时候复杂的action我们会将它抽出来
console.log(store.getState())
// [ 'Use Redux', 'Read the docs' ]
以后是在根组件Provider(通过react-redux import进来)上把store当props传进去(相当于context)
ReactDOM.render(
<Provider store={store}>
<Index />
</Provider>,
document.getElementById('root')
)‘
子组件要使用还比较麻烦,如果需要使用 state 中的数据,就必须是「被 connect 过的」组件——使用 connect 方法对「你编写的组件(MyComp)」进行包装后的产物。
class MyComp extends Component {
// content...
}
const Comp = connect(...args)(MyComp);
connect函数的参数:
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
其实connect的主要参数就两个,作用分别是将state和actions绑定到自身的props上。
例:
const MainState = (state) => {
return {
data: state.job //state.job是reducer(reducer的名字就叫job)的初始数据
}
}
const BindAction = dispatch => bindActionCreators(actions, dispatch) //将actions变成可以直接调用的函数,即只要调用action就直接触发dispatch,即时改变数据
export default connect(MainState, BindAction)(Page) //将state和action绑定到page组件的props上
//action:
changeFilter(filter) {
return {
type: actionType.TASK_LIST_CHANGE_FILTER,
payload: {
filter
}
}
},
当需要改变redux里的值时需要action方法里使用新的值,即把原数据拷贝一份再调用这个方法,例:
// 修改input
handleChangeInput = (field,e) => {
let newFilter = Object.assign({},this.props.filter)
let value = e.target.value
// if(validationType === 'number' && !/^[0-9]*$/.test(value)){
// return
// }
newFilter[field] = value
this.props.onChangeFilter(newFilter)
}
下面是connect的文档:
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
mapStateToProps(state, ownProps) : stateProps
这个函数允许我们将 store 中的数据作为 props 绑定到组件上。
const mapStateToProps = (state) => {
return {
count: state.count
}
}
这个函数的第一个参数就是 Redux 的 store,我们从中摘取了 count 属性。因为返回了具有 count 属性的对象,所以 MyComp 会有名为 count 的 props 字段。
class MyComp extends Component {
render(){
return <div>计数:{this.props.count}次</div>
}
}
const Comp = connect(…args)(MyComp);
当然,你不必将 state 中的数据原封不动地传入组件,可以根据 state 中的数据,动态地输出组件需要的(最小)属性。
const mapStateToProps = (state) => {
return {
greaterThanFive: state.count > 5
}
}
函数的第二个参数 ownProps,是 MyComp 自己的 props。有的时候,ownProps 也会对其产生影响。比如,当你在 store 中维护了一个用户列表,而你的组件 MyComp 只关心一个用户(通过 props 中的 userId 体现)。
const mapStateToProps = (state, ownProps) => {
// state 是 {userList: [{id: 0, name: '王二'}]}
return {
user: _.find(state.userList, {id: ownProps.userId})
}
}
class MyComp extends Component {
static PropTypes = {
userId: PropTypes.string.isRequired,
user: PropTypes.object
};
render(){
return <div>用户名:{this.props.user.name}</div>
}
}
const Comp = connect(mapStateToProps)(MyComp);
当 state 变化,或者 ownProps 变化的时候,mapStateToProps 都会被调用,计算出一个新的 stateProps,(在与 ownProps merge 后)更新给 MyComp。
这就是将 Redux store 中的数据连接到组件的基本方式。
mapDispatchToProps(dispatch, ownProps): dispatchProps
connect 的第二个参数是 mapDispatchToProps,它的功能是,将 action 作为 props 绑定到 MyComp 上。
const mapDispatchToProps = (dispatch, ownProps) => {
return {
increase: (...args) => dispatch(actions.increase(...args)),
decrease: (...args) => dispatch(actions.decrease(...args))
}
}
class MyComp extends Component {
render(){
const {count, increase, decrease} = this.props;
return (<div>
<div>计数:{this.props.count}次</div>
<button onClick={increase}>增加</button>
<button onClick={decrease}>减少</button>
</div>)
}
}
const Comp = connect(mapStateToProps, mapDispatchToProps)(MyComp);
由于 mapDispatchToProps 方法返回了具有 increase 属性和 decrease 属性的对象,这两个属性也会成为 MyComp 的 props。
如上所示,调用 actions.increase() 只能得到一个 action 对象 {type:’INCREASE’},要触发这个 action 必须在 store 上调用 dispatch 方法。diapatch 正是 mapDispatchToProps 的第一个参数。但是,为了不让 MyComp 组件感知到 dispatch 的存在,我们需要将 increase 和 decrease 两个函数包装一下,使之成为直接可被调用的函数(即,调用该方法就会触发 dispatch)。
Redux 本身提供了 bindActionCreators 函数,来将 action 包装成直接可被调用的函数。
import {bindActionCreators} from 'redux';
const mapDispatchToProps = (dispatch, ownProps) => {
return bindActionCreators({
increase: action.increase,
decrease: action.decrease
});
}
同样,当 ownProps 变化的时候,该函数也会被调用,生成一个新的 dispatchProps,(在与 statePrope 和 ownProps merge 后)更新给 MyComp。注意,action 的变化不会引起上述过程,默认 action 在组件的生命周期中是固定的。
mergeProps(stateProps, dispatchProps, ownProps): props
之前说过,不管是 stateProps 还是 dispatchProps,都需要和 ownProps merge 之后才会被赋给 MyComp。connect 的第三个参数就是用来做这件事。通常情况下,你可以不传这个参数,connect 就会使用 Object.assign 替代该方法。
其他
最后还有一个 options 选项,比较简单,基本上也不大会用到(尤其是你遵循了其他的一些 React 的「最佳实践」的时候),本文就略过了。希望了解的同学可以直接看文档。