我们为什么需要redux

1.1.1 redux是什么

通俗的来讲,redux就是一个state管理库

1.1.2 不使用redux构建应用

  1. 一般构建的React组件内部可能是一个完整的应用,它自己工作良好,你可以通过属性作为API控制它。但是更多的时候发现React根本无法让两个组件互相交流,使用对方的数据。然后这时候不通过DOM沟通(也就是React体制内)解决的唯一办法就是提升state,将state放到共有的父组件中来管理,再作为props分发回子组件。
  2. 子组件改变父组件state的办法只能是通过事件触发父组件声明好的回调,也就是父组件提前声明好函数或方法作为契约描述自己的state将如何变化,再将它同样作为属性交给子组件使用。这样就出现了一个模式:数据总是单向从顶层向下分发的,但是只有子组件回调在概念上可以回到state顶层影响数据。这样state一定程度上是响应式的。
  3. 为了面临所有可能的扩展问题,最容易想到的办法就是把所有state集中放到所有组件顶层,然后分发给所有组件。这样就造成了很多中间组件为了传递props而增加一些冗余的属性。
  4. 最重要的是父组件与子组件的通信,会造成数据的重复,带来的一个问题是如何保证数据重复的数据一致,如果数据存储多份而且不一致,就很难决定到底使用哪个数据作为正确结果了。
  5. 对于数据重复的问题,一致很直观的解决方法就是以某一个组件的状态为准,这个组件是状态的”领头羊”,其余组件都保持和”领头羊”的状态同步,但是实际情况下这种方法可能难以实施。
  6. 另一种思路是,干脆不要让任何一个react组件扮演”领头羊”角色,把数据源放在react组件之外形成全局状态。这便是redux的store,全局唯一的数据源。

1.1.3 redux为我们做了什么

image

1.1.4 按照redux思想来设计

redux真正的灵魂在其设计思想,很多时候我们可能并不需要redux库本身,我们可以尝试着来应用其思想.

例如:使用组件内state的计数器

import React, { Component } from 'react';

class Counter extends Component {
state = { value: 0 };

increment = () => {
this.setState(prevState => ({
value: prevState.value + 1
}));
};

decrement = () => {
this.setState(prevState => ({
value: prevState.value - 1
}));
};

render() {
return (
<div>{this.state.value}
<button>+</button>
<button>-</button></div>
)
}

应用redux思想的计数器

import React, { Component } from 'react';

const counter = (state = { value: 0 }, action) => {
switch (action.type) {
case 'INCREMENT':
return { value: state.value + 1 };
case 'DECREMENT':
return { value: state.value - 1 };
default:
return state;
}
}

class Counter extends Component {
state = counter(undefined, {});

dispatch(action) {
this.setState(prevState => counter(prevState, action));
}

increment = () => {
this.dispatch({ type: 'INCREMENT' });
};

decrement = () => {
this.dispatch({ type: 'DECREMENT' });
};

render() {
return (
<div>{this.state.value}
<button>+</button>
<button>-</button></div>
)
}
}

1.1.5 用redux的好处

