redux在函数组件的正确进入姿势

1,487 阅读5分钟

1.redux介绍

react和vue都已经全面拥抱了hooks,想必大家在使用react函数组件的时候导入redux学习时,却发现网上的redux资料大都是基于react的类组件,这时心里会有许多蛋蛋的忧伤。不要怕,经过多方面的搜集整理,我将介绍如何在函数组件中正确使用redux,本人实力有限,如果介绍不当请多多批评。

2.redux安装和配置

下载

npm i redux

项目初始化

store目录下创建index.js

import {legacy_createStore} from "redux"

//初始的数据
let num = 1

//reducer控制数据的修改
function reducers(state=num,action) {
  switch(action.type) {
    case "add":
      return state+action.num
    case "del":
      return state-action.num
    default:return state
  }
}

//核心store
const store = legacy_createStore(reducers)
export default store

根目录下的index.js中导入store

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import {Provider } from "react-redux"
import store from "./store/index"
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    <Provider store={store}>
      <App />
    </Provider>
);

使用redux数据

函数组件中使用redux组件十分十分的简单,只需要导入useSelector就可以让redux的数据具备响应式,通过useDispatch我们可以完成redux数据的修改操作。

import {useDispatch, useSelector} from "react-redux"
export default function() {
  const dispatch = useDispatch()
  const num = useSelector((state)=>{
    return state
  })
  return <div>
    <div>{num}</div>
    <div onClick={()=>dispatch({type:'add',num:1})}>增加num</div>
    <div onClick={()=>dispatch({type:'del',num:1})}>减少Num</div>
  </div>
}

页面效果

1.gif

3.多个reducer的问题

看完上面的问题,大家感觉函数组件的redux操作是不是很简单,但是实际我们还有一部分问题没有解决,假设我们现在有多个模块的数据,例如用户信息数据,产品详情数据等。如果我们使用一个reducer函数的switch去操作redux数据,出现的问题如下代码所示,switch判断和数据变得十分臃肿,不利于代码的维护和阅读。

const data = {
    user:{},
    list:{}
}
function reducer(state=data,actions) {
    switch(actions.type) {
        case "xx" :
        case "xx" :
        case "xx" :
        
        .....
        
    }
}

对于上面的问题,我们通常需要对reducer进行拆分,通常一个模块对应一个reducer,最后我们通过api进行reducer的合并就可以了。

image.png

1. store目录下新建reducer文件夹

2. reducer文件夹下存放不同的reudcer

我们在reducer文件夹下创建了两个reducer,每个reducer都有自己的数据和自己的数据处理方法。如下

books.js

let num = 1
export default function reducers(state=num,actions) {
  switch(actions.type) {
    case "addbook":
      return state+actions.num
    case "delbook":
      return state-actions.num
    default:
      return state
  }
}

user.js

let num = 2
export default function reducers(state=num,actions) {
  switch(actions.type) {
    case "adduser":
      return state+actions.num
    case "deluser":
      return state-actions.num
    default:
      return state
  }
}

3. store/reudcer/index.js下进行多个reducer的合并操作

import books from "./books";
import user from "./user";

import {combineReducers} from "redux" 

export default combineReducers({
  books,user
})


4. store/index.js配置

import {legacy_createStore } from 'redux'
import rootReducer from "./reducers/index"
export default legacy_createStore(rootReducer)

页面数据的测试

import {useDispatch, useSelector} from "react-redux"
export default function() {
  const dispatch = useDispatch()
  
  //user模块
  const numUsers = useSelector((state)=>{
    return state.user
  })
  
  //books模块
  const numBooks = useSelector((state)=>{
    return state.books
  })
  return <div>
    <div>
      <p>user模块</p>
      <div>num:{numUsers}</div>
      <div></div>
      <div onClick={()=>dispatch({type:'adduser',num:1})}>增加num</div>
      <div onClick={()=>dispatch({type:'deluser',num:1})}>减少Num</div>
    </div>
    <div>---------------------------------</div>
    <div>
      <p>books模块</p>
      <div>num:{numBooks}</div>
      <div></div>
      <div onClick={()=>dispatch({type:'addbook',num:1})}>增加num</div>
      <div onClick={()=>dispatch({type:'delbook',num:1})}>减少Num</div>
    </div>
  </div>
}

我们设置了两个不同模块的reducer,并且各自有各自的数据,尽管它们都是num,但是这个num是属于不同模块的,是互相独立的数据。如下是两个模块的测试,可以看到两个模块的num是各自独立的。

2.gif

4.dispatch的优化

image.png

观察上面dispatch部分,传入一个对象到redux的reducer中,这部分出现了大量重复臃肿设计代码,如果reducer内部发生了改变,我们需要对这些dispatch部分进行逐个的修改。因此在日常开发中,我们会对dispatch传递一个纯函数,函数内部返回一个对象。

