Redux的一些常见工具以及与React的联动

在接触Redux的内容时,不免接触了一些它的配套工具,一时之间被这些工具搞得有些混乱,不知道哪个是哪个,他们各自的作用与联系。这篇文章就来简单记录下我的查询结果。

首先说一下这几个工具,redux,redux-thunk,redux-toolkit,react-redux。

  • Redux:是核心库,功能简单,只是一个单纯的状态机,但是蕴含的思想不简单,是传说中的“百行代码,千行文档”。
  • React-Redux:是跟React的连接库,当Redux状态更新的时候通知React更新组件。
  • Redux-Thunk:提供Redux的异步解决方案,弥补Redux功能的不足。
  • Redux-Toolkit:Redux Toolkit附带了一些有用的软件包,例如Immer,Redux-Thunk和Reselect。它使React开发人员的工作变得更加轻松,允许他们直接更改状态(不处理不可变性),并应用Thunk之类的中间件(处理异步操作)。它还使用了Redux的一个简单的“选择器”库Reselect来简化reducer函数。

也就是说这四者之间的关系时,redux是核心,是一个状态机,redux-thunk的作用是让这个状态机能更好地处理异步(并不是redux没法处理异步),redux-toolkit则是更进一步,让redux写起来更简洁,更可靠,它包含了redux-thunk,所以如果不与react一起,我们可以使用react+react-toolkit

然而单纯的状态机内部变化没法出发react的重新渲染,所以我们需要一个工具帮助我们做这件事,那就是react-redux。

那我们就按照redux,配合redux-thunk,配合redux-toolkit,最终配合react-redux的顺序来看看这个代码怎么写。

Redux

首先是redux的写法,这部分内容可以直接看我的上篇博客,Redux入门笔记

Redux + Redux-Thunk

这是redux-thunk官方文档中的例子

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
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';

// createStore的时候传入thunk中间件
const store = createStore(rootReducer, applyMiddleware(thunk));

// 发起网络请求的方法
function fetchSecretSauce() {
return fetch('https://www.baidu.com/s?wd=Secret%20Sauce');
}

// 下面两个是普通的action
function makeASandwich(forPerson, secretSauce) {
return {
type: 'MAKE_SANDWICH',
forPerson,
secretSauce,
};
}

function apologize(fromPerson, toPerson, error) {
return {
type: 'APOLOGIZE',
fromPerson,
toPerson,
error,
};
}

// 这是一个异步action,先请求网络,成功就makeASandwich,失败就apologize
function makeASandwichWithSecretSauce(forPerson) {
return function (dispatch) {
return fetchSecretSauce().then(
(sauce) => dispatch(makeASandwich(forPerson, sauce)),
(error) => dispatch(apologize('The Sandwich Shop', forPerson, error)),
);
};
}

// 最终dispatch的是异步action makeASandwichWithSecretSauce
store.dispatch(makeASandwichWithSecretSauce('Me'));

也就是添加了redux-thunk中间件,你就可以dispatch一个参数为dispatch的函数了,在这个函数内部你就可以做一些异步操作,然后在异步成功以后再真正地dispatch。

同时这个redux-thunk的源码其实非常简单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => (next) => (action) => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}

return next(action);
};
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

Redux + Redux-Toolkit

因为Redux-Toolkit已经集成了Redux-Thunk,所以就不需要再单独使用Redux-Thunk了。

React和Redux被认为是大规模React应用中管理状态的最佳组合。然而,随着时间的推移,Redux的受欢迎程度下降,原因是:

  • 配置Redux Store并不简单。
  • 我们需要几个软件包来使Redux与React一起工作。
  • Redux需要太多样板代码。

Redux Toolkit附带了一些有用的软件包,例如Immer,Redux-Thunk和Reselect。

  • Immer可以使React开发人员的工作变得更加轻松,允许他们直接更改状态(不处理不可变性)。

  • Thunk之类的中间件可以用来处理异步操作。

  • 它还使用了Redux的一个简单的“选择器”库Reselect来简化reducer函数。

Create a Redux Store#

Create a file named src/app/store.js. Import the configureStore API from Redux Toolkit. We’ll start by creating an empty Redux store, and exporting it:

1
2
3
4
5
6
7
//app/store.js

import { configureStore } from '@reduxjs/toolkit'

export default configureStore({
reducer: {},
})

This creates a Redux store, and also automatically configure the Redux DevTools extension so that you can inspect the store while developing.

Provide the Redux Store to React#

Once the store is created, we can make it available to our React components by putting a React-Redux <Provider> around our application in src/index.js. Import the Redux store we just created, put a <Provider> around your <App>, and pass the store as a prop:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//index.js
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')
)

Create a Redux State Slice#

Add a new file named src/features/counter/counterSlice.js. In that file, import the createSlice API from Redux Toolkit.

Creating a slice requires a string name to identify the slice, an initial state value, and one or more reducer functions to define how the state can be updated. Once a slice is created, we can export the generated Redux action creators and the reducer function for the whole slice.

Redux requires that we write all state updates immutably, by making copies of data and updating the copies. However, Redux Toolkit’s createSlice and createReducer APIs use Immer inside to allow us to write “mutating” update logic that becomes correct immutable updates.

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
//features/counter/counterSlice.js
import { createSlice } from '@reduxjs/toolkit'
export const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0,
},
reducers: {
increment: (state) => {
// Redux Toolkit allows us to write "mutating" logic in reducers. It

// doesn't actually mutate the state because it uses the Immer library,

// which detects changes to a "draft state" and produces a brand new

// immutable state based off those changes
state.value += 1
},
decrement: (state) => {
state.value -= 1
},
incrementByAmount: (state, action) => {
state.value += action.payload
},
},
})

// Action creators are generated for each case reducer function
export const { increment, decrement, incrementByAmount } = counterSlice.actions
export default counterSlice.reducer

Add Slice Reducers to the Store#

Next, we need to import the reducer function from the counter slice and add it to our store. By defining a field inside the reducers parameter, we tell the store to use this slice reducer function to handle all updates to that state.

1
2
3
4
5
6
7
8
9
//app/store.js
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from '../features/counter/counterSlice'

export default configureStore({
reducer: {
counter: counterReducer,
},
})

Redux + React-Toolkit + React-Redux

Use Redux State and Actions in React Components#

Now we can use the React-Redux hooks to let React components interact with the Redux store. We can read data from the store with useSelector, and dispatch actions using useDispatch. Create a src/features/counter/Counter.js file with a <Counter> component inside, then import that component into App.js and render it inside of <App>.

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
//features/counter/Counter.js

import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { decrement, increment } from './counterSlice'

export function Counter() {
const count = useSelector((state) => state.counter.value)
const dispatch = useDispatch()

return (
<div>
<div>
<button
aria-label="Increment value"
onClick={() => dispatch(increment())}
\>
Increment
</button>
<span>{count}</span>
<button
aria-label="Decrement value"
onClick={() => dispatch(decrement())}
\>
Decrement
</button>
</div>
</div>
)
}

Connect API

这种是典型的HOC将容器组件和展示组件分离的方式,在hook之前很是流行

具体可以看这里:https://react-redux.js.org/tutorials/connect

参考链接:

https://juejin.cn/post/6869950884231675912

https://segmentfault.com/a/1190000039806379

https://redux-toolkit.js.org/introduction/getting-started