Redux的同步数据流
徐徐 抱歉选手

Redux Store

Create a Redux store using the Redux Toolkit configureStore API

在使用Redux管理React全体数据/状态的时候,可以将全体数据按该app功能function/特征features来划分成不同的Redux Slice,这些数据块被包裹在configureStore方法中,被保存在store.js中。通过顶层文件访问state.redux-slice-name来访问数据块。

什么样的数据才需要放到store中

Global state that is needed across the app should go in the Redux store. State that’s only needed in one place should be kept in component state.

In a React + Redux app, your global state should go in the Redux store, and your local state should stay in React components.

redux slice

A “slice” is a collection of Redux reducer logic and actions for a single feature in your app, typically defined together in a single file.

configureStore实现redux slice

在文件树中store.js和features/functions这个文件夹在同一层,features/functions文件夹中又是各种redux slices的子文件夹,这些子文件夹的命名就是configureStore中reducer对象的key。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { configureStore } from '@reduxjs/toolkit'
import usersReducer from '../features/users/usersSlice'
import postsReducer from '../features/posts/postsSlice'
import commentsReducer from '../features/comments/commentsSlice'

export default configureStore({
reducer: {
users: usersReducer,
// Since usersReducer is responsible for updating the state.users slice, we refer to it as a "slice reducer" function.
// reducer对象的key就是features文件夹中的子文件夹名
posts: postsReducer,
comments: commentsReducer
}
})

configureStore手写


以上调用了redux中的configureStore,这是一个封装好的函数。configureStore内部实现是怎样的呢?

当我们把花括号包裹起来的{}reducer object传入给configureStore的reducer中去之后,他会把这个对象在传递给combineReducer函数用来生成一个RootReducer。


如何实现手写configureStore

configureStore的本质就是把各个slice结合起来RootReducer,这个RootReducer的参数就是普通reducer所要求的state和action,只不过这个state是全体数据的state。

1
2
3
4
5
6
7
function rootReducer(state = {}, action) {
return {
users: usersReducer(state.users, action),
posts: postsReducer(state.posts, action),
comments: commentsReducer(state.comments, action)
}
}

RootReducer也可以通过调用combineReducers这个方法来写。他接受一个包含slice reducers的对象作为参数,返回的就是root reducer函数。

1
2
3
4
5
const rootReducer = combineReducers({
users: usersReducer,
posts: postsReducer,
comments: commentsReducer
})

可以把这个RootReducer直接传入到configureStore中去。

1
2
3
const store = configureStore({
reducer: rootReducer
})

Actions

Actions本质上就是对象object。这个对象中包括:值为字符串的type field,能够返回action的action creator functions。

手写Actions

定义各种Actions是复杂且无聊的,因为多个action可能就属于一个功能块。比如论坛中的post板块,可能有post/postUpdated,也可能有post/postDeleted等多个actions。

因此Redux写了一个API把这些工作都集合了起来,这就是createSlice。

使用createSlice写Actions

Redux Toolkit has a function called createSlice, which takes care of the work of generating action type strings, action creator functions, and action objects.

在createSlice内部需要用字符串定义name,用reducer functions组成的对象定义reducers。同时定义initalstate,因为需要给createSlice传递一个初始状态值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { createSlice } from '@reduxjs/toolkit'

export const counterSlice = createSlice({
name: ' ',
initialState: { },
reducers: {
action1: state => { },
action2: state => { },
action3: (state, action) => { }
}
})

export const { action1, action2, action3 } = counterSlice.actions

export default counterSlice.reducer

调用createSlice就会自动生成对应的action code。在createSlice中生成的action code的type就从createSlice的name以及每个reducer function的key name而来。比如, "counter" name + the "increment" reducer function generated an action type of {type: "counter/increment"}.

