redux学习,在react中使用redux,RTK

前面为redux基础笔记 项目使用看最终方案

redux的基本流程

流程大概就是创建一个公用的仓库store来存储我们的数据,通过state来渲染到UI页面上,我们在UI层面想要修改state数据需要通过dispatch来派发一个action对象,action对象的type会找到对应的reducer来修改state

搭建一个基本的store

第一步安装redux

npm i redux

 在项目src/下创建store/index.ts

import { createStore } from 'redux'

function countReducer(state = { count: 1 }, action) {
    switch (action.type) {
        case 'add':
            // return state.count + 1  错误写法 redux 不允许直接修改state 而是需要返回一个新值不影响之前传递的值
            return { count: state.count + 1 }  // 正确的写法
        default:
            return state
    }
}
const store = createStore(countReducer)

export default store

这里我们定义了一个最基础的store声明count以及修改count的方法

在页面中如何使用

import React from 'react'
import store from '@/store'
export default function Layout() {
  return (
    <div>
      {/* 通过store.getState()获取store中的数据 */}
      {store.getState().count}
    </div>
  )
}

页面:

如何修改state

在最开始的流程图下说过UI如果想要修改state需要通过dispatch派发一个action对象来调用reducer来修改state

import React from 'react'
import store from '@/store'
export default function Layout() {
  const setCount = () => {
    store.dispatch({
      type: 'add',
    })
  }
  return (
    <div>
      {/* 通过store.getState()获取store中的数据 */}
      {store.getState().count}
      {/* 修改count */}
      <button onClick={setCount}>+</button>
    </div>
  )
}

这时候我们点击button的时候,页面并不会更新数据,但是实际上state是发生变化了,但是页面没有监控的到,这时我们需要用redux提供的一个方法,并且需要使用useState来配合实现改变

import React from 'react'
import store from '@/store'
export default function Layout() {
  const [count, setc] = React.useState(store.getState().count)
  const setCount = () => {
    store.dispatch({
      type: 'add',
    })
    // 每次state发生变化的时候会执行
    store.subscribe(() => {
      setc(store.getState().count)
    })
  }
  return (
    <div>
      {/* 通过store.getState()获取store中的数据 */}
      {count}
      {/* 修改count */}
      <button onClick={setCount}>修改count</button>
    </div>
  )
}

页面:

这就算是完成一个redux的基本流程

使用react-redux来简化代码

安装react-redux

npm i react-redux

 之后需要在main.ts中进行Provider包裹

import React from 'react'
import ReactDOM from 'react-dom/client'
import './index.css'
import router from './router'
import { RouterProvider } from 'react-router-dom'
// 引入你的store
import store from './store'
// 引入redux中的Provider
import { Provider } from 'react-redux'
ReactDOM.createRoot(document.getElementById('root')!).render(
  // 严格模式
  <React.StrictMode>
    {/* store包裹 */}
    <Provider store={store}>
      {/* 路由组件 */}
      <RouterProvider router={router} />
    </Provider>
  </React.StrictMode>,
)

使用Provider包裹你的ui组件 把store挂载到上面

简化页面代码

import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
// useSelector用于获取store中的数据
// usedispatch用于修改store中的数据
export default function Layout() {
  // 获取count
  const count = useSelector((state: any) => state.count)
  const dispatch = useDispatch()
  const setCount = () => {
    dispatch({
      type:'add'
    })
  }
  return (
    <div>
      {count}
      {/* 修改count */}
      <button onClick={setCount}>修改count</button>
    </div>
  )
}

这是我们修改页面会直接响应 所使用的代码也简捷了许多

如何对reducer来进行传参

我们只需要在dispatch的对象中写入第二个参数

dispatch({
      type: 'add',
      payload:5
 })

store中

import { createStore } from 'redux'
function countReducer(state = { count: 1 }, action) {
    switch (action.type) {
        case 'add':
            // action:{} type:执行的操作 payload:操作的参数 现在每次点击count就会加5
            return { count: state.count + action.payload }
        default:
            return state
    }
}
const store = createStore(countReducer)

export default store

处理多个reducer

在vuex中我们可以通过module属性来处理多个store 在redux中我们可以通过combineReducers方法来处理多个reducer

第一步创建module文件夹 新建count.ts 把store中的countReducer抽离出来

