redux和React-redux

redux

Redux 是 JavaScript 状态容器,提供可预测化的状态管理。官网
安装redux

1
2
3
npm install --save redux

yarn add redux

redux三大核心属性

action

Action 本质上是 JavaScript 普通对象。我们约定,action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作。多数情况下,type 会被定义成字符串常量。

1
2
// 定义了一个add函数,函数返回一个action对象
const add = (payload) => ({ type: 'add', payload })

reducer

reducer 就是一个纯函数,接收旧的 state 和 action,返回新的 state。
注意 reducer 是纯函数。它仅仅用于计算下一个 state。它应该是完全可预测的:多次传入相同的输入必须产生相同的输出。它不应做有副作用的操作,如 API 调用或路由跳转。这些应该在 dispatch action 前发生。
下面代码就是reducer接收了一个旧的state和action,返回了新的state

1
2
3
4
5
6
7
8
9
10
11
const reducer = (state, action) => {
switch (action.type) {
case 'add':
return (state = state + action.payload)
default:
return state
}
}
reducer(0,add(1))
// 等价于
// reducer(0,{ type: 'add', payload:1 })

store

在前面的代码中,我们学会了使用 action 来描述“发生了什么”,和使用 reducers 来根据 action 更新 state 的用法。
Store 就是把它们联系到一起的对象。Store 有以下职责:
维持应用的 state;
提供 getState() 方法获取 state;
提供 dispatch(action) 方法更新 state;
通过 subscribe(listener) 注册监听器;
通过 subscribe(listener) 返回的函数注销监听器。
再次强调一下 Redux 应用只有一个单一的 store。当需要拆分数据处理逻辑时,你应该使用 reducer 组合 而不是创建多个 store。
根据已有的 reducer 来创建 store

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { configureStore } from '@reduxjs/toolkit'
// action
const Add = (payload) => ({ type: 'add', payload })
// reducer
const reducer = (state = 0, action) => {
switch (action.type) {
case 'add':
return state + action.payload
default:
return state
}
}
// 创建一个store实例
const store = configureStore({ reducer })
// 每次state更新时,打印日志,getState,获取最新state
// 返回一个函数来注销监听器
const unsubscribe = store.subscribe(() => console.log('state:',store.getState())) //1 3
// dispatch,更新state
store.dispatch(Add(1))
store.dispatch(Add(2))

// 停止监听state更新
unsubscribe()

configureStore

1
2
3
4
5
// configureStore是用来替代createStore方法
const store = configureStore({
reducer:xxx,
preloadedState:xxx, //初始化状态
})

Redux获取状态默认值的执行过程

只要创建store,那么,Redux就会调用一次 reducer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// action
const Add = (payload) => ({ type: 'add', payload })
// reducer
const reducer = (state, action) => { // 这里没有给 state赋值,所以返回undefined.可以这样给state赋值:state = 0
console.log('reducer',state,action); //reducer undefined {type: '@@redux/INITr.5.x.p.5'}
switch (action.type) {
case 'add':
return state + action.payload
default:
return state
}
}
// store
const store = configureStore({reducer}) // 只要创建了store,redux就会调用一次reducer

这一次调用renucer的目的:获取状态的默认值,
第一次调用reducer时,redux机制会让action.type的value是一个随机的字符串,以确保return的是state
redux内部第一次调用reducer:

1
reducer undefined {type: '@@redux/INITr.5.x.p.5'}

redux数据流

严格的单向数据流是 Redux 架构的设计核心。
这意味着应用中所有的数据都遵循相同的生命周期,这样可以让应用变得更加可预测且容易理解。同时也鼓励做数据范式化,这样可以避免使用多个且独立的无法相互引用的重复数据。

React-redux

Provider

在项目入口文件,一般是src目录下的index.js,为整个项目(App组件)提供redux状态管理

1
2
3
4
5
6
7
8
9
10
import ReactDOM from 'react-dom/client'
import { Provider } from 'react-redux'
import App from './App'
import store from './store'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<Provider store={store}>
<App />
</Provider>
)

完整示例

获取个人信息的示例,完整的展示了redux的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 1. 在pages/Layout/index.js中,分发获取个人信息的异步action
import { useDispatch, useSelector } from 'react-redux' //useDispatch用来分发一个action
import { getUserInfo } from '@/store/actions'
export default function GeekLayout() {
const dispatch = useDispatch()
useEffect(() => {
dispatch(getUserInfo()) // 这里的函数由actions组件提供,根据接口所需要的数据,来决定getUserInfo()要不要填入实参
}, [dispatch])
...
return(
...
{name}
)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 2. 在 store/actions/user.js中,创建异步action并获取个人信息
import { http } from '@/utils' // 封装了axios,简写了请求地址
export const getUserInfo = () => { // 异步action可以是一个函数,也可以是一个对象,同步action只能是一个对象
return async (dispatch, getState) => { // dispatch, getState是由thunk中间件提供的参数
const { login: token } = getState() // getState是一个函数,返回redux中存储的状态,这里解构拿到了login对象的值,重命名为token
const res = await http.get('/v1_0/user/profile', {
headers: {
Authorization: `Bearer ${token}`, // 传入个人信息所需要提供的token
},
})
// 3.将接口返回的信息 dispatch到reducer来存储该状态
dispatch({ type: 'user/userInfo', payload: res.data.data })
}
}
1
2
3
4
5
6
7
8
9
10
// 4. 在 store/reducers/user.js中,处理个人信息的action,将状态存储到redux中
const initialState = {} // 因为个人信息状态是一个对象,所以默认状态为一个对象
export const user = (state = initialState, action) => {
switch (action.type) {
case 'user/userInfo':
return action.payload
default:
return state
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 5. 在pages/Layout/index.js中,获取个人信息并展示
import { useDispatch, useSelector } from 'react-redux'
import { getUserInfo } from '@/store/actions'
export default function GeekLayout() {
const dispatch = useDispatch()
useEffect(() => {
dispatch(getUserInfo())
}, [dispatch])
// 拿到redux中的状态
const { name } = useSelector((state) => state.user)
...
return(
...
{name} // 渲染状态
)
}

reducer的分离与合并

根 reducer 应该把多个子 reducer 输出合并成一个单一的 state 树。

根 reducer 的结构完全由你决定。Redux 原生提供combineReducers()辅助函数,来把根 reducer 拆分成多个函数,用于分别处理 state 树的一个分支。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import {combineReducers} from 'redux'
function todos(state = [], action) {
// 省略处理逻辑...
return nextState
}

function visibleTodoFilter(state = 'SHOW_ALL', action) {
// 省略处理逻辑...
return nextState
}

// 将多个reducer合并根reducer
let todoApp = combineReducers({
todos,
visibleTodoFilter
})

当你触发 action 后,combineReducers 返回的 todoApp 会负责调用两个 reducer:

1
2
let nextTodos = todos(state.todos, action)
let nextVisibleTodoFilter = visibleTodoFilter(state.visibleTodoFilter, action)

然后会把两个结果集合并成一个 state 树:

1
2
3
4
return {
todos: nextTodos,
visibleTodoFilter: nextVisibleTodoFilter
}

action type or constants

为了解决action和reducer中字符串误写错和命名冲突,建议单独抽离放在 actiontypes或constants目录中,一般放在store目录下
目录结构

|– store
|– actions 负责对应组件的action
|– actiontypes 抽离字符串
|– reducers 负责对应组件的reducer
|– index.js 入口文件,创建唯一的store

1
2
3
4
// actiontypes目录
const add = 'counter/add'
const sub = 'counter/sub'
export { add, sub }