我在学习
Redux的过程中的最大感受就是,这个东西真的很复杂,还是Pinia简单,符合直觉,pinia可以使用setup语法写,可以说是非常简单。
1.原生Redux
Redux不是依赖框架的,黑马老师用一个html页面演示了Redux的写法:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="decrement">减</button>
<span id="count">0</span>
<button id="increment">加</button>
<script src="https:unpkg.com/redux@4.2.0/dist/redux.min.js"></script>
<script>
// 1.定义reducer函数
// 作用:根据不同的action对象,不同的新state
// state:管理的数据初始状态
// action: 对type标记当前要做什么样的修改
function reducer(state = { count: 0 }, action) {
// 数据不可变,要生成新的状态
if (action.type === 'INCREMENT') {
return { count: state.count + 1 }
}
if (action.type === 'DECREMENT') {
return { count: state.count - 1 }
}
return state
}
// 2.使用reducer实例生成新的store实例
const store = Redux.createStore(reducer)
// 3.通过store实例subscribe订阅数据变化
// 回调函数每次state发生变化的时候重新执行
store.subscribe(()=> {
console.log('state 变化了!')
document.querySelector('#count').innerText = store.getState().count
})
// 4.修改数据
const inBtn = document.querySelector('#increment')
inBtn.addEventListener('click', ()=> {
// 增
store.dispatch({
type: 'INCREMENT'
})
})
const dBtn = document.querySelector('#decrement')
dBtn.addEventListener('click', ()=> {
// 减
store.dispatch({
type: 'DECREMENT'
})
})
</script>
</body>
</html>
效果图:
可以参考下面的图:
以后开发中,几乎不用这种原生的Redux,所以捋一下思路就可以了,不用深究。当然能搞明白最好。
2.React中使用Redux
react中使用Redux需要安装两个插件,分别是
RTK(Redux Toolkit)和React-Redux。
Redux Toolkit (RTK) : Redux 官方推荐的工具库,简化了 Redux 的配置和使用,提供了createSlice和configureStore等方法。
React-Redux: 这个库将 Redux 和 React 结合起来,让你能够在 React 组件中方便地访问 Redux 的状态和派发 actions。
安装
Redux的安装可以参考官网
安装Redux Toolkit (RTK):
# NPM
npm install @reduxjs/toolkit
# Yarn
yarn add @reduxjs/toolkit
#PNPM
pnpm add @reduxjs/toolkit
安装React-Redux:
# NPM
npm install react-redux
# PNPM
pnpm add react-redux
使用步骤
四步 首先我们要进行文件夹的创建,在src目录下创建下面文件:
Redux的使用可以分下面四个步骤进行:
- 定义 Slice
- 创建 Redux Store
- 把React和Redux的连接起来
- 派发 actions
步骤1:定义 Slice
在 Redux 中,slice 是一种包含 reducer 和 actions 的结构。我们可以使用 createSlice 来定义它。
下面是CounterStore.jsx:
import { createSlice } from '@reduxjs/toolkit'
//定义 slice 的初始状态和 reducer
const counterStore = createSlice({
// 记住,这个name,很重要
name: 'counter',
// 初始化state
initialState: {
count: 0
},
// 修改数据的方法,支持直接修改
reducers: {
increment(state) {
state.count++
},
decrement(state) {
state.count--
},
// 在组件中传递的数据直接可以用action.payload来获取
//不要问payload从哪里来的,固定写法,老师说的😁
addToNum(state, action) {
state.count += action.payload
}
}
})
// 导出 actions(自动生成的)
// 按需导出的这些在组件中使用
export const { increment, decrement, addToNum } = counterStore.actions
// 导出 reducer,最终会加入到 store 中
// 默认导出的这个在index.js中使用
export default counterStore.reducer
我在代码中写了注释,这些注释很重要,可以对比Redux原生用法看可能比较好理解。
图片中的这两行代码黑马老师是这样写的:
我觉得第一种写法更简洁。
步骤2:创建 Redux Store
使用 configureStore 来创建一个 Redux store。
下面是index.jsx:
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from './modules/counterStore.jsx'
const store = configureStore({
reducer: {
counter: counterReducer,
}
})
// 这个导出的store在进行 React和Redux的链接的时候使用
export default store
步骤3:把React和Redux的连接起来
在React 应用的根组件中使用 Provider 来将 Redux store 提供给整个应用。
下面是main.jsx(看你的构建工具,我的是vite):
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './styles/index.css'
import App from './App.jsx'
// 导入在store目录下index.jsx中定义store
import store from './store/index.jsx'
// 导入 Provider,用于react和redux的链接
import { Provider } from 'react-redux'
createRoot(document.getElementById('root')).render(
<StrictMode>
{/* 这里通过Provider把redux和react连接起来 */}
<Provider store={store}>
<App />
</Provider>
</StrictMode>,
)
步骤4:派发 actions
import { useDispatch, useSelector } from 'react-redux' // 导入 hooks
import { increment, decrement, addToNum } from './store/modules/counterStore' // 导入 actions
function App() {
// 这里的counter就是在counterStore中的name:'counter'
const count = useSelector(state => state.counter.count) // 获取 Redux 状态
const dispatch = useDispatch() // 获取 dispatch 函数
return (
<div>
{/*
还记得原生Redux是怎么修改数据的吗
回忆一下,用dispatch修改的
所以我们在这里也使用dispatch
注意,这里要用调用函数的方式写actions:increment(), decrement(), addToNum()
addToNum(-10)这里的参数-10会传递到action.payload中
*/}
<button onClick={()=>dispatch(addToNum(-10))}>-10</button>
<button onClick={ () => dispatch(increment()) }>-</button>
<span>{ count }</span>
<button onClick={ () => dispatch(decrement()) }>+</button>
<button onClick={()=> dispatch(addToNum(10))}>+10</button>
</div>
)
}
export default App
效果图:
3.Redux异步操作
异步操作也没有很难的地方。
下面是channelStore.jsx:
import { createSlice } from '@reduxjs/toolkit'
import axios from 'axios'
// 同步代码,因为这些代码跟同步代码没有任何区别
const channelStore = createSlice({
name: 'channel',
initialState: {
channelList: []
},
reducers: {
setChannelList(state, action) {
state.channelList = action.payload
}
}
})
// 导出 actions(自动生成的)
const { setChannelList } = channelStore.actions
// 异步请求
const fetchChannelList = () => {
return async (dispatch) => {
const response = await axios.get('http://geek.itheima.net/v1_0/channels')
// 这里通过dispatch来调用setChannelList
dispatch(setChannelList(response.data.data.channels))
}
}
// 这个fetchChannelList也相当于一个actions,将来用dispatch触发
export { fetchChannelList }
export default channelStore.reducer
然后再store目录下的index.js中进行reducer的注入:
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from './modules/counterStore.jsx'
import channelReducer from './modules/channelStore.jsx'
const store = configureStore({
reducer: {
counter: counterReducer,
channel: channelReducer
}
})
export default store
然后定义一个组件AsyncList.jsx的组件:
import { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { fetchChannelList } from '../store/modules/channelStore'
function AsyncList() {
const dispatch = useDispatch()
const channels = useSelector(state => state.channel.channelList) // 从 Redux Store 获取频道列表
useEffect(() => {
dispatch(fetchChannelList()) // 组件挂载时,发起异步请求
}, [dispatch])
return (
<ul>
{ channels.map(channel => (<li key={ channel.id }>{ channel.name }</li>)) }
</ul>
)
}
export default AsyncList
然后把这个AsyncList.jsx在根组件中导入使用就可以拿到异步数据。
4.一些小疑惑
然后我问了chatGpt,它是这样回答的:
[dispatch]作为依赖项,虽然dispatch本身几乎不会变化,但理论上如果它发生变化(比如在某些特殊情况下或通过某些 React 中的高级用法),useEffect会重新执行。在大多数情况下,效果和使用[]是一样的。
在实际开发中,写[dispatch]是为了更加明确地表达你是依赖于dispatch这个钩子的,即便它不会改变。某些代码风格或团队可能推荐这种写法,作为一种对dispatch依赖的显式声明。实际上,dispatch是不会变化的,所以使用[]和[dispatch]在这个场景下几乎没有区别,主要取决于你的代码风格。
5.自测
参考: