# ReactRedux 源码解析

# 依赖

项目 版本号
react 18.2.0
react-redux 8.0.5
react-redux 4.2.0
@reduxjs/toolkit 1.9.1

调试源码项目 (opens new window)

# 使用

我们先看下我们是怎么使用 react-redux 的

Demo (opens new window)

// src/Example/one/index.tsx
import { Provider } from "react-redux";
import { store } from "./app/store";
import App from "./App";

const container = document.getElementById("root")!;
const root = createRoot(container);

root.render(
  <Provider store={store}>
    <App />
  </Provider>
);

// src/Example/one/app/store.ts
import { configureStore, ThunkAction, Action } from "redux-toolkit";
import counterReducer from "../features/counter/counterSlice";
// 这里生成 store
export const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
});

// src/Example/one/features/counter/Counter.tsx
import { useAppSelector, useAppDispatch } from '../../app/hooks';
import { decrement } from './counterSlice';

export function Counter() {
  const count = useAppSelector(selectCount); // 这里触发 useSelector
  const dispatch = useAppDispatch(); // 这里触发 useDispatch

  return (
    <div>
      <button onClick={() => dispatch(decrement())}>-</button>
    </div>
  )
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# 源码

react-redux 的 Provider 组件在 src/react-redux/src/components/Provider.tsx 下

注意: 入口文件我们初始化了 useSelector 下的 useSyncExternalStoreWithSelector 方法
// src/react-redux/src/components/Provider.tsx
function Provider<A extends Action = AnyAction, S = unknown>({
  store,
  context,
  children,
  serverState,
}: ProviderProps<A, S>) {
  const contextValue = useMemo(() => {
    // 创建监听参数 放入 react 的 context 中
    const subscription = createSubscription(store)
    return {
      store,
      subscription,
      getServerState: serverState ? () => serverState : undefined,
    }
  }, [store, serverState])

  const previousState = useMemo(() => store.getState(), [store])

  useIsomorphicLayoutEffect(() => {
    const { subscription } = contextValue
    // 声明 onStateChange 方法为 notifyNestedSubs 方法
    subscription.onStateChange = subscription.notifyNestedSubs
    // 初始化 store.subscribe 当执行 strore.dispatch 的时候 会触发 onStateChange( notifyNestedSubs ) 方法
    subscription.trySubscribe()
    console.log(
      'previousState !== store.getState()',
      previousState !== store.getState()
    )
    if (previousState !== store.getState()) {
      // 触发更新
      subscription.notifyNestedSubs()
    }
    return () => {
      console.log('销毁订阅')
      subscription.tryUnsubscribe()
      subscription.onStateChange = undefined
    }
  }, [contextValue, previousState])
  // 默认使用内部初始化的 context
  const Context = context || ReactReduxContext

  return <Context.Provider value={contextValue}>{children}</Context.Provider>
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

我们看下 createSubscription 做了什么

function createListenerCollection() {
  const batch = getBatch()
  let first: Listener | null = null
  let last: Listener | null = null

  return {
    clear() {
      first = null
      last = null
    },

    notify () {
      batch(() => {
        debugger
        let listener = first
        while (listener) {
          // 这个callback 就是 react-dom 的 subscribeToStore 也就是 告知react重新渲染 那也就是能拿到最新的 redux 的值了
          listener.callback()
          listener = listener.next
        }
      })
    },

    get() {
      let listeners: Listener[] = []
      let listener = first
      while (listener) {
        listeners.push(listener)
        listener = listener.next
      }
      return listeners
    },

    subscribe (callback: () => void) {
      let isSubscribed = true

      let listener: Listener = (last = {
        callback,
        next: null,
        prev: last,
      })

      if (listener.prev) {
        listener.prev.next = listener
      } else {
        first = listener
      }

      return function unsubscribe() {
        if (!isSubscribed || first === null) return
        isSubscribed = false

        if (listener.next) {
          listener.next.prev = listener.prev
        } else {
          last = listener.prev
        }
        if (listener.prev) {
          listener.prev.next = listener.next
        } else {
          first = listener.next
        }
      }
    },
  }
}

export function createSubscription(store: any, parentSub?: Subscription) {
  let unsubscribe: VoidFunc | undefined
  let listeners: ListenerCollection = nullListeners

  function addNestedSub (listener: () => void) {
    trySubscribe()
    return listeners.subscribe(listener)
  }
  // 触发更新
  function notifyNestedSubs () {
    listeners.notify()
  }
  // 每次执行 dispatch 都会 触发这个方法 因为下面 subscribe 了
  // onStateChange 就是 notifyNestedSubs 也就是 notify
  function handleChangeWrapper () {
    if (subscription.onStateChange) {
      subscription.onStateChange()
    }
  }
  // 判断是否已经初始化订阅
  function isSubscribed() {
    return Boolean(unsubscribe)
  }
  // 初始化订阅
  function trySubscribe() {
    if (!unsubscribe) {
      unsubscribe = parentSub
        ? parentSub.addNestedSub(handleChangeWrapper)
        : store.subscribe(handleChangeWrapper)

      listeners = createListenerCollection()
    }
  }
  // 销毁订阅
  function tryUnsubscribe() {
    if (unsubscribe) {
      unsubscribe()
      unsubscribe = undefined
      listeners.clear()
      listeners = nullListeners
    }
  }

  const subscription: Subscription = {
    addNestedSub,
    notifyNestedSubs,
    handleChangeWrapper,
    isSubscribed,
    trySubscribe,
    tryUnsubscribe,
    getListeners: () => listeners,
  }

  return subscription
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123

到这里我们可以理解了, react-redux 主要就拿到 redux 生成的 store, store 张这样

{
  @@observable:ƒ observable(),
  dispatch:action => {}, // 触发 action
  getState:ƒ getState(), // 获取最新的 state
  replaceReducer:ƒ replaceReducer(nextReducer),
  subscribe:ƒ subscribe(listener), // 订阅功能 当执行 dispatch 会触发
}
1
2
3
4
5
6
7
所以 react-redux 就是拿到 store, 然后通过 react 的 context 功能将数据绑定上去, 再暴露出 dispatch 方法, 当用户执行 dispatch 方法更新 state 的时候, 触发 subscribe, 从而触发 react-dom 方法, 触发更新, 重新执行 render. 触发组件执行, 页面重新渲染更新

我们再看下 useSelector, useDispatch 是怎么实现的 (其实就是一个自定义hook)

export function createSelectorHook(
  context = ReactReduxContext
): <TState = unknown, Selected = unknown>(
  selector: (state: TState) => Selected,
  equalityFn?: EqualityFn<Selected>
) => Selected {
  // 这里拿到了 useContext
  const useReduxContext =
    context === ReactReduxContext
      ? useDefaultReduxContext
      : () => useContext(context)

  return function useSelector<TState, Selected extends unknown>(
    selector: (state: TState) => Selected,
    equalityFn: EqualityFn<NoInfer<Selected>> = refEquality
  ): Selected {
    if (process.env.NODE_ENV !== 'production') {
      if (!selector) {
        throw new Error(`You must pass a selector to useSelector`)
      }
      if (typeof selector !== 'function') {
        throw new Error(`You must pass a function as a selector to useSelector`)
      }
      if (typeof equalityFn !== 'function') {
        throw new Error(
          `You must pass a function as an equality function to useSelector`
        )
      }
    }
    // 这里拿到的 context 上的 value
    const { store, subscription, getServerState } = useReduxContext()!
    // 这里就是 react 自己封装的库 use-sync-external-store 暴露的方法 目的就是为了提供给三方库使用
    // 作用就是可以拿到最新的 state 因为在react18开始, concurrent 模式是异步的, 可中断的, 所以如果使用useState可能拿到的不是最新的值 这里的底层实现方式也就是将默认的异步更新强制转为同步模式, 所以可能会存在一直执行更新 我感觉这里也是不很优美 会消耗性能
    // selector 是一个函数 目的就是通过 store.getState() 拿到最新的 state, 然后格式化拿到具体需要的参数
    const selectedState = useSyncExternalStoreWithSelector(
      subscription.addNestedSub,
      store.getState,
      getServerState || store.getState,
      selector,
      equalityFn
    )

    useDebugValue(selectedState)

    return selectedState
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
export function createDispatchHook<
  S = unknown,
  A extends Action = AnyAction
  // @ts-ignore
  > (context?: Context<ReactReduxContextValue<S, A>> = ReactReduxContext) {
    // @ts-ignore
  console.log('context === ReactReduxContext', context === ReactReduxContext) // true
  const useStore =
    // @ts-ignore
    context === ReactReduxContext ? useDefaultStore : createStoreHook(context)

  return function useDispatch<
    AppDispatch extends Dispatch<A> = Dispatch<A>
  >(): AppDispatch {
    const store = useStore()
    // @ts-ignore
    // 所以拿到的就是 redux 生成的 dispatch
    return store.dispatch
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

我们这里画一个流程图方便大家理解

Last Updated: 3/15/2023, 2:54:57 PM