  1. 前端开发state可控。
  2. 前端数据结构统一管理。
  3. 数据流向单一,团队开发互相影响较小。

JavaScript面向对象系列:三、理解对象

js中的对象是动态的,可以在代码执行的任意时刻发生改变。基于类的语言会根据类的定义锁定对象,js对象没有这种限制。js编程的一大重点就是管理对象。

当一个属性第一次被添加给对象时,js在对象上调用一个名为 [[Put]] 的内部方法。 [[Put]] 方法会在对象上创建一个新的节点来保存属性们就像第一次在哈希表上添加一个键一样。这个操作不仅指定了初始值,也定义了属性的一些特征。调用 [[Put]] 的结果是在对象上创建了一个自有属性。一个自有属性表明仅仅该指定对象实例拥有该属性。该属性被直接保存在实力内,对该属性的所有操作都必须通过该对象进项。

当一个已有的属性被赋予一个新值的时候,调用一个名为 [[Set]] 的方法。该方法将属性的当前方法替换为新值。

var person = {
name:"laowang" //调用 [[Put]] 方法
};
person.age = 25; //调用 [[Put]] 方法

person.name = "xiaowang"; //调用 [[Set]] 方法

属性探测

不要使用一下模式探测属性是否存在

if(person.age){
//todo
}

因为当person.age 为一个对象、非空字符串、非零数字、true时,会判断为真。当person为 null、undefined、0、false、NaN或空字符串会判断为假。

更可靠的属性探测方式是使用 in 操作符。

例如

console.log("name" in person); //true
console.log("sex" in person); //false

使用 in 操作符,在大多数情况下是属性探测的最好的办法。它还有一个额外的好处是不会评估属性的值。当此类评估会导致性能问题或者错误时,这一点很重要。

在某些情况下,希望检查一个属性是不是自有属性。

in 操作符会检查自有属性和原型属性,所以不能检查出正确结果。

另一个方法是 hasOwnProperty方法,这个方法只有在属性存在并且是自有属性时才返回true。

例如

var person = {
name:"laowang" //调用 [[Put]] 方法
};

console.log("name" in person); //true
console.log(person.hasOwnProperty("name")); //true

console.log("toString" in person); //true
console.log(person.hasOwnProperty("toString")); //false

##删除对象属性

使用 delete 操作符可以将属性从一个对象中删除。将对象的一个属性设置为null并不能将属性从对象中彻底删除,这种做法只会将原值替换为null。

使用 delete 操作符会调用对象内部名为 [[Delete]] 的内部方法。相当于从哈希表中移除了一个键值对。

属性枚举

对象内部属性 [[Enumerable]] 设置为true,则属性可以循环遍历。可以使用 for-in 操作符来遍历一个对象。es5中新增 Object.keys() 来遍历对象的键,返回一个键组成的数组。

这两种操作符循环遍历有区别, for-in 操作符会遍历自有属性和原型属性,Object.keys() 只会遍历自由属性。

并不是所有属性都是可枚举的,实际上,对象大部分原生方法的 [[Enumerable]] 设置为false,可以使用 propertyIsEnumerable() 方法来检查一个属性是否为可枚举,每个对象都有这个方法。

访问器属性

getset 属性时访问器属性,可以定义属性被读取或者写入时候的行为。这两个属性可以只设置其中一个,如果只设置 get 这个属性变为只读,如果只设置 set 这个属性变为只写,这两种情况在严格模式下会报错.

例如

var person = {
name:"laowang", //调用 [[Put]] 方法
set name(value){
console.log('set');
this.name = value;
},
get name(){
console.log('get');
return this.name;
}
};

person.name; //get
person.name = 'xiaowang'; //set

##通用特征

有两个属性是数据和访问器都具有的。一个是 [[Enumerable]] ,决定了你是否可以遍历该属性。另一个是
[[Configurable]] ,决定了该属性是否可配置.可以随时删除或改变一个可配置属性。改变属性特征,可以使用 Object.defineProperty() 方法。

例如

var person = {
name:"laowang",
};

Object.defineProperty(person,"name",{
enumerable:false
});

console.log("name" in person); //true
console.log(person.propertyIsEnumerable("name")); //false

var propertites = Object.keys(person);
console.log(propertites.length); //0

Object.defineProperty(person,"name",{
configurable:false
});

delete person.name;
console.log("name" in person); //true
console.log(person.name); //laowang

Object.defineProperty(person,"name",{
configurable:true
}); //报错

一个属性被设置为不可配置了,就不能再修改了。无法将一个不可配置属性变成可配置属性 。删除一个不可配置属性,非严格模式下会失败,严格模式下会报错。

数据属性特征

数据属性拥有两个访问器属性不具备的特征:

第一个是 [[Value]] ,包含属性的值,在对象上创建属性时该特征被自动赋值。所有的属性的值都保存在 [[Value]] 中,即使值是一个函数。

第二个是 [[Writable]] ,该特种是一个布尔值,只是该属性是否可以写入,所有的属性默认是可写的。

例如

var person = {};

Object.defineProperty(person,"name",{
value:"laowang"
});

console.log("name" in person); //true
console.log(person.propertyIsEnumerable("name")); //false

delete person.name;
console.log("name" in person); //true

person.name = "xiaowang";
console.log(person.name); //laowang

使用 Object.defineProperty() 定义新的属性时,一定要为所有特征指定一个值,否则布尔值类型的特征会被默认设置为false。上面这个name就是不可枚举,不可配置,不可写。

定义多重属性

使用 Object.defineProperty() 可以定义一个属性的特征。使用 Object.defineProperties() 可以定义多个属性的特征。

例如

var person = {};

Object.defineProperties(person,{
name:{
value:"laowang",
configurable:true
},
age:{
value:25,
configurable:false,
enumerable:false
}
});

获取属性特征

使用 Object.getOwnPropertyDescriptor() 可以获取一个属性的所有特征

例如

var person = {
name:"laowang"
};

var descriptor = Object.getOwnPropertyDescriptor(person,"name");

console.log(descriptor.enumerable); //true
console.log(descriptor.configurate); //true
console.log(descriptor.writable); //true
console.log(descriptor.value); //laowang

对象禁止扩展

对象也有内部特征,禁止扩展方法是 Object.preventExtensions() 。可以用 Object.isExtensible() 来检查对象的 [[Extensible]] 的值。

例如

var person = {
name:"laowang"
};

console.log(Object.isExtensible(peson)); //true

Object.preventExtensions(peson);
console.log(Object.isExtensible(peson)); //false

person.sayName = function (){
console.log(this.name);
}

console.log("sayName" in person); //false

对象封印

对象封印时创建不可扩展对象的第二种方法。对象被封印后是不可扩展的并且所有属性都不可配置。不仅不能给对象添加新属性,也不能删除属性或改变其类型(从数据属性编程访问器属性或者相反)。一个对象被封印,则只能读写它的属性。

使用 Object.seal() 方法来封印一个对象。对象的 [[Extensible]] 被设置为false。 其余所有属性的 [[Configurate]] 特征被设置为false。可以使用 Object.isSealed() 来判断一个对象是否被封印。

例如

var person = {
name:"laowang"
};

console.log(Object.isExtensible(peson)); //true
console.log(Object.isSealed(peson)); //false

Object.seal(peson);
console.log(Object.isExtensible(peson)); //false
console.log(Object.isSealed(peson)); //true

person.sayName = function (){
console.log(this.name);
}

console.log("sayName" in person); //false

person.name = "xiaowang";
console.log(person.name); //xiaowang

delete person.name;
console.log("name" in person); //true
console.log(person.name); //xiaowang

var descriptor = Object.getOwnPropertyDescriptor(person,"name");

console.log(descriptor.enumerable); //true
console.log(descriptor.configurate); //false

对象冻结

创建不可扩展对象的最后一种方法是冻结对象。如果一个对象被冻结,则不能在对象上添加或者删除属性,不能改变属性类型,也不能写入任何数据属性。被冻结对象是一个数据属性都为只读的封印对象。被冻结的对象无法解冻。使用 Object.freeze() 来冻结一个对象,使用 Object.isFrozen() 来判断一个对象是否被冻结。

例如

var person = {
name:"laowang"
};

console.log(Object.isExtensible(peson)); //true
console.log(Object.isSealed(peson)); //false
console.log(Object.isFrozen(peson)); //false

Object.freeze(peson);
console.log(Object.isExtensible(peson)); //false
console.log(Object.isSealed(peson)); //true
console.log(Object.isFrozen(peson)); //true

person.sayName = function (){
console.log(this.name);
}

console.log("sayName" in person); //false

person.name = "xiaowang";
console.log(person.name); //laowang

delete person.name;
console.log("name" in person); //true
console.log(person.name); //laowang

var descriptor = Object.getOwnPropertyDescriptor(person,"name");

console.log(descriptor.writable); //false
console.log(descriptor.configurate); //false

总结

属性有两种类型:数据属性、访问器属性。数据属性可以保存值,访问器属性不保存值。