Redux的介绍和使用

这篇文章主要是介绍redux的基本概念,以及redux如何使用!!

Redux简述

Redux 实际上是:

  • 包含全局状态的单一仓库
  • 当应用中发生某些事情时,分发普通对象(plain object) 动作(action)给仓库
  • Pure reducer 函数查看这些动作(action)并且返回不可更新的状态。

简单来说Redux是一个可预测的状态容器

状态(State)

状态就是一个变量,用以记录程序执行的情况。组件可以根据不同的状态值切换为不同的显示,比如,用户登录和没登录看到页面应该是不同的,那么用户的登录与否就应该是一个状态。

容器(Container)

容器当然是用来装东西的,状态容器即用来存储状态的容器。状态多了,自然需要一个东西来存储,但是容器的功能却不是仅仅能存储状态,它实则是一个状态的管理器,除了存储状态外,它还可以用来对state进行查询、修改等所有操作。

可预测(Predictable)

可预测指我们在对state进行各种操作时,其结果是一定的。即以相同的顺序对state执行相同的操作会得到相同的结果。简单来说,Redux中对状态所有的操作都封装到了容器内部,外部只能通过调用容器提供的方法来操作state,而不能直接修改state。这就意味着外部对state的操作都被容器所限制,对state的操作都在容器的掌控之中,也就是可预测。

总的来说,Redux是一个稳定、安全的状态管理器。

react中使用Redux(旧的方式)

安装

npm install -S redux react-redux

举例:

现在有一个PageA组件,点击减少数字变小,点击增加数字变大,我想要通过redux来操作

import React from 'react'

export default function () {
  return (
    <div>
      <button>增加</button>
      <p>20</p>
      <button>减少</button>
    </div>
  )
}

创建reducer

reducer用来整合关于state的所有操作,容器修改state时会自动调用该函数,函数调用时会接收到两个参数:state和action,

state表示当前的state,可以通过该state来计算新的state。state = {num: 20}这是在指定state的默认值,如果不指定,第一次调用时state的值会是undefined。也可以将该值指定为createStore()的第二个参数。

action是一个普通对象,用来存储操作信息。

const pageAReducer = ((state = {
  num: 20
}, action) => {
  switch (action.type) {
    case 'ADD':
      return {
        ...state,
        num: state.num + action.num
      };
    case 'SUB':
      return {
        ...state,
        num: state.num - action.num
      };
    default:
      return state;
  }
})

创建store(createStore)

创建store是利用createStore

createStore(reducer, [preloadedState], [enhancer])

createStore用来创建一个Redux中的容器对象,它需要三个参数:reducer、preloadedState、enhancer。

  • reducer是一个函数,是state操作的整合函数,每次修改state时都会触发该函数,它的返回值会成为新的state。
  • preloadedState就是state的初始值,可以在这里指定也可以在reducer中指定。
  • enhancer (Function): Store enhancer。你可以选择指定它以使用第三方功能,如middleware、时间旅行、持久化来增强 store。
const store=createStore(pageaReducer);

将pageReducer传递进createStore后,我们会得到一个store对象:

store对象创建后,对state的所有操作都需要通过它来进行

设置provider

创建store后,需要引入react-redux中提供的Provider组件,将其设置到所有组件的最外层,并且将刚刚创建的store设置为组件的store属性,只有这样才能使得Redux中的数据能被所有的组件访问到。

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
)

访问数据(useSelector)

useSelector,用于获取Redux中存储的数据,它需要一个回调函数作为参数,回调函数的第一个参数就是当前的state,回调函数的返回值,会作为useSelector的返回值返回,所以state => state表示直接将整个state作为返回值返回。现在就可以通过pagea来读取state中的数据了:

const pagea = useSelector(state => state);

<p>{pagea.num}</p>

操作数据(useDispatch)

useDispatch同样是react-redux提供的钩子函数,用来获取redux的派发器,对state的所有操作都需要通过派发器来进行。

