Redux
2024-11-19
Redux
基础
Redux 是 JavaScript 应用的状态容器,提供可预测的状态管理。
创建一个redux react应用:
npx create-react-app my-app --template redux
由于单向数据流,可通过”提升state“实现多个组件共享state,将整个组件树作为大view,任何组件都可以访问state或触发action,这就是redux的思想:应用中使用集中式的全局状态来管理,并明确更新状态的模式,以便让代码具有可预测性。
术语
action
action 是一个具有 type
字段的普通 JavaScript 对象。你可以将 action 视为描述应用程序中发生了什么的事件.type
字段给action一个描述性的名,一般采用 ”域(所属特征或类别)/事件名字“, 附加信息放在payload
里
const addTodoAction = {
type: 'todos/todoAdded',
payload: 'Buy milk'
}
reducer
一个函数,接收当前的 state 和一个 action 对象,必要时决定如何更新状态,并返回新状态。可以作为事件监听器看待
函数内部逻辑一般遵循:
- 检查reducer是否关心action,如果关心就复制state,使用新的state副本,然后返回新state
- 否则返回原来的state
const initialState = { value: 0 }
function counterReducer(state = initialState, action) {
// 检查 reducer 是否关心这个 action
if (action.type === 'counter/increment') {
// 如果是,复制 `state`
return {
...state,
// 使用新值更新 state 副本
value: state.value + 1
}
}
// 返回原来的 state 不变
return state
}
store
store 是通过传入一个 reducer
来创建的,并且有一个名为 getState
的方法,它返回当前状态值:
import { configureStore } from '@reduxjs/toolkit'
const store = configureStore({ reducer: counterReducer })
console.log(store.getState())
// {value: 0}
dispatch
store的方法,更新 state 的唯一方法是调用 store.dispatch()
并传入一个 action 对象。 store 将执行所有 reducer
函数并计算出更新后的 state。
dispatch 一个 action 可以形象的理解为 "触发一个事件"
const increment = () => {
return {
type: 'counter/increment'
}
}
store.dispatch(increment())
console.log(store.getState())
// {value: 2}
selector
Selector 函数可以从 store 状态树中提取指定的片段。随着应用变得越来越大,会遇到应用程序的不同部分需要读取相同的数据,selector 可以避免重复这样的读取逻辑
redux数据流
-
初始启动:
-
使用最顶层的 root reducer 函数创建 Redux store
-
store 调用一次 root reducer,并将返回值保存为它的初始 state
-
当视图 首次渲染时,视图组件访问 Redux store 的当前 state,并使用该数据来决定要呈现的内容。同时监听 store 的更新,以便他们可以知道 state 是否已更改。
-
-
更新环节:
- 应用程序中发生了某些事情,例如用户单击按钮
-
dispatch
一个 action 到 Redux store,例如dispatch({type: 'counter/increment'})
- store 用之前的 state 和当前的 action 再次运行
reducer
函数,并将返回值保存为新的 state - store 通知所有订阅过的视图,通知它们 store 发生更新
- 每个订阅过 store 数据的视图 组件都会检查它们需要的 state 部分是否被更新
- 发现数据被更新的每个组件都强制使用新数据重新渲染,紧接着更新网页
应用结构
目录(以计数器为例)
-
/src
-
index.js:app入口
-
App.js:顶级react入口
-
/app
- store.js创建redux store实例
-
/features
- Counter.js:展示counter特性的react组件
- counterSlice.js:counter特性相关的redux逻辑
-
创建store
redux slice:“slice” 是应用中单个功能的 Redux reducer 逻辑和 action 的集合, 通常一起定义在一个文件中。该名称来自于将根 Redux 状态对象拆分为多个状态 “slice”。
比如,在一个博客应用中,store 的配置大致长这样:
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,
posts: postsReducer,
comments: commentsReducer
}
})
创建slice、action
createSlice()
函数createSlice
,它负责生成 action 类型字符串、action creator 函数和 action 对象的工作。你所要做的就是为这个 slice 定义一个名称,编写一个包含 reducer 函数的对象,它会自动生成相应的 action 代码。name
选项的字符串用作每个 action 类型的第一部分,每个 reducer 函数的键名用作第二部分。因此,"counter"
名称 + "increment"
reducer 函数生成了一个 action 类型 {type: "counter/increment"}
。除了 name
字段,createSlice
还需要我们为 reducer 传入初始状态值,以便在第一次调用时就有一个 state。在这种情况下,我们提供了一个对象,它有一个从 0 开始的 value 字段。
export const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0
},
reducers: {
increment: state => {
// Redux Toolkit 允许我们在 reducers 写 "可变" 逻辑。
// 并不是真正的改变 state 因为它使用了 immer 库
// 当 immer 检测到 "draft state" 改变时,会基于这些改变去创建一个新的
// 不可变的 state
state.value += 1
},
decrement: state => {
state.value -= 1
},
incrementByAmount: (state, action) => {
state.value += action.payload
}
}
})
createSlice
和createReducer
内部使用Immer库,可在内部编写mutation逻辑
thunk异步
thunk 是一种特定类型的 Redux 函数,可以包含异步逻辑。Thunk 是使用两个函数编写的:
- 一个内部 thunk 函数,它以
dispatch
和getState
作为参数 - 外部创建者函数,它创建并返回 thunk 函数
// 下面这个函数就是一个 thunk ,它使我们可以执行异步逻辑
// 你可以 dispatched 异步 action `dispatch(incrementAsync(10))` 就像一个常规的 action
// 调用 thunk 时接受 `dispatch` 函数作为第一个参数
// 当异步代码执行完毕时,可以 dispatched actions
export const incrementAsync = amount => dispatch => {
setTimeout(() => {
dispatch(incrementByAmount(amount))
}, 1000)
}
组件
使用 useSelector 提取数据
useSelector
这个 hooks 让我们的组件从 Redux 的 store 状态树中提取它需要的任何数据。
const count = useSelector(selectCount)
每当一个 action 被 dispatch
并且 Redux store 被更新时,useSelector
将重新运行我们的选择器函数。如果选择器返回的值与上次不同,useSelector
将确保我们的组件使用新值重新渲染。
使用 useDispatch 来 dispatch action
useDispatch
hooks 为我们完成了这项工作,并从 Redux store 中为我们提供了实际的 dispatch 方法:
const dispatch = useDispatch()
providing the store
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'
import * as serviceWorker from './serviceWorker'
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
为了让像 useSelector
这样的 hooks 正常工作,我们需要使用一个名为 <Provider>
的组件在幕后传递 Redux store,以便他们可以访问它。
我们在这里引用来自 app/store.js
中创建的 store。然后,用 <Provider>
包裹整个 <App>
,并传入 store:<Provider store={store}>
。