当您学习React时,几乎总是会听到人们说Redux很棒,并且您应该尝试一下。 React生态系统正在Swift发展,有太多库可以与React挂钩,例如流,redux,中间件,mobx等。
学习React很容易,但是要习惯整个React生态系统需要花费时间。 本教程是对React生态系统不可或缺的组件之一Redux的介绍。
基本非Redux术语
以下是一些您可能不熟悉的常用术语,但它们本身并不是Redux特有的。 您可以浏览本节,如果/如果没有任何意义,请回到此处。
纯功能
纯函数只是一个普通函数,它必须满足两个附加约束:
- 给定一组输入,该函数应始终返回相同的输出。
- 它不会产生副作用。
例如,这是一个纯函数,它返回两个数字的和。
/* Pure add function */
const add = (x,y) => {
return x+y;
}
console.log(add(2,3)) //5
纯函数给出可预测的结果并且是确定性的。 当函数执行除计算其返回值以外的任何操作时,都会变得不纯。
例如,下面的add函数使用全局状态来计算其输出。 此外,该功能还将值记录到控制台,这被认为是副作用。
const y = 10;
const impureAdd = (x) => {
console.log(`The inputs are ${x} and ${y}`);
return x+y;
}
可观察到的副作用
“可观察到的副作用”是一个功能与外界的交互作用的花哨术语。 如果函数试图将值写入到函数外部存在的变量中或试图调用外部方法,则可以放心地调用这些方法带来的副作用。
但是,如果一个纯函数调用另一个纯函数,则该函数可以视为纯函数。 以下是一些常见的副作用:
- 进行API调用
- 记录到控制台或打印数据
- 变异数据
- DOM操作
- 检索当前时间
容器和演示组件
在使用React应用程序时,将组件架构分为两部分非常有用。 您可以将它们大致分为两类:容器组件和表示组件。 它们也被普遍称为智能和哑组件。
容器组件与事物的工作方式有关,而呈现组件与事物的外观有关。 为了更好地理解这些概念,我在另一篇教程中进行了介绍 : React中的容器与演示组件 。
可变对象与不可变对象
可变对象可以定义如下:
可变对象是一种对象,其状态在创建后即可修改。
不变性恰好相反—不变对象是指其状态在创建后无法修改的对象。 在JavaScript中,字符串和数字是不可变的,但是对象和数组不是。 该示例更好地说明了差异。
/*Strings and numbers are immutable */
let a = 10;
let b = a;
b = 3;
console.log(`a = ${a} and b = ${b} `); //a = 10 and b = 3
/* But objects and arrays are not */
/*Let's start with objects */
let user = {
name: "Bob",
age: 22,
job: "None"
}
active_user = user;
active_user.name = "Tim";
//Both the objects have the same value
console.log(active_user); // {"name":"Tim","age":22,"job":"None"}
console.log(user); // {"name":"Tim","age":22,"job":"None"}
/* Now for arrays */
let usersId = [1,2,3,4,5]
let usersIdDup = usersId;
usersIdDup.pop();
console.log(usersIdDup); //[1,2,3,4]
console.log(usersId); //[1,2,3,4]
若要使对象不可变,请使用Object.assign
方法创建一个新方法或全新的散布运算符。
let user = {
name: "Bob",
age: 22,
job: "None"
}
active_user = Object.assign({}, user, {name:"Tim"})
console.log(user); //{"name":"Bob","age":22,"job":"None"}
console.log(active_user); //{"name":"Tim","age":22,"job":"None"}
什么是Redux?
官方页面对Redux的定义如下:
Redux是JavaScript应用程序的可预测状态容器。
尽管这可以准确地描述Redux,但是当您第一次看到Redux的大图时,很容易迷路。 它有很多移动部件,您需要将它们组合在一起。 但是一旦您做到了,我向您保证,您将开始爱Redux。
Redux是一个状态管理库,您可以将其连接到任何JavaScript库,而不仅仅是React。 但是,由于React的功能特性,它与React一起工作得很好。 为了更好地理解这一点,让我们看一下状态。
![状态的任何更改都将重新呈现组件,并且任何用户交互都会更新状态](https://i-blog.csdnimg.cn/blog_migrate/9e178df9de91c17406cb3d5a706f7c46.png)
如您所见,组件的状态决定了要渲染的内容及其行为方式。 该应用程序具有初始状态,并且任何用户交互都会触发更新状态的操作。 状态更新后,页面将重新呈现。
使用React,每个组件都有一个本地状态,可以从该组件内部进行访问,也可以将它们作为道具传递给子组件。 我们通常使用状态来存储:
- UI状态和过渡数据。 这包括用于导航菜单或受控组件中的表单输入的UI元素列表。
- 应用程序状态,例如从服务器获取的数据,用户的登录状态等。
当您有一个带有几个组件的基本React应用程序时,以组件状态存储应用程序数据是可以的。
![有几个组件的React Project](https://i-blog.csdnimg.cn/blog_migrate/a8d8e6f37cc58f0e6a802a65c4f2feff.png)
但是,大多数现实应用都将具有更多功能和组件。 当组件层次结构中的级别数增加时,状态管理就会成问题。
![使用React的聊天应用程序的组件层次结构](https://i-blog.csdnimg.cn/blog_migrate/7242826859cbe13f630301f590a99467.png)
为什么要使用Redux?
在使用React时,可能会遇到一个非常可能的场景。
- 您正在构建一个中型应用程序,并且您的组件被巧妙地拆分为智能组件和哑组件。
- 智能组件处理状态,然后将其传递给哑组件。 他们负责进行API调用,从数据源获取数据,处理数据,然后设置状态。 哑组件接收道具并返回UI表示形式。
- 当您要编写一个新组件时,并不总是很清楚将状态放置在何处。 您可以让状态成为表示组件的直接父容器的一部分。 更好的是,您可以将状态在层次结构中向上移动,以便多个表示组件可以访问该状态。
- 当应用程序增长时,您会看到状态分散在各处。 当组件需要访问它无法立即访问的状态时,您将尝试将状态提升到最接近的组件祖先。
- 经过不断的重构和清理后,您最终将大部分状态保存在组件层次结构的顶部。
- 最后,您决定让顶部的组件全局处理状态,然后将所有内容传递给下一个是一个好主意。 其他每个组件都可以订阅所需的道具,而忽略其余的道具。
这就是我亲身体验过的React,其他许多开发人员也会同意。 React是一个视图库,专门管理状态不是React的工作。 我们正在寻找的是关注点分离原则。
Redux帮助您将应用程序状态与React分开。 Redux创建一个全局存储,该存储位于应用程序的顶层,并将状态馈送到所有其他组件。 与Flux不同,Redux没有多个存储对象。 应用程序的整个状态都在该存储对象中,并且您可以将视图层与存储完整的另一个库交换。
每次更新商店时,组件都会重新渲染,而对性能的影响很小。 这是个好消息,并且带来了很多好处。 您可以将所有React组件视为愚蠢的组件,而React只能专注于事物的视图方面。
现在我们知道了Redux为何有用,让我们深入研究Redux架构。
Redux架构
在学习Redux时,您需要习惯一些核心概念。 下图描述了Redux架构以及所有事物如何连接在一起。
![Redux入门Redux架构](https://i-blog.csdnimg.cn/blog_migrate/4cab999b096f87236f44e1b010a6b614.png)
如果您习惯了Flux,其中一些元素可能看起来很熟悉。 如果没有,那也没关系,因为我们将从基础上介绍所有内容。 首先,请确保已安装Redux:
npm install redux
使用create-react-app或您喜欢的webpack配置来设置开发服务器。 由于Redux是独立的状态管理,因此我们不会插入React。 因此,删除index.js的内容,在本教程的其余部分中,我们将使用Redux。
商店
商店是一个很大JavaScript对象,具有成对的键-值对,代表应用程序的当前状态。 与React中分散在不同组件上的状态对象不同,我们只有一个商店。 存储提供应用程序状态,并且每当状态更新时,视图就会重新显示。
但是, 您永远不能更改或更改商店。 而是,您创建商店的新版本。
(previousState, action) => newState
因此,从浏览器启动应用程序起,您就可以在所有状态下进行时间旅行。
该商店有三种与架构其余部分通信的方法。 他们是:
- Store.getState()-访问应用程序的当前状态树。
- Store.dispatch(action)-根据操作触发状态更改。 有关以下操作的更多信息。
- Store.subscribe(listener)-收听状态的任何更改。 每次分派动作时都会调用它。
让我们创建一个商店。 Redux具有createStore
方法来创建新商店。 您需要给它传递一个减速器,尽管我们不知道那是什么。 因此,我将创建一个称为reducer的函数。 您可以选择指定第二个参数来设置商店的初始状态。
src / index.js
import { createStore } from "redux";
// This is the reducer
const reducer = () => {
/*Something goes here */
}
//initialState is optional.
//For this demo, I am using a counter, but usually state is an object
const initialState = 0
const store = createStore(reducer, initialState);
现在,我们将监听商店中的所有更改,然后使用console.log()
查看商店的当前状态。
store.subscribe( () => {
console.log("State has changed" + store.getState());
})
那么我们如何更新商店? Redux有一些称为操作的东西可以使这种情况发生。
动作/动作创作者
动作也是普通JavaScript对象,可将信息从您的应用程序发送到商店。 如果您有一个带有增量按钮的非常简单的计数器,则按下它将导致触发如下所示的操作:
{
type: "INCREMENT",
payload: 1
}
它们是商店的唯一信息来源。 存储的状态仅响应于动作而改变。 每个动作应具有描述动作对象打算做什么的type属性。 除此之外,动作的结构完全取决于您。 但是,请使操作保持较小,因为操作表示转换应用程序状态所需的最少信息量。
例如,在上面的示例中,类型属性设置为“ INCREMENT”,并且包括一个附加的有效负载属性。 您可以将有效负载属性重命名为更有意义的名称,或者在我们的情况下,将其完全省略。 您可以像这样向商店发送操作。
store.dispatch({type: "INCREMENT", payload: 1});
在对Redux进行编码时,通常不会直接使用操作。 相反,您将调用返回操作的函数,这些函数通常被称为操作创建者。 这是我们前面讨论的增量操作的操作创建者。
const incrementCount = (count) => {
return {
type: "INCREMENT",
payload: count
}
}
因此,要更新计数器的状态,您将需要分派incrementCount
操作,如下所示:
store.dispatch(incrementCount(1));
store.dispatch(incrementCount(1));
store.dispatch(incrementCount(1));
如果您转到浏览器控制台,您会看到它正在部分工作。 我们尚未定义,因为我们尚未定义减速器。
![浏览器控制台为getState返回未定义](https://i-blog.csdnimg.cn/blog_migrate/3de850998336c2d494954f2c86f6ae45.png)
现在,我们讨论了动作和存储。 但是,我们需要一种机制来转换动作提供的信息并转换商店的状态。 减速器用于此目的。
减速器
一个动作描述了问题,减速器负责解决问题。 在前面的示例中, incrementCount
方法返回一个操作,该操作提供了有关我们要对该状态进行的更改类型的信息。 减速器使用此信息来实际更新状态。 使用Redux时,您应该始终牢记文档中的一个重点:
给定相同的参数,Reducer应该计算下一个状态并将其返回。 没什么好奇怪的 没有副作用。 没有API调用。 没有突变。 只是计算而已。
这意味着减速器应该是纯函数。 给定一组输入,它应始终返回相同的输出。 除此之外,它不应做任何其他事情。 同样,reduce并不是发生副作用的地方,例如进行AJAX调用或从API提取数据。
让我们为柜台填写减速器。
// This is the reducer
const reducer = (state = initialState, action) => {
switch (action.type) {
case "INCREMENT":
return state + action.payload
default:
return state
}
}
减速器接受两个参数(状态和动作),并返回一个新状态。
(previousState, action) => newState
该状态接受默认值initialState
,仅当状态值未定义时才使用。 否则,将保留状态的实际值。 我们使用switch语句选择正确的操作。 刷新浏览器,一切正常。
让我们为DECREMENT
添加一个大小写,如果没有大小写,计数器将不完整。
// This is the reducer
const reducer = (state = initialState, action) => {
switch (action.type) {
case "INCREMENT":
return state + action.payload
case "DECREMENT":
return state - action.payload
default:
return state
}
}
这是动作创建者。
const decrementCount = (count) => {
return {
type: "DECREMENT",
payload: count
}
}
最后,将其发送到商店。
store.dispatch(incrementCount(4)); //4
store.dispatch(decrementCount(2)); //2
而已!
摘要
本教程旨在作为使用Redux管理状态的起点。 我们已经介绍了了解Redux基本概念所必需的所有内容,例如存储,操作和reducer。 在本教程结束时,我们还创建了一个工作正常的redux演示计数器。 尽管数量不多,但我们了解了难题的各个部分如何组合在一起。
在过去的几年中,React越来越受欢迎。 实际上,我们在市场上有许多商品可供购买,查看,实施等。 如果您正在寻找有关React的其他资源,请随时检查 。
在下一个教程中,我们将利用我们在这里学到的东西来使用Redux创建一个React应用程序。 敬请期待。 在评论区分享你的观点。
翻译自: https://code.tutsplus.com/tutorials/getting-started-with-redux-why-redux--cms-30349