旧的设计

dispatch({type:"addbook",num:1})

改造后设计

function addbook(num) {
    return {
        type:'addbook"
        num:num
    }
}

dispatch(addbook(1)

项目中的设计

通常我们会在store目录下创建一个actions文件夹保存不同模块的actions函数

image.png

actions/user.js

export function adduser(num) {
  return {
    type:"adduser",
    num
  }
}

export function deluser(num) {
  return {
    type:"deluser",
    num
  }
}

项目中使用

import {useDispatch, useSelector} from "react-redux"
import {adduser,deluser} from "./../store/actions/user"
export default function() {
  const dispatch = useDispatch()
  //user模块
  const numUsers = useSelector((state)=>{
    return state.user
  })
  return <div>
    <div>
      <p>user模块</p>
      <div>num:{numUsers}</div>
      <div></div>
      <div onClick={()=>dispatch(adduser(1))}>增加num</div>
      <div onClick={()=>dispatch(deluser(1))}>减少Num</div>
    </div>
  </div>
}

5.actions-type引入

经过上面的优化,已经完成了99%的工作,还差1%就是优化type参数的传递,上面的type我们都是传递一个字符串,但是实际开发中,随着项目工作量的加剧,很容易在传递type字符串的时候出现错误。因此我们需要使用变量代替字符串,这样我们就可以保证唯一性和正确性。通常在store目录下创建一个type文件夹保存所有的type

image.png

store/types/books.js

export const ADDBOOK = "addbook"
export const DELBOOK = 'delbook'

store/reducer/books.js

import {ADDBOOK,DELBOOK} from "../types/books"
let name = 1

export default function(state=name,action) {
  switch(action.type) {
    case ADDBOOK:
      return state+action.num
    case DELBOOK:
      return state-action.num
    default: return state
  }
} 

store/actions/books.js

import {ADDBOOK,DELBOOK} from "./../types/books"
export function addbook (num) {
  return {
    type:ADDBOOK,
    num
  }
}

export function delbook (num) {
  return {
    type:DELBOOK,
    num
  }
}

使用

import {useDispatch, useSelector} from "react-redux"
import {addbook,delbook} from "./../store/actions/books"
export default function() {
  const dispatch = useDispatch()
  const numBooks = useSelector((state)=>{
    return state.books
  })
  return <div>
    <div>
      <p>books模块</p>
      <div>num:{numBooks}</div>
      <div></div>
      <div onClick={()=>dispatch(addbook(1))}>增加num</div>
      <div onClick={()=>dispatch(delbook(1))}>减少Num</div>
    </div>
  </div>
}

6.redux-thunk引入(处理异步过程)

终于来到最后一部分了,如果我们需要在redux引入异步修改数据时,如何解决?此处我们需要使用一个中间件 redux-thunk,有了这个中间件,我们可以在dsipatch修改redux数据的时候,传递一个函数,这个函数内部就可以执行异步修改redux数据的操作。

下载

npm i redux-thunk

store/index.js配置

import { applyMiddleware,legacy_createStore } from 'redux'
import rootReducer from "./reducers/index"
import thunk from "redux-thunk"
export default legacy_createStore(rootReducer,applyMiddleware(thunk))

store/actions/user.js

我们按照上面的规范,在actions中操作dispatch传递的参数。我们直接在books模块添加一个异步模拟。

import {ADDBOOK,DELBOOK} from "./../types/books"
export function addbook (num) {
  return {
    type:ADDBOOK,
    num
  }
}

export function delbook (num) {
  return {
    type:DELBOOK,
    num
  }
}

//异步的actions
export function asyncAdd(num) {
  return function(dispatch) {
    setTimeout(() => {
      dispatch(addbook(num))
    }, 2000);
  }
}

使用

import {useDispatch, useSelector} from "react-redux"
import {addbook,delbook,asyncAdd} from "./../store/actions/books"
export default function() {
  const dispatch = useDispatch()

  const numBooks = useSelector((state)=>{

    return state.books
  })
  return <div>
    <div>
      <p>books模块</p>
      <div>num:{numBooks}</div>
      <div></div>
      <div onClick={()=>dispatch(addbook(1))}>增加num</div>
      <div onClick={()=>dispatch(delbook(1))}>减少Num</div>

      <div onClick={()=>dispatch(asyncAdd(100))}>异步添加</div>
    </div>
  </div>
}

测试

可以看到,当我们点击异步添加的时候,页面在2s后修改了redux的数据。

3.gif

总结

希望大家认真练习相关redux的操作和规范化设计,如果有任何疑问,评论区留言啊。