浅谈redux1

浅谈redux1

学习自阮一峰的博客

阮一峰的技术博客1

阮一峰的技术博客2

阮一峰的技术博客3

b站Mosh教程

Getting Started

What is Redux

Redu是JavaScript应用程序的状态管理库.我们可以在React,Angular,Vue甚至原生JS中都能用

为什么需要Redux? 如果我们曾经建立了一个带有复杂的用户界面的应用程序。我们需要保持不同部分UI显示上的同步。如果我们通过普通的数据流的方式,需要用很多代码,很复杂的逻辑,甚至进入一个死循环。所以我们需要一个状态管理库。

与其读取不同UI组件的状态变量,不如把他们都放到一个地方(store)

使用了这种架构,不同的ui将不再保存自己的状态,相反他们从仓库中得到状态信息。如果有必要更新某个数据,我们就从Store中去拿。这样能立刻解决不同UI的数据共享问题

Redux页很容易让我们理解应用程序的数据变化

Pros and Cons of Redux

这里有一个购物车式的软件。如果我们商品的数量增加了,那么Price也会随之增加,总计也会随之增加

这是因为我们有一个单一的仓库,保存应用程序的所有状态

Redux的优缺点

Is Redux for You

webpack文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const path = require("path");

module.exports = {
//Webpack会从这个文件开始,然后搜索出所有的js文件,把他们合并成app.js
//他会重新保存在dist文件夹中
entry: "./src/index.js",
output: {
filename: "app.js",
path: path.resolve(__dirname, "dist")
},
//这是服务器的配置,我们告诉webpack 从这个文件夹加载应用程序,端口号为9000
devServer: {
contentBase: path.join(__dirname, "dist"),
port: 9000
},
mode: "development"
};

Functional Programming in JavaScript 函数式编程

What is Functional Programming

函数式编程是众多编程范式,或者说风格的一种:OOP,Functional,事件驱动…

函数式编程,就是使用一些可以复用的函数组合来解决问题。这些函数接收输入并返回结果,他们不修改原始数据。有了这样的原则,我们可以将这些函数组合成更复杂的函数

Functions as First-class Citizens

函数式js中的一等公民,也就是说他们可以用在任何地方。我们可以把它赋值给变量,我们可以把它们当作参数传入,可以作为返回值

Higher-order Functions

Higher-order functions就是说吧函数作为一个参数,或者把函数作为一个返回值,或者两者都有的函数。不同于处理字符串,数字,他们以更高级的方式处理函数

比如说map函数,它接收一个函数作为它的参数

setTimeout()也是一个Higher-order function

Function Composition

最理想的函数式编程,就是创造一些函数,将这些函数组合成更复杂的函数。

1
2
let input = "  javascript  ";
let output = "<div>" + input.trim()+"</div>"

我们通过函数式编程改写

1
2
3
4
const trim = str=>str.trim();
const wrapInDiv = str =>`<div>${str}</div>`
const toLowerCase = str=>str.toLowerCase();
const result=wrapInDiv(toLowerCase(trim(input)))

Composing and Piping

但是随着函数的复杂性增加,这些小括号会越来越多。

借助loadash库来简化,他包含了很多针对函数式编程的包

1
2
3
4
5
6
7
import {compose,pipe} from "lodash/fp"
let input = " javascript ";
const trim = str=>str.trim();
const wrapInDiv = str =>`<div>${str}</div>`
const toLowerCase = str=>str.toLowerCase();
const transform = compose(wrapInDiv,toLowerCase,trim);
transform(input)

compose 也是一个 higer -order Functions,因为他传入了三个函数并返回了一个新的包含了这几个参数的函数。

但是特别要注意处理顺序,compose函数是要从右向左执行。

我们可以通过pipe函数来达到我们觉得舒服的从左向右执行

1
2
const transform = pipe(trim,toLowerCase,wrapInDiv);
transform(input)

Currying curry化

我在原来的基础上再新建一个函数

1
const wrapInSpan = str =>`<span>${str}</span>`

但是因为这个和wrapInSpan长得很像,所以我们可以对两者模板化

1
const wrap = (type,str)=>{`<type>${str}</type>`}

但这样直接输出以后是\undefined\

是因为wrap会把传入的JavaScript当成第一个type,我们同样也不能pipe(trim,toLowerCase,wrap(“div”));因为这实际上是调用了wrap并传入了div

