tech-blog
tech-blog copied to clipboard
漫谈Redux & React-Redux 设计精髓
漫谈Redux & React-Redux 设计精髓
Redux 设计精髓
Redux 是为 Javascript 应用而生的可预估的状态容器,而React-Redux是针对React应用而提供的可预测状态管理机制。
Redux 由三部分组成:action,store,reducer。action顾名思义是你发起的一个操作,然后丢给reducer,reducer接收一个之前的state和action参数,然后返回一个新的 state 给 store(state 只允许在 reducer 中进行改变)。Store是一个容器,state 存储在这个容器中,redux规定Store仅能有唯一一个,而mobx可以有多个Store。
Store
在redux中,Store 管理着应用程序的状态,我们无法直接修改Store,唯一的方法是通过reducer,而唯一可以触发reducer的是通过Store去dispatch一个action(包含状态变更的信息)。因此,要改变数据,我们需要dispatch一个action。所以,redux应用数据的改变为:
const action = {
type: 'COMPLETE_TODO',
payload: 0
}
1.store.dispatch(action)
2.newState = reducer(previousState, action)
- 创建Store
// createStore
const store = createStore(
rootReducer,
initialState,// initialState = {}
compose(
applyMiddleware(...middleware)
)
)
Redux使用“Store”将应用程序的整个状态存储在一个地方。因此,所有组件的状态都存储在Store中,并且它们从Store本身接收更新。单状态树使跟踪 随时间的变化以及调试或检查应用程序变得更容易。
reducer
一个reducer是一个纯函数:(previousState, action) => newState
,用来执行根据指定 action 来更新 state 的逻辑。reducer决定应用数据的改变
纯函数即只要参数相同,相同的输入永远会有相同的输出,纯函数不能修改previousState。reducer每次都要返回一个新状态,新状态由旧状态和action决定。类似的:
import { createStore } from 'redux'
const MathReducer = (state = {count: 0}, action) => {
switch (action.type){
case 'INCREASE': return {count: state.count + 1};
case 'DECREASE': return {count: state.count - 1};
default: return state;
}
}
const store = createStore(MathReducer);// createStore(rootReducer,initState)
store.subscribe(() => {
console.log(store.getState())
})
const actions = {
increase: () => ({type: 'INCREASE'}),
decrease: () => ({type: 'DECREASE'})
}
store.dispatch(actions.increase()) ;// { count: 1}
store.dispatch(actions.decrease());// { count: 0}
Store中的数据通过dispatch action,但action只是提供了actionType和payload,并没有决定Store应如何更新。
reducer 接收actionType和payload,然后switch actionType,在相关条件下进行数据整合,最后结合原Store 的state和action对象生成新的state给Store,进行Store的更新
action 类型常量
其实action就是传递的数据(对象的形式),Redux将所有事件抽象为action。当dispatch action后,Store 收到了 Action 必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。
在大型项目中,为了项目规范,一般把action type定义为常量,单独放一个文件。
因此,redux缺点也是显而易见的,为了一个功能,既要写reducer,还要写action,还要单独写一个文件定义action type
源码简单分析
createStore(reducer, preloadedState, enhancer)
createStore对外暴露了dispatch,subscribe,getState和replaceReducer方法。
- dispatch(action):触发状态变化的唯一方法就是dispatch一个action。
function dispatch(action) {
// ...省略一堆数据判断,错误捕获
// 将当前和下一监听函数赋值给listeners
const listeners = (currentListeners = nextListeners)
// 遍历所有监听函数
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener();// 执行每一个监听函数
}
return action;//返回传入的action对象
}
- subscribe(listener):任何时候只要dispatch了action,subscribe将会被调用。redux采用了观察者模式,store内部维护listener数组,用于存储所有通过
store.subscribe
注册的listener,并返回了unsubscribe
方法,用于注销当前listener。当store tree更新后,依次执行数组中的listener:
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected listener to be a function.')
}
if (isDispatching) {
throw new Error(/* some error msg*/)
}
let isSubscribed = true
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {
if (!isSubscribed) {
return
}
if (isDispatching) {
throw new Error(/* some error msg*/)
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}
- getState():读取由Store管理的状态树
function getState() {
return currentState
}
compose.js: 从右向左来组合多个单参函数
compose调用了reduce方法,将形如fn(arg1)(arg2)(arg3)...的柯里化函数按照顺序执行。
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
compose(f, g, h)
形如(...args) => f(g(h(...args)))
,将函数h(...args)
得到的结果作为参数传给函数g,以此类推。例如:
// eg.1
function add(x){
return function(y){
return x + y;
}
}
compose(add(2)(3));// 5
// eg.2
function add1(x){
return x + 10
}
function add2(a){
return a * a;
}
compose(add1(add2(2)));// 14
applyMiddleware.js
applyMiddleware接收中间件为参数,并返回一个createStore为参数的函数。中间件会在每次dispatch的时候执行。
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args)
let dispatch = () => {
throw new Error('some err msg')
}
let chain = []
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
applyMiddleware把中间件放在一个chain数组中,通过compose方法,让每个中间件按照顺序依次传入dispatch参数执行,再组合出新的dispatch
React-Redux
React-Redux
提供了两个重要的对象,<Provider store>
和 connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
。redux提供一个全局的Store, react 从 store 拿去数据,store 发生变化,react 重新进行渲染。
redux与react-redux关系图
Provider
Provider 是在原有的 APP 上面包一层,然后接收 store 作为 props,然后给 connect 用。connect 接收 store 提供的 state 和 action,返回给我们的 react 组件
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内的组件要使用state中的数据,需要使用connect方法进行连接:
class MyComp extends Component {
// content...
}
const Comp = connect(...args)(MyComp);
connect
connect函数是将React组件连接到Redux Store,允许我们将 store 中的数据作为 props 绑定到组件上。用法如下:
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
配合babel,使用es7 decorator:
@connect(
(state) => ({
increase: state.MathReducer.increase,
// 店铺详情页数据
decrease: state.MathReducer.decrease,
})
class Calculate extends React.Component{
// content...
}
decorator是一个对类进行处理的函数,第一个参数就是所要修饰的类。
对于多个reducer,redux也提供了combineReducers
方法,可以把多个 reducer 合并成一个 root reducer,最后再将该reducer丢给createStore()
。
// root-reducers.js
const rootReducer = combineReducers({
MathReducer: MathReducer,
otherReducer: otherReducer
})
export default rootReducer
// store.js
import rootReducer from './root-reducers'
import {createStore} from 'redux'
let store = createStore(rootReducer);
export default store
Redux 数据流动
第一步:调用store.dispatch(action)
如果在React项目中使用react-redux,则通过connect方法,可以将dispatch方法映射到props上。可以通过:
// increase.js
import {IncreaseActions } from '../actions/increase'
this.props.dispatch(IncreaseActions())
// increase.js
import { START_INCREASE,FINISH_INCREASE } from '../actionTypes'
const startCalculate = () => ({ type: START_INCREASE })
const finishCalculate = (payload) => {
return {
type: FINISH_INCREASE,
payload: payload
}
}
export const IncreaseActions = createFetchAction(riderSearchApi,startAddServer,finishAddServer)
第二步:Redux Store调用rootReducer
redux 收到 action 过后,调用根 reducer 并返回最新的状态数据。
第三步:接收新状态并publish给订阅者
reducer函数负责处理数据,返回新的state(数据),state变化,触发store.subscribe()
,所有订阅 store.subscribe(listener)
的监听器都将被调用;监听器里可以调用 store.getState() 获得当前 state。
赞楼主,不过最后一段,
reducer函数负责处理数据,返回新的state(数据)
不一定是新的state哦,如果dispatch了一个无效action,state返回的还是上一个(===比较为true)