> For the complete documentation index, see [llms.txt](https://1425816423.gitbook.io/my-knowledge-base/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://1425816423.gitbook.io/my-knowledge-base/qian-duan-ji-shu/react/redux-ru-men.md).

# Redux入门

## redux入门

## 什么是Redux

redux是一个全局状态管理库，通常是集成到react中使用。Redux 提供的模式和工具使你更容易理解应用程序中的状态何时、何地、为什么、state 如何被更新，以及当这些更改发生时你的应用程序逻辑将如何表现。

## Redux基础概念

### 三个基础部分

一个redux应用拥有三个部分：

* view：基于当前状态的视图声明性描述
* state：驱动应用的真实数据
* actions：应用程序中发生的事件，用于触发状态更新

### 单向数据流

数据的流动是单向的，用户与视图（View）交互后会通过action来通知数据**state**更新，**state**更新后又会重新渲染视图(View)。

### 不可变性

JavaScript中对象和数组都是“可变”的，内存中保存的是对象的引用。

```tsx
const obj = {}
console.log(obj)
obj.a = 1;
/* console
{
  a: 1
}
*/
```

我们先获取对象的值，然后再改变对象，但是最后看到的仍然是更新后的对象。

**Redux 期望所有状态更新都是使用不可变的方式**。这通常可以利用展开运算符实现。

### 三原则

* ***单一事实来源**：整个应用程序的状态存储在单个存储中的对象树中。*
* ***状态是只读的**：更改状态的唯一方法是发出一个操作，一个描述所发生情况的对象。*
* ***使用纯函数进行更改**：若要指定状态树如何通过操作转换，请编写纯化简器。*

### Redux原生API

redux通过createStore创建一个redux应用，它必须接收一个`reducer`函数作为第一个参数，第二个参数是初始`state`，它是可选的。

```tsx
const state = {
  play: false,
  player: '周杰伦',
  musicName: '青花瓷',
  time: new Date(),
};

const reducer = (state, action) => {
  switch (action.type) {
    case 'Change_Play':
      return { ...state, play: !state.play };
    case 'ChangeMusicInfo': {
      return { ...state, ...action };
    }
    default: {
      return { ...state };
    }
  }
};

export const store = createStore(reducer, state);
```

如果reducer比较复杂，可以将其拆分成多个`reduer`，然后在通过`combineReducers`进行合并。注意不应该创建多个`store`。

```tsx
rootReducer = combineReducers({potato: potatoReducer, tomato: tomatoReducer})
// 这将返回如下的 state 对象
{
  potato: {
    // ... potatoes, 和一些其他由 potatoReducer 管理的 state 对象 ...
  },
  tomato: {
    // ... tomatoes, 和一些其他由 tomatoReducer 管理的 state 对象，比如说 sauce 属性 ...
  }
}
```

`createStore`得到的`store`上挂载了四个方法：

* [getState()](https://cn.redux.js.org/api/store#getState)：返回应用当前的 state 树。 它与 store 的最后一个 reducer 返回值相同。
* [dispatch(action)](https://cn.redux.js.org/api/store#dispatchaction)：触发state更新
* [subscribe(listener)](https://cn.redux.js.org/api/store#subscribelistener)：订阅一个监听器，当state变化时执行回调
* [replaceReducer(nextReducer)](https://cn.redux.js.org/api/store#replacereducernextreducer)：替换 store 当前用来计算 state 的 reducer。

事实上你通常不需要直接操作这些API，因为实际开发时通常使用用官方工具库`redux-toolkit`来简化操作。

### Demo

这里有一个使用redux原生API与react结合的Demo。

<https://codesandbox.io/s/reduxyuan-sheng-g8l1px?from-embed=&file=/src/App.js>

## Redux实际应用

在实际开发中使用redux时通常会结合redux-toolkit和react-redux一起使用。

### **configureStore**

在原生Redux中我们用`createStore`来创建一个redux状态树，但是现在有了redux-toolkit后，我们可以借助`configureStore`这个函数来创建redux状态树。和createStore相似，这里需要传递一个reducer，只不过现在是通过对象参数的方式来传递。

```tsx
export const store = configureStore({
  reducer: rootReducer
});
```

### createAction和createReducer

action是触发state更新的唯一方式，它一个至少包含type字段的对象。我们之前用action的时候每次都要写一遍`action`对象，但是其实可以通过`action creator`函数来简化操作。

```tsx
export function playStateAction(payload:any){
	return {
		type: "CHANGE_PLAY_STATE",
		payload
	}
}
```

这样我们就不用每次都写一遍action对象了，只需要调用`playStateAction`函数，并传递所需的参数就能得到一个action。

redux-toolkit提供了`createAction`函数来实现这个操作，只需要传递一个字符串作为`type`，就会返回一个`action creator`函数。

```tsx
const changePlay = createAction("CHANGE_PLAY_STATE");
console.log(changePlay({ a: 1 }));
/*
{
type: "CHANGE_PLAY_STATE"
payload: {a: 1}
}
*/
```

redux-toolkit同样提供了简化编写reducer的API，即`createReducer`。createReducer有两种使用方式，一种是链式函数调用，一种是查找表对象。

链式函数调用

```tsx
const rootReducer = createReducer(initState /*初始状态*/, (builder) => {
  builder
    .addCase(changePlay, (state, action) => {
      return {...state, play: !state.play};
    })
    .addDefaultCase((state) => state);
});
```

查找表对象

```tsx
const rootReducer = createReducer(initState, {
	// 利用es6计算属性获取type
  [changePlay]: (state) => ({ ...state, play: !state.play })
});
```

### CreateSlice

基于前面的三个API已经可以实现redux状态库了，但是还不够好，因为此时所有的状态都被放在了一个对象里，如果应用较为复杂，那么状态对象(store)、action就很容易变得杂乱无章。更好地方法是将状态对象分隔成多个部分，相同逻辑的状态放在一起，并且将相关联的action也整合进去，那么最后得到的这一个个的部分我们称为Slice，而用于创建Slice的函数就是createSlice。

```tsx
// 原来的写法
export const changePlay = createAction("CHANGE_PLAY_STATE");
const initState = {
  play: false
};
const rootReducer = createReducer(initState, {
  [changePlay]: (state) => ({ ...state, play: !state.play })
});
export const store = configureStore({
  reducer: { rootReducer }
});

// Slice的写法
const initState = {
  play: false
};
const playSlice = createSlice({
  name: "playStatus",
	initialState: initState,
  reducers: {
    changePlay: (state) => ({ ...state, play: !state.play })
  }
});
export const store = configureStore({
  reducer: playSlice.reducer
});
export const { changePlay } = playSlice.actions;

```

在创建Slice的过程中redux-toolkit会自动生成`action creator` ，相比较原来的写法省略了编写`action creator`这一步，并且相同逻辑的`action`和`reducer`都被整合在一个`Slice`中，代码结构更加清晰。

如果有多个`Slice`，可以将它们放到一个对象中传给`configureStore`。

```tsx
export const store = configureStore({
  reducer: {
    [playSlice.name]: playSlice.reducer,
    [musicSlice.name]: musicSlice.reducer
  }
});
```

## react-redux

如果要在react中使用redux，最好的方法是使用`react-redux`。

假设我们已经通过redux-toolkit创建了一个store。如下所示：

```tsx
const initState = {
  play: false,
  musicName: "青花瓷"
};
const playSlice = createSlice({
  name: "playStatus",
	initialState: initState,
  reducers: {
    changePlay: (state) => ({ ...state, play: !state.play })
  }
});
export const store = configureStore({
  reducer: playSlice.reducer
});
export const { changePlay } = playSlice.actions;
```

### Provider

首先我们需要为react提供redux store，这可以通过Provider组件实现,Provider需要传递store作为prop。

```tsx
import { Provider } from "react-redux";
import { store } from "./store/index";

import Child from "./Child";

export default function App() {
  return (
    <div className="App">
      <Provider store={store}>
        <Child />
      </Provider>
    </div>
  );
}
```

之后被Provider直接或间接包裹的子孙组件都可以访问到store。

### 获取State和更新state

通过react-redux提供的`useSelector` API来获取state。`useSelector` 需要传递一个`selector`函数，通过这个函数来提取数据。

同时还提供`useDispatch` API，通过它来获取`dispatch`函数，向`dispatch`函数传递`action`对象来更新`state`。

```tsx
import { useSelector, useStore, useDispatch } from "react-redux";
import { changePlay } from "./store/index";

export default function Child() {
  const dispatch = useDispatch();
  const playStatus = useSelector((state) => state);

  console.log(playStatus);

  function onPlay() {
    dispatch(changePlay());
  }

  return (
    <div>
      <p>播放状态：{playStatus.play ? "播放中" : "暂停中"}</p>
      <div>
        <button onClick={onPlay}>切换播放状态</button>
      </div>
    </div>
  );
}
```

### Demo

<https://codesandbox.io/s/reverent-joliot-bpqwdh?file=/src/Child.js:32-43>