通过派发器修改state:

const dispatch = useDispatch();

dispatch({type:'ADD', num:1})
dispatch({type:'SUB', num:2})

完整代码和目录结构

Redux的介绍和使用

src\store\index.jsx

import { createStore } from "redux";

const pageAReducer = ((state = {
  num: 20
}, action) => {
  switch (action.type) {
    case 'ADD':
      return {
        ...state,
        num: state.num + action.num
      };
    case 'SUB':
      return {
        ...state,
        num: state.num - action.num
      };
    default:
      return state;
  }
})

const store =createStore(pageAReducer)

export default store

src\components\PageA.jsx

import React from 'react'
import { useDispatch, useSelector } from 'react-redux'

export default function () {
  const pagea = useSelector(state => state)
  const dispatch = useDispatch()

  const onClickOne = () => {
    dispatch({ type: 'ADD', num: 1 })

  }

  const onClickTwo = () => {
    dispatch({ type: 'SUB', num: 2 })
  }
  return (
    <div>
      <button onClick={onClickOne}>增加</button>
      <p>{pagea.num}</p>
      <button onClick={onClickTwo}>减少</button>
    </div>
  )
}

App.jsx

import PageA from './components/PageA';

function App() {

  return (
    <>
      <PageA></PageA>
    </>
  )
}

export default App

main.jsx

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import { Provider } from 'react-redux'
import store from './store/'

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
)

多个reducer

比如有个PageB页面,显示数组的内容,点击变化按钮,数组push一个我们随便定义的数据,也要用redux进行操作

export default function Page2() {
  
  const onClick=()=>{
    
  }

  return (
    <div>
      <div>Page2</div>
      <div>{11}</div>
      <button onClick={onClick}>变化</button>
    </div>
  )
}

跟PageA一样,先创建reducer

const pageBReducer = (state = { arr: [10,20,30] }, action) => {
  switch (action.type) {
    case 'pushNum':
      state.arr.push(action.num)
      return {
        ...state
      }
    default:
      return state;
  }
}

combineReducer

创建完reducer以后,因为是多个reducer,要使用Redux为我们提供的函数combineReducer将多个reducer进行合并,合并后才能传递进createStore来创建store。

combineReducer需要一个对象作为参数,对象的属性名可以根据需要指定,比如我们有两种reducer。读取数据时,直接通过state.pageAReducer读取PageA的数据,通过state.pageBReducer读取PageB的数据。

const reducers=combineReducers({
  pageAReducer,
  pageBReducer
})

const store = createStore(reducers)

export default store

完整代码

src\components\PageB.jsx

import React from 'react'
import { useDispatch, useSelector } from 'react-redux'

export default function PageB() {
  const dispatch = useDispatch();
  const pageb = useSelector(state => state.pageBReducer)

  const onClick = () => {
    dispatch({ type: 'pushNum', num: 50 })
  }

  return (
    <div>
      <button onClick={onClick}>变化</button>
      <p>{pageb.arr.join(" ")}</p>
    </div>
  )
}

src\store\index.jsx

import { combineReducers, createStore } from "redux";

const pageAReducer = (state = { num: 20 }, action) => {
  switch (action.type) {
    case 'ADD':
      return {
        ...state,
        num: state.num + action.num
      }
    case 'SUB':
      return {
        ...state,
        num: state.num - action.num
      }
    default:
      return state;
  }
}

const pageBReducer = (state = { arr: [10,20,30] }, action) => {
  switch (action.type) {
    case 'pushNum':
      state.arr.push(action.num)
      return {
        ...state
      }
    default:
      return state;
  }
}

const reducers=combineReducers({
  pageAReducer,
  pageBReducer
})

const store = createStore(reducers)

export default store

模块化及优化

由上面可知,我是把store和reducer放在一个文件中的,当有多个reducer时,可以看到代码越来越多,结构就越来越复杂,代码的耦合性变高,不利于维护,因此进行模块化,将reducer和store进行拆分

