前言
在上文中我们学习了react的工作流程,知道了用户点击操作先是分发action,然后reducer根据接收到的action来做具体值的改变的这个曲折过程。
仔细看redux工作流的盆友一定发现了,上文中并没有用到最中心的store,那么下文中我们将引入新的东西:store
知识点:
Provider createStore connect
store类似与vue的vuex,在vue中vuex是一个集中数据和方法的中心仓库,同样,store也有异曲同工之妙。
接下来我们开始真正地接触redux。
安装:
npm install --save redux
npm install --save react-redux
以下贴一个使用了store,connect等知识点的同样的计数器例子,只是这次的计数器的值是放在props里公共管理的
/**
* @desc redux-example
*/
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
import { createStore } from 'redux'
import { Provider, connect } from 'react-redux'
// UI组件
/** [1]
* 两个参数:
* value:由props的得到
* 方法:向外发出action
*/
class Counter extends Component {
render() {
const { value, onIncreaseClick } = this.props
return (
<div>
<span>{value}</span>
<button onClick={onIncreaseClick}>Increase</button>
</div>
)
}
}
/**
* [2] 定义映射 输入输出逻辑
* 将UI组件的value值[props]映射到state
* 将UI组件的方法映射到action 通过dispatch触发action
*/
//将UI组件的props与redux的state映射
function mapStateToProps(state) {
return {
value: state.count
}
}
//将UI组件的props与redux的action映射
function mapDispatchToProps(dispatch) {
return {
//用户的onIncreaseClick方法与action映射([3]定义action),通过dispatch触发reducer
onIncreaseClick: () => dispatch(increaseAction)
}
}
/**
* [3] 给UI组件用connect()方法附上输入输出逻辑[逻辑],生成一个容器组件App
*/
const App = connect(
mapStateToProps,
mapDispatchToProps
)(Counter)
/**
* [4] 定义action 纯函数 输入什么输出什么 收到的action返回一个type字段
* dispatch拿着这个type字段去找reducer,reducer根据不同type匹配不同的动作 并最终返回一个新state
*/
// Action
const increaseAction = { type: 'increase' }
/**
* [5] 定义reducer
* 用户触发什么action对应返回一个经过reducer处理的新state,不同type对应不同的处理方式
*/
// Reducer
function counter(state = { count: 0 }, action) {
const count = state.count
switch (action.type) {
case 'increase':
return { count: count + 1 }
default:
return state
}
}
/**
* [6] 以reducer做为传入值生成一个store对象
*/
// Store
const store = createStore(counter)
/**
* [7] 使用Provider组件在根组件外面包一层 App及其子组件就可以拿到state了
*/
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
总结:
接上文, 看了代码的可能会惊讶,哇,怎么一下多了这么多东西!
其实我们只是进入了真正的战斗状态而已哈哈。数据的管理思路还是没变。
以下说说此文中我们接触的真正的”战斗状态的redux“:
我们看看同样的计数器”这篇”较前文不同的部分:
|-- mapStateToProps
|-- mapDispatchToProps
|-- App = connect(
mapStateToProps,
mapDispatchToProps
)(Counter)
|-- store = createStore(counter)
|-- <Provider store={store}>
<App />
</Provider>
细心的盆友一定发现了,本组件Couter没有自身属性了,即state。它的参数和方法都来自于props。如果按照上文的比喻理解,可以认为,小县城连本地仓库和记录本地仓库数据的会计(或笔杆)都没啦!哦漏,现在小县城就仅仅是个”废城”啦,没有自身属性(展示的数据来源于外部),别人给我什么我就展示什么,也会和用户交互但是并不能改变展示的东西,只能发出一个action,传递这个动作的意义让别人来改变组件数据状态。俗称木偶组件
1. 现在理理故事背景:
小县城仍然想得到计算机王国的粮仓储值情况,县城的粮食收益会报告给王国粮食中心,县城的粮食值来源是”王国粮食中心 “。
2. UI组件和容器组件:
我们提到过,现在的小县城是木偶组件了,只负责展示数据和发出动作的action,但是为什么要把小城变为木偶组件?
我们想一下哈,王国有多个县城,多个县城的粮食情况都需要与粮食中心实时,相当于我们将粮食管理控制采取了中央集权制了,让一个完整的组件划分成UI和容器两类,UI专注展示,容器专注逻辑和数据处理。
3. 我们有的“实物”:
1. 一个小县城Couter组件-UI组件(功能:展示数据 + 发出改变数据的action)
2.一个粮仓Store (王国的粮食储备中心,里面实时记录着本国的粮食数量)
3. 一个信号中转站 Reducer (可以实时读取到store的数据及里面生活着一个可以计算的会计师(方法))
我们还需要生成一个县城的容器组件,负责处理县城的数据和逻辑交互,这个组件可以拿到仓库store的数据,同时也可以将UI组件发出的action传给reducer
4.建立联系:
建立县城与粮食中心的联系:
const App = connect(
mapStateToProps,//将redux的state与UI组件的props映射
mapDispatchToProps//将redux的action与UI组件的props映射
)(Counter)
说明:
1. store是个仓库,它的state值被下发到各县城组件,作为县城组件的props。即不要被state和props绕晕了
2. connect 奇怪的写法,其实这是个函数式编程写法,在这里可以姑且理解为输入的两个逻辑通过connect被完美地糅合在一起,并携带上counter这个UI组件一起生成了容器组件。
总结:
通过上文我们学习了真实的redux,我们总结下如下几个我认为比较重要的知识点:
0. 容器组件和UI组件
1. state和props区别
2. redux和react关系
笔记详情见:react初学笔记
在回顾了本文学习的redux知识之余,我们跳出redux这个框可以思考如下几个问题:
1.redux的存在意义?数据集中管理方案的其他出路?
2.木偶、容器组件的意义?(我们为什么要将好端端的一个组件分成木偶组件和容器组件?)即为什么推荐无状态组件?
code例子来源:simplest-redux-example