这时候我们就需要curry化。curry化允许将多个参数传入,然后转化为只需要一个参数的函数.所以我们吧原来的返回一个字符串或者是数字的函数改成一个返回一个函数的函数.

在curry化之后,我们不是用逗号来分隔参数了,我们使用括号来分隔

1
2
3
function(a,b){
return a+b;
}

curry化之后

1
2
3
4
5
6
function(a){
return function(b){
return a+b;
}
}
add(1)(2);

用箭头函数简化后

1
const add2 = a=>b=>a+b;

所以我们对wrap函数进行改造

1
const wrap = (type) => (str) => `<${type}>${str}</${type}>`;

现在,

1
2
const transform = pipe(trim,toLowerCase,wrap("div"));
console.log(transform(input))

打印出来的就是 \javascript\</div> 现在想换成span标签,只要把div改成span即可

Pure Functions

纯函数就是我每次传入相同的参数的话,他就返回一个相同的值。很多函数都是纯函数,但是随机函数就不是.如:

1
2
3
function myFunction(number){
return number * Math.random();
}

redux中reducer就是一个纯函数。纯函数中的参数不能够变化。

自述性:因为所有的这个函数需要的东西在声明的时候已经都说清楚了

易测试: 因为我无需去设置某些全局状态用来测试这个函数

独立的:无需去设置某些全局状态

可缓存的:我们可以保留函数的运算结果

Immutability

一旦创建,无法改变,如果想修改对象,那么就要复制他,然后再修改它的样本。js中可以修改对象和数组,所以js不是严格意义上的函数式的编程语言。

js中为了达到函数式语言的特性,可以利用const ,const的作用是防止再次赋值,但是可以修改对象中的属性

不可变的好处:

可预测性:减少了突发情况

更快的感知变化:

并发性:如果我们知道函数不会修改对象本身,我们就知道实现多任务是安全的。不会因多重加载对象而造成整个系统的混乱。

但是不可变的对象也会带来一些问题:

带来潜在的性能消耗:我们每次修改对象,所有原对象的字段都要复制到新的对象。可能会减慢速度

内存消耗

Updating Objects

我们如果要更新对象的话 用spread运算符

1
2
const person = {name:"John"};
const updated = {...person};// {...person,name:"Bob"}也可以加上别的信息

注意,这里的拷贝都是浅拷贝,也就是把地址一摸一样的拷贝回来了,所以这样在修改数组的时候,两个地方都会修改,所以要利用深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const person = {
name:"John",
address:{
country:"USA",
city:"San Francisco"
}
};
const updated = {
...person,
address:{
...person.address,
city:"New York"
},
name:"Bob"
};

我们可以看到这样随着嵌套的增多,深拷贝会变得很麻烦。所以我们需要专门实现不可能变性的库

Updating Arrays

1
2
3
4
5
6
7
8
9
10
11
12
13
const numbers = [1,2,3];
//adding
const added = [...numbers,4];
// particular position
const index = numbers.indexOf(2);
const added2 = [...numbers.slice(0,index),
4,
...numbers.slice(index)]
console.log(added2) // 1,4,2,3
//remove
const removed = numbers.filter(n=>n!==2);
//Updating
const updated = numbers.map(n=>n===2?20:n)

Enforcing Immutability

不变的库

immutable 提供了很多不变的数据结构,因为我们不希望有可变的对象

immer

mori

Immutable.js

1
2
3
import { Map } from "immutable";
let book = Map({ title: "Harry Potter" });
console.log(book);

这样这个对象就是不可改变的了,但是获取对象内的属性变得有点困难,需要用get方法。而且如果要转化到原生js语言还需要toJS()比较烦

1
2
3
4
5
6
7
import { Map } from "immutable";
let book = Map({ title: "Harry Potter" });
function publish(book){
return book.set("isPublished",true);
}
book = publish(book)
console.log(book.toJS());

Immer

1
2
3
4
5
6
7
8
9
10
11
import { produce } from "immer";
let book = { title: "Harry Potter" };
function publish(book) {
// draftBook只是复制了book对象然后在里面进行修改,我们返回的是修改过后的函数,这比spread方便
return produce(book, (draftBook) => {
draftBook.isPublished = true;
});
}
let updated = publish(book);
console.log(book);
console.log(updated);

Quiz

Coding Challenges

1
2
3
4
5
6
7
8
9
10
import { pipe } from "lodash/fp";

const pickTag = obj => obj.tag;
const toLowerCase = str => str.toLowerCase();
const bracketify = str => `(${str})`;