// count.ts
export default function countReducer(state = { count: 1 }, action) {
    switch (action.type) {
        // 加入命名空间
        case 'count/add':
            // action:{} type:执行的操作 payload:操作的参数 现在每次点击count就会加5
            return { count: state.count + action.payload }
        default:
            return state
    }
}

 store使用combineReducers(包裹)

// store
import { createStore, combineReducers } from 'redux'
// 引入抽离的countcountReducer
import countReducer from './module/count'

const store = createStore(combineReducers({
    // 命名count
    count: countReducer
    // 模块2命名:模块2Reducer ....
}))

export default store

页面获取操作也需要加上命名空间

import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
// useSelector用于获取store中的数据
// usedispatch用于修改store中的数据
export default function Layout() {
  // 获取count命名空间模块下的count数据
  const count = useSelector((state: any) => state.count.count)
  const dispatch = useDispatch()
  const setCount = () => {
    dispatch({
      // 修改count命名模块下的count数据
      type: 'count/add',
      payload:5
    })
  }
  return (
    <div>
      {count}
      {/* 修改count */}
      <button onClick={setCount}>修改count</button>
    </div>
  )
}

在redux中如何处理异步

上面的操作其实一直是针对同步修改,在实际场景中我们很多时候会需要异步修改state,例如http请求等,但是在redux中dispatch默认只支持action对象字面量,这时候我们可以通过redux-thunk来让dispatch支持回调函数形式

下载redux-thunk

npm i redux-thunk

 使用applyMiddleware注册中间件

import { createStore, combineReducers, applyMiddleware } from 'redux'
import { thunk } from 'redux-thunk';
// 引入抽离的countcountReducer
import countReducer from './module/count'
// applyMiddleware(中间件,中间件2...)
const store = createStore(combineReducers({ count: countReducer }), applyMiddleware(thunk) as any)

export default store

页面调用异步代码

import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
// useSelector用于获取store中的数据
// usedispatch用于修改store中的数据
export default function Layout() {
  // 获取count命名空间模块下的count数据
  const count = useSelector((state: any) => state.count.count)
  const dispatch = useDispatch()
  const setCount = () => {
    // 模仿异步 延迟2秒修改count
    dispatch((dispatch) => {
      // 使用thunk之后 可以传入一个回调函数 
      // 回调第一个参数也是dispatch可以用来执行完异步操作继续dispatch
      setTimeout(() => {
        dispatch({
          type: 'count/add',
          payload:5
        })
      }, 2000);
    })
  }
  return (
    <div>
      {count}
      {/* 修改count */}
      <button onClick={setCount}>修改count</button>
    </div>
  )
}

异步的方法进行抽离

// count.ts
export default function countReducer(state = { count: 1 }, action) {
    switch (action.type) {
        // 加入命名空间
        case 'count/add':
            // action:{} type:执行的操作 payload:操作的参数 现在每次点击count就会加5
            return { count: state.count + action.payload }
        default:
            return state
    }
}

// 把异步操作放抽出来
export const asyncAddCount = () => {
    return (dispatch) => {
        setTimeout(() => {
            dispatch({
                type: 'count/add',
                payload: 5
            })
        }, 2000);
    }
}

这样在主页引入方法调用即可

import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
// 引入抽离的异步方法
import {asyncAddCount} from '../../store/module/count'
export default function Layout() {
  const count = useSelector((state: any) => state.count.count)
  const dispatch = useDispatch()
  const setCount = () => {
    // 调用
    dispatch(asyncAddCount())
  }
  return (
    <div>
      {count}
      {/* 修改count */}
      <button onClick={setCount}>修改count</button>
    </div>
  )
}

最终方案RTK

优点

可以自动跟redux devtools结合,不需要下载模块进行生效

数据不需要在通过返回值进行修改,可以跟vue一样直接进行修改

内置了redux-thunk异步插件 不需要额外安装

代码风格更好 采用选项式编程风格

安装@reduxjs/toolkit

npm i @reduxjs/toolkit

 RTK官网:https://redux-toolkit.js.org/

基本使用

重建store

// store.ts
import { configureStore } from '@reduxjs/toolkit'
// 引入reducer
import countReducer from './module/count'
// 创建store
const store = configureStore({
    reducer: {
        // 命名空间 state.count.xxx
        count: countReducer
        // 多个reducer ... 
    }
})

export default store

module/count.ts

import { createSlice } from '@reduxjs/toolkit'

