Redux学习笔记

154 阅读5分钟

我在学习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>

效果图:

image.png 可以参考下面的图:

image.png

以后开发中,几乎不用这种原生的Redux,所以捋一下思路就可以了,不用深究。当然能搞明白最好。

2.React中使用Redux

react中使用Redux需要安装两个插件,分别是RTK(Redux Toolkit)React-Redux
Redux Toolkit (RTK) : Redux 官方推荐的工具库,简化了 Redux 的配置和使用,提供了 createSliceconfigureStore 等方法。
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目录下创建下面文件:

image.png

Redux的使用可以分下面四个步骤进行:

  1. 定义 Slice
  2. 创建 Redux Store
  3. 把React和Redux的连接起来
  4. 派发 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原生用法看可能比较好理解。

image.png 图片中的这两行代码黑马老师是这样写的:

image.png 我觉得第一种写法更简洁。

步骤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

效果图:

image.png

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.一些小疑惑

image.png 然后我问了chatGpt,它是这样回答的:

[dispatch] 作为依赖项,虽然 dispatch 本身几乎不会变化,但理论上如果它发生变化(比如在某些特殊情况下或通过某些 React 中的高级用法),useEffect 会重新执行。在大多数情况下,效果和使用 [] 是一样的。

在实际开发中,写 [dispatch] 是为了更加明确地表达你是依赖于 dispatch 这个钩子的,即便它不会改变。某些代码风格或团队可能推荐这种写法,作为一种对 dispatch 依赖的显式声明。实际上,dispatch 是不会变化的,所以使用 [][dispatch] 在这个场景下几乎没有区别,主要取决于你的代码风格。

5.自测

image.png

参考:

image.png