react-redux 使用小结
好久没写 Redux 了,最近老板让写一个 POC 去把现在的 usecase+repo 的结构转成 redux,所以就……复习一下。这里的案例用的是 react-redux,原生 redux 的笔记可以参考一下这个:一文快速上手 Redux
functional based component
functional component 真的方便很多,因为不需要考虑 state 和 props 的 mapping……
store 以及 index.js 的配置也放在这里一起写了:
store:
这里用的是最新的 @reduxjs/toolkit
,如果项目比较老的话,没有用最新版本的 react-redux 和 redux,就得用 import { createStore } from 'redux';
。configureStore
和 createStore
的语法也稍有一些变化,详情可以参考官方文档。
import { configureStore } from '@reduxjs/toolkit';
const counterReducer = (state = { counter: 0 }, action) => {
if (action.type === 'increment') {
return { counter: state.counter + 1 };
} else if (action.type === 'decrement') {
return { counter: state.counter - 1 };
}
return state;
};
const stroe = configureStore({
reducer: counterReducer,
});
export default stroe;
index.js:
主要用来挂载 store,也可以挂在 App level。
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import './index.css';
import App from './App';
import store from './store';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App />
</Provider>
);
Counter:
这里实现的也就是一个 counter 的功能:
import { useDispatch, useSelector } from 'react-redux';
import classes from './Counter.module.css';
const Counter = () => {
const dispatch = useDispatch();
// useSelector 接受一个函数,用来返回 redux 中需要抠出来的状态
const counter = useSelector((state) => state.counter);
const incrementHandler = () => {
dispatch({ type: 'increment' });
};
const decrementHandler = () => {
dispatch({ type: 'decrement' });
};
const toggleCounterHandler = () => {};
return (
<main className={classes.counter}>
<h1>Redux Counter</h1>
<div className={classes.value}>{counter}</div>
<div>
<button onClick={incrementHandler}>increment</button>
<button onClick={decrementHandler}>decrement</button>
</div>
<button onClick={toggleCounterHandler}>Toggle Counter</button>
</main>
);
};
export default Counter;
页面如下:
class based component
class based component 会稍微麻烦一点,每个组件都需要实现 mapStateToProps
和 mapDispatchToProps
,并且末尾调用 connect
这个 HOC 进行组件内 state 和 props 的 mapping。
import React, { Component } from 'react';
import { connect } from 'react-redux';
import classes from './Counter.module.css';
export class Counter extends Component {
incrementHandler() {
this.props.increment();
}
decrementHandler() {
this.props.decrement();
}
toggleCounterHandler() {}
render() {
const { counter } = this.props;
return (
<main className={classes.counter}>
<h1>Redux Counter</h1>
<div className={classes.value}>{counter}</div>
<div>
<button onClick={this.incrementHandler.bind(this)}>increment</button>
<button onClick={this.decrementHandler.bind(this)}>decrement</button>
</div>
<button onClick={this.toggleCounterHandler}>Toggle Counter</button>
</main>
);
}
}
// connect is a HOC
const mapStateToProps = (state) => {
return {
counter: state.counter,
};
};
const mapDispatchToProps = (dispatch) => {
return {
increment: () => dispatch({ type: 'increment' }),
decrement: () => dispatch({ type: 'decrement' }),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Counter);
我记得以前项目配置过不需要进行 mapping 来着……抽空的时候回忆一下看看能不能记一下
添加 payload
有些情况下需要在组件内部向 reducer 中传值,这里就用 functional component 来实现了。class based component 以前都是封装好了的,一下子还真的有点想不起来怎么做……
const Counter = () => {
const increaseHandler = () => {
dispatch({ type: 'increase', amount: 5 });
};
};
const counterReducer = (state = { counter: 0 }, action) => {
if (action.type === 'increase') {
return { counter: state.counter + action.amount };
}
};
添加多个状态
store 的修改:
import { configureStore } from '@reduxjs/toolkit';
const initialState = {
counter: 0,
showCounter: true,
};
const counterReducer = (state = initialState, action) => {
if (action.type === 'increment') {
return {
counter: state.counter + 1,
showCounter: state.showCounter,
};
} else if (action.type === 'decrement') {
return {
counter: state.counter - 1,
showCounter: state.showCounter,
};
} else if (action.type === 'increase') {
return {
counter: state.counter + action.amount,
showCounter: state.showCounter,
};
} else if (action.type === 'toggle') {
return {
showCounter: !state.showCounter,
counter: state.counter,
};
}
return state;
};
const stroe = configureStore({
reducer: counterReducer,
});
export default stroe;
对比起来内部调用就方便很多了:
const Counter = () => {
const showCounter = useSelector((state) => state.showCounter);
return (
{showCounter && <div className={classes.value}>{counter}</div>}
)
}
redux 的状态是不可变的,因此在使用老版的操作就需要返回一个新的状态,而不能直接修改原有的状态。
redux toolkit 部分更新
找了一下资料,突然发现之前的写法已经落伍了……
但是写都写好了就懒得删除了……
所以这里对 toolkit 的配置进行一下更新。
使用 toolkit 的优点包括:
-
可以直接对状态进行修改
toolkit 内部会进行监听,如果察觉到状态被修改了,那么 toolkit 内部会创建一个新的状态并进行返回。也就意味着对于开发来说,就少一个状态需要进行手动管理。
-
少些很多代码
不需要额外设立一个变量去监听对应的
type
reducers 直接可以归并为可调用的函数,也不用反复写很多的 switch/if-else 流程控制
store 部分代码:
import { configureStore, createSlice } from '@reduxjs/toolkit';
const initialState = {
counter: 0,
showCounter: true,
};
const counterSlice = createSlice({
name: 'counter',
initialState: initialState,
reducers: {
increment(state) {
// 可以直接进行修改
state.counter++;
},
decrement(state) {
state.counter--;
},
increase(state, action) {
state.counter += action.payload;
},
toggle(state) {
state.showCounter = !state.showCounter;
},
},
});
const stroe = configureStore({
reducer: counterSlice.reducer,
});
// 这样只要导出一个actions即可
// 虽然也可以说自己再封装一下,不过使用toolkit可以少写一写封装的代码也方便很多
export const counterActions = counterSlice.actions;
export default stroe;
Counter 部分代码:
import { useDispatch } from 'react-redux';
import { useSelector } from 'react-redux';
// 可以直接导入action并且在下面函数中调用,省了调用一些 type 的烦恼
import { counterActions } from '../store';
import classes from './Counter.module.css';
const Counter = () => {
const dispatch = useDispatch();
const counter = useSelector((state) => state.counter);
const showCounter = useSelector((state) => state.showCounter);
const incrementHandler = () => {
dispatch(counterActions.increment());
};
const increaseHandler = () => {
dispatch(counterActions.increase(5)); // { type: SOME_UNIQUE_VALUE, payload: 10 }
};
const decrementHandler = () => {
dispatch(counterActions.decrement());
};
const toggleCounterHandler = () => {
dispatch(counterActions.toggle());
};
return (
<main className={classes.counter}>
<h1>Redux Counter</h1>
{showCounter && <div className={classes.value}>{counter}</div>}
<div>
<button onClick={incrementHandler}>increment</button>
<button onClick={increaseHandler}>Increase By 5</button>
<button onClick={decrementHandler}>decrement</button>
</div>
<button onClick={toggleCounterHandler}>Toggle Counter</button>
</main>
);
};
export default Counter;
toolkit 管理多个状态
store 部分代码:
对于 store 来说就是创建多个 slice,并且在 configureStore
中合并创建 slices 的 reducers。
import { configureStore, createSlice } from '@reduxjs/toolkit';
const initialCounterState = {
counter: 0,
showCounter: true,
};
const counterSlice = createSlice({
name: 'counter',
initialState: initialCounterState,
reducers: {
increment(state) {
state.counter++;
},
decrement(state) {
state.counter--;
},
increase(state, action) {
state.counter += action.payload;
},
toggle(state) {
state.showCounter = !state.showCounter;
},
},
});
const initialAuthState = {
isAuthenticated: false,
};
const authSlice = createSlice({
name: 'auth',
initialState: initialAuthState,
reducers: {
login(state) {
state.isAuthenticated = true;
},
logout(state) {
state.isAuthenticated = false;
},
},
});
const stroe = configureStore({
reducer: { counter: counterSlice.reducer, auth: authSlice.reducer },
});
export const counterActions = counterSlice.actions;
export const authActions = authSlice.actions;
export default stroe;
counter:
const Counter = () => {
const dispatch = useDispatch();
const { counter, showCounter } = useSelector((state) => state.counter);
};
这里需要注意就是,如果不使用解构,那么就会直接获得 counter
这个 slice 中包含的状态: