Skip to content

redux概念及部分源码分析 #3

@evenMai92

Description

@evenMai92

redux学习

参考一
参考二

设计思想

  1. Web 应用是一个状态机,视图与状态是一一对应的。
  2. 所有的状态,保存在一个对象里面。

基本概念和API

1. Store
Store 就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 Store。

Redux 提供createStore这个函数,用来生成 Store。

  import { createStore, applyMiddleware } from 'redux'
  import thunk from 'redux-thunk'

  const middleware = [ thunk ];
  const store = createStore(
    reducer,
    applyMiddleware(...middleware)
  )

2. State
Store对象包含所有数据。如果想得到某个时点的数据,就要对 Store 生成快照。这种时点的数据集合,就叫做 State。
当前时刻的 State,可以通过store.getState()拿到。

  const state = store.getState();

Redux 规定, 一个 State 对应一个 View。只要 State 相同,View 就相同。你知道 State,就知道 View 是什么样,反之亦然。

3. Action
State 的变化,会导致 View 的变化。但是,用户接触不到 State,只能接触到 View。所以,State 的变化必须是 View 导致的。

Action 就是 View 发出的通知,表示 State 应该要发生变化了。

Action 是一个对象。其中的type属性是必须的,表示 Action 的名称。其他属性可以自由设置,社区有一个规范可以参考。
4. store.dispatch()
store.dispatch()是 View 发出 Action 的唯一方法。

  store.dispatch({
    type: 'ADD_TODO',
    payload: 'Learn Redux'
  })

5. Reducer
Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。

Reducer 是一个纯函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。只要是同样的输入,必定得到同样的输出。
纯函数是函数式编程的概念,必须遵守以下一些约束。

  • 不得改写参数
  • 不能调用系统 I/O 的API
  • 不能调用Date.now()或者Math.random()等不纯的方法,因为每次会得到不一样的结果

工作流程

流程

部分源码分析

1. getState
redux中获取state的值,通过getState获取,从源码可以看出,只做了是否正在dispatching的校验,也就是说获取的state是可以直接修改,没任何提示,当然页面也不会刷新,因为没触发订阅者listener

function getState(): S {
  if (isDispatching) {
    throw new Error(
      'You may not call store.getState() while the reducer is executing. ' +
        'The reducer has already received the state as an argument. ' +
        'Pass it down from the top reducer instead of reading it from the store.'
    )
  }

  return currentState as S
}

2. dispatch
dispatch只做两件事,一:调用reducer,二:通知所有订阅者

function dispatch(action: A) {
    // ...省略校验
  try {
    isDispatching = true
    currentState = currentReducer(currentState, action)
  } finally {
    isDispatching = false
  }

  const listeners = (currentListeners = nextListeners)
  for (let i = 0; i < listeners.length; i++) {
    const listener = listeners[i]
    listener()
  }

  return action
}

3. 应用中间件applyMiddleware

function applyMiddleware(
  ...middlewares: Middleware[]
): StoreEnhancer<any> {
  return (createStore: StoreEnhancerStoreCreator) => <S, A extends AnyAction>(
    reducer: Reducer<S, A>,
    preloadedState?: PreloadedState<S>
  ) => {
    const store = createStore(reducer, preloadedState)
    let dispatch: Dispatch = () => {
      throw new Error(
        'Dispatching while constructing your middleware is not allowed. ' +
          'Other middleware would not be applied to this dispatch.'
      )
    }

    const middlewareAPI: MiddlewareAPI = {
      getState: store.getState,
      dispatch: (action, ...args) => dispatch(action, ...args)
    }
    // 首次调用中间,并将middlewareAPI传入进去,因此每个中间都可以拿到getState和dispatch,只不过此时的dispatch是改造过的
    // 因为在中间件里面调用改dispatch,都会重新再经过所有中间一遍
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    // middleware中间件列表中,最后一个能取到真正的dispatch,即此处传入的store.dispatch
    dispatch = compose<typeof dispatch>(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

// compose函数将所有中间件函数组合成一个按顺序调用的函数[a, b, c] => a(b(c(...args))),所以最后一个中间件能拿到传入的参数
// 也就是store.dispatch
function compose(...funcs: Function[]) {
  if (funcs.length === 0) {
    // infer the argument type so it is usable in inference down the line
    return <T>(arg: T) => arg
  }
  

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args: any) => a(b(...args)))
}

// dispatch能传入函数参数的中间件
const thunk = ({ getState, dispatch }) => next => action => {
  if(typeof action === 'function') {
    action(dispatch, getState);
  } else {
    // 非最后一个中间件,next是上一个中间件的返回值,此处是logger的返回值,即next => action => {...};
    next(action);
  }
}
// 日志中间件
const logger = ({ getState, dispatch }) => next => action => {
  if (process.env.NODE_ENV !== 'production') {
    console.log(action);
  }
  // 此处的next是真正的dipatch,就是compose<typeof dispatch>(...chain)(store.dispatch)传入的store.dispatch
  return next(action);
}

applyMiddleware(thunk, logger)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions