Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

漫谈Redux & React-Redux 设计精髓 #21

Open
hawx1993 opened this issue Dec 28, 2017 · 1 comment
Open

漫谈Redux & React-Redux 设计精髓 #21

hawx1993 opened this issue Dec 28, 2017 · 1 comment
Labels

Comments

@hawx1993
Copy link
Owner

hawx1993 commented Dec 28, 2017

漫谈Redux & React-Redux 设计精髓

Redux 设计精髓

Redux 是为 Javascript 应用而生的可预估的状态容器,而React-Redux是针对React应用而提供的可预测状态管理机制。

1514515687136

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本身接收更新。单状态树使跟踪 随时间的变化以及调试或检查应用程序变得更容易。
593627-20160418100236976-196339185

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关系图

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。

@hawx1993 hawx1993 changed the title Redux 漫谈Redux & React-Redux 设计精髓 Dec 29, 2017
@hawx1993 hawx1993 added the redux label Dec 29, 2017
@jiajianrong
Copy link

赞楼主,不过最后一段,

reducer函数负责处理数据,返回新的state(数据)

不一定是新的state哦,如果dispatch了一个无效action,state返回的还是上一个(===比较为true)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants