Just Redux:完整指南
封面图片由Josh Weirick在Unsplash上提供。
嘿!👋
如果您是前端开发人员或渴望成为前端开发人员,我相信您现在可能已经遇到过 Redux。
你可能知道 Redux 是什么,也可能不知道。也许你已经用了很久了,但还没完全理解。你新建一个项目,然后从别处复制一堆东西,然后就设置好了。说实话,我以前也这么做过。我对 Redux 的所有内容以及需要哪些准备工作都略知一二。到目前为止,这种方法对我来说还算有效,但有时也会遇到一些需要更多知识才能理解的问题。
🙇♂️ 因此,我决定研究一下 Redux API。我看了很多在线视频,也阅读了文档。同时,我还写了这篇文章。
🤯 令我惊讶的是,我发现我们在 Redux 中做的 80% 到 90% 的事情都只是简单的 JS。它们只是对象和函数。如果你觉得 Redux 很复杂,你可能需要回到 JS 基础。但如果你在 JS 方面很有信心,那么 Redux 并不难。
⚠️ 在开始之前,我想声明一下,本文仅涵盖 Redux。本文不涉及 React 或任何其他框架,以及它们与 Redux 的交互。
👨💻 为了充分利用本文,您可以跟着一起写代码。我为我们将要讨论的所有内容都添加了代码片段。
🤨什么是 Redux?
好吧,如果你点开了这篇文章,我敢肯定你已经知道答案了。但为了解答这个问题,我们还是来做个简单的介绍吧。
Redux 是一个状态管理库。它存储应用程序的状态并提供与该状态交互的方法。它可以与任何框架(例如 React、Angular、Vue 等)一起使用。
安装
npm install redux
对于本文,我们只需要安装redux
,不需要其他任何东西。
Redux API 表面仅带有 5 种方法。
我们将详细研究其中的每一个。
👉compose
这个方法和 Redux 根本没关系,它的目的是把多个函数捆绑成一个。
假设我们有 3 个数学函数:half
、square
和double
。
如果我们想要按顺序应用所有这三种操作,我们需要执行以下操作:
const double = (num) => num * 2;
const square = (num) => num * num;
const half = (num) => num / 2;
const halfSquareDouble = (num) => half(square(double(num)));
console.log(halfSquareDouble(2)); // 8
但我们可以使用更简洁的方式实现同样的目的compose
:
import { compose } from "redux";
const double = (num) => num * 2;
const square = (num) => num * num;
const half = (num) => num / 2;
const halfSquareDouble = compose(half, square, double);
console.log(halfSquareDouble(2)); // 8
compose
将把我们所有的功能组合成一个功能。
🧪注意: compose
将从右端开始拾取函数。这意味着,如果顺序为compose(half, double, square)
4,则相同调用的结果将为 4。
👉createStore
此方法创建 Redux 存储。它接受一个必填参数reducer
,以及两个可选参数 - preloadedState
(也称为initialState
)和enhancer
。
那么,什么是 Reducer?简单来说,Reducer 只是一个纯函数,它接受两个参数 -state
和 ,action
并返回一个值,即 new state
。
可以这样理解:存在一个完美的世界/模拟,它存在于某个state
X 中。某些事情发生了;某些事情action
被执行了。我们不关心这些行为发生在哪里,也不关心谁是罪魁祸首。我们只知道发生了某些事情,并且这可能会改变我们世界的状态。reducer 的工作就是找出新的state
Y。
const reducer = (state, action) => {
return state
}
这是您可以创建的最简单的减速器。
当我们调用createStore
方法时,它会返回一个对象。
import { createStore } from 'redux'
const reducer = (state, action) => {
return state
}
const initialState = { value: 0 }
const store = createStore(reducer, initialState)
该对象有 4 种方法:
1️⃣ getState
:此方法用于获取应用程序的状态。
console.log(store.getState()) // { value: 0 }
2️⃣ subscribe
:此方法用于订阅我们商店的更改。将一个函数传递给此方法,它将在状态更改时被调用。
store.subscribe(() => console.log("State changed!"))
3️⃣ dispatch
:此方法用于调度操作。操作会根据应用的当前状态进入 Reducer,并可能更新状态。
🏌️♂️ 我们在这里又引入了一个术语 - action
,让我们来讨论一下它。
如果你还记得,Reducer 会执行 action 来更新状态。action 会告诉 Reducer 刚刚发生了某件事。可以是用户点击按钮、用户登录、用户添加产品等等。任何旨在改变应用状态的操作都属于 action。
当然,我们完全掌控它们。我们才是定义它们的人。那么如何创建它们呢?嗯,你应该遵循一种特定的风格。
const incrementAction = {
type: 'INCREMENT'
}
动作本质上是带有type
键的对象。就是这样。它也可以有其他键,但type
键是必需的。
现在让我们重构我们的 reducer 来利用这个动作。
const reducer = (state = initialState, action) => {
if (action.type === 'INCREMENT') {
return { value: state.value + 1 }
}
return state
}
在第一行,我们添加了intialState
一个默认参数。通过这样做,我们可以将其从createStore()
调用中移除。这实际上是一种最佳实践。
在第 2 行,我们检查收到的操作是否属于类型INCREMENT
。
在第 3 行,我们正在准备新的状态。这一点很重要。切勿直接修改状态。始终返回一个新创建的对象。如果不这样做,对状态对象的引用将不会改变,您的应用也不会收到有关更改的通知。
state.value++ // 🙅♂️ DON'T DO THIS
return { value: state.value + 1 } // 👍 WORKS FINE
在第 4 行,我们最终返回了旧状态,以防我们找不到匹配的 action。这一点也很重要。你的 Reducer 应该始终返回一个 state。
现在,我们的 reducer 已经更新,让我们来发送一个动作。
import { createStore } from "redux";
const initialState = { value: 0 };
const incrementAction = {
type: "INCREMENT"
};
const reducer = (state = initialState, action) => {
if (action.type === "INCREMENT") {
return { value: state.value + 1 };
}
return state;
};
const store = createStore(reducer);
console.log(store.getState()); // { value: 0 }
store.dispatch(incrementAction);
console.log(store.getState()); // { value: 1 }
如果我们想增加 5 怎么办?我现在做不到。但仔细想想,到目前为止我们写的都是一些基本的 JavaScript 代码,你可能已经了解了。我们可以稍微扩展一下代码,实现目标。
还记得动作可以有其他按键吗?我们将再创建一个动作。
import { createStore } from "redux";
const initialState = { value: 0 };
const incrementAction = {
type: "INCREMENT"
};
const addAction = {
type: "ADD",
payload: 5,
}
const reducer = (state = initialState, action) => {
if (action.type === "INCREMENT") {
return { value: state.value + 1 };
}
if (action.type === "ADD") {
return { value: state.value + action.payload }
}
return state;
};
const store = createStore(reducer);
store.dispatch(addAction)
console.log(store.getState()) // { value: 5 }
好的!到目前为止一切都很好。但是 5 还不够,我们再创建一个 10 的,然后再创建一个 100 的?感觉很蠢!我们不可能覆盖所有数字。
好的!如果我们这样做会怎么样?
store.dispatch({ type: "ADD", payload: 5 })
store.dispatch({ type: "ADD", payload: 10 })
store.dispatch({ type: "ADD", payload: 100 })
是的!这确实完成了任务,但可扩展性不强。如果之后我们决定用INCREASE_BY
而不是 来调用它ADD
,那么我们就必须在所有地方都更新它。而且,我们有可能创建了一个类型,结果却写成了INCRAESE_BY
。祝你好运,别再找错字了!😬
有一种优雅的方法可以解决这个问题:使用 Action Creators。
🤖 动作创建器只是为您创建动作的功能。
const add = (number) => {
return {
type: "ADD",
payload: number
}
}
store.dispatch(add(5))
store.dispatch(add(10))
store.dispatch(add(100))
我们创建了一个add
返回 action 对象的函数。我们可以在任何地方调用它,它会为我们创建一个 action 对象。
该解决方案更加清洁并且被广泛使用。
我们更新后的代码现在如下所示:
import { createStore } from "redux";
const initialState = { value: 0 };
// constants
const INCREMENT = "INCREMENT";
const ADD = "ADD";
// action creators
const increment = () => ({ type: INCREMENT });
const add = (number) => ({ type: ADD, payload: number });
const reducer = (state = initialState, action) => {
if (action.type === INCREMENT) {
return { value: state.value + 1 };
}
if (action.type === ADD) {
return { value: state.value + action.payload };
}
return state;
};
const store = createStore(reducer);
console.log(store.getState()); // { value: 0 }
store.dispatch(increment());
store.dispatch(add(2));
console.log(store.getState()); // { value: 3 }
注意,我们将"INCREMENT"
和存储"ADD"
为常量。这是因为我们在 Reducer 中重复使用了它们,因此存在拼写错误的风险。将 Action 类型存储为常量并集中存放是一个好习惯。
🎉 如果你已经读到这里,恭喜你!凭借你目前掌握的所有知识,你可以开始使用 Redux 创建应用了。当然,还有很多内容需要学习,但你已经掌握了相当一部分 API。干得漂亮!
4️⃣ replaceReducer
:此方法用于将当前的根 Reducer 函数替换为新的。调用此方法将更改内部 Reducer 函数的引用。当你为了提高性能而拆分代码时,这会发挥作用。
const newRootReducer = combineReducers({
existingSlice: existingSliceReducer,
newSlice: newSliceReducer
});
store.replaceReducer(newRootReducer);
👉bindActionCreators
现在我们对动作创建者和调度有了一些了解,我们可以讨论这种方法了。
dispatch(increment())
dispatch(add(5))
到目前为止,这就是我们调度操作的方式。但还有一种更简单的方法。
const actions = bindActionCreators({ add, increment }, store.dispatch)
actions.increment()
actions.add(4)
bindActionCreators
接受两个参数:
- 一个包含所有动作创建者的对象。
- 我们想要将我们的动作创建者绑定到的方法。
它返回一个对象,看起来与我们传入的第一个参数相同。唯一的区别是,现在我们可以直接调用这些方法,而无需明确调用 dispatch。
这样做有什么好处?
bindActionCreators 的唯一用例是当你想将一些 action creators 传递给一个不知道 Redux 的组件,并且你不想将 dispatch 或 Redux store 传递给它。 - Redux 文档
另外,请注意,我们所做的只是普通的 JS,我们可以通过编写自己的函数将动作创建者绑定到 dispatch; 来实现相同的结果,而无需调用bindActionCreators
。
👉combineReducers
当你开发一个需要隔离数据的大型应用时,使用多个 Reducer 来降低复杂性是合理的。此方法会将所有这些小型 Reducer 组合起来,并返回一个可供我们createStore
方法使用的 Reducer,通常称为根 Reducer。
首先,我们来看看为什么我们需要多个 Reducer。请考虑以下代码。
import { createStore } from "redux";
// constants
const CHANGE_USER_EMAIL = "CHANGE_USER_EMAIL";
const ADD_PRODUCT = "ADD_PRODUCT";
// action creators
const changeUserEmail = (email) => ({
type: CHANGE_USER_EMAIL,
payload: { email }
});
const addProduct = (product) => ({
type: ADD_PRODUCT,
payload: { product }
});
const initialState = {
user: {
name: "Mark",
email: "mark@facebook.com"
},
cart: {
products: []
}
};
const reducer = (state = initialState, action) => {
if (action.type === CHANGE_USER_EMAIL) {
return {
...state,
user: {
...state.user,
email: action.payload.email
}
};
}
if (action.type === ADD_PRODUCT) {
return {
...state,
cart: {
...state.cart,
products: [...state.cart.products, action.payload.product]
}
};
}
return state;
};
const store = createStore(reducer);
console.log(store.getState());
// { user: { name: 'Mark', email: 'mark@facebook.com' }, cart: { products: [] } }
store.dispatch(changeUserEmail("mark@instagram.com"));
console.log(store.getState());
// { user: { name: 'Mark', email: 'mark@instagram.com' }, cart: { products: [] } }
我们可以看到,这个 Reducer 已经看起来有点复杂了。随着应用规模的增长,数据嵌套的层次会越来越深,Reducer 的大小也会随之增大。
仔细想想,user
和cart
是两个完全不同的数据点。我们可以将它们拆分到两个不同的 Reducer 中。我们开始吧。
const initialState = {
user: {
name: "Mark",
email: "mark@facebook.com"
},
cart: {
products: []
}
};
const userReducer = (user = initialState.user, action) => {
if (action.type === CHANGE_USER_EMAIL) {
return {
...user,
email: action.payload.email
};
}
return user;
}
const cartReducer = (cart = initialState.cart, action) => {
if (action.type === ADD_PRODUCT) {
return {
...cart,
products: [...cart.products, action.payload.product]
};
}
return cart;
}
现在我们有两个简单的 Reducer,代码看起来很简洁。但是createStore
只接受一个 Reducer,我们应该传递哪一个呢?
两者皆可。使用combineReducers
。
const rootReducer = combineReducers({
user: userReducer,
cart: cartReducer
});
const store = createStore(rootReducer);
此方法接受一个对象,其中的键可以是任何值,但值必须是我们的 Reducer。它将返回一个可以传递给 的 Reducer createStore
。
我们的完整代码现在看起来像这样。
import { combineReducers, createStore } from "redux";
// constants
const CHANGE_USER_EMAIL = "CHANGE_USER_EMAIL";
const ADD_PRODUCT = "ADD_PRODUCT";
// action creators
const changeUserEmail = (email) => ({
type: CHANGE_USER_EMAIL,
payload: { email }
});
const addProduct = (product) => ({
type: ADD_PRODUCT,
payload: { product }
});
const initialState = {
user: {
name: "Mark",
email: "mark@facebook.com"
},
cart: {
products: []
}
};
const userReducer = (user = initialState.user, action) => {
if (action.type === CHANGE_USER_EMAIL) {
return {
...user,
email: action.payload.email
};
}
return user;
};
const cartReducer = (cart = initialState.cart, action) => {
if (action.type === ADD_PRODUCT) {
return {
...cart,
products: [...cart.products, action.payload.product]
};
}
return cart;
};
const rootReducer = combineReducers({
user: userReducer,
cart: cartReducer
});
const store = createStore(rootReducer);
console.log(store.getState());
// { user: { name: 'Mark', email: 'mark@facebook.com' }, cart: { products: [] } }
store.dispatch(changeUserEmail("mark@instagram.com"));
console.log(store.getState());
// { user: { name: 'Mark', email: 'mark@instagram.com' }, cart: { products: [] } }
🌸 商店增强功能
如果你还记得的话,createStore
它接受一个可选参数 - enhancers
。
增强器其实就是高阶函数。它们为我们的 store 添加了一些额外的功能。例如,Redux dev tools 就是一个增强器。
我们这里不会过多讨论增强器,因为我们很少会创建新的增强器。我们会在另一篇文章中详细讨论这个问题。
⛓ 中间件
中间件使我们能够拦截操作,并在操作到达 Reducer 之前执行我们想要的操作。我们可以记录操作、记录存储状态、记录崩溃报告等等。
让我们创建一个中间件,用于在分派动作时记录动作。
const logger = (store) => (next) => (action) => {
console.log("DISPATCHED ACTION: ", action);
next(action);
}
这是您可以创建的最简单的中间件之一。它记录操作,然后将调用转发给管道中的其他中间件和 Reducer。
但是我们如何使用我们新创建的中间件?
👉applyMiddleware
此方法将接受一组中间件并返回一个增强器。增强器会进入createStore
函数调用。
import { applyMiddleware, createStore } from 'redux'
const logger = (store) => (next) => (action) => {
console.log("DISPATCHED ACTION: ", action);
next(action);
}
const store = createStore(rootReducer, applyMiddleware(logger));
现在,每次我们发送一个动作时,我们的控制台中都会有一个日志。
🎉 至此,我们讲解了 Redux 中的 final 方法。Redux API 的全部内容都讲完了。
✨ 如果不告诉大家现代 Redux 与本文讨论的不同,那就太不公平了。基本概念仍然适用,但代码量会减少。感谢 Redux 团队。🙏 想了解吗?点击这里。
💡 我知道你现在还无法掌握 100% 的知识,但了解你掌握的所有工具总是好的。
💾 这篇文章可以作为你在面试前或实施过程中复习某些内容的指南。保存起来!
🤝 如果您想阅读我即将发表的更多文章,您可以通过LinkedIn或Twitter与我联系。
🙏 感谢您的阅读!
文章来源:https://dev.to/thesanjeevsharma/just-redux-the-complete-guide-44d5