拆分reducer

Redux的介绍和使用

pageAReducer里面既有给数据设置默认值又有操作数据的动作,比如匹配到ADD以后,我们可以把对数据的操作封装成一个函数,在reducer里面直接调用,因为后续可能还有其他对数据的操作,会使得代码结构越来越复杂

因此我把pageAReducer拆分成两部分,一部分是reducer,负责根据匹配的case更新数据,一部分是status,负责去定义状态的初始值,以及更新状态的一些方法

在store文件下新建reducer文件夹,reducer下新建PageA文件夹

PageA文件夹下包含两个文件:reducer和status

Redux的介绍和使用

src\store\reducers\PageA\status.jsx
在handlerStatus中对方法名映射为reducer中的action.type,这里的action.type也对应了dispatch中的type

let handlerStatus = {
  state: {
    num: 20
  },
  actions: {
    ADD(newState, action) {
      newState.num = newState.num + action.num
    },
    SUB(newState, action) {
      newState.num = newState.num - action.num
    }
  },
  ADD:"ADD",
  SUB:"SUB"
}

export default handlerStatus

src\store\reducers\PageA\reducer.jsx

定义newState,对state进行深拷贝,统一返回newState

import handlerStatus from "./status";

const reducer = (state = { ...handlerStatus.state }, action) => {
  let newState = JSON.parse(JSON.stringify(state))

  switch (action.type) {
    case handlerStatus.ADD:
      handlerStatus.actions.ADD(newState, action)
      break;
    case handlerStatus.SUB:
      handlerStatus.actions.SUB(newState, action)
      break;
    default:
      break;
  }

  return newState
}

export default reducer

在reducers文件夹下新建index.jsx,为了引入多个reducer,并进行组合

src\store\reducers\index.jsx

import { combineReducers } from "redux";
import pageAReducer from './PageA/reducer'

const reducers = combineReducers({
  pageAReducer
})

export default reducers

在store中引入reducers

import { createStore } from 'redux'
import reducers from './reducers'

const store = createStore(reducers)

export default store

关于PageB也是一样的,这里就不再重复,后面直接贴完整代码

优化reducer和status

因为我们对方法名和action.type进行了映射,所以我们可以把方法名统计用一个属性进行管理,通过遍历来将方法名和action.type进行映射

src\store\reducers\PageA\status.jsx

let handlerStatus = {
  state: {
    num: 20
  },
  actions: {
    ADD(newState, action) {
      newState.num = newState.num + action.num
    },
    SUB(newState, action) {
      newState.num = newState.num - action.num
    }
  },
  actionNames: {}
}

let actionNames = {}

Object.keys(handlerStatus.actions).forEach((key) => {
  actionNames[key] = key
})

export default handlerStatus

同样的在reducer中通过遍历actionNames,根据匹配到action.type来调用具体的方法,优化了swtich结构,用some是为了减少不必要的遍历

import handlerStatus from "./status";

const reducer = (state = { ...handlerStatus.state }, action) => {
  let newState = JSON.parse(JSON.stringify(state))

  Object.keys(handlerStatus.actionNames).some((actionName) => {
    if (actionName === action.type) {
      handlerStatus.actions[actionName](newState, action)
      return true
    }
    return false
  })

  return newState
}

export default reducer

PageB也是相似的写法,不再重复介绍

完整代码和目录结构

Redux的介绍和使用

components

src\components\PageA.jsx

import React from 'react'
import { useDispatch, useSelector } from 'react-redux'

export default function PageA() {
  const dispatch=useDispatch();
  const pagea=useSelector(state=>state.pageAReducer)

  const onClick1=()=>{
    dispatch({type:'ADD',num:1})
  }

  const onClick2=()=>{
    dispatch({type:'SUB',num:2})
  }

  return (
    <div>
      <button onClick={onClick1}>增加</button>
      <p>{pagea.num}</p>
      <button onClick={onClick2}>减少</button>
    </div>
  )
}