const countSlice = createSlice({
    // 命名空间 dispatch(count/...)
    name: 'count',
    // 初始值
    initialState: {
        count: 0,
        name: 'count'
    },
    reducers: {
        add(state) {
            state.count++
        },
        // 传递参数
        setName(state, action) {
            state.name = action.payload
        }
    }
})

export default countSlice.reducer

页面使用

import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
export default function Layout() {
  const count = useSelector((state: any) => state.count.count)
  const name = useSelector((state: any) => state.count.name)
  const dispatch = useDispatch()
  const setCount = () => {
    dispatch({
      type: 'count/add',
    })
    dispatch({
      type: 'count/setName',
      payload: '张三',
    })
  }
  return (
    <div>
      <div>数:{count}</div>
      <div>名:{name }</div>
      <button onClick={setCount}>修改count-name</button>
    </div>
  )
}

初始页面

点击按钮

使用异步方法

createAsyncThunk创建异步方法

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
// 使用createAsyncThunk来创建异步方法 第一个参数是命名空间 第二个参数是异步任务
export const asyncAdd = createAsyncThunk('count/asyncAdd', async () => {
    // 模拟一个异步任务
    let res = await new Promise(resolve => {
        setTimeout(() => {
            resolve({ code: 200 })
        }, 1000)
    })
    return res
})
const countSlice = createSlice({
    // 命名空间 dispatch(count/...)
    name: 'count',
    // 初始值
    initialState: {
        count: 0,
    },
    reducers: {
        add(state, action) {
            state.count = action.payload
        },
    }
})

export default countSlice.reducer

 页面可以通过.then(res)拿到对应的结果进行操作

import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import {asyncAdd} from '@/store/module/count'
export default function Layout() {
  const count = useSelector((state: any) => state.count.count)
  const dispatch = useDispatch()
  const setCount = () => {
    dispatch(asyncAdd()).then(res => {
      // 再使用dispatch调用同步方法
      dispatch({
        type: 'count/add',
        payload:res.payload.code
      })
    })
  }
  return (
    <div>
      <div>数:{count}</div>
      <button onClick={setCount}>异步修改count</button>
    </div>
  )
}

控制台打印结果为

除了.then形式也可以使用extraReducers配置来拿到结果处理

从控制台打印我们可以看到存在type属性

extraReducers会根据primise返回的状态来执行对应的操作

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
// 使用createAsyncThunk来创建异步方法 第一个参数是命名空间 第二个参数是异步任务
export const asyncAdd = createAsyncThunk('count/asyncAdd', async () => {
    // 模拟一个异步任务
    let res = await new Promise(resolve => {
        setTimeout(() => {
            resolve({ code: 200 })
        }, 1000)
    })
    return res
})
const countSlice = createSlice({
    // 命名空间 dispatch(count/...)
    name: 'count',
    // 初始值
    initialState: {
        count: 0,
    },
    reducers: {
        add(state, action) {
            state.count = action.payload
        },
    },
    extraReducers(builder) {
        // 根据返回的primise状态来修改state fulfilled:成功 pending:等待 rejected:失败
        builder.addCase(asyncAdd.fulfilled, (state, action) => {
            state.count = action.payload.code
        })
    },
})

export default countSlice.reducer

数据持久化处理

安装redux-persist

npm i redux-persist

配置如下

// store.ts
import { configureStore } from '@reduxjs/toolkit'
// 引入reducer
import countReducer from './module/count'
// 持久化引入......................................
import {
    persistStore,
    persistReducer,
    FLUSH,
    REHYDRATE,
    PAUSE,
    PERSIST,
    PURGE,
    REGISTER,
} from 'redux-persist'
import storage from 'redux-persist/lib/storage'
// 持久化引入......................................

// 持久化配置1
const persistConfig = {
    // 存储名称
    key: 'root',
    version: 1,
    // 存储方式
    storage,
    // 白名单 那个需要存储
    whitelist: ['count'],
    // 黑名单 写在这块的数据不会存在storage
    // blacklist: ['fuseIm']
}

// 创建store
const store = configureStore({
    reducer: {
        // 持久化配置2 包裹 参数一 持久化配置 参数二 reducer
        count: persistReducer(persistConfig, countReducer)
    },
    // 持久化配置3
    middleware: (getDefaultMiddleware) =>
        getDefaultMiddleware({
            serializableCheck: {
                ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
            },
        }),
})
// 配置4
persistStore(store)
export default store

完结撒花❀❀❀

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值