const transform = pipe(pickTag, toLowerCase, bracketify);

const output = transform({ tag: "JAVASCRIPT" });
console.log(output);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const recipe = {
name: "Spaghetti Bolognese",
ingredients: ["egg", "salt"]
};

// Add an ingredient
const added = {
...recipe,
ingredients: [...recipe.ingredients, "cream"]
};

// Update an ingredient
const updated = {
...recipe,
ingredients: recipe.ingredients.map(ingredient =>
ingredient === "egg" ? "egg white" : ingredient
)
};

// Remove an ingredient
const removed = {
...recipe,
ingredients: recipe.ingredients.filter(ingredient => ingredient !== "egg")
};

Redux Fundamentals

Redux Architecture

比如在一个 购物软件中有这样一个对象,对象中可以存放任何类型

1
2
3
4
5
6
{
categories:[],
products:[],
cart:{},
user:{}
}

我们不能直接改变store,因为Redux是建立在函数式编程之上的。在函数式编程中不能改变对象。如果需要改变对象需要编写一个函数然后通过一些库或者…运算符在原对象基础上形成一个新的对象。那么这个函数就被称之为reducer.

1
2
3
function reducer(store){
const updated = {...store};
}

reducer从store中获取 current instance of store and return a data store。

现在问题来了,reducer是怎么知道store中的那些属性需要修改呢?这时候我们引入了action,action就是一个对象,它描述了刚刚发生了什么:用户登陆了,登出了,向购物车加了什么东西了之类的,也就是app中可能发生的事件。所以我们传入reducer还应该有第二个参数action

1
2
3
function reducer(store,action){
const updated = {...store};
}

下图截取自阮一峰的技术博客1

每一个reducer掌管一个store中的内容,在阮一峰的博客中写道:Reducer 函数负责生成 State。由于整个应用只有一个 State 对象,包含所有数据,对于大型应用来说,这个 State 必然十分庞大,导致 Reducer 函数也十分庞大。上面代码中,三种 Action 分别改变 State 的三个属性。

  • ADD_CHAT:chatLog属性
  • CHANGE_STATUS:statusMessage属性
  • CHANGE_USERNAME:userName属性

这三个属性之间没有联系,这提示我们可以把 Reducer 函数拆分。不同的函数负责处理不同属性,最终把它们合并成一个大的 Reducer 即可。

所以我们可以有多个reducer,也可以只有一个,取决于store中数据的多少

可以这样来理解:Action是event,Reducer是Event Handler。Reducer只是一个纯函数,他只是接收action然后返回更新过后的store中的值

现在我们用一个例子来描述一样redux的机制:

当我们向购物车里增加了一件商品的时候,我们创建了一个action然后dispatch(调遣)了它给Store, Reducer 函数不用手动调用,store.dispatch方法会触发 Reducer 的自动执行。为此,Store 需要知道 Reducer 函数Store 收到 Action 以后,必须给出一个新的 State,这样 UI 才会发生变化。

Your First Redux App

这是我们要建的redux应用

这是我们构建redux的步骤

npm i redux@4.0

Designing the Store

我们要建一个store,就要搞清楚Store要存放什么数据。分析一下上面的ui我们可以这样写一个结构

1
2
3
4
5
6
7
8
9
[
{
id:1,
description:"",
resolved: false
},
{...},
{...},
]

在real-world app中,我们可以这样写, this store has two slices . 在没有登陆的时候currentUser为null,登陆了以后把它设置成一个包含了登录信息的对象

1
2
3
4
5
6
7
8
[
bugs:{
id:1,
description:"",
resolved: false
},
currentUser:{}
]

Defining the Actions

现在我们来定义一个Action,在这个app当中我们可以写这样几个actions,在 real-world app中我们会有更多其它的actions

之前说了Action 是一个对象。其中的type属性是必须的,表示 Action 的名称。其他属性可以自由设置,社区有一个规范可以参考。

1
2
3
4
5
{
type:"ADD_BUG",
// type:"bugAdded"
descriptionL:"..."
}
1
2
3
4
5
6
{
type:"ADD_BUG",
payload:{
id:1
}
}

Creating a Reducer

Creating the Store

Dispatching Actions

Subscribing to the Store

Action Types

Action Creators

Exercise

Solution

Building Redux from Scratch

Redux Store

Private Properties

Dispatching Actions

Subscribing to the Store

A Quick Note

-------------本文结束,感谢您的阅读-------------