• 日常搜索
  • 百度一下
  • Google
  • 在线工具
  • 搜转载

Redux入门:为什么选择Redux?

当你学习 react 时,你几乎总是会听到人们说 Redux 有多棒,你应该试一试。React 生态系统正在快速发展,有很多库可以与 React 挂钩,例如 flow、redux、middlewares、mobx 等。 

学习 React 很容易,但要适应整个 React 生态系统需要时间。本教程介绍了 React 生态系统不可或缺的组成部分之一——Redux。

基本的非 Redux 术语

以下是一些您可能不熟悉的常用术语,但它们并不特定于 Redux 本身。您可以浏览此部分并在/如果某些内容没有意义时返回此处。  

纯函数

纯函数只是一个普通函数,它必须满足两个额外的约束: 

  1. 给定一组输入,该函数应始终返回相同的输出。 

  2. 它不会产生副作用。

例如,这是一个返回两个数字之和的纯函数。

/* 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 配合得很好。为了更好地理解这一点,让我们看一下状态。


Redux入门:为什么选择Redux?  第1张

如您所见,组件的状态决定了渲染的内容及其行为方式。应用程序具有初始状态,任何用户交互都会触发更新状态的操作。当状态更新时,页面会重新呈现。

使用 React,每个组件都有一个可以从组件内部访问的本地状态,或者您可以将它们作为 props 传递给子组件。我们通常使用状态来存储:

  1. UI 状态和过渡数据。这包括用于导航菜单或受控组件中的表单输入的 UI 元素列表。

  2. 应用程序状态,例如服务器获取的数据、用户的登录状态等。

当你有一个包含几个组件的基本 React 应用程序时,将应用程序数据存储在组件的状态中是可以的。 

Redux入门:为什么选择Redux?  第2张基本应用程序的组件层次结构

但是,大多数现实生活中的应用程序将具有更多的功能和组件。当组件层次结构中的级别数量增加时,管理状态就会出现问题。 

Redux入门:为什么选择Redux?  第3张中型应用程序的 草图

为什么要使用 Redux?

这是您在使用 React 时可能会遇到的一个非常可能的场景。

  1. 您正在构建一个中型应用程序,并且您将组件整齐地拆分为智能组件和哑组件。 

  2. 智能组件处理状态,然后将它们传递给哑组件。他们负责进行 API 调用,从数据源获取数据,处理数据,然后设置状态。哑组件接收道具并返回 UI 表示。 

  3. 当您要编写一个新组件时,并不总是清楚将状态放置在哪里。您可以让状态成为表示组件的直接父级容器的一部分。更好的是,您可以将状态在层次结构中向上移动,以便多个表示组件可以访问该状态。

  4. 当应用程序增长时,您会看到状态分散在各处。当组件需要访问它无法立即访问的状态时,您将尝试将状态提升到最近的组件祖先。 

  5. 在不断地重构和清理之后,您最终会在组件层次结构的顶部获得大部分状态保存位置。 

  6. 最后,您决定让顶部的组件全局处理状态,然后将所有内容向下传递是个好主意。每个其他组件都可以订阅他们需要的道具,而忽略其余的。

这是我个人使用 React 的经验,许多其他开发人员都会同意。React 是一个视图库,专门管理状态不是 React 的工作。我们正在寻找的是关注点分离原则。 

Redux 帮助您将应用程序状态与 React 分离。Redux 创建一个全局存储,它驻留在应用程序的顶层,并将状态提供给所有其他组件。与 Flux 不同,Redux 没有多个存储对象。应用程序的整个状态都在该 store 对象中,您可以将视图层与另一个库交换,而 store 则完好无损。

每次更新商店时组件都会重新渲染,对性能的影响很小。这是个好消息,同时也带来了很多好处。你可以把所有的 React 组件都当成哑巴,而 React 可以只关注事物的视图方面。

现在我们知道了 Redux 为什么有用,让我们深入了解 Redux 架构。

Redux 架构

在学习 Redux 时,您需要习惯一些核心概念。下图描述了 Redux 架构以及一切如何连接在一起。 

Redux入门:为什么选择Redux?  第4张简而言之 Redux

如果您习惯使用 Flux,则其中一些元素可能看起来很熟悉。如果没有,那也没关系,因为我们将从基础开始涵盖所有内容。首先,确保你已经安装了 redux:

npm install redux

使用create-react-app或您喜欢的webpack配置来设置开发服务器。由于 Redux 是一个独立的状态管理,我们暂时不打算插入 React。所以删除 index.js 的内容,我们将在本教程的其余部分使用 Redux。

店铺

store 是一个大型的 JavaScript 对象,它有大量的键值对代表应用程序的当前状态。与分散在不同组件中的 React 中的状态对象不同,我们只有一个商店。商店提供应用程序状态,每次状态更新时,视图都会重新呈现。 

但是,您永远不能改变或更改存储。 相反,您创建商店的新版本。 

(previousState, action) => newState

因此,您可以在浏览器上启动应用程序后的所有状态中进行时间旅行。

商店有三种方法与架构的其余部分进行通信。他们是:

  • Store.getState()——访问应用程序的当前状态树。 

  • Store.dispatch(action)——基于动作触发状态改变。有关以下操作的更多信息。

  • Store.subscribe(listener)——监听状态的任何变化。每次调度动作时都会调用它。

让我们创建一个商店。Redux 有一个createStore创建新商店的方法。你需要给它传递一个 reducer,虽然我们不知道那是什么。所以我将创建一个名为 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);

现在我们要监听 store 中的任何变化,然后是 storeconsole.log()的当前状态。

store.subscribe( () => {
    console.log("State has changed"  + store.getState());
})

那么我们如何更新商店呢?Redux 有一种叫做动作的东西可以实现这一点。

动作/动作创作者

操作也是纯 JavaScript 对象,可将信息从您的应用程序发送到商店。如果你有一个带有增量按钮的非常简单的计数器,按下它会触发一个动作,如下所示:

{
  type: "INCREMENT",
  payload: 1
}

它们是商店的唯一信息来源。商店的状态仅在响应操作时更改。每个动作都应该有一个类型属性来描述动作对象打算做什么。除此之外,动作的结构完全取决于你。但是,请尽量减少您的操作,因为操作表示转换应用程序状态所需的最少信息量。 

例如,在上面的示例中,type 属性设置为“INCREMENT”,并且包含了一个额外的 payload 属性。您可以将有效负载属性重命名为更有意义的名称,或者在我们的示例中完全省略它。您可以像这样向商店发送操作。

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));

如果您前往浏览器控制台,您会看到它部分工作。我们得到 undefined 是因为我们还没有定义 reducer。

Redux入门:为什么选择Redux?  第5张

所以现在我们已经介绍了动作和商店。但是,我们需要一种机制来转换 action 提供的信息并转换 store 的状态。减速器用于此目的。

减速机

一个动作描述了问题,reducer 负责解决问题。在前面的示例中,该incrementCount方法返回了一个操作,该操作提供了有关我们想要对状态进行的更改类型的信息。reducer 使用这些信息来实际更新状态。使用 Redux 时您应该始终记住的文档中有一个重点:

给定相同的参数,Reducer 应该计算下一个状态并返回它。没有惊喜。无副作用。没有 API 调用。没有突变。只是一个计算。

这意味着 reducer 应该是一个纯函数。给定一组输入,它应该总是返回相同的输出。除此之外,它不应该做更多的事情。此外,reducer 不是产生副作用的地方,例如进行ajax调用或从 API 获取数据。 

让我们为我们的计数器填写减速器。

// This is the reducer

const reducer = (state = initialState, action) => {
    switch (action.type) {
	    case "INCREMENT":
	      return state + action.payload
	    default:
	      return state
  }
}

reducer 接受两个参数——状态和动作——并返回一个新状态。

(previousState, action) => newState

state 接受默认值 the initialState,仅当 state 的值未定义时才会使用。否则,将保留状态的实际值。我们使用 switch 语句来选择正确的动作。刷新浏览器,一切正常。 

让我们添加一个 case 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 概念(例如 store、action 和 reducer)所需的所有内容。在教程快结束时,我们还创建了一个工作的 redux 演示计数器。虽然数量不多,但我们了解了拼图的所有部分是如何组合在一起的。 


文章目录
  • 基本的非 Redux 术语
    • 纯函数
    • 可观察到的副作用
    • 容器和展示组件
    • 可变对象与不可变对象
  • 什么是 Redux?
  • 为什么要使用 Redux?
  • Redux 架构
  • 店铺
      • src/index.js
  • 动作/动作创作者
  • 减速机
  • 概括