src\components\PageB.jsx

import React from 'react'
import { useDispatch, useSelector } from 'react-redux'

export default function PageB() {
  const dispatch = useDispatch();
  const pageb = useSelector(state => state.pageBReducer)

  const onClick = () => {
    dispatch({ type: 'pushNum', num: 50 })
  }

  return (
    <div>
      <button onClick={onClick}>变化</button>
      <p>{pageb.arr.join(" ")}</p>
    </div>
  )
}

store

src\store\index.jsx

import { createStore } from 'redux'
import reducers from './reducers'

const store = createStore(reducers)

export default store

reducers

src\store\reducers\index.jsx

import { combineReducers } from "redux";
import pageAReducer from './PageA/reducer'
import pageBReducer from './PageB/reducer'

const reducers = combineReducers({
  pageAReducer,
  pageBReducer
})

export default reducers
PageA

src\store\reducers\PageA\reducer.jsx

import handlerStatus from "./status";

const reducer = (state = { ...handlerStatus.state }, action) => {
  let newState = JSON.parse(JSON.stringify(state))

  Object.keys(handlerStatus.actionNames).some((actionName) => {
    if (actionName === action.type) {
      handlerStatus.actions[actionName](newState, action)
      return true
    }
    return false
  })

  return newState
}

export default reducer

src\store\reducers\PageA\status.jsx

let handlerStatus = {
  state: {
    num: 20
  },
  actions: {
    ADD(newState, action) {
      newState.num = newState.num + action.num
    },
    SUB(newState, action) {
      newState.num = newState.num - action.num
    }
  },
  actionNames: {}
}

let actionNames = {}

Object.keys(handlerStatus.actions).forEach((key) => {
  actionNames[key] = key
})

handlerStatus.actionNames=actionNames

export default handlerStatus
PageB

src\store\reducers\PageB\reducer.jsx

import handlerStatus from "./status";

const reducer = (state = { ...handlerStatus.state }, action) => {
  let newState = JSON.parse(JSON.stringify(state))

  Object.keys(handlerStatus.actionNames).some((actionName) => {
    if (actionName === action.type) {
      handlerStatus.actions[actionName](newState, action)
      return true
    }
    return false
  })

  return newState
}

export default reducer

src\store\reducers\PageB\status.jsx

let handlerStatus = {
  state: {
    arr: [10, 20, 30]
  },
  actions: {
    pushNum(newState, action) {
      newState.arr.push(action.num)
    },
  },
  actionNames: {}
}

let actionNames = {}

Object.keys(handlerStatus.actions).forEach((key) => {
  actionNames[key] = key
})

handlerStatus.actionNames=actionNames

export default handlerStatus

总结

使用redux,可以使用一套固定的模版

status

const handlerStatus = {
  state: {
    // 放数据

  },
  actions: {
    // 放同步方法

  },
  asyncActions: {
    // 放异步方法

  },
  actionNames: {}
}

let actionNames = {}

Object.keys(handlerStatus.actions).forEach((key) => {
  actionNames[key] = key
})

handlerStatus.actionNames=actionNames

export default handlerStatus

reducer

import handlerStatus from "./status";

const reducer = (state = { ...handlerStatus.state }, action) => {
  let newState = JSON.parse(JSON.stringify(state))

  Object.keys(handlerStatus.actionNames).some((actionName) => {
    if (actionName === action.type) {
      handlerStatus.actions[actionName](newState, action)
      return true
    }
    return false
  })

  return newState
}

export default reducer

原文链接:https://juejin.cn/post/7344245529491505171 作者:ZL不懂前端

(0)
上一篇 2024年3月10日 下午5:03
下一篇 2024年3月10日 下午5:13

相关推荐

发表回复

登录后才能评论