Redux
react 中最常用状态管理库😄
纯函数
纯函数:①确定的输入,一定会产生确定的输出。②函数在执行过程中,不能产生副作用。
slice是纯函数splice不是redux中的reducer要被要求是一个纯函数- React 要求函数组件和类组件,必须像纯函数一样,保护它们的
props不被修改
Redux基本使用
核心理念: store action reducer
React 可以用 Redux 来管理 state。注意 Redux 是可以脱离 React 的,比如在 Node 环境下亦可以使用
-
action
所有数据的变化,都必须通过派发(dispatch)
action来实现,action是一个普通的 JS 对象,用来描述这次更新的type和contentaction 对象类似于这样:
const action = { type: "INC_COUNT", count: 0}
store.dispatch(action)
-
reducer
reducer的作用是将传入的state(旧的) 和action结合在一起生成一个新的state并return出去作为新的 state
首先我们有一些基本数据
const intialState = {
}
怎么让 redux 管理呢?
import {createStore} from 'redux'
function reducer() {}
const store = createStore(reducer)//该函数需要传入一个 reducer
reducer 中可以返回我们定义的 state
import {createStore} from 'redux'
const intialState = {
}
function reducer() {
return intialState
}
const store = createStore(reducer)//该函数需要传入一个 reducer
export default store
怎么获取 redux 中的 state 呢?
import store from './store'
console.log(store.getState())
怎么修改 redux 中的 state 呢?
要是能像 Pinia 那样,直接修改😄,怎么说?
store.getState().name = "newName"
千万不要这样改!😏
要这样😉
const nameAction = {type:"change_name", name:"kobe"}
store.dispatch(nameAction)
reducer怎么接受呢?😆
function reducer(state, action) {
return initialState
}
参数 action 即可!😄
优化体验
我们会注意到,当 store 创建时 reducer 是会被调用一次的,此时最好传入初始 state
因此,准确的写法应该是
function reducer(state = initialState, action) {
if (action.type === "change_name"){
return { ...state, name: action.name }
}
return state;
}
我们没必要每次派发完都手动的 getState(),我们希望每次修改 state 后,能自动刷新页面,这就需要我们订阅store
注意:此处的订阅只有当数据发生改变后才会执行,第一次加载时不会
store.subscribe(() => {
store.getState() //当数据变化时会自动调用该函数
})
取消订阅
const unSubscribe = store.subscribe(() => {
store.getState() //当数据变化时会自动调用该函数
})
unSubscribe()//取消订阅
if 语句优化
function reducer(state = initialState, action){
switch(action.type) {
case:"change_name":
return {...state, name: action.name}
case : "add_num":
return {...state, num: state.num + action.num}
default:
return state
}
}
动态生成 action
const changeNameAction = (name) => {
return {
type: "change_name",
name
}
}
store.dispatch(changeNameAction("kobe"))
store.dispatch(changeNameAction("kobe"))
const addNumAction = (num) =>{
return {
type: "add_num",
num
}
}
store.dispatch(addNumAction("2"))
你这个文件想派发 action,我这个文件也想派发 action,我们都想要派发 action 🔥🔥🔥
我们一般会在 store 文件夹下建立一手 actionCreator.js,在此文件中定义所有的 action 函数,然后哪个文件需要,就在哪个文件中引入即可。
对于 action 的类型,我们也在当前文件夹下建立一手保存常量的 constants.js,这样我们之后用常量防止出错
const ADD_NUMBER = "add_number"
const CHANGE_NAME = "change_name"
module.exports = {
ADD_NUMBER,
CHANGE_NAME
}
const { ADD_NUMBER, CHANGE_NAME } = require("./constants")
const changeNameAction = (name) => ({
type: CHANGE_NAME,
name
})
const addNumberAction = (num) => ({
type: ADD_NUMBER,
num
})
module.exports = {
changeNameAction,
addNumberAction
}
一般情况下,reducer 函数会越来越复杂,所以通常情况下我们也把它单独抽离到一个文件中 reducer.js
const { ADD_NUMBER, CHANGE_NAME } = require("./constants")
// 初始化的数据
const initialState = {
name: "why",
counter: 100
}
function reducer(state = initialState, action) {
switch(action.type) {
case CHANGE_NAME:
return { ...state, name: action.name }
case ADD_NUMBER:
return { ...state, counter: state.counter + action.num }
default:
return state
}
}
module.exports = reducer
总结一下上面做的几点优化:
- 将派发的
action生成过程放到一个actionCreators函数中
- 将定义的所有
actionCreators的函数, 放到一个独立的文件中:actionCreators.js
actionCreators和reducer函数中使用字符串常量是一致的, 所以将常量抽取到一个独立constants的文件中*
- 将
reducer和默认值(initialState)放到一个独立的reducer.js文件中, 而不是在index.js
综上,早期的 redux 使用方式我们已经介绍完毕,一般目录结构就是如此😆
store
├─ actionCreators.js
├─ constants.js
├─ index.js
└─ reducer.js
Redux的三大原则
-
单一数据源
- 整个应用程序的
state被存储在一颗 object tree 中,并且这棵树只存储在一个store中 - 不像
pinia,Redux中你应该只创建一个store
- 整个应用程序的
-
State中的数据应该是只读的
- 修改
state的唯一方式是派发action - 这样可以避免 race condition (竞态)的问题(两个进程同时修改)
- 修改
-
只能通过纯函数来修改
- 通过
reducer将旧的state和actions结合,返回一个新的state - 随着
reducer日益增大,我们可以将其拆分成多个小的reducers reducer应该是一个纯函数
- 通过