Redux学习笔记
newtwg@qq.com 最后修改2020.02.20
React技术出来好多年了,一直没有好好学习, 近年来做项目一直都在用vue。
这次将要做个基于React-Redux的源码二次开发项目,所以不得不好好学习了。
趁着宅家,我手上拿着书,在github与csdn中摸爬打滚,已经奋战了四五天,一阵恶补后总算对React有了个初步的认识。
这里简单做个记录,也许也能助后来者更快入门。
其中不免理解有误之处,还请多多指正。
(一)Flux概念
An application architecture for React utilizing a unidirectional data flow.
(二)Redux概念
A Predictable State Container for JS Apps
Redux是一种对Flux的优化实现
(三)使用纯Redux
https://github.com/reduxjs/redux/tree/master/examples/counter
1.通过<script>引用Redux.min.js
2.定义reducer:
理解:即定义实际的数据与操作,要确定2项内容:
(1)对state数据的操作
根据store.dispatch过来的action进行操作
(2)返回操作后的state数据
function myReducer(state=initState, action) {
switch (action.type) {
...
default:
return state;
}
}
3.创建store实例(以定义的reducer为参数)
理解:即把reducer封装成store实例,后面引用store就可以完成所有事情
var store = Redux.createStore(myReducer)
4.注册store内state改变时的事件函数(如重新渲染)
理解:store.dispatch()时会调度store中关联的reducer对state作出改变,随后调用store.suscribe()注册的事件函数
store.subscribe(render)
5.在view中创建action,并通过store.dispatch调度
理解:调度的目的是让store中的reducer对state作出修改
store.dispatch({ type: 'ACTION_NAME',,data:...})
view中可以用store.getState()来获得store中的state数据
6.要点小结:
(1)核心就是store对象,其中包括:
①内容: reducer, 定义了数据的结构与操作逻辑
②入口: store.dispatch()
③产出: state, 在view中通过store.getState()获取
(2)代码要点
function myReducer(state=initState, action)
var store = Redux.createStore(myReducer)
store.subscribe(render)
store.dispatch({ type: 'ACTION_NAME',,data:...})
store.getState()
(四)配合React使用
https://github.com/reduxjs/redux/tree/master/examples/counter
一图看懂(by newtwg):示例工程项目结构及代码关键点
1.通过import引用redux
import { createStore } from 'redux'
2.定义reducer:
一般在reducers中创建单独的文件来定义,定义方式之前类似:
export default (state=0,action)=>{
switch (action.type) {
...
default:
return state
}
}
然后在创建store的js文件中通过import引用
3.创建store实例(以定义的reducer为参数)
import counter from './reducers'
const store = createStore(counter)
4.将store(或其部分)作为props传给组件,可能需要逐级下传
<Counter
value={store.getState()}
onIncrement={()=>store.dispatch({type:'INCREMENT'})}
onDecrement={()=>store.dispatch({type:'DECREMENT'})}
/>,
5.注册store发生改变时的事件函数(如重新渲染)
理解:store.dispatch()时会调度store中关联的reducer对state作出改变,随后调用store.suscribe()注册的事件函数
store.subscribe(render)
6.要点小结:
(1)store对象应放在上层的入口文件中,通过props逐级下传
(2)reducers文件夹可用于组织多个reducer文件
(3)代码要点
import { createStore } from 'redux'
import counter from './reducers'
const store = createStore(counter)
<Counter value={store.getState()} onIncrement={()=>store.dispatch({type:'INCREMENT'})} onDecrement={()=>store.dispatch({type:'DECREMENT'})}/>,
store.subscribe(render)
(五)通过React-Redux使用
Official React bindings for Redux
主要是解决在React组件中Redux的store(state与dispatch)需要层层传递的问题
https://github.com/reduxjs/redux/tree/master/examples/todomvc
一图看懂(by newtwg):示例工程项目结构及代码关键点
1.定义reducer:
可以在src/reducers文件夹中创建多个单独的reducer文件(定义方式和之前类似),然后在src/reducers/index.js中用redux的combineReducers()合并,生成多个state数据节点:
import { combineReducers } from 'redux'
import todos from './todos'
import visibilityFilter from './visibilityFilter'
const rootReducer = combineReducers({
todos, visibilityFilter
})
export default rootReducer
这里想说一点,关于这里的reducer这个单词,网上好多地方都直接按字面翻译成缩减器、还原器或减速器之类,我认为有些不妥,这很容易让人费解。reduce有分解归纳之意,比如译作分解归纳器也许更恰当一些,因为它完成的功能正是把redux的store中所有可预见性的数据及其操作进行分解和归纳。(个人见解仅供参考了)
2.入口文件中:
(1)创建store实例(以定义的reducer为参数)
import { createStore } from 'redux'
import counter from './reducers' //或写全 './reducers/index.js'
const store = createStore(counter)
(2)用Provider对原根组件进行包装,并传入store
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
3.不直接使用原组件,需以connect函数封装成高阶组件再调用
(1)对于有读取或操作数据需求的组件,都要进行封装,以传入state和dispatch
(2)原组件不管在view中使用时,处在多深的层级,只要封装后,都可以通过props引用传入的state和dispatch
(3)原组件文件夹为src/components, 高阶容器组件文件夹为src/containers
//导入需要读取和操作数据的原组件MainSection
import MainSection from '../components/MainSection'
const mapStateToProps = state => ({
todos: state.todos,
filter: state.visibilityFilter
})
const mapDispatchToProps = dispatch => ({
actions:{
action1:()=>dispatch({type:'ACTION_NAME1'}),
action2:(v)=>dispatch({type:'ACTION_NAME2',val:v})
}
})
export default connect(
mapStateToProps, mapDispatchToProps
)(MainSection) //导出对原组件进行封装后的新组件
4.在原组件中(不管层级多深)可通过props(可配合ES6解构)引用state和dispatch
const MainSection=({todos,filter,actions})=>(
//原组件中可以:
//用todos和filter读取数据
//用actions.acton1()、actions.acton2(v) 发送操作调度
)
export default MainSection;
5.要点小结:
(1)store对象应放在上层的入口文件中,通过props逐级下传
(2)reducers文件夹可用于组织多个reducer文件
(3)代码要点
export default combineReducers({todos, visibilityFilter})
import reducer from './reducers'
const store = createStore(reducer)
<Provider store={store}><App /></Provider>
export default connect(mapStateToProps, mapDispatchToProps)(MainSection)
(六)配合redux-thunk使用Redux
Thunk middleware for Redux
https://github.com/reduxjs/redux-thunk
1.为什么要用redux-thunk
主要是解决在Redux中异步调度(或有条件调度)的问题,
(1)不用中间件的实现方式:
①在组件中单独进行异步及条件调度
弊端:在每个组件中都要单独实现一次
②在用react-redux的connect()封装时处理异步及条件调度
通过第2个参数mapDispatchToProps是型如function(dispatch,ownProps)的这个机制,可以用这个dispatch发起异步调度
const mapDispatchToProps = (dispatch, ownProps) => ({
action1 : (v)=>{
setTimeout(()=>{
dispatch({type : 'ACTION_NAME1',val : v});
},1000)
}
})
export default connect(null, mapDispatchToProps)(BaseComponent)
弊端:在每个connect封装前都要单独实现一次
(2)使用redux-thunk的实现方式
但如果调度的这个action本身就始终是一个异步的或有条件的操作怎么办,在每个react-redux的conect()前(甚至在组件中)敲一遍处理代码显然太麻烦,不利于后期业务变更时进行维护。
当然还有一种方式理论上应该可以,就是在reducer中处理。但reducerk中本应该就是对state的纯数据操作,不宜有太多的业务逻辑,以reducer中处理有违react的设计理念,主要是会大大增加耦合程度,从而使系统后期变得难以维护。
而这时,我们使用redux-thunk这个中间件,就可以从store.dispatch()的参数action本身入手,使store.dispatch不但可以接受plain objects类型的action,还可以接受function类型的action。这个function可以带两个参数dispatch为getState,分别指向store的dispatch()方法和getState()方法,然后我们就可以在function中进行做相应异步或条件处理后再调用dispatch()对store发起调度了。
一图看懂(by newtwg):项目结构及代码关键点
2.import引用
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducers' //或写全 './reducers/index.js'
3.创建store:
const store = createStore(reducer, applyMiddleware(thunk));
4.在actions.js中导出function类型的action:
export const actioin1 = text=>{
return function(dispatch,getState){
setTimeout(()=>{
//console.log(getState());
dispatch({type:types.ADD_TODO, text});
},2000);
}
}
5.高阶组件传dispatch到原组件props中
Import { action1 } from '../actions'
export default connect(null, { action1 })(Header);
或
import * as TodoActions from '../actions'
const mapDispatchToProps = dispatch => ({
actions:bindActionCreators(TodoActions,dispatch)
})
export default connect(null, mapDispatchToProps)(BaseComponent)
这样就不用在connect之前再作处理
6.在原组件中发起调度的方法
const BaseComponent = ({ actions }) => (
...
actions.actioin1 (text)
...
)
7.要点小结:
(1)Action写在单独文件中,多了的话也可分组成多个文件,要用哪个引哪个
(2)如果不是特列需求,尽量考虑写在action中进行异步处理
(3)代码要点
①入口js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
const store = createStore(reducer, applyMiddleware(thunk));
②actions.js
export const action1 = text=>{return function(dispatch,getState){/*作异步处理等*/};};
③React-Redux高阶封装
Import { action1 } from '../actions'
export default connect(null, { action1 })(Header)
④原组件
const BaseComponent = ({ actions }) => ( ... actioin1 (text); ... )