redux reducer规则

  • 必须是state+action=》state的形式

    They should only calculate the new state value based on the state and action arguments

  • immutable updates/ No mutating states/ 不可以部分数据更新,必须全体更新

    They are not allowed to modify the existing state. Instead, they must make immutable updates, by copying the existing state and making changes to the copied values.

    但是实现imuutable update每次都需要原来数据的copy,copy的更改,用copy替换原来的数据这三个步骤,十分麻烦。

    createSlice和createRedux通过使用Immer library实现了”mutable” update,Immer承担了copy&return的工作,我们只需要关心update和change即可。

    Immer uses a special JS tool called a Proxy to wrap the data you provide, and lets you write code that “mutates” that wrapped data. But, Immer tracks all the changes you’ve tried to make, and then uses that list of changes to return a safely immutably updated value

  • 不含异步逻辑

    They must not do any asynchronous logic or other “side effects”

异步数据

同步逻辑的操作流程如下:

  1. Actions are dispatched
  2. the store runs the reducers and calculates the new state
  3. the dispatch function finishes.

但是当我们需要从一个API获取数据的时候需要使用异步逻辑。

使用thunk实现异步

使用thunk需要把redux-thunk middleware添加到新建的redux store中。


为什么需要middleware?

因为reducer的设计规则不允许我们把异步逻辑放在里面,之所以要把异步逻辑放在reducer去是因为能够访问到store数据。那异步逻辑放在哪里呢?

如果我们能访问到store,那么书写异步逻辑就不是问题。访问store,并且把disptach的逻辑放到异步函数promise/setTimeout之类的就行。那么这涉及到import store into other files,但这也是不可行的,因为redux的设计不允许这种import store的行为。

解决办法就是用middleware去拓展redux store,让store拥有其他的功能。The Redux store can be extended with “middleware”, which are a kind of add-on or plugin that can add extra abilities.


middleware的两个主要功能:

  • 让使用middleware的代码块有异步逻辑的同时也能访问全体store。
  • 修改普通的dispatch函数,让它可以接受functions or Promises,为不实普通的action objects。The Redux Thunk middleware modifies the store to let you pass functions into dispatch.

thunk详解

当使用configureStore创建redux store时,configureStore API会自动配置好thunk。

A thunk is a specific kind of Redux function that can contain asynchronous logic. Thunks are written using two functions:

  • An inside thunk function, which gets dispatch and getState as arguments

    内部的thunkfunction本质上就是异步函数带有promise或者async之类

  • The outside creator function, which creates and returns the thunk function

    外部的creator function会传入一个从服务器获得的数据

UI component访问store

The React-Redux library has a set of custom hooks that allow your React component to interact with a Redux store.

访问state

最常见的就是用useSelector Hook从全体数据的store中获取你想要的那一部分数据(extract whatever pieces of data it needs from the Redux store state)。

手写slector

一个功能是允许从state中获取某个值的函数就叫selector。

1
export const selectCount = state => state.counter.value

如果我们的UI component能够访问store中的数据,就可以在UI component中如下调用selector函数。

1
2
3
const count = selectCount(store.getState())
console.log(count)
// 0

问题就在于Our components can’t talk to the Redux store directly, because we’re not allowed to import it into component files. 也就是store不能被import。因此就需要useSelector Hook作为中介与Redux store对话。

使用useSelector Hook实现

1
const posts = useSelector( state => state.posts)

useSelector总会在一个action被dispatched切redux store更新之后重新运行,并比较前后选择的值。如果前后值不相同,useSelector会让相关的组件用新的数据/状态重新渲染。

访问action

在UI component中,如果用户做了什么行为,会触发action store中的某个action creator,那么我们就能把它当作eventListener一样使用。

但是问题是UI component本身是没有办法访问store的,这个时候需要使用useDispatch Hook。

1
2
3
4
5
6
7
8
9
10
11
const dispatch = useDispatch()

render({
<button
className={styles.button}
aria-label="Increment value"
onClick={() => dispatch(increment())}
>
+
</button>
})

Hook如何定位store

虽然UI子组件可以通过useSelector和useDispatch访问redux store,但是这些hook如何知道他们要访问的store是哪个store?

方法就是在app的入口文件index.js中引入Provider,并把我们希望访问的store作为参数传入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'

import store from './app/store'
import { Provider } from 'react-redux'

ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
  • 本文标题:Redux的同步数据流
  • 本文作者:徐徐
  • 创建时间:2020-12-06 11:25:22
  • 本文链接:https://machacroissant.github.io/2020/12/06/redux-sync-data-flow/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
 评论