到如今redux的已经不是react程序中必须的一部分内容了,
我们应该在本地需要大量更新全局变量时才使用它!
redux vs reducer
reducer的工作机制:
手动构造action对象传入dispatch函数中
dispatch函数将 action传入reducer当中
reducer结合当前state与action生成新的state
重新渲染
redux的工作机制:
其中store中有多个reducer
单独编写生成action的函数,集中管理action
传统实现案例
import { createStore } from "redux";
const initialState = {
balance: 0,
loan: 0,
loanPurpose: "",
};
function reducer(state = initialState, action) {
switch (action.type) {
case "account/deposit":
return {
...state,
balance: state.balance + action.payload,
};
case "account/withdraw":
return {
...state,
balance: state.balance - action.payload,
};
case "account/requestLoan":
if (state.loan > 0) return state;
return {
...state,
loan: action.payload.amount,
loanPurpose: action.payload.loanPurpose,
balance: state.balance + action.payload.amount,
};
case "account/payLoan":
return {
...state,
loan: 0,
loanPurpose: "",
balance: state.balance - state.loan,
};
default:
return state;
}
}
const store = createStore(reducer);
store.dispatch({ type: "account/deposit", payload: 500 });
console.log(store.getState());
这与reducer的实现非常相似
我们不能一直手写类型,这非常不方便。所以要有相关函数来做这部分内容。
import { createStore } from "redux";
const initialState = {
balance: 0,
loan: 0,
loanPurpose: "",
};
function reducer(state = initialState, action) {
switch (action.type) {
case "account/deposit":
return {
...state,
balance: state.balance + action.payload,
};
case "account/withdraw":
return {
...state,
balance: state.balance - action.payload,
};
case "account/requestLoan":
if (state.loan > 0) return state;
return {
...state,
loan: action.payload.amount,
loanPurpose: action.payload.loanPurpose,
balance: state.balance + action.payload.amount,
};
case "account/payLoan":
return {
...state,
loan: 0,
loanPurpose: "",
balance: state.balance - state.loan,
};
default:
return state;
}
}
const store = createStore(reducer);
function deposit(payload) {
return { type: "account/deposit", payload: payload };
}
function withdraw(payload) {
return { type: "account/withdraw", payload: payload };
}
function requestLoan(payload) {
return { type: "account/requestLoan", payload: payload };
}
function payLoan(payload) {
return { type: "account/payLoan" };
}
store.dispatch(deposit(500));
store.dispatch(withdraw(200));
store.dispatch(requestLoan({ amount: 1000, loanPurpose: "Buy a car" }));
store.dispatch(payLoan());
redux中一般store中有多个reducer,我们可以组织代码如下:
accountSlice文件中放置account相关的state:
const initialState = {
balance: 0,
loan: 0,
loanPurpose: "",
};
export default function accountReducer(state = initialState, action) {
switch (action.type) {
case "account/deposit":
return {
...state,
balance: state.balance + action.payload,
};
case "account/withdraw":
return {
...state,
balance: state.balance - action.payload,
};
case "account/requestLoan":
if (state.loan > 0) return state;
return {
...state,
loan: action.payload.amount,
loanPurpose: action.payload.loanPurpose,
balance: state.balance + action.payload.amount,
};
case "account/payLoan":
return {
...state,
loan: 0,
loanPurpose: "",
balance: state.balance - state.loan,
};
default:
return state;
}
}
export function deposit(amount) {
return { type: "account/deposit", payload: amount };
}
export function withdraw(amount) {
return { type: "account/withdraw", payload: amount };
}
export function requestLoan(payload) {
return { type: "account/requestLoan", payload: payload };
}
export function payLoan() {
return { type: "account/payLoan" };
}
customerSlice文件中放置customer相关的state:
const initialCustormerState = {
fullName: "",
nationalID: "",
createdAt: "",
};
export default function customerReducer(state = initialCustormerState, action) {
switch (action.type) {
case "customer/createCustomer":
return {
...state,
fullName: action.payLoad.fullName,
nationalID: action.payLoad.nationalID,
createdAt: action.payLoad.createdAt,
};
case "customer/updateName":
return {
...state,
fullName: action.payLoad.fullName,
};
default:
return state;
}
}
export function createCustomer(fullName, nationalID) {
return {
type: "customer/createCustomer",
payLoad: {
fullName: fullName,
nationalID: nationalID,
createdAt: new Date().toISOString(),
},
};
}
export function updateName(fullName) {
return {
type: "account/updateName",
payLoad: fullName,
};
}
store文件中对reducer进行组合:
import { createStore, combineReducers } from "redux";
import customerReducer from "./features/customer/customerSlice";
import accountReducer from "./features/account/accountSlice";
const rootReducer = combineReducers({
customer: customerReducer,
account: accountReducer,
});
const store = createStore(rootReducer);
export default store;
导入index.js中即可:
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import store from "./store";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
然后将redux与react联系起来,这与contextAPI非常相似,提供一个Provider,并且传入store:
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import { Provider } from "react-redux";
import store from "./store";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
然后各个组件中就可以通过useSelector hook获取store中的state,通过useDispatch hook就可以获取dispatch传递action
import { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { deposit, payLoan, requestLoan, withdraw } from "./accountSlice";
function AccountOperations() {
const [depositAmount, setDepositAmount] = useState("");
const [withdrawalAmount, setWithdrawalAmount] = useState("");
const [loanAmount, setLoanAmount] = useState("");
const [loanPurpose, setLoanPurpose] = useState("");
const [currency, setCurrency] = useState("USD");
const { loan: currentLoan, loanPurpose: currencyLoanPurpose } = useSelector(
(store) => store.account
);
const dispatch = useDispatch();
function handleDeposit() {
if (!depositAmount) return;
dispatch(deposit(depositAmount));
setDepositAmount("");
}
function handleWithdrawal() {
if (!withdrawalAmount) return;
dispatch(withdraw(withdrawalAmount));
setWithdrawalAmount("");
}
function handleRequestLoan() {
if (!loanAmount || !loanPurpose) return;
dispatch(requestLoan(loanAmount, loanPurpose));
setLoanAmount("");
setLoanPurpose("");
}
function handlePayLoan() {
dispatch(payLoan());
}
return (
<div>
<h2>Your account operations</h2>
<div className="inputs">
<div>
<label>Deposit</label>
<input
type="number"
value={depositAmount}
onChange={(e) => setDepositAmount(+e.target.value)}
/>
<select
value={currency}
onChange={(e) => setCurrency(e.target.value)}
>
<option value="USD">US Dollar</option>
<option value="EUR">Euro</option>
<option value="GBP">British Pound</option>
</select>
<button onClick={handleDeposit}>Deposit {depositAmount}</button>
</div>
<div>
<label>Withdraw</label>
<input
type="number"
value={withdrawalAmount}
onChange={(e) => setWithdrawalAmount(+e.target.value)}
/>
<button onClick={handleWithdrawal}>
Withdraw {withdrawalAmount}
</button>
</div>
<div>
<label>Request loan</label>
<input
type="number"
value={loanAmount}
onChange={(e) => setLoanAmount(+e.target.value)}
placeholder="Loan amount"
/>
<input
value={loanPurpose}
onChange={(e) => setLoanPurpose(e.target.value)}
placeholder="Loan purpose"
/>
<button onClick={handleRequestLoan}>Request loan</button>
</div>
{currentLoan > 0 && (
<div>
<span>
Pay back {currentLoan} {currencyLoanPurpose}
</span>
<button onClick={handlePayLoan}>Pay loan</button>
</div>
)}
</div>
</div>
);
}
export default AccountOperations;
middleware 与 redux chunk
由于store本身的局限性无法处理异步情况,所以在dispatch传递时,不直接到reducer中,而是先通过middle ware。而redux chunk就是reudx目前最流行的middle ware第三方库。
首先在store中进行middle ware的配置:
import { createStore, combineReducers, applyMiddleware } from "redux";
import { thunk } from "redux-thunk";
import customerReducer from "./features/customer/customerSlice";
import accountReducer from "./features/account/accountSlice";
const rootReducer = combineReducers({
customer: customerReducer,
account: accountReducer,
});
const store = createStore(rootReducer, applyMiddleware(thunk));
export default store;
然后在action中进行异步情况的处理:
export function deposit(amount, currency) {
if (currency === "USD") return { type: "account/deposit", payload: amount };
return async function (dispatch, getState) {
dispatch({ type: "account/convertingCurrency" });
const res = await fetch(
`https://api.frankfurter.app/latest?amount=${amount}&from=${currency}&to=USD`
);
const data = await res.json();
console.log(data);
const converted_deposit = data.rates.USD;
dispatch({ type: "account/deposit", payload: converted_deposit });
};
}
当我们返回的不是一个对象,而是一个函数时,redux就会意识到这个函数是一个“thunk”,redux就不会马上将其传递到store的reducer中,而是会运行这个函数“thunk”,并将dispatch和getState作为函数参数传递到函数“thunk”中。
redux toolkit实现案例
accountSlice如下:
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
balance: 0,
loan: 0,
loanPurpose: "",
isLoading: false,
};
const accountSlice = createSlice({
name: "account",
initialState,
reducers: {
deposit(state, action) {
state.balance = state.balance + action.payload;
state.isLoading = false;
},
withdraw(state, action) {
state.balance = state.balance - action.payload;
},
requestLoan: {
prepare(amount, purpose) {
return {
payload: { amount, purpose },
};
},
reducer(state, action) {
if (state.loan > 0) return;
state.loan = action.payload.amount;
state.loanPurpose = action.payload.loanPurpose;
state.balance = state.balance + action.payload.amount;
},
},
payLoan(state, action) {
state.loan = 0;
state.loanPurpose = "";
state.balance = state.balance - state.loan;
},
convertingCurrency(state) {
state.isLoading = true;
},
},
});
export const { withdraw, requestLoan, payLoan } = accountSlice.actions;
export default accountSlice.reducer;
export function deposit(amount, currency) {
if (currency === "USD") return { type: "account/deposit", payload: amount };
return async function (dispatch, getState) {
dispatch({ type: "account/convertingCurrency" });
const res = await fetch(
`https://api.frankfurter.app/latest?amount=${amount}&from=${currency}&to=USD`
);
const data = await res.json();
const converted_deposit = data.rates.USD;
dispatch({ type: "account/deposit", payload: converted_deposit });
};
}
custom如下:
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
fullName: "",
nationalID: "",
createdAt: "",
};
const customerSlice = createSlice({
name: "customer",
initialState,
reducers: {
createCustomer: {
prepare(fullName, nationalID, createAt) {
return {
payload: {
fullName,
nationalID,
createAt,
},
};
},
reducer(state, action) {
state.fullName = action.payload.fullName;
state.nationalID = action.payload.nationalID;
state.createdAt = action.payload.createdAt;
},
},
updateName(state, action) {
state.fullName = action.payload;
},
},
});
export default customerSlice.reducer;
export const { createCustomer, updateName } = customerSlice.actions;
有如下好处:
注意:其中chunk部分仍然是我们自己实现的 ;export时reducer和actions可以直接借助slice导出了。
store.js如下所示:
import { configureStore } from "@reduxjs/toolkit";
import customerReducer from "./features/customer/customerSlice";
import accountReducer from "./features/account/accountSlice";
const store = configureStore({
reducer: {
customer: customerReducer,
account: accountReducer,
},
});
export default store;