Giter VIP home page Giter VIP logo

blog's Introduction

🐳 Imitation & learning& and combination & creating

blog's People

Contributors

yuxino avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

Forkers

oirodolfo muzm

blog's Issues

Spring Mvc 学习笔记

官方的文档地址。实际上这是一个比较超前的版本。主要是因为排版好看才看的

以下都是我的个人笔记。偏差在所难免。

如果你想跟着我的笔记走的话。请请下载一个PostMan。因为用PostMan做接下里的测试比较轻松。

当然你可以用jq的ajax,axios来做这个事。

Java Enum

Java 1.5 提供了枚举类型。枚举类型(enum type)是指由一组固定的常量组成合法值的类型。例如一年中的季节,太阳系中的行星或者一副牌中的花色。在编程语言还没有引入枚举之前。通常都是用int常量来代表每一个成员:

// The int enum pattern - severely deficient!
public static final int APPLE_FUJI         = 0;
public static final int APPLE_PIPPIN       = 1;
public static final int APPLE_GRANNY_SMITH = 2;

public static final int ORANGE_NAVEL  = 0;
public static final int ORANGE_TEMPLE = 1;
public static final int ORANGE_BLOOD  = 2;

这种写法叫做int枚举模式。存在很多不足。它在类型安全性和使用性上没有任何帮助。如果你将apple闯到想要orange的方法里也是可以的。并且编译器不会出现警告。然后还会用==来判断他们是否相等。甚至你还可以让它们相加减乘除 = =

// Tasty citrus flavored applesauce
int i = (APPLE_FUJI - ORANGE_TEMPKLE) / APPLE_PIPPIN;

因为没有给int提供命名空间。所以都是在前面加前缀避免冲突。

采用int枚举模式的程序是非常脆弱的。因为int枚举是编译常量。被编译到它们的客户端里。如果与枚举常量关联的int发生了变化。客户端就必须重新编译。如果没有重新编译,程序还是可以运行的,但是它们的行为就不确定了。

将int枚举常量翻译成可打印的字符串并没有便利的办法。如果你直接把变量打印出来你只能看到个数字。并没有什么卵用。除此之外你还不能获取这一组枚举常量的大小(总数)。也无法遍历这一组枚举常量。

Java8 特性

Java 8新特性非常的多。但是这里不做介绍。只介绍主要能在SE上平时用的上的特性。

着重讲以下内容

  • Java 8 接口的默认方法
  • Lambda表达式
  • 方法引用 (Lambda 相关)
  • Lambda作用域
  • 一些新的API。比如Optionals,streams,map的扩展,functional interfaces,新的DateApi。
  • 可重复注解

参考地址What's new in java se 8

ref={callback} or ref ={string} ?

Before using React . I was a Vue developer.

We know in Vue, ref is a special attributes. ref used to register a reference to an element or a child component. The reference will be registered under the parent component’s $refs object. If used on a plain DOM element, the reference will be that element; if used on a child component, the reference will be component instance.

Let's give an example 🌰 if we want to get context2d instance of this canvas , we should get the canvas element itselft first . So we add a ref attributes.

<canvas ref="canvas" />

And then we got that in mounted function .

mounted () {
  const 2d = this.$refs.canvas.getContext('2d');
}

For the first time I used react . I guess React also can do that . So i pass a string to ref , then i got plain object successfully . I thought everything was so for granted . But i am wrong.

I realized this is a mistake when one day i was looking at the source code of a component.

image

I found that it componet author did not use string and it was using callback function . Why he used callback instead of string . Provoked my curiosity.

I started looking for reasons at Google , Soon I found a react issues.

We can see gaearon say ref="string" is a flawed pattern. There are multiple problems with it:

  • It requires that React keeps track of currently rendering component (since it can't guess this). This makes React a bit slower.
  • It doesn't work as most people would expect with the "render callback" pattern (e.g. ) because the ref would get placed on DataGrid for the above reason.
  • It is not composable, i.e. if a library puts a ref on the passed child, the user can't put another ref on it (e.g. #8734). Callback refs are perfectly composable.

And someone ask him ....

It doesn't work as most people would expect with the "render callback" pattern (e.g. ) because the ref would get placed on DataGrid for the above reason.
@gaearon Could you please elaborate more, what's the ref declaration and use in your example?

That is example

class MyComponent extends Component {
  renderRow = (index) => {
    // This won't work. Ref will get attached to DataTable rather than MyComponent:
    return <input ref={'input-' + index} />;

    // This would work though! Callback refs are awesome.
    return <input ref={input => this['input-' + index] = input} />;
  }
 
  render() {
    return <DataTable data={this.props.data} renderRow={this.renderRow} />
  }
}

So we use callback as much as possible instead of string . Because maybe one of the string will be discarded.We can see more in official document

Async-validator 学习

async-validator是ant design和element ui等很多ui库的表单验证使用到的工具库。非常的方便。这里记录一下怎么使用。

基本用法

传入descriptor创建schema实例。使用validate就能验证传入的对象了。

var schema = require('async-validator');
var descriptor = {
  name: {type: "string", required: true}
}
var validator = new schema(descriptor);
validator.validate({name: "muji"}, (errors, fields) => {
  if(errors) {
    // validation failed, errors is an array of all errors
    // fields is an object keyed by field name with an array of
    // errors per field
    return handleErrors(errors, fields);
  }
  // validation passed
});

validate 方法

function(source, [options], callback)
  • source: 需要被验证的对象(必选)
  • options: 描述要怎样处理对象(可选).
  • callback: 验证完成后的回调方法 (必选).

options

options 接受两个参数

  • first: Boolean , 如果有错误的话就不会执行后面的验证了。适合用于异步验证。
  • firstFields: Boolean|String[], Invoke callback when the first validation rule of the specified field generates an error, no more validation rules of the same field are processed. true means all fields. (妈的智障不知道干嘛用的)

Rules

Rules是用来执行验证的函数 (程序员自己写的验证扩展函数)

function(rule, value, callback, source, options)
  • rule: 里面包含了一些有用的信息,但是其实并不太常用的感觉,只是做占位。
  • value: 当前需要验证的值
  • callback: 验证完成后调用的回调函数。需要接受一个Error类型的数组。
  • source: 验证的对象
  • options: 就是前面的options参数
  • options.message: 包含了验证的错误信息会和默认的错误信息深度合并。
var schema = require('async-validator');
var descriptor = {
  name(rule, value, callback, source, options) {
    var errors = [];
    if(!/^[a-z0-9]+$/.test(value)) {
      errors.push(
        new Error(
          util.format("%s must be lowercase alphanumeric characters",
            rule.field)));
    }
    callback(errors);
  }
}
var validator = new schema(descriptor);
validator.validate({name: "Firstname"}, (errors, fields) => {
  if(errors) {
    return handleErrors(errors, fields);
  }
  // validation passed
});

这个案例展示了自己实现的验证规则。验证是可以多个一起进行的。比如说这样。

var descriptor = {
  email: [
    {type: "string", required: true, pattern: schema.pattern.email},
    {validator(rule, value, callback, source, options) {
      var errors = [];
      // test if email address already exists in a database
      // and add a validation error to the errors array if it does
      callback(errors);
    }}
  ]
}

混合了自带的验证和自己实现的验证。

Type

指示要使用的验证程序类型。识别的类型值为: (有点多不翻译了 挺好懂的)

  • string: Must be of type string. This is the default type.
  • number: Must be of type number.
  • boolean: Must be of type boolean.
  • method: Must be of type function.
  • regexp: Must be an instance of RegExp or a string that does not generate an exception when creating a new RegExp.
  • integer: Must be of type number and an integer.
  • float: Must be of type number and a floating point number.
  • array: Must be an array as determined by Array.isArray.
  • object: Must be of type object and not Array.isArray.
  • enum: Value must exist in the enum.
  • date: Value must be valid as determined by Date
  • url: Must be of type url.
  • hex: Must be of type hex.
  • email: Must be of type email.

Required

required代表需不需要验证

Pattern

Pattern 就是需要一个符合规则的正则表达式

Range

Range就是范围。写len代表固定长度。也可以写min,max

Enumerable

Enumerable 枚举。得符合枚举的类型才能验证通过。

Whitespace

whitespace 判断是否允许空格。true就是不允许。false就是允许。默认来说的话是true。因为一般有空格会被视为错误。

Deep Rules

如果要验证对象的话需要提供fields字段。

var descriptor = {
  address: {
    type: "object", required: true,
    fields: {
      street: {type: "string", required: true},
      city: {type: "string", required: true},
      zip: {type: "string", required: true, len: 8, message: "invalid zip"}
    }
  },
  name: {type: "string", required: true}
}
var validator = new schema(descriptor);
validator.validate({ address: {} }, (errors, fields) => {
  // errors for street, address.city, address.zip and address.name
});

如果不写required或者为false不检查字段。validator.validate({ }) 就像这样。

Deep Rules 会创建schmea。所以也可以传options

var descriptor = {
  address: {
    type: "object", required: true, options: {single: true, first: true},
    fields: {
      street: {type: "string", required: true},
      city: {type: "string", required: true},
      zip: {type: "string", required: true, len: 8, message: "invalid zip"}
    }
  },
  name: {type: "string", required: true}
}
var validator = new schema(descriptor);
validator.validate({ address: {} }, (errors, fields) => {
  // now only errors for street and name
});

这个single: true不知道做什么的好像没什么卵用。提了个issues提问中。

Deep rule 也可以用于数组。

var descriptor = {
  roles: {
    type: "array", required: true, len: 3,
    fields: {
      0: {type: "string", required: true},
      1: {type: "string", required: true},
      2: {type: "string", required: true}
    }
  }
}

defaultField

检查数组可以用defaultField

var descriptor = {
  urls: {
    type: "array", required: true,
    defaultField: {type: "url"}
  }
}
var validator = new schema(descriptor);
validator.validate( { urls: ['https://www.baidu.com', 'asd'] }  , (errors, fields) => {
  // now only errors for street and name
});

transform

把值转换完成后再验证。

var schema = require('async-validator');
var sanitize = require('validator').sanitize;
var descriptor = {
  name: {
    type: "string",
    required: true, pattern: /^[a-z]+$/,
    transform(value) {
      return sanitize(value).trim();
    }
  }
}
var validator = new schema(descriptor);
var source = {name: " user  "};
validator.validate(source, (errors, fields) => {
  assert.equal(source.name, "user");
});

message

错误提示信息。

{name:{type: "string", required: true, message: "Name is required"}}

错误信息可以是任何类型。比如JSX

{name:{type: "string", required: true, message: <b>Name is required</b>}}

可以调用message方法提供不同语言的验证。

var schema = require('async-validator');
var cn = {
  required: '%s 必填',
};
var descriptor = {name:{type: "string", required: true}};
var validator = new schema(descriptor);
// deep merge with defaultMessages
validator.messages(cn);

Why use getters and setters?

What's the advantage of using getters and setters - that only get and set - instead of simply using public fields for those variables?

If getters and setters are ever doing more than just the simple get/set, I can figure this one out very quickly, but I'm not 100% clear on how:

public String foo;

is any worse than:

private String foo;
public void setFoo(String foo) { this.foo = foo; }
public String getFoo() { return foo; }

Whereas the former takes a lot less boilerplate code.

Compiling the list up here at the top of what seemed winners to me, from the viewpoint of a Java web dev

  1. When you realize you need to do more than just set and get the value, you don't have to change every file in the codebase.
  2. You can perform validation here.
  3. You can change the value being set.
  4. You can hide the internal representation. getAddress() could actually be getting several fields for you.
  5. You've insulated your public interface from changes under the sheets.
  6. Some libraries expect this. Reflection, serialization, mock objects.
  7. Inheriting this class, you can override default functionality.
  8. You can have different access levels for getter and setter.
  9. Lazy loading.

拷贝复制命令行输出放在系统剪贴板上

为什么要这么做?

直接把命令的输出(比如grep/awk/sed/find或是你的程序输出结果)放到剪切板上,这么就可以在IM中CTRL + V粘贴后发出去。
避免操作的繁琐和跳跃:把结果输出到文件、用文本编辑器打开文件、选中文本、CTRL + C。
通过命令将文件内容拷贝到剪切板,以避免拷贝错误、操作的跳跃(跳到文件编辑器)

Windows下

使用系统自带的clip命令。
# 位于C:\Windows\system32\clip.exe

echo Hello | clip 
# 将字符串Hello放入Windows剪贴板

dir | clip
# 将dir命令输出(当前目录列表)放入Windows剪贴板

clip < README.TXT   
# 将readme.txt的文本放入Windows剪贴板

echo | clip 
# 将一个空行放入Windows剪贴板,即清空Windows剪贴板

Linux下

使用xsel命令。

cat README.TXT | xsel
cat README.TXT | xsel -b # 如有问题可以试试-b选项
xsel < README.TXT 
# 将readme.txt的文本放入剪贴板

xsel -c
# 清空剪贴板

Mac下

使用pbcopy命令。对应有个pbpaste命令。

echo 'Hello World!' | pbcopy
# 将字符串Hello World放入剪贴板

Typescript 小记

Enum

Typescript的Enum给Javscript提供了更加友好的枚举方式。给数字赋上了更加友好的名字。

enum Color {Red, Green, Blue}
let c: Color = Color.Green;

默认的数字是从0开始的,但是你也是可以改的。比如下面的从1开始。

enum Color {Red = 1, Green, Blue}
let c: Color = Color.Green;

当然你还可以是自定义的数字。

enum Color {Red = 1, Green = 2, Blue = 4}
let c: Color = Color.Green;

如果你想知道枚举的值是什么,而不是单纯的数字。你可以进行类型转换。

enum Color {Red = 1, Green, Blue}
let colorName: string = Color[2];

console.log(colorName); // 这样就会显示Green

但是如果你直接通过Color.Green访问是无法编译过去的

Nerver

Nerver 用在抛出异常和永不返回值的函数里。下面有几个例子。

// Function returning never must have unreachable end point
// 
function error(message: string): never {
    throw new Error(message);
}

// Inferred return type is never
function fail() {
    return error("Something failed");
}

// Function returning never must have unreachable end point
function infiniteLoop(): never {
    while (true) {
    }
}

Type assertions

类型断言/转换。告诉编译器你知道你在干啥,强转类型。有两种写法。一种是尖括号。一种是使用as作为关键字。

尖括号写法如下

let someValue: any = "this is a string";

let strLength: number = (<string>someValue).length;

as语法如下

let someValue: any = "this is a string";

let strLength: number = (someValue as string).length;

这两种语法都是等价的。唯一的区别是在JSX里面只能用as

readyonly

readyonley用在interface里,表示这个属性值是只读的,只有在初次赋值的时候是允许赋值的,之后都是不允许的。

interface Point {
    readonly x: number;
    readonly y: number;
}

let p1: Point = { x: 10, y: 20 };
p1.x = 5; // error!

ReadonlyArray

ReadonlyArray是一个不可以修改的数组。为了避免我们修改原数组。删除了所有的变异方法。在会后一行里我们试图把ReadyonlyArray的ro直接赋值给a在这里也是不允许的。

let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error!

但是我们可以通过as赋值。

a = ro as number[];

readonly vs const

常量变量使用const。属性读写使用readonly

Excess Property Checks

Excess Property Checks的意思是多余的类型检测。通常我们会定义好一些optional也就是可选的类型。但是如果我们传一些不存在的类型。在Js里面不会出什么问题。会被悄悄的忽略掉,但是在Ts里面会认为是异常,出现警告。

interface SquareConfig {
    color?: string;
    width?: number;
}

function createSquare(config: SquareConfig): { color: string; area: number } {
    // ...
}

let mySquare = createSquare({ colour: "red", width: 100 });

我们看这个colour。Ts给出了警告。

// error: 'colour' not expected in type 'SquareConfig'
let mySquare = createSquare({ colour: "red", width: 100 });

事实上我们可以绕过这个检查。比如这样。

let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig);

但是有点沙雕。所以我们换一种方式。

interface SquareConfig {
    color?: string;
    width?: number;
    [propName: string]: any;
}

让[propName]变得动态。让类型变得无关紧要。这样编译器就不会警告了。

let squareOptions = { colour: "red", width: 100 };
let mySquare = createSquare(squareOptions);

Class

private 修饰符

Class的private修饰符编译出来的结果其实并不是真的是private的。只是在用的时候警告。并不是真正意义上的。

readonly

readonly也可以作用在class的属性上面。

class Octopus {
    readonly name: string;
    readonly numberOfLegs: number = 8;
    constructor (theName: string) {
        this.name = theName;
    }
}
let dad = new Octopus("Man with the 8 strong legs");
dad.name = "Man with the 3-piece suit"; // error! name is readonly.

Function

contextual typing

一般写函数的类型会这样写

let myAdd: (x: number, y: number) => number =
    function(x: number, y: number): number { return x + y; };

但是其实还可以这样:

// myAdd has the full function type
let myAdd = function(x: number, y: number): number { return  x + y; };

// The parameters 'x' and 'y' have the type number
let myAdd: (baseValue: number, increment: number) => number =
    function(x, y) { return x + y; };

第一种是完全的函数类型。第二种叫做contextual typing。右侧不写任何的类型或者返回类型由Ts自己推到出来。

Module,NameSpace的理解

个人认为只有在写一整个库的声明的时候需要用到Module。Namespace的话是不能import的,并且是全局的,在写一些东西的时候很有用。平时写的js文件只要export就好了,别想那么多。

声明合并

Declaration Type Namespace Type Value
Namespace X   X
Class   X X
Enum   X X
Interface   X  
Type Alias   X  
Function     X
Variable     X

详细看这里

使用Object.assign来定义颜色

日常中我们会定义很多的颜色。这些颜色有时候会有亮色和暗色调。我们可以这样定义。举个例子

var colors = {
  gray: {
     default: "#DDDDDD",
     light: "#EEEEEE",
     dark: "#CCCCCC"
  })
}

比如在styled-components里面。在我们想用的时候就可以这样。

const Components =  styled.div`
  background: ${ colors.gray.default }; // background: #DDDDDD
`
const Components2 =  styled.div`
  background: ${ colors.gray.light }; // background: #EEEEEE
`
const Components3 =  styled.div`
  background: ${ colors.gray.dark }; // background: #CCCCCC
`

但是有一个更好的操作是这样。

const colors = {
  gray: Object.assign("#DDDDDD", {
     light: "#EEEEEE",
     dark: "#CCCCCC"
  })
}

于是在我们使用的时候就可以不需要烦人的default了。而且更加的直观。

const Components =  styled.div`
  background: ${ colors.gray }; // background: #DDDDDD
`
const Components2 =  styled.div`
  background: ${ colors.gray.light }; // background: #EEEEEE
`
const Components3 =  styled.div`
  background: ${ colors.gray.dark }; // background: #CCCCCC
`

写一个不会被误解的名字

命名上应该避免二义性。在命名的时候应该多问自己几遍: "这个名字会被别人误解成其他的含义吗?"

如果怕出现二义性,我们应该主动找到"误解点"。

例子 Filter()

假设你在写一段操作数据库结果的代码

results = Database.all_objects.filter("year <= 2011");

results现在包含哪些信息

  • 年份小于或等于2011年的对象?
  • 年份不小于或等于2011年的对象?

这里的问题是"filter"是个二义性单词。我们不清楚它的含义。到底是"过滤掉"还是"抛出"。最好避免使用"filter"这个名字,因为它太容易被误解。

例子 Clip(text,length)

假设你有个函数用来剪切一个段落的内容:

# Cuts off the end of the text, and appends "..."
def Clip(text, length):
...

你可能会想象到Clip()的两种行为方式

  • 从尾部删除length的长度
  • 截掉最大长度为length的一段

第二种方式(截掉)的可能性最大,但还是不能肯定。与其让读者乱猜代码,还不如把函数的名字改成Truncate(text, length)。

然而,参数名length也不太好。如果叫max_length的话可能会更清楚。

这样也还没有完。就算是max_length这个名字也还是会有多种解读:

  • 字节数
  • 字符数
  • 字数

所以不应该用max_length,而要用max_chars因为这样更准确。

推荐用first和last来表示包含的范围

下面是另一个例子,你没法判断它是“少于”还是“少于且包含”:

print integer_range(start=2, stop=4)
# 这会输出 [2,3] 或者 [2,3,4] (还是其他的东西呢)?

尽管start是个合理的参数名,但stop可以有多种解读。对于这样包含的范围(这种范围包含开头和结尾),一个好的选择是first/last。例如:

set.PrintKeys(first="Bart", last="Maggie")

不像stop,last这个名字明显是包含的。

除了first/last,min/max这两个名字也适用于包含的范围,如果它们在上下文中“听上去合理”的话。

推荐用begin和end来表示包含/排除范围

在实践中,很多时候用包含/排除范围更方便。例如,如果你想打印所有发生在10月16日的事件,那么写成这样很简单:

PrintEventsInRange("OCT 16 12:00am", "OCT 17 12:00am")

这样写就没那么简单了:

PrintEventsInRange("OCT 16 12:00am", "OCT 16 11:59:59.9999pm")

因此对于这些参数来讲,什么样的一对名字更好呢?对于命名包含/排除范围典型的编程规范是使用begin/end。

但是end这个词有点二义性。例如,在句子“我读到这本书的end部分了”,这里的end是包含的。遗憾的是,英语中没有一个合适的词来表示“刚好超过最后一个值”。

因为对begin/end的使用是如此常见(至少在C++标准库中是这样用的,还有大多数需要“分片(Slice)”的数组也是这样用的),它已经是最好的选择了。

给布尔值命名

当为布尔变量或者返回布尔值的函数选择名字时,要确保返回t r u e和f a l s e的意义很明确。

下面是个危险的例子:

bool read_password = true;

这会有两种截然不同的解释:

  • 我们需要读取密码。
  • 已经读取了密码。

在本例中,最好避免用“read”这个词,用need_password或者user_is_authenticated这样的名字来代替。

通常来讲,加上像is、has、can或should这样的词,可以把布尔值变得更明确。

例如,SpaceLeft()函数听上去像是会返回一个数字,如果它的本意是返回一个布尔值,可能HasSapceLeft()个这名字更好一些。

最后,最好避免使用反义名字。例如,不要用:

bool disable_ssl = false;

而更简单易读(而且更紧凑)的表示方式是:

bool use_ssl = true;

与使用者的期望相匹配

有些名字之所以会让人误解是因为用户对它们的含义有先入为主的印象,就算你的本意并非如此。在这种情况下,最好放弃这个名字而改用一个不会让人误解的名字。

例子 get*()

很多程序员都习惯了把以get开始的方法当做“轻量级访问器”这样的用法,它只是简单地返回一个内部成员变量。如果违背这个习惯很可能会误导用户。

以下是一个用Java写的例子,请不要这样做:

public class StatisticsCollector {
    public void addSample(double x) { ... }
    public double getMean() {
    // Iterate through all samples and return total / num_samples
    }
    ...
}

在这个例子中,getMean()的实现是要遍历所有经过的数据并同时计算中值。如果有大量的数据的话,这样的一步可能会有很大的代价!但一个容易轻信的程序员可能会随意地调用getMean(),还以为这是个没什么代价的调用。

相反,这个方法应当重命名为像computeMean()这样的名字,后者听起来更像是有些代价的操作。(另一种做法是,用新的实现方法使它真的成为一个轻量级的操作。)

例子 list::size()

下面是一个来自C++标准库中的例子。曾经有个很难发现的缺陷,使得我们的一台服务器慢得像蜗牛在爬,就是下面的代码造成的:

void ShrinkList(list<Node>& list, int max_size) {
    while (list.size() > max_size) {
        FreeNode(list.back());
        list.pop_back();
    }
}

这里的“缺陷”是,作者不知道list.size()是一个O(n)操作——它要一个节点一个节点地历数列表,而不是只返回一个事先算好的个数,这就使得ShrinkList()成了一个O(n2)操作。

这段代码从技术上来讲“正确”,事实上它也通过了所有的单元测试。但当把ShrinkList()应用于有100万个元素的列表上时,要花超过一个小时来完成!

可能你在想:“这是调用者的错,他应该更仔细地读文档。”有道理,但在本例中,list.size()不是一个固定时间的操作,这一点是出人意料的。所有其他的C++容器类的size()方法都是时间固定的

假使size()的名字是countSize()或者countElements(),很可能就会避免相同的错误。C++标准库的作者可能是希望把它命名为size()以和所有其他的容器一致,就像vector和m a p。但是正因为他们的这个选择使得程序员很容易误把它当成一个快速的操作,就像其他的容器一样。谢天谢地,现在最新的C++标准库把size()改成了O(1)。

总结

不会误解的名字是最好的名字——阅读你代码的人应该理解你的本意,并且不会有其他的理解。遗憾的是,很多英语单词在用来编程时是多义性的,例如filter、length和limit。

在你决定使用一个名字以前,要吹毛求疵一点,来想象一下你的名字会被误解成什么。最好的名字是不会误解的。

当要定义一个值的上限或下限时,max_和min_是很好的前缀。对于包含的范围,first和last是好的选择。对于包含/排除范围,begin和end是最好的选择,因为它们最常用。

当为布尔值命名时,使用i s和h a s这样的词来明确表示它是个布尔值,避免使用反义的词(例如disable_ssl)。

要小心用户对特定词的期望。例如,用户会期望get()或者size()是轻量的方法。

SVG Essentials

Chapter 1

The two major systems for representing graphic information on computers are raster and vector graphics.

Raster Graphics

In raster graphics, an image is represented as a rectangular array of picture elements or pixels (see Figure 1-1).Each pixel is represented either by its RGB color values or as an index into a list of colors. This series of pixels, also called a bitmap, is often stored in a compressed format. Since most modern display devices are also raster devices, displaying an image requires a viewer program to do little more than uncompress the bitmap and transfer it to the screen.

Figure 1-1. Raster graphic rectangle

image

Vector Graphics

In a vector graphic system, an image is described as a series of geometric shapes (see Figure 1-2). Rather than receiving a finished set of pixels, a vector viewing program receives commands to draw shapes at specified sets of coordinates.

Figure 1-2. Vector graphic rectangle

image

If you think of producing an image on graph paper, raster graphics work by describing which squares should be filled in with which colors. Vector graphics work by describing the grid points at which lines or curves are to be drawn. Some people describe vector graphics as a set of instructions for a drawing, while bitmap graphics (rasters) are points of color in specific places. Vector graphics "understand" what they are — a square "knows" it's a square and text "knows" that it's text. Because they are objects rather than a series of pixels, vector objects can change their shape and color, whereas bitmap graphics cannot. Also, all text is searchable because it really is text, no matter how it looks or how it is rotated or transformed.

Another way to think of raster graphics is as paint on canvas, while vector graphics are lines and shapes made of a stretchable material which can be moved around on a background.

Uses of Raster Graphics

Raster graphics are most appropriate for use with photographs, which are rarely composed of distinct lines and curves. Scanned images are often stored as bitmaps; even though the original may be "line art," we want to store the image as a whole and don't care about its individual components. A fax machine, for example, doesn't care what you've drawn; it simply transmits pixels from one place to another in raster form.

Tools for creating images in raster format are widespread and generally easier to use than many vector-based tools. There are many different ways to compress and store a raster image, and the internal representation of these formats is public. Program libraries to read and write images in compressed formats such as JPEG, GIF, and PNG are widely available. These are some of the reasons that web browsers have, until the arrival of SVG, supported only raster images.

Uses of Vector Graphics

Vector graphics are used in:

  • Computer Assisted Drafting (CAD) programs, where accurate measurement and the ability to zoom in on a drawing to see details are essential
  • Programs such as Adobe Illustrator, which are used to design graphics that will be printed on high-resolution printers
  • The Adobe PostScript printing and imaging language; every character that you print is described in terms of lines and curves
  • The vector-based Macromedia Flash system for designing animations, presentations, and web sites
    Because most of these files are encoded in binary format or as tightly packed bitstreams, it is diff

Container Components

All me know the container in react is everywhere. But do you know what is container component ?

Okay !! I don't know . So i find a awesome blog article in medium . That is link , If you are interested, you can see it directly.

So now let's look at what this article is talking about . image

One React pattern that’s had the impact on my code is the container component pattern.

In Jason Bonta talk High Performance Components, there’s this little gem about container components.

A container does data fetching and then renders its corresponding sub-component. That’s it.

Yes . A container just does fetching data and render it sub-componet !

“Corresponding” meaning a component that shares the same name:

StockWidgetContainer => StockWidget
TagCloudContainer => TagCloud
PartyPooperListContainer => PartyPooperList

You get the idea.

Why containers?

Say you have a component that displays comments. You didn’t know about container components. So, you put everything in one place:

class CommentList extends React.Component {
  this.state = { comments: [] };

  componentDidMount() {
    fetchSomeComments(comments =>
      this.setState({ comments: comments }));
  }
  render() {
    return (
      <ul>
        {this.state.comments.map(c => (
          <li>{c.body}{c.author}</li>
        ))}
      </ul>
    );
  }
}

Your component is responsible for both fetching data and presenting it. There’s nothing “wrong” with this but you miss out on a few benefits of React.

Reusability

CommentList can’t be reused unless under the exact same circumstances.

Data structure

Your markup components should state expectations of the data they require. PropTypes are great for this.

Once again. This time with a container

First, lets pull out data-fetching into a container component.

class CommentListContainer extends React.Component {
  state = { comments: [] };
  componentDidMount() {
    fetchSomeComments(comments =>
      this.setState({ comments: comments }));
  }
  render() {
    return <CommentList comments={this.state.comments} />;
  }
}

Now, let’s rework CommentList to take comments as a prop.

const CommentList = props =>
  <ul>
    {props.comments.map(c => (
      <li>{c.body}{c.author}</li>
    ))}
  </ul>

Article has a example but it not a container component , so i write a new one here.

So, what did we get?

We actually got a lot…

We’ve separated our data-fetching and rendering concerns.

We’ve made our CommentList component reusable.

We’ve given CommentList the ability to set PropTypes and fail loudly.

Give them a try and watch Jason’s talk. It’s excellent!

Carry on, nerds.

The anyother awesome article.

JavaScript prototype 和 __proto__的关系

首先要知道new关键字。这是用来实例化js方法用的。它的作用是把方法的prototype变成__proto__。换句话说prototype是未被实例化的方法才有的。而拥有__proto__的都是实例化后的对象。实例化后的对象可以访问自身的属性。支持调用prototype里面的方法。然后将不再含有prototype属性。

引用一个StackOverFlow的答案

proto_ is the actual object that is used in the lookup chain to resolve methods, etc. prototype is the object that is used to build proto when you create an object with new.

你不知道的location.href

在工作中发现了一个现象。location.href跳转的时机是在主线程执行完后才执行的。

跳转的时机

这里用了一个递归的fib(斐波那契算法)来模拟耗时操作。

Html 部分

<input type="button" id="btn" value="嘎嘎嘎"> 

Js 部分

function fib(index){
    if (index<0) {
        return 0;
    }
    if (index<=2) {
        return 1;
    }
    return fib(index-1) + fib(index-2);
}

var btn = document.getElementById('btn');
var q = 0
btn.onclick = function(){
  location.href = "https://codepen.io/Nbsaw";
  var q = fib(40);
  console.log(q);
}

代码丢codepen

利用location.href打开App

拿知乎举个例子。可以直接在手机上尝试一下。

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>this's a demo</title>
  <meta name='apple-itunes-app' content='app-id=477927812'>
</head>
<body>
  <a href="javascript:;" id="openApp">知乎客户端</a>
</body>
</html>
<script type="text/javascript">
  document.getElementById('openApp').onclick = function(e){
    if(navigator.userAgent.match(/(iPhone|iPod|iPad);?/i)) {
      location.href = "zhihu://app";
      setTimeout(function() {
        location.href = "https://itunes.apple.com/cn/app/id477927812";
      }, 2000)
    }
    if(navigator.userAgent.match(/android/i)) {
      window.location.href = "zhihu://app";
      window.setTimeout(function() {
        location.href = "https://****.apk";//android 下载地址
      }, 2000)
    }
  };
</script>

对等加密

对等加密(Reciprocal cipher)是对称密钥加密(Symmetric-key algorithm)的一个特例。该类密码的加密算法是它自己本身的逆反函数,所以其解密算法等同于加密算法。如果要还原对等加密的密文,套用加密同样的算法即可得到明文。换句话说,使用相同的密钥,两次连续的对等加密运算后会回复原始文字。在数学上,这有时称之为对合。
举例来说,ROT13 算法是将26个英文字母依续排列即1=A、2=B、3=C、...、26=Z、27=A...,加密后将数字加13,即A(1)→N(14)、B(2)→O(15)、...、M(13)→Z(26),欲得到原始讯息再将数字加13即可。ILOVEYOU加密过后即变成VYBIRLBH。
常见的对等加密算法有 ROT13、阿特巴希密码、博福特密码、恩尼格玛密码机、异或密码、RC4、瓦茨亚亚那加密。

ROT13

ROT13(回转13位,rotate by 13 places,有时中间加了个连字符称作ROT-13)是一种简易的替换式密码。它是一种在英文网络论坛用作隐藏八卦(spoiler)、妙句、谜题解答以及某些脏话的工具,目的是逃过版主或管理员的匆匆一瞥。ROT13被描述成“杂志字谜上下颠倒解答的Usenet点对点体”。(Usenet equivalent of a magazine printing the answer to a quiz upside down.)ROT13 也是过去在古罗马开发的凯撒加密的一种变体。

ROT13是它自己本身的逆反;也就是说,要还原ROT13,套用加密同样的算法即可得,所以他满足以下公式

ROT13( ROT13(x) ) = ROT26( x ) = x

因为这个算法曾经见到过,也非常简单,所以我拿JS实现了一遍,如下

function rot(str,places = 13){
  if(str.length === 0) return "";
  let result = "";
  let code = 0;
  let total = 0;
  for(let s of str){
    code = s.charCodeAt();
    total = code+places;
    if(65 <= code && code <= 90) 
       result += String.fromCharCode(total>90?total-26:total);
    else if(97 <= code  && code <= 122) 
       result += String.fromCharCode(total>122?total-26:total);
    else 
       result += s;
  }
  return result;
}

上StackOverflow然后看了一眼别人的代码,发现自己写的像shit一样? 好吧,原来replace可以加回调函数的,学到了

function rot(str,places = 13){
  str.replace(/[a-zA-Z]/g,function(c){
   return String.fromCharCode((c<="Z"?90:122)>=(c=c.charCodeAt(0)+places)?c:c-26);
  });
}

于是随便测试一下

> console.log(rot("Ubj pna lbh gryy na rkgebireg sebz na vagebireg ng AFN? In the elevators, the extrovert looks at the OTHER guy's shoes."))
How can you tell an extrovert from an introvert at NSA? Va gur ryringbef, gur rkgebireg ybbxf ng gur BGURE thl'f fubrf.

OK,没问题,下一个算法

阿特巴希密码 (Atbash)

第一次看还以为是阿希吧加密。阿特巴希密码是一种开始由希伯来字母使用的简易替换密码。

等一下希伯来字母又是啥?

希伯来字母

现代希伯来语字母表是从亚拉姆语字母表的基础上发展起来的,类似阿拉伯字母。希伯来语使用者称他们的字母表为“aleph-bet”。现时除了希伯来语以外,尚有另一种过去犹太人常用的语言意第绪语亦采用希伯来语字母。

真的阿西吧,啥玩意这个,于是看看wiki给的几个例子:

阿特巴希密码将字母表整个扭转:第一个字母(aleph)与最后一个(taw)相替换,第二个(beth)与倒数第二个(shin)相替换,如此类推。密码学上的示沙克之谜出现在《耶利米书》中,ששך-示沙克(25:26;51:41)其实是加密后的בבל-巴别(即巴比伦)。

现代希伯来文配合阿特巴希密码:

明文: אבגדהוזחטיכלמנסעפצקרשת
密文: תשרקצפעסנמלכיטחזוהדגבא

而罗马字母使用阿特巴希密码则是:

明文: abcdefghijklmnopqrstuvwxyz
密文: ZYXWVUTSRQPONMLKJIHGFEDCBA

而有一种更简单、更快的方法是:

首13個字母: A|B|C|D|E|F|G|H|I|J|K|L|M
後13個字母: Z|Y|X|W|V|U|T|S|R|Q|P|O|N

或者是:

首13個字母: A |B |G|D|H |V|Z|CH|T|Y|K
後13個字母: TH|SH|R|O|TZ|P|O|S |N|M|L

阿特巴希密码不只是用在此两种字母,理论上但凡拼音式字母都可以使用此方法来加密。
这个非常简单的替代密码与ROT13最大的不同是ROT13将字母偏移13位,** 而阿特巴希密码则是将整个字母表对折 **。
例如在阿特巴希密码中“nlmvb”代表“MONEY”。
英文中某些字经过阿特巴希加密后会变成另一个正常的英文字

原文 加密
hob sly
hold bar
baz slow
holy slob
horn slim
zoo all
irk rip
low old
glow told
grog util

这是一个非常薄弱的** 单字母替换密码 **,因为它只有一个固定的密钥。然而,在当时这可能不是一个问题。

于是抓住关键字字母表,字母表,那我就实现一个罗马字母的吧

function atbash(str){
 return str.replace(/[a-zA-Z]/g,function(c){
   const public = "abcdefghijklmnopqrstuvwxyz";
   const private = "ZYXWVUTSRQPONMLKJIHGFEDCBA";
   let flag = true; 
   let index = public.indexOf(c)!=-1?public.indexOf(c):(private.indexOf(c),flag=false);
   return flag?private[index]:public[index];
 });
}

So give me 'MONEY' !

> console.log(atbash("nlmvb"))
MONEY

恩尼格玛密码机(Enigma)

在密码学史中,恩尼格玛密码机(德语:Enigma,又译哑谜机,或“谜”式密码机)是一种用于加密与解密文件的密码机。确切地说,恩尼格玛是对二战时期纳粹德国使用的一系列相似的转子机械加解密机器的统称,它包括了许多不同的型号,为密码学对称加密算法的流加密。

恩尼格玛密码机在1920年代早期开始被用于商业,一些国家的军队与政府也曾使用过它,其中的主要使用者是第二次世界大战时的纳粹德国。

在恩尼格玛密码机的所有版本中,最著名的是德国使用的军用版本。尽管此机器的安全性较高,但盟军的密码学家们还是成功地破译了大量由这种机器加密的信息。1932年,波兰密码学家马里安·雷耶夫斯基、杰尔兹·罗佐基和亨里克·佐加尔斯基根据恩尼格玛机的原理破译了它。1939年中期,波兰政府将此破译方法告知了英国和法国,但直到1941年英国海军捕获德国U-110潜艇,得到密码机和密码本才成功破译。密码的破译使得纳粹海军对英美商船补给船的大量攻击失效。盟军的情报部门将破译出来的密码称为ULTRA,这极大地帮助了西欧的盟军部队。ULTRA到底有多大贡献还在争论中,但是人们都普遍认为盟军在西欧的胜利能够提前两年,完全是因为恩尼格玛密码机被成功破译

尽管恩尼格玛密码机在加密方面具有不足之处,但是经它加密的文件还是很难破译,盟军能够破译它的密码是因为德国还犯了其它一些大错误(如加密员的失误、使用步骤错误(黑人问号)、机器或密码本被缴获等等)。

不是很感兴趣也没看见wiki上面的算法上的描述于是跳过,但是百度谷歌都能搜索到,感兴趣的话看看这个链接

异或密码

简单异或密码(英语:simple XOR cipher)是密码学中一种简单的加密算法,它按照如下原则进行运算:

异或稍微懂程序的frineds应该都懂,两个位一样为假,两个位不同为真,用表格来描述的话就是酱紫

XOR 0 1
0 0 1
1 1 0

用数学AB来表示就是 A⊕0=A , A⊕A=0

按这种逻辑,文本串行的每个字符可以通过与给定的密钥进行按位异或运算来加密。如果要解密,只需要将加密后的结果与密钥再次进行按位异或运算即可。
例如,字符串“Wiki”(8位ASCII:01010111 01101001 01101011 01101001) 可以按如下的方式用密钥11110011进行加密:

     01010111 01101001 01101011 01101001
⊕ 11110011 11110011 11110011 11110011
   10100100 10011010 10011000 10011010

此种加密方法类似对称加密,故解密的方式如下:

     10100100 10011010 10011000 10011010
⊕ 11110011 11110011 11110011 11110011
=   01010111 01101001 01101011 01101001

这是利用了取两次XOR等于原来的值的特性。假设B为密钥,我们可以通过公式推导出来

   A ⊕ B ⊕ B
= A ⊕ ( B ⊕ B )
= A ⊕ 0
= A

那么,实现一个!不转八位了,但还是用XOR运算

function xor(str,key = 1111){
  let result = "";
  for(let c of str) 
     result += String.fromCharCode(c.charCodeAt() ^ key);
  return result;
}

жФФѷРвѷджй

这些算法完全用不上,就到此为止吧,不再深入了

响应式图片 / Responsive images

响应式图片就是能在绝大多数不同屏幕上都能很好的显示的图片。实现响应式图片可以通过CSS。但是本文利用的是srcsetsizes两个属性,以及更加进阶的<picture /><source />标签。**这个标签在Edge非IE浏览器都已经得到支持。**你可以在Can i use搜索看看。

为什么要响应式

在这之前我们要搞懂为什么我们需要响应式图片。我们精彩用到的max-width: 100%不够用了吗?

我们这里看一个例子。很常见的例子,一个普通的文章页面。点击打开

website preview

看起来很好不管在电脑,还是平板上面。但是如果你把分辨率调到手机的看。

mobile not respontive preview

发现什么不对了吗。什么,没有?? 如果你没有发现,可能是因为你平时就是这么做的,没有在意到这个细节。对于用户来说,那张抱着小女孩的图太小了。压根看不到人脸啊 !!! 根本就没有意义了。

为了解决看不到脸的问题

我们使用了两个新的属性srcsetsizes, 提供多个附加源图像以及提示,以帮助浏览器选择正确的源图像。为了证明给你看这两个属性是有用的,我们弄了一个案例。代码可以在Github看见。

responsive preview

现在的案例比之前的要好太多。

新的两个属性

<img srcset="elva-fairy-320w.jpg 320w,
             elva-fairy-480w.jpg 480w,
             elva-fairy-800w.jpg 800w"
     sizes="(max-width: 320px) 280px,
            (max-width: 480px) 440px,
            800px"
     src="elva-fairy-800w.jpg" alt="Elva dressed as a fairy">

srcsetsizes属性看起来很复杂,但是如果你按照上图所示进行格式化,那么他们并不是很难理解,每一行有不同的属性值。每个值都包含逗号分隔的列表。列表的每一部分由三个子部分组成。让我们来看看现在的每一个内容:

srcset定义了我们允许浏览器选择的图像集,以及每个图像的大小。在每个逗号之前,我们写:

  • 一个文件名 (elva-fairy-480w.jpg.)

  • 一个空格

  • 图像的固有宽度(以像素为单位)(480w)——注意到这里使用w单位,而不是你预计的px。这是图像的真实大小,可以通过检查你电脑上的图片文件找到(例如,在Mac上,你可以在Finder上选择这个图像,然后按 Cmd + I 来显示信息)。

sizes定义了一组媒体条件(例如屏幕宽度)并且指明当某些媒体条件为真时,什么样的图片尺寸是最佳选择—我们在之前已经讨论了一些提示。在这种情况下,在每个逗号之前,我们写:

  • 一个媒体条件((max-width:480px))——你会在 CSS topic中学到更多的。但是现在我们仅仅讨论的是媒体条件描述了屏幕可能处于的状态。在这里,我们说“当视窗的宽度是480像素或更少”。
  • 一个空格
  • 当媒体条件为真时,图像将填充的槽的宽度(440px)

就是这样!所以在这里,如果支持浏览器以视窗宽度为480px来加载页面,那么(max-width: 480px)的媒体条件为真,因此440px的槽会被选择,所以elva-fairy-480w.jpg将加载,因为它的的固定宽度(480w)最接近于440px。800px的照片大小为128KB而480px版本仅有63KB大小—节省了65KB。现在想象一下,如果这是一个有很多图片的页面。使用这种技术会节省移动端用户的大量带宽。

老旧的浏览器不支持这些特性,它会忽略这些特征。并继续正常加载 src属性引用的图像文件。

这种方案比css/js好

当浏览器开始加载一个页面, 它会先下载 (预加载) 任意的图片,这是发生在主解析器开始加载和解析页面的 CSS 和 JavaScript 之前的。这是一个非常有用的技巧,平均来说,页面加载的时间少了 20%。但是, 这对响应式图片一点帮助都没有, 所以需要实现类似 srcset的方法。因为你不能先加载好 元素后, 再用 JavaScript 检测视图的宽度,如果觉得大小不合适,就动态地加载小的图片替换已经加载好的图片,这样的话, 原始的图像已经被加载了, 然后你也加载了小的图像, 这样的做法对于响应式图像的理念来说,是很糟糕的。

使用prciture标签

HTML 元素是一个容器,用来为其内部特定的 元素提供多样的 元素。浏览器会根据当前页面(即图片所在的盒子的容器)的布局以及当前浏览的设备(比如普通的屏幕和高清屏幕)去从中选择最合适的资源。

<picture>
  <source type="image/svg+xml" srcset="pyramid.svg">
  <source type="image/webp" srcset="pyramid.webp"> 
  <img src="pyramid.png" alt="regular pyramid built from four equilateral triangles">
</picture>

使用media

可以非常平稳的退化。同时<picture>也能做到和srcset / sizes一样的事情。同时他也支持mediatype

<picture>
 <source srcset="mdn-logo-wide.png" media="(min-width: 600px)">
 <img src="mdn-logo-narrow.png" alt="MDN">
</picture>

使用type

<picture>
 <source srcset="mdn-logo.svg" type="image/svg+xml">
 <img src="mdn-logo.png" alt="MDN">
</picture>

总结

记录来自MDN。响应式图片/新属性/新标签的好处

  • 更好的用户体验
  • 节省带宽,因为在不同的屏幕上显示不同的图片,小屏幕无需加载一个很大/很高清的图片资源。
  • 比CSS/JS更加友好,容易控制,语义化
  • 能够平稳的退化

Jdbc & Jpa

主要写下它们的关系。以及它们分别是做什么的。 ╮(╯▽╰)╭ 超基础的知识。

来写一个超级简陋的编辑器

github上有个项目叫做pell。这是一个轻量级的文本编辑器。如它的介绍所说。

📝 the simplest and smallest (1kB) WYSIWYG text editor for web, with no dependencies

pell.js 没有依赖完完全全是用原生js写的。用了Es6的一些语法还有Sass编写样式。

这里还做了一张表对比其他编辑器(只是比较大小,功能啥的没有比较)

library size (min+gzip) size (min) jquery bootstrap
pell 1.11kB 2.85kB    
medium-editor 27kB 105kB    
quill 43kB 205kB    
ckeditor 163kB 551kB    
summernote 26kB 93kB x x
froala 52kB 186kB x  
tinymce 157kB

目前只有常规的一些操作.还有一大把操作没有。比如更改字体大小啥的。未来会有(大概吧)。

浏览器的兼容性还是不错的。

IE 9+
Chrome 5+
Firefox 4+
Safari 5+
Opera 11.6+

可以用npm,yarn安装。安完就可以用了。可以在这里体验一下。这个编辑器唯一吸引我的地方是官网的字体(用的是VT323可以在Google Front找到)很好看

好了介绍完了。开始水一篇。因为很简单。简单到我都不知道为啥要写这一篇。

Emmmmm。先把项目克隆到本地。然后打开这个项目。打开pell.js。首先我们可以看actions部分。这是一个pell.js维护的数据结构。里面定义了pell.js全部自带的编辑功能。有icon,title,还有result三个字段。

这个数据结构非常直观。icon就是显示在页面上的图标。比如Bold它的图标就是<b>B</b>。我们在页面上看到的图标也是这一个。title就是悬停时候的提示文字。

image

最后一个字段result是一个方法。是点击图标时候会调用的方法。我们发现所有的result都使用了exec方法。那我们可以看看exec方法写了什么,为什么大家都调用它。

export const exec = (command, value = null) => {
  document.execCommand(command, false, value)
}

exec方法使用了document.execCommand,这个方法的作用是允许运行命令(Command)来操纵可编辑区域的内容。可编辑区域指的是带有contentEditable=true的元素。如果你不知道的话可以查一下这个。总而言之因为这个方法,让我们有能力去操作编辑区的文字的内容,比如加粗,斜体等等。这些加粗,斜体都是命令。更详细的可以看MDN。第二个参数总是为false,不知道有什么卵用。第三个参数是value默认为null,有一些命令会用到这个value,比如link链接,执行的时候需要一个value插入到文本编辑区,就像这样使用。

document.execCommand('createLink', false, 'what u see')

然后编辑区就会插入一个链接。其他的比如加粗,在我们选中某一段文字以后使用这个

document.execCommand('bold', false, null)

就会加粗选中的文字。之后输入的文字也是加粗的。除非我们手动取消(再点一次加粗按钮)。

可能文字部分描述的不够清晰。所以我写了一个示例。看完实例应该就很好理解了吧。这一整个actions的目的作用。

然后我们暂时把源码放一边。看看demo.html是如何使用pell.js的吧。我们发现pell.js支持传递三个选项到pell.init方法里面。第一个参数是element。只要是HtmlElement就可以了。会作为编辑器的容器被使用。第二个参数是StyleWithCSS,这是一个可选选项默认是false。如果你认真看了MDN应该会理解这个选项。如果你看了还是无法理解。我可以给你讲讲。

我们通过两张图来说明这个选项的作用。这里都使用了加粗功能。

先来看第一种情况把styleWithCss设置为false的情况。我们看看输出部分。

image

再来看看第二种把styleWithCSS设置为true情况。同样也是看输出部分。

image

看到不同了吗。两者的区别就是一个是直接用标签控制样式。一个是通过style控制样式。

根据不同场景会使用不同的选项。第三个参数就是actions。就是我们刚刚了解过的数据结构。这个选项也是可选选项。如果我们不写这个选项的话。默认pell.js会生成actions里面的所有的功能到操作栏里面。但是如果我们有了这个选项。pell.js会根据我们的需求来生成功能。

这个选项可以是string也可以是object。如果是string的话。pell.js会依靠这个stringactions里面找有没有对应的有的话就用,没有的话就啥都不干像个智障。

如果是object的话。我们需要传递一个name选项作为功能的名字。其他的部分和actions的数据结构一致。pell.js会依靠这个name去查actions里面有没有对应的功能。然后我们可以传icon,title,result修改默认功能。第二种情况是自定义功能。这时候每个选项都要传。因为不传的话。渲染出来的按钮根本毫无卵用。。

总结一下就是使用string的话。pell.js会使用默认的功能 (存在的话)。如果是object可以覆盖原有的功能的icontitle,result。否则就是新建按钮。

然后我们这时候看源码就会发现非常明了整个过程。

// 要用到的样式名
const classes = {
  actionbar: 'pell-actionbar',
  button: 'pell-button',
  content: 'pell-content'
}

// 执行命令
export const exec = (command, value = null) => {
  document.execCommand(command, false, value)
}

// 阻止tab键 不知道为什么要阻止
const preventTab = event => {
  if (event.which === 9) event.preventDefault()
}

export const init = settings => {
  // 处理action选项
  settings.actions = settings.actions
    // 如果有actions选项
    ? settings.actions.map(action => {
      // 如果是字符 直接返回一个内置的 action
      if (typeof action === 'string') return actions[action]
      // 如果是自定的action。解构把它变成
      // { name: '' , icon : '' , title : '' , result : f }
      // 第二次解构的目的是覆盖原来对象的值
      else if (actions[action.name]) { return { ...actions[action.name], ...action } }
      return action
    })
    // 没有actions选项获取默认的所有的actions
    : Object.keys(actions).map(action => actions[action])

  settings.classes = { ...classes, ...settings.classes }

  // 操作栏
  const actionbar = document.createElement('div')
  actionbar.className = settings.classes.actionbar
  settings.element.appendChild(actionbar)

  // 这个是编辑框
  settings.element.content = document.createElement('div')
  settings.element.content.contentEditable = true
  settings.element.content.className = settings.classes.content
  settings.element.content.oninput = event => settings.onChange(event.target.innerHTML)
  settings.element.content.onkeydown = preventTab
  settings.element.appendChild(settings.element.content)

  // 把各种操作的按钮建立出来
  settings.actions.forEach(action => {
    const button = document.createElement('button')
    button.className = settings.classes.button
    button.innerHTML = action.icon
    button.title = action.title
    button.onclick = action.result
    actionbar.appendChild(button)
  })

  if (settings.styleWithCSS) exec('styleWithCSS')

  return settings.element
}

Emmmm 就这些。整个流程就完了。

Dva 爱你哦 | Love, D.Va!

Dva 是啥

dva = React-Router + Redux + Redux-saga

社区上其实也有很多别的方案比如rematch,看过dva之后我觉得dva真的非常的棒。记录一波咯。如果要学习dva必须对Redux,Redux Saga都了解。这是一个高阶的库。

Dva 概念

这张图展示了Dva的数据流向。同时也是社区的基本共识。我们通过connect把componet和redux的state连接起来。component可以通过dispatch action来调用reducer或者effect。action又可以分为同步和异步(副作用不纯)的如果是同步 action 会直接通过 Reducers 改变 State ,如果是异步行为(副作用)会先触发 Effects 然后流向 Reducers 最终改变 State。这个Subscription是dva特有的。后面会讲到。

flow

直接进入入门课。因为Model那层的概念非常basic,对于熟悉Redux的人来说不需要再提起了。

React 对状态的表示

React 表示法

按照 React 官方指导意见, 如果多个 Component 之间要发生交互, 那么状态(即: 数据)就维护在这些 Component 的最小公约父节点上, 也即是 <App/>

<TodoList/> <Todo/> 以及<AddTodoBtn/> 本身不维持任何 state, 完全由父节点<App/> 传入 props 以决定其展现, 是一个纯函数的存在形式, 即: Pure Component

Redux 表示法

React 只负责页面渲染, 而不负责页面逻辑, 页面逻辑可以从中单独抽取出来, 变成 store

Redux 表示法

与图一相比, 几个明显的改进点:

  1. 状态及页面逻辑从 <App/>里面抽取出来, 成为独立的 store, 页面逻辑就是 reducer

  2. <TodoList/><AddTodoBtn/>都是 Pure Component, 通过 connect 方法可以很方便地给它俩加一层 wrapper 从而建立起与 store 的联系: 可以通过 dispatch 向 store 注入 action, 促使 store 的状态进行变化, 同时又订阅了 store 的状态变化, 一旦状态有变, 被 connect 的组件也随之刷新

  3. 使用 dispatch 往 store 发送 action 的这个过程是可以被拦截的, 自然而然地就可以在这里增加各种 Middleware, 实现各种自定义功能, eg: logging

  4. 这样一来, 各个部分各司其职, 耦合度更低, 复用度更高, 扩展性更好

加入 Saga

Saga

上面说了, 可以使用 Middleware 拦截 action, 这样一来异步的网络操作也就很方便了, 做成一个 Middleware 就行了, 这里使用 redux-saga 这个类库, 举个栗子:

  1. 点击创建 Todo 的按钮, 发起一个 type == addTodo 的 action

  2. saga 拦截这个 action, 发起 http 请求, 如果请求成功, 则继续向 reducer 发一个 type == addTodoSucc 的 action, 提示创建成功, 反之则发送 type == addTodoFail 的 action 即可

Dva 又是怎样

Dva 图示

有了前面的三步铺垫, Dva 的出现也就水到渠成了, 正如 Dva 官网所言, Dva 是基于 React + Redux + Saga 的最佳实践沉淀, 做了 3 件很重要的事情, 大大提升了编码体验:

  1. 把 store 及 saga 统一为一个 model 的概念, 写在一个 js 文件里面

  2. 增加了一个 Subscriptions, 用于收集其他来源的 action, eg: 键盘操作

  3. model 写法很简约, 类似于 DSL 或者 RoR, coding 快得飞起✈️

app.model({
  namespace: 'count',
  state: {
    record: 0,
    current: 0,
  },
  reducers: {
    add(state) {
      const newCurrent = state.current + 1;
      return { ...state,
        record: newCurrent > state.record ? newCurrent : state.record,
        current: newCurrent,
      };
    },
    minus(state) {
      return { ...state, current: state.current - 1};
    },
  },
  effects: {
    *add(action, { call, put }) {
      yield call(delay, 1000);
      yield put({ type: 'minus' });
    },
  },
  subscriptions: {
    keyboardWatcher({ dispatch }) {
      key('⌘+up, ctrl+up', () => { dispatch({type:'add'}) });
    },
  },
});

并行执行

如果要执行并行的任务,非顺序执行,应该这样写。

yield [];

而不是

yield* [];

这两者含义是不同的,后者会顺序执行。

API

  • dva: 默认输出文件。

  • dva/fetch: 异步请求库,输出 isomorphic-fetch 的接口。不和 dva 强绑定,可以选择任意的请求库。

  • dva/router: 默认输出 react-router 接口, react-router-redux 的接口通过属性 routerRedux 输出。

  • dva/saga: 输出 redux-saga 的接口,主要用于用例的编写。(用例中需要用到 effects)

  • dva/dynamic: 解决组件动态加载问题的 util 方法。

在Nuxt.js中使用Storybook管理组件

Nuxt和Storybook的安装

这个这里不做介绍,请自己安装。

Nuxt.js是做什么的

Nuxt.js 是一个基于 Vue.js 的通用应用框架。

通过对客户端/服务端基础架构的抽象组织,Nuxt.js 主要关注的是应用的 UI渲染。

Nuxt.js 是一个Vue的服务端渲染应用框架。

StoryBook是做什么的 ?

Storybook is a development environment for UI components. It allows you to browse a component library, view the different states of each component, and interactively develop and test components.

Storybook是一个UI组件的开发环境。允许你去浏览组件库,观察组件的不同状态,并可以以交互方式开发和测试组件。

准备拿Storybook做什么 ?

我们准备拿Storybook来管理公司内部的公用组件,避免重复造轮子。减少劳动力,提升开发效率。

我们期待的样子是怎样的 ?

这里有一份别人的DEMO。这个Demo里面添加了很多Addon。Addon就是面板上的功能。就是你能看见的STORY,ACTION LOGGER,NOTES等等功能。他这里比较多,但是我只要一部分。

我想要哪一些ADDON ?

我们家的项目只需要下面的。

  • 用来查看源代码的 STORY
  • 用来查看触发什么事件的 ACTION LOGGER
  • 用来查看各种设备上的样子的 VIEWPORT
  • 用来作为提示的 NOTES

直接安装到开发依赖就好了。

yarn add @storybook/addon-actions -D
yarn add @storybook/addon-links -D
yarn add @storybook/addon-notes -D
yarn add @storybook/addon-storysource -D

你想了解更多的Addon

公司Nuxt.js项目结构

公司项目结构大概如下。已经精简掉了不需要的部分。把components拆分成了baseComponentsbusinessComponents。分表代表公用组件和业务组件(或者一些只有该页面会用到的组件,但是过于庞大拆分了出来)。

├── .storybook
├── assets
├── baseComponents
├── businessComponents
├── layouts
├── pages
├── middleware
├── nuxt.config.js
├── pages

一般的的Vue项目可能会类似于这样。在page下面丢components代表业务组件。但是由于Nuxt.js的特殊性。丢在page下面的文件和文件夹都会变成路由。所以不能这么做,取而代之的用了上面的做法。

├── .storybook
├── assets
├── components
├── layouts
├── pages
├────pageA
├──────components
├────────componentsA.vue
├──────index.vue
├── middleware
├── nuxt.config.js
├── pages

因为改成了这样子所以要修改配置文件nuxt.config.js。在扩展方法(extend)里面加上这一段。方便我们在项目中使用别名。

Object.assign(config.resolve.alias, {
  baseComponents: path.resolve(__dirname, 'baseComponents'),
  businessComponents: path.resolve(__dirname, 'businessComponents')
})

配置Storybook

创建.storybook文件夹。添加下面的配置文件就好了。

webpack.config.js

const path = require("path");
const rootPath = path.resolve(__dirname, "../");

module.exports = (baseConfig, env, defaultConfig) => {
  defaultConfig.resolve.alias["@"] = rootPath;
  defaultConfig.resolve.alias["~"] = rootPath;
  defaultConfig.resolve.alias["assets"] = `${rootPath}/assets`;
  // 同样是设置别名
  defaultConfig.resolve.alias["baseComponents"] = `${rootPath}/baseComponents`;
  defaultConfig.resolve.alias["businessComponents"] = `${rootPath}/businessComponents`;
  // addon-storysource需要添加额外的配置
  // 因为有引用SASS所以这里添加了Loader
  defaultConfig.module.rules.push(
    {
      test: /_story\.js$/,
      loaders: [require.resolve("@storybook/addon-storysource/loader")],
      enforce: "pre"
    },
    {
      test: /\.scss$/,
      use: [
          "style-loader", // creates style nodes from JS strings
          "css-loader", // translates CSS into CommonJS
          "sass-loader" // compiles Sass to CSS, using Node Sass by default
      ]
    }
  );
  return defaultConfig;
};

config.js

NuxtLink是nuxt的组件因为某些组件用到了,所以这里全局注册一下,同样这里还用了ElmentUI。看项目视情况删除就好了。这里我最后一个Function是加载文件的。意思是匹配baseComponents下面叫做_story.js的文件作为storybook的例子。

import { configure } from '@storybook/vue'

import Vue from 'vue'
import Vuex from 'vuex' // Vue plugins
import Element from 'element-ui'
import '../node_modules/element-ui/lib/theme-chalk/index.css'
import '~/assets/styles/index.scss'
import NuxtLink from 'nuxt/lib/app/components/nuxt-link';

// Install Vue plugins.
Vue.use(Vuex)
Vue.use(Element)
Vue.component('nuxt-link', NuxtLink);

// 加载所有指定文件
function requireAll(requireContext) {
  return requireContext.keys().map(requireContext)
}

// 加载指定的文件
function loadStories() {
  requireAll(require.context("../baseComponents", true, /_story\.js$/))
}

configure(loadStories, module)

addons.js

注册addons

import '@storybook/addon-storysource/register';
import '@storybook/addon-actions/register';
import '@storybook/addon-viewport/register';
import '@storybook/addon-notes/register';

全部引进来就好啦。

_story.template

这是一个可选的文件。我拿来复制粘贴用的模板文件。

import { storiesOf } from '@storybook/vue';

import Component from './';

const BasicExample = `
  <component>
    ...
  </baseComponents>
`

storiesOf('Component', module)
  .add('basic example', () => ({
    components: { Component },
    template: BasicExample
  }))

baseComponents目录结构

呀至于怎么玩看你们团队。这个只是参考目录。

├── baseComponents
├────componentA
|──────_story.js
|──────componentA.vue
|──────index.js

Script加上启动命令

package.jsonscripts加上这个。端口可以自己定。这里是9001, 我最喜欢的数字,才怪,乱打的。

"storybook": "start-storybook -p 9001 -c .storybook"

最后

终端启动。

yarn storybook

然后 Enjoy it .

杂记

一个完整的命令行程序

昨天在看Commander.js的时候了解到了一个命令行程序是如何运行的。这里记录一下。

首先我们先研究下命令行程序是怎么排版的。-short是短标记也就是通常说的简写。--long或者--no-long是我们常说的命令的完整写法。<...>尖括号包含的参数是必选参数。[...]包含的是可选参数。

大致如下所示

Usage: 描述这个命令行程序是怎么使用的

Options: -short --long requied  <...>  参数必选
         -short --long optional [...]  参数可选
         -short --no-long              非选项,表示使用的时候不需要这个选项

Command: 可以理解为子命令, 它们也拥有自己完整的usage和options

拿一个deploy命令来说明一下

$ node deploy -h

  Usage: deploy [options] [command]


  Options:

    -V, --version        output the version number
    -C, --chdir <path>   change the working directory
    -c, --config <path>  set config path. defaults to ./deploy.conf
    -T, --no-tests       ignore test hook
    -h, --help           output usage information


  Commands:

    setup                          run remote setup commands
    exec <cmd>                     run the given remote command
    teardown <dir> [otherDirs...]  run teardown commands
                                   deploy the given env

Commands可以理解为子命令行。一样可以使用-h来查看属于他的完整的帮助选项

$ node deploy setup -h                  
                                        
  Usage: setup [options]                
                                        
  run remote setup commands             
                                        
                                        
  Options:                              
                                        
    -h, --help  output usage information

其中发现一段比较有意思的就是这个pr和对应的issues。我们会发现Commander.js的源码里面没有用到console.log而是用的process.stdout.write。但是在最开始的版本使用的是console.log。后面不用的原因是为有一些第三方库会改动console.log函数让他变成其他的格式。打印的时候会出问题。

StackOverflow上有一个比较两个函数的区别的问题。但是已经过时了。实现已经更改。

Cross-Orgin , host ,hostname ,domain name

同源策略里面的跨域指的不是Cross domain而是Cross-originOrgin的组成如下:

orgin = scheme + host(:port)

只有这部分完全一致才是同域(同源)。比如说下面这两个就不是同域的。他们的scheme部分不一致。

http://www.baidu.com

https://www.baidu.com

下面三个也不同,因为他们的host部分不一致。

http://www.baidu.com

http:/baidu.com

http:/tieba.baidu.com

以下是一致的

http://www.baidu.com

http://www.baidu.com/fuck

host组成如下。其中hostnamedomain name算是一个意思,wiki可查。hostname可以是ip也可以是字符。

host = domain name : port

比如下面的都是合法的hostname

www.baidu.com

baidu.com

127.0.0.1

其中port(端口)部分是可以省略的。省略的话根据协议有个默认值。http默认是80,https默认是443。

domain name可以分为顶级域名以及二级域名还有子域名(三级域名)。还是拿www.baidu.com做例子。.com部分叫做顶级域名。baidu是二级域名,往后的www或者其他的都叫做子域名。但是一般国内的叫法是这样的baidu.com算作是顶级域名。而www算作是二级域名。其实这个叫法不正确但是用的多了被广泛接受,就也算是对的了。

Js的Bind函数

Bind 函数绑定过this以后。这个this无法被修改。即便是使用call或者apply

Bind 是ES5的函数。不支持的话可以这样实现一个类似的Polyfill。

BInd 在ES6以后。会带一个name属性。输出的是源函数的名字。格式是bound <origin>

Why does JavaScript hoist variables ?

As Stoyan Stefanov explains in "JavaScript Patterns" book, the hoisting is result of JavaScript interpreter implementation.

The JS code interpretation performed in two passes. During the first pass, the interpreter processes variable and function declarations.

The second pass is actually code execution step. The interpreter processes function expressions and undeclared variables.

Thus, we can use the "hoisting" concept to describe such behavior.

Hope this helps.

Debounce

适合做debunce的地方有很多我没有发现的。除了都知道的输入框意外。hover在头像也可以做。因为hover在用户的头像上,可能只是因为用户的误触,这里也是一个适合做debounce的地方。

Svg Mask

Svg Mask 计算公式

(0.0125*red value + 0.7154 * green value +0.0721 * blue value) *opacity value

这里有demo可以看

已经打不开了 感兴趣的可以看SVG_Essentials的Clipping_and_Masking章节

redux-saga 实现的delay函数

export function delay(ms, val = true) {
  let timeoutId
  const promise = new Promise(resolve => {
    timeoutId = setTimeout(() => resolve(val), ms)
  })

  promise[CANCEL] = () => clearTimeout(timeoutId)

  return promise
}

nvm 使用

昨天把node升到了v10.0.0。今天装依赖的时候出问题了。

image

原因是: Expected version ">=4 <=9

于是下载了nvm。node version manger。安装v8.11.1。并切换版本。

image

愉快的进行安装

image

Github Archiving repositories

Just because a repository isn’t actively developed anymore and you don’t want to accept additional contributions doesn’t mean you want to delete it. Now archive repositories on GitHub to make them read-only.

偶尔我们会看见一些人的项目标记了一个archived,比如这个项目, 顶部有一条黄色的东东。比如babel-preset-env。为什么要归档项目。上面说到了。你不想更新了也不想接受pr了,但你不想删掉项目。你就可以开启归档,让项目变成只读的。

require.main === module

当 Node.js 直接运行一个文件时,require.main 会被设为它的 module
这意味着可以通过 require.main === module 来判断一个文件是否被直接运行:

{ component: Component, ...rest }

这个写法(解构部分的冒号)我不知道叫什么,但是很有用在使用React的时候。

const Example = ({ component: Component, ...rest }) => (
  return (
     <Component {...rest} />
  )
)

React异步加载组件指北

在日常开发中我们都会碰见需要异步加载组件的情况,就比如说一个后台项目。拥有各种权限的分配,部分成员看得见一些表图页面,而一部分人看不见。我们知道表图库比如Echart之累的都满大的,如果我们直接加载进来全部打包到一个bundle的话,整个bundle的体积会比较大。并且除此之外很多页面组件用不上,也许用户一直都不会访问到,所以也没有理由加载,浪费带宽和资源。

这里说的异步加载都是在围绕着router来说,因为router一般都会做这个处理。react开发者的话会选择react-router作为处理控制路由的库。我们先来看看两种不同的场景。使用了异步加载和没有使用异步加载。以我的博客dura来举例子。

没有异步加载组件的情况

我们来看看没有异步载的情况。全部都打包在一个bundle里面的写法。

import React, { Component } from 'react'
import './css/App.css'
import { Route, Switch } from 'react-router'

import HomePage from 'page/HomePage'
import AboutPage from 'page/AboutPage'
import ArchivesPage from 'page/ArchivesPage'
import ClosedPage from 'page/ClosedPage'
import LabelPage from 'page/LablePage'
import LabelsPage from 'page/LablesPage'
import TimeLinePage from 'page/TimelinePage'
import NotFoundPage from 'page/NotFoundPage'

class App extends Component {
  render () {
    return (
      <Switch>
        <Route exact path="/" component={HomePage}  />
        <Route exact path="/about" component={AboutPage}  />
        <Route exact path="/archives" component={ArchivesPage}  />
        <Route exact path="/closed" component={ClosedPage}  />
        <Route exact path="/labels" component={LabelsPage}  />
        <Route exact path="/label/:number" component={LabelPage}  />
        <Route exact path="/timeline" component={TimeLinePage} />
        <Route component={NotFoundPage} />
      </Switch>
    )
  }
}

export default App;

NetWorkd加载的情况

全部打包的情况

我们发现所有东西都打包在了一起,而不是针对页面进行打包,这样会加载无用资源。此时的bundle会是700kb,对我这个项目来说。

使用异步加载

我们来看看异步加载的情况。

我们可以利用webpack 2.0+的特性import(...)语法来做异步加载,这样webpack会自动进行代码的chunk。只有在需要用到的时候才会引用。利用这一特性我们创建一个AsyncComponet的HOC组件帮助我们进行异步加载。

AsyncComponet.js

import React, { Component } from 'react';

export default function asyncComponent(importComponent) {
  class AsyncComponent extends Component {
    constructor(props) {
      super(props);
      this.state = {
        component: null,
      };
    }
    async componentDidMount() {
      const { default: component } = await importComponent();
      this.setState({
        component: component
      });
    }
    render() {
      const C = this.state.component;
      return C
        ? <C {...this.props} />
        : null;
    }
  }
  return AsyncComponent;
}

App.js

import React, { Component } from 'react'
import './css/App.css'
import { Route, Switch } from 'react-router'

// using webpack import syntax up performance
import AsyncComponent from 'hoc/AsyncComponent'
const HomePage = AsyncComponent(() => import('page/HomePage'))
const AboutPage = AsyncComponent(() => import('page/AboutPage'))
const ArchivesPage = AsyncComponent(() => import('page/ArchivesPage'))
const ClosedPage = AsyncComponent(() => import('page/ClosedPage'))
const LabelPage = AsyncComponent(() => import('page/LablePage'))
const LabelsPage = AsyncComponent(() => import('page/LablesPage'))
const TimeLinePage = AsyncComponent(() => import('page/TimelinePage'))
const NotFoundPage = AsyncComponent(() => import('page/NotFoundPage'))

class App extends Component {
  render () {
    return (
      <Switch>
        <Route exact path="/" component={HomePage}  />
        <Route exact path="/about" component={AboutPage}  />
        <Route exact path="/archives" component={ArchivesPage}  />
        <Route exact path="/closed" component={ClosedPage}  />
        <Route exact path="/labels" component={LabelsPage}  />
        <Route exact path="/label/:number" component={LabelPage}  />
        <Route exact path="/timeline" component={TimeLinePage} />
        <Route component={NotFoundPage} />
      </Switch>
    )
  }
}

export default App;

NetWorkd加载的情况

chunk

我们现在再来看bundle的大小,我们会发现bundle缩小到了400kb左右,虽然这个小项目不是太明显也就是缩少了大概百分之40这样子。但是对于大型项目来说,这个倍率会网上递增。

除此之外在我们访问不同的页面的时候,才会对应的chunk

总结

AsyncCompoent利用了webpack懒加载/code spliting特性达到了异步加载的目的。但是AsyncComponet并不完整,存在着很多的问题,比如说加载失败的问题,这里就没有处理,延迟加载,服务端处理,细节上的也没有,比如加载组件过程中的占位(placeholder)组件。

一个更好的解决方案会是使用react-loadable组件做这个事情。不过这篇不做介绍,放下一次吧(可能)。

npm hook

我们可以在script的前面加pre或者post做一些预操作和执行后的操作。举个例子

{
  "name": "myproject",
  "devDependencies": {
    "eslint": "latest"
    "karma": "latest"
  },
  "scripts": {
    "lint": "eslint --cache --ext .js --ext .jsx src",
    "test": "karma start --log-leve=error karma.config.js --single-run=true",
    "pretest": "npm run lint",
    "posttest": "echo 'Finished running tests'"
  }
}

当我们执行npm test的时候会照这个顺序执行。

pretest #执行 npm lint
test
posttest

以下是npm提供的一些hook,抄自阮一峰:

script name 作用
prepublish 发布一个模块前执行。
postpublish 发布一个模块后执行。
preinstall 用户执行npm install命令时,先执行该脚本。
postinstall 用户执行npm install命令时,安装结束后执行该脚本,通常用于将下载的源码编译成用户需要的格式,比如有些模块需要在用户机器上跟本地的C++模块一起编译。
preuninstall 卸载一个模块前执行。
postuninstall 卸载一个模块后执行。
preversion 更改模块版本前执行。
postversion 更改模块版本后执行。
pretest 运行npm test命令前执行。
posttest 运行npm test命令后执行。
prestop 运行npm stop命令前执行。
poststop 运行npm stop命令后执行。
prestart 运行npm start命令前执行。
poststart 运行npm start命令后执行。
prerestart 运行npm restart命令前执行。
postrestart 运行npm restart命令后执行。

对于最后一个npm restart命令,如果没有设置restart脚本,prerestartpostrestart会依次执行stop和start脚本。

Java Nested Classes

Nested Classes is a very important concept in Java . This note Contains the main parts:

  • Introduce Nested Classes
  • Inner Class Example
  • Local Classes
  • Anonymous Classes
  • Lambda in Java
    • Method References

Reference from Java Tutorials.

React Native Explore [一]

先下载cli 点按钮 Building Projects with Native Code

http://facebook.github.io/react-native/docs/getting-started.html

照着做就好了。

都很简单 唯一难的是我没有MacBook, orz,,,

打包教程也非常简单,照着这个做,设置好变量然后就好了。

http://facebook.github.io/react-native/docs/signed-apk-android.html

...
android {
    ...
    defaultConfig { ... }
    signingConfigs {
        release {
            if (project.hasProperty('MYAPP_RELEASE_STORE_FILE')) {
                storeFile file(MYAPP_RELEASE_STORE_FILE)
                storePassword MYAPP_RELEASE_STORE_PASSWORD
                keyAlias MYAPP_RELEASE_KEY_ALIAS
                keyPassword MYAPP_RELEASE_KEY_PASSWORD
            }
        }
    }
    buildTypes {
        release {
            ...
            signingConfig signingConfigs.release
        }
    }
}
...

下面那个signingConfig 别忘了,我第一次弄没太注意到,注意力集中在了上面那一块,打包出了一个没有签名的apk = = 打包出来的大小可以接受只有7MB。debug版本的大一点点。

顺便一提。如果你没有代理会很慢。如果你有的话改一下配置。C:\Users\你的用户名\.gradle里面的gradle.properties下面加两行。速度飙升。如果没有这个文件就新建一个(如果你按照教程做的话,看到这里理应应该建好了)

systemProp.https.proxyPort=1080
systemProp.https.proxyHost=127.0.0.1

ok,总之就这样可以开始探索一波RN了。整个过程非常流畅没什么阻碍,大概这就是大佬的项目吧...

PS: 本来想用vue写的就看中了阿里的weex。由于对阿里家的东西本来就有点点抵触就放弃了(其实不是因为抵触是因为点开项目看见乱七八糟,展示页面的图片还是发虚的怕了怕了)。

you need to understand some of the basic React concepts, like JSX, components, state, and props. If you already know React, you still need to learn some React-Native-specific stuff, like the native components.

学习RN之前要先看看React的文档。起码要知道JSX,组件,state,props之类的概念如果知道的话。剩下的就是了解一下RN特有的一些东东。

RN的文档很舒服前面还会帮你复习一下React的知识。样式之类的是用js写的。基本上要用的都有,比如background-color就是backgroundColor。采用Camel-Case命名。

我发现照着文档做的话生成的APP是一个安卓图标叫做AwesomeProject的东东。于是我把它换成了老子的形状我想要的样子。打开android/app/src/rec那个文件夹。这个文件夹是放一些资源的,recrecouses的缩写。你会发现有好几个不同分辨率的文件夹。分别是hdpi,mdpi,xhdpi,xxhdpi里面都对应了一个图标。我们可以用Android Studio生成一个,但是我不会。于是我用了StackOverFlow老铁推荐的一个网站。选一张你喜欢的图片然后搞搞搞,勾上这四个分辨率的勾勾然后下载就好了。下下来替换一下名字。你还会发现有个values文件夹里面有个strings.xml里面的内容也很好懂就是你的app_name改成你想要的就好了。。

都弄完了就

react-native run android

运行完就看得到你想要的应用图标和名字啦。

image

image别问我为什么不写iphone相关的,因为我没有开发环境(也不是很想搞,官網没有钦定window可以开发)...

RN支持FlexBox但是有一点点差异

Flexbox works the same way in React Native as it does in CSS on the web, with a few exceptions. The defaults are different, with flexDirection defaulting to column instead of row, and the flex parameter only supporting a single numb

Emmmm 还行。

学习RN的主要目的是做出一个手机音乐播放器。我查了一圈官方文档并没有自带的audio或者video组件。然后就找了一个比较热门的react-native-video。音乐的API打算用这个

刚装完react-native-video就报错了。

This might be related to https://github.com/facebook/react-native/issues/4968
To resolve try the following:
  1. Clear watchman watches: `watchman watch-del-all`.
  2. Delete the `node_modules` folder: `rm -rf node_modules && npm install`.
  3. Reset Metro Bundler cache: `rm -fr $TMPDIR/react-*` or `npm start -- --reset-cache`.
    at ModuleResolver.resolveNodeDependency (C:\Users\nbsaw\Desktop\examples\AwesomeProject\node_modules\metro-bundler\src\node-haste\DependencyGraph\ModuleResolution.js:292:11)
    at ModuleResolution.tryResolveSync (C:\Users\nbsaw\Desktop\examples\AwesomeProject\node_modules\metro-bundler\src\node-haste\DependencyGraph\ResolutionRequest.js:103:16)
    at Object.tryResolveSync (C:\Users\nbsaw\Desktop\examples\AwesomeProject\node_modules\metro-bundler\src\node-haste\DependencyGraph\ModuleResolution.js:118:12)
....

让我看这条issues。Emmmm 试一下,让我删掉依赖。看看是否能否工作。

五分钟过去了....有点慢,如果有代理的话改改

vim ~/.npmrc

加两行

proxy = http://127.0.0.1:1080
https-proxy = http://127.0.0.1:1080

Boom

C:\Users\nbsaw\Desktop\examples\AwesomeProject\node_modules\weak>if not defined npm_config_node_gyp (node "C:\Users\nbsaw\AppData\Roaming\npm\node_modules\npm\node_modules\npm-lifecycle\node-gyp-bin\\..\..\node_modules\node-gyp\bin\node-gyp.js" rebuild )  else (node "C:\Users\nbsaw\AppData\Roaming\npm\node_modules\npm\node_modules\node-gyp\bin\node-gyp.js" rebuild )
gyp ERR! configure error
gyp ERR! stack Error: Can't find Python executable "C:\Users\nbsaw\AppData\Local\Programs\Python\Python36-32\python.EXE", you can set the PYTHON env variable.
gyp ERR! stack     at PythonFinder.failNoPython (C:\Users\nbsaw\AppData\Roaming\npm\node_modules\npm\node_modules\node-gyp\lib\configure.js:483:19)
gyp ERR! stack     at PythonFinder.<anonymous> (C:\Users\nbsaw\AppData\Roaming\npm\node_modules\npm\node_modules\node-gyp\lib\configure.js:508:16)
gyp ERR! stack     at C:\Users\nbsaw\AppData\Roaming\npm\node_modules\npm\node_modules\graceful-fs\polyfills.js:284:29
gyp ERR! stack     at FSReqWrap.oncomplete (fs.js:152:21)
gyp ERR! System Windows_NT 10.0.15063
gyp ERR! command "C:\\Program Files\\nodejs\\node.exe" "C:\\Users\\nbsaw\\AppData\\Roaming\\npm\\node_modules\\npm\\node_modules\\node-gyp\\bin\\node-gyp.js" "rebuild"
gyp ERR! cwd C:\Users\nbsaw\Desktop\examples\AwesomeProject\node_modules\weak
gyp ERR! node -v v8.9.1
gyp ERR! node-gyp -v v3.6.2
gyp ERR! not ok
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: [email protected] (node_modules\fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for [email protected]: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: [email protected] (node_modules\weak):
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: [email protected] install: `node-gyp rebuild`
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: Exit status 1

up to date in 22.782s

看了下是node-gyp的问题。官网看了下原因是因为我是Python3.6不支持,只支持Python2.7..

Python 2.7 (v3.x.x is not supported),

还要安一些其他的[东西] 很麻烦直接用选项一image

npm install --global --production windows-build-tools

好了搞定。运行一下项目试试。运行正常。据其他人说这个项目是可以播放mp3的可是没有放出来。MP4倒是很正常。

好了work了 但不知道为什么...

image 本地的mp3播放不出来,丢个外链却好了。暂时先不管.. 今天到这里... 明天要面试,刷刷面试题

Count-primes

Let's start with a isPrime function. To determine if a number is prime, we need to check if it is not divisible by any number less than n. The runtime complexity of isPrime function would be O(n) and hence counting the total prime numbers up to n would be O(n2). Could we do better?

As we know the number must not be divisible by any number > n / 2, we can immediately cut the total iterations half by dividing only up to n / 2. Could we still do better?

Let's write down all of 12's factors:

2 × 6 = 12
3 × 4 = 12
4 × 3 = 12
6 × 2 = 12

As you can see, calculations of 4 × 3 and 6 × 2 are not necessary. Therefore, we only need to consider factors up to √n because, if n is divisible by some number p, then n = p × q and since p ≤ q, we could derive that p ≤ √n.

Our total runtime has now improved to O(n^1.5), which is slightly better. Is there a faster approach?

public int countPrimes(int n) {
   int count = 0;
   for (int i = 1; i < n; i++) {
      if (isPrime(i)) count++;
   }
   return count;
}

private boolean isPrime(int num) {
   if (num <= 1) return false;
   // Loop's ending condition is i * i <= num instead of i <= sqrt(num)
   // to avoid repeatedly calling an expensive function sqrt().
   for (int i = 2; i * i <= num; i++) {
      if (num % i == 0) return false;
   }
   return true;
}

Less 学习笔记

上午面了一家公司,面试官是公司的技术总监感觉特别nb说话起来,于是问了一下公司的技术栈等等。好奇心驱使问了一下公司用了哪一种预处理器。得知选择了Less作为预处理器。因为公司用使用了Ant Design那一套,于是心里就有b数了为什么选Less。因为Ant D用的预处理器就是Less。所以我想借此机会看看Less,之前有看过Sass,还有css in js (styled-components)等等方案。所以看Less也很轻松,这里记录一下看到的一些东西。

变量设置

@width: 10px;
@height: @width + 10px;

#header {
  width: @width;
  height: @height;
}

Outputs:

#header {
  width: 10px;
  height: 20px;
}

Mixins

Mixins就是把一组规则和其他规则混合(组合)在一起。假设有下面这个样式

.bordered {
  border-top: dotted 1px black;
  border-bottom: solid 2px black;
}

我们想要把这组规则利用到其他的规则集里。我们只需要在需要使用这些属性的类名中插入这样的东西:

#menu a {
  color: #111;
  .bordered();
}

.post a {
  color: red;
  .bordered();
}

现在.bordered的属性会做用到#menu a.post a里(我们也可以用#ids做为mixin的命名)。

Nesting

Less 让我们能够写出内联的而非级联的CSS样式。假设我们有下面的级联样式

#header {
  color: black;
}
#header .navigation {
  font-size: 12px;
}
#header .logo {
  width: 300px;
}

但是在Less里面我们可以写成这样

#header {
  color: black;
  .navigation {
    font-size: 12px;
  }
  .logo {
    width: 300px;
  }
}

这样的代码更加的简洁。而且和HTML的内联相似。

我们还可以在内联里面使用伪类。只需要通过一个&符号。这里有一个非常经典的清理浮动的样式。

.clearfix {
  display: block;
  zoom: 1;

  &:after {
    content: " ";
    display: block;
    font-size: 0;
    height: 0;
    clear: both;
    visibility: hidden;
  }
}

Nested At-Rules and Bubbling

有一些规则选择器比如@media@supports也可以内联。如果内敛的话这些规则会被放在顶层而不是内部。举个例子

.component {
  width: 300px;
  @media (min-width: 768px) {
    width: 600px;
    @media  (min-resolution: 192dpi) {
      background-image: url(/img/retina2x.png);
    }
  }
  @media (min-width: 1280px) {
    width: 800px;
  }
}

输出结果

.component {
  width: 300px;
}
@media (min-width: 768px) {
  .component {
    width: 600px;
  }
}
@media (min-width: 768px) and (min-resolution: 192dpi) {
  .component {
    background-image: url(/img/retina2x.png);
  }
}
@media (min-width: 1280px) {
  .component {
    width: 800px;
  }
}

Operations

算术表达式+,-,*,/可以计算任何的数字,颜色和变量。如果可能,数学运算会将单位考虑在内,并在进行加法,减法或者比较之前转换数字。计算出来的结果会依赖最左边的单位。如果单位之间无法转换,单位会被忽略掉,然后直接计算出来。

// numbers are converted into the same units
@conversion-1: 5cm + 10mm; // result is 6cm
@conversion-2: 2 - 3cm - 5mm; // result is -1.5cm

// conversion is impossible
@incompatible-units: 2 + 5px - 3cm; // result is 4px

// example with variables
@base: 5%;
@filler: @base * 2; // result is 10%
@other: @base + @filler; // result is 15%

乘法和加法都不会转换数字的单位。

@base: 2cm * 3mm; // result is 6cm

我们可以对颜色进行算术运算:

@color: #224488 / 2; //results in #112244
background-color: #112244 + #111; // result is #223355

虽然可以但是更推荐使用Less自带的Color Function

calc 表达式

calc表达式和算数表达是不一样的,他只会执行变量和数学方法的结果。

@var: 50vh/2;
width: calc(50% + (@var - 20px));  // result is calc(50% + (25vh - 20px))

Escaping

Escaping 允许我们把一个任意的字符串作为变量名或者一个属性。语法是~"anything"或者~'anything'。这样自负就不会被解释器转换。

@min768: ~"(min-width: 768px)";
.element {
  @media @min768 {
    font-size: 1.2rem;
  }
}

结果

@media (min-width: 768px) {
  .element {
    font-size: 1.2rem;
  }
}

Less 3.5 之前的话,直接写就可以了

@min768: (min-width: 768px);
.element {
  @media @min768 {
    font-size: 1.2rem;
  }
}

Functions

Less自带了很多操作颜色,操作字符串和进行数学运算的方法。这些方法使用起来都非常的简单。下面这个例子展示了使用percentage方法把0.5转换成50%。把@base的饱和度调成5%。把background设置为25%的明亮再旋转8%的角度。

@base: #f04615;
@width: 0.5;

.class {
  width: percentage(@width); // returns `50%`
  color: saturate(@base, 5%);
  background-color: spin(lighten(@base, 25%), 8);
}

Namespaces and Accessors

如果你想要使用命名空间重用或者分发一些样式的时候

#bundle() {
  .button {
    display: block;
    border: 1px solid black;
    background-color: grey;
    &:hover {
      background-color: white;
    }
  }
  .tab { ... }
  .citation { ... }
}

你可以直接这样使用

#header a {
  color: orange;
  #bundle.button();  // can also be written as #bundle > .button
}

Maps

从3.5开始我们可以使用map . 就像js的对象一样。

#colors() {
  primary: blue;
  secondary: green;
}

.button {
  color: #colors[primary];
  border: 1px solid #colors[secondary];
}

Scope

个人认为Less的作用域很奇怪。两个例子可以证明。

@var: red;

#page {
  @var: white;
  #header {
    color: @var; // white
  }
}

第一个例子很正常。而第二个感觉就比较反人类。

@var: red;

#page {
  #header {
    color: @var; // white
  }
  @var: white;
}

输出的结果如下

.button {
  color: blue;
  border: 1px solid green;
}

Comments

可以使用多行和单行注释

/* One heck of a block
 * style comment! */
@var: red;

// Get in line!
@var: white;

Importing

如果想要引用其他的less直接使用@import语法就好了

@import "library"; // library.less
@import "typo.css";

Less仿佛没有流程控制语句,体流程控制是作为Function的形式实现的。

TCP/IP

网络协议简述

网络协议通常分不同层次进行开发,每一层分别负责不同的通信功能。一个协议族,比如 TCP/IP,是
一组不同层次上的多个协议的组合。 TCP/IP通常被认为是一个四层协议系统

image

  • 链路层, 有时也称作数据链路层或网络接口层,通常包括操作系统中的设备驱动程序计算机中对应的网络接口卡。它们一起处理与电缆(或其他任何传输媒介)的物理接口细节。

  • 网络层,有时也称作互联网层,处理分组在网络中的活动,例如分组的选路。在TCP/IP协议族中,网络层协议包括IP协议(网际协议),ICMP协议(Internet互联网控制报文协议),以及I G M P协议(Internet组管理协议)

  • 运输层主要为两台主机上的应用程序提供端到端的通信。在TCP/IP协议族中,有两个互不相同的传输协议:TCP(传输控制协议)和UDP(用户数据报协议)。TCP为两台主机提供高可靠性的数据通信。它所做的工作包括把应用程序交给它的数据分成合适的小块交给下面的网络层,确认接收到的分组,设置发送最后确认分组的超时时钟等。由于运输层提供了高可靠性的端到端的通信,因此应用层可以忽略所有这些细节。而另一方面,UDP则为应用层提供一种非常简单的服务。它只是把称作数据报的分组从一台主机发送到另一台主机,但并不保证该数据报能到达另一端。任何必需的可靠性必须由应用层来提供。简单来说就是TCP提供了可靠的传输。应用程可以不用自己保证数据的完整性。UDP只负责传输数据。不管数据成不成功。应用层可能需要自己去保证数据的完整。

应用层负责处理特定的应用程序细节。几乎各种不同的 TCP/IP实现都会提供下面这些通用的应用程序:

  • Telnet 远程登录
  • FTP 文件传输协议
  • SMTP 简单邮件传送协议
  • SNMP 简单网络管理协议

虽然TCP/IP通常被认为是一个四层协议系统。但真正的网络模型分了七层。它是由ISO制定的。如图下所示

image

相关资料: OSI模型

React Context API

Context provides a way to pass data through the component tree without having to pass props down manually at every level.

Context提供了一种在组件树中传递数据的方法,而不必在每个级别手动向下传递道具。

In a typical React application, data is passed top-down (parent to child) via props, but this can be cumbersome for certain types of props

通常来说在一个典型的React应用中,数据自上而下(从父到子)通过props传播,但是这样对某些情景来说很复杂,很不爽很笨重。

Context is designed to share data that can be considered “global” for a tree of React components, such as the current authenticated user, theme, or preferred language. For example, in the code below we manually thread through a “theme” prop in order to style the Button component:

概述: 你可以把Context视为全局的状态。我们可以用一个简单的theme的例子来说明:

class App extends React.Component {
  render() {
    return <Toolbar theme="dark" />;
  }
}

function Toolbar(props) {
  return (
    <div>
      <ThemedButton theme={props.theme} />
    </div>
  );
}

function ThemedButton(props) {
  return <Button theme={props.theme} />;
}

在这个例子中Button只是为了获取theme,经过了一层一层的组件传递 最终才拿到了theme。

Using context, we can avoid passing props through intermediate elements:

但是利用context的话。我们能避免一层一层的传props:

const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton(props) {
  return (
    <ThemeContext.Consumer>
      {theme => <Button {...props} theme={theme} />}
    </ThemeContext.Consumer>
  );
}

我们可以直接通过Consumer获得theme。中间层的Toolbar无需传递props了。

更多详细的API可以自己查阅。Context API设计的很简,但是很强大。不过这只是一个featrue,未来也许会被remove。

Styled Components 项目结构

这篇笔记能给不知道如何组织styled-component项目文件的人一点灵感。

styled-component帮助我们创建了可预测,可组合的样式我们可以在整个应用中服复用它们。我们借用了BEM的想法来组织我们的代码。也许这不是最佳实践但是多多少少会让你有一些感觉。

根据BEM的风格。我们把样式分成了: Block , Element, Modifiers 这三种。这也是BEM的简介。分别对应的是块级元素,元素,修饰符 (一些可以共用复用的样式)。

通过项目一张图或者说一个Card组件能非常好的解释这一切。

image

BLOCKS

Block是Block,Elements, Modifiers三种里面最高级的抽象。Block负责为Elements提供上下文、呈现Elements和处理UI逻辑。Block不连接到应用程序状态,也不处理任何业务逻辑, 把Elements拼起来。Elments负责展示图片文字视图级别的东西。Elements可以是某个Block专属的Elements,或者是可以共用的Elements(例如A,Link,H1)。Modifiers是用来修饰Elements的。

这个Card组件是一个Block。Card不会响应任何业务逻辑。它由这些child Elements:组成Header, Image, Text, 还有 Title

Card Block的文件结构

├ src/
├── blocks/
| ├── Card/
| | ├── Header.js
| | ├── Image.js
| | ├── Text.js
| | ├── Title.js
| | └── index.js // <- Block

NOTE: 需要注意的一点是Block里面最好不要嵌套Block。文件目录应该是平滑的,避免过度嵌套。我们的目标是复用元素。

Card Block Component

// src/blocks/Card/index.js

import styled from 'styled-components';

import Header from './Header';
import Image from './Image';
import Text from './Text';
import Title from './Title';

const Card = styled.div`
  background: #ffffff;
  border-radius: 2px;
  margin: 5px 5px 10px;
  padding: 5px;
  position: relative;
  box-shadow: 2px 2px 4px 0px #cfd8dc;
`;

Card.Header = Header;
Card.Image = Image;
Card.Text = Text;
Card.Title = Title;

export default Card;

这样的写法允许我们这样使用Block:

// SomeComponent.js

import Card from './blocks/Card';

...
<Card>
  <Card.Header>
    <Card.Image
      alt=”bob-ross-headshot
      src=”www.example.com/bob-ross.jpg”
    />
    <Card.Title>
      Bob Ross
    </Card.Title>
  </Card.Header>
  <Card.Text>
    Robert Norman Ross (October 29, 1942 – July 4, 1995) was an American painter,
    art instructor, and television host. He was the creator and host of
    The Joy of Painting, an instructional television program that aired from
    1983 to 1994 on PBS in the United States…
  </Card.Text>
</Card>
...

这种写法非常清晰且可读。相较于这种引入方式写法会更加简洁:

import Card from './blocks/Card';
import CardHeader from './blocks/Card/Header';
import CardImage from './blocks/Card/Image';
import CardText from './blocks/Card/Text';
import CardTitle from './blocks/Card/Title';

所有的Elements都可以从Block里面获取到这样可以避免团队中的很多摩擦。

Elements

Elements 是最小的单元。用来组成Block,可以用Modifer修饰。

Elements 可以是Block专有。也可以是整个项目的。一些通用的元素可以放在elements里面。文件目录结构如下。

├ src/
├── blocks/
| ├── Card/
| | ├── Header.js // <- Element
| | ├── Image.js  // <- Element
| | ├── Text.js   // <- Element
| | ├── Title.js  // <- Element
| | └── index.js  // <- Block
├── elements/
| ├── A.js        // <- Element
| ├── H3.js       // <- Element
| ├── Link.js     // <- Element
| ├── P.js        // <- Element

Modifiers

关于Modifiers这个不是styled-component官方提供的东西。但是的确是蛮不错的。但是我不打算用就是了。Modifiers就是组件可以共用的一些行为,比如hover,字符大小写或者其他的一些交互行为。

const modifierConfig = {
  darkGreyText: ({ theme }) => `
    color: ${theme.colors.base.text};
  `,

  midGreyText: ({ theme }) => `
    color: ${theme.colors.base.textLight};
  `,
  
  uppercase: () => `
    text-transform: uppercase;
  `,
};

看起来就会像这样。用起来会是这样。

<A
  href="https://example.com"
  modifiers={['darkGreyText', 'uppercase']}
/>

但是你要用的话 你得用styled-components-modifiers这个库哦。

参考

Structuring our Styled Components

Hello Redux

简介

Redux is a predictable state container for JavaScript apps. It helps you write applications that behave consistently, run in different environments (client, server, and native), and are easy to test.

Redux是一个可以预测的状态管理容器。帮助我们在不同的环境写出运行一致并且易于测试的应用程序。

You can use Redux together with React, or with any other view library.

我们可以和React一起使用Redux或者其他的任何视图库。

It is tiny (2kB, including dependencies).

很轻(只有2kb,包括了依赖)。

Learn Redux from its creator:

Part 1: Getting Started with Redux (30 free videos)
Part 2: Building React Applications with Idiomatic Redux (27 free videos)

和Redux的创造者一起学习Redux:

视频都是免费的。没看 我也不知道质量怎样... 应该不错吧 嗯

使用Redux的动机

As the requirements for JavaScript single-page applications have become increasingly complicated, our code must manage more state than ever before.Managing this ever-changing state is hard. If a model can update another model, then a view can update a model, which updates another model, and this, in turn, might cause another view to update. When a system is opaque and non-deterministic, it's hard to reproduce bugs or add new features.

随着单页面应用日渐复杂必须管理比以往更多的状态。管理每个状态很麻烦。如果有一个模型更新了其他的模型,然后一个视图又可以更新其他的模型,该模型会更新另一个模型,而这又可能导致另一个视图更新。当系统日渐复杂,就很难重现bug和添加新特性了。

Redux attempts to make state mutations predictable

是不是很可怕,怕了就来用Redux吧。Redux紧随Flux,CQRS和Event Sourcing,Redux让状态变化变得可以预测,是不是很叼。快来使用吧,介是你没用过的全新版本。

赞誉

“Love what you're doing with Redux”
Jing Chen, creator of Flux

“I asked for comments on Redux in FB's internal JS discussion group, and it was universally praised. Really awesome work.”
Bill Fisher, author of Flux documentation

“It's cool that you are inventing a better Flux by not doing Flux at all.”
André Staltz, creator of Cycle

反正就是说Redux牛逼。Flux的创造者和Cycle的创造者都赞赏有加。

核心概念

Redux itself is very simple.

Redux本身非常简单。

Imagine your app’s state is described as a plain object. For example, the state of a todo app might look like this:

想像一下你的app状态被描述成一种纯对象。举个例子,一个todo程序的状态看起来是这样的:

{
  todos: [{
    text: 'Eat food',
    completed: true
  }, {
    text: 'Exercise',
    completed: false
  }],
  visibilityFilter: 'SHOW_COMPLETED'
}

This object is like a “model” except that there are no setters. This is so that different parts of the code can’t change the state arbitrarily, causing hard-to-reproduce bugs.

这个状态看起来像极了"模型"除了没有setter。也是因为没有setter,所以这段代码不能随意的更改状态,导致难以复现的bug。

To change something in the state, you need to dispatch an action. An action is a plain JavaScript object (notice how we don’t introduce any magic?) that describes what happened. Here are a few example actions:

如果要改变状态,需要dispatch一个action。action是一个纯js对象描述了发生了啥。下面有一个action的栗子:

{ type: 'ADD_TODO', text: 'Go to swimming pool' }
{ type: 'TOGGLE_TODO', index: 1 }
{ type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_ALL' }

Enforcing that every change is described as an action lets us have a clear understanding of what’s going on in the app. If something changed, we know why it changed.Actions are like breadcrumbs of what has happened. Finally, to tie state and actions together, we write a function called a reducer. Again, nothing magical about it—it’s just a function that takes state and action as arguments, and returns the next state of the app. It would be hard to write such a function for a big app, so we write smaller functions managing parts of the state:

强制每个变化都需要描述一个action让我们清晰的明白app正在发生什么。如果有东西变了,我们会知道为什么变了。Action就像面包屑一样指引着我们到底发生了什么。最后我们为了讲state和action绑在一起。我们编写了一个叫reducer的函数。reducer也没什么大不了的 。 接收一个state和action作为参数。然后返回下一个state到app。如果你想用一个fuction管理一个巨大的应用那很麻烦啊。所以我们会把reducer拆成较小的方法去管理state:

function visibilityFilter(state = 'SHOW_ALL', action) {
  if (action.type === 'SET_VISIBILITY_FILTER') {
    return action.filter
  } else {
    return state
  }
}

function todos(state = [], action) {
  switch (action.type) {
    case 'ADD_TODO':
      return state.concat([{ text: action.text, completed: false }])
    case 'TOGGLE_TODO':
      return state.map(
        (todo, index) =>
          action.index === index
            ? { text: todo.text, completed: !todo.completed }
            : todo
      )
    default:
      return state
  }
}

And we write another reducer that manages the complete state of our app by calling those two reducers for the corresponding state keys:

然后我们编写另一个reducer,通过调用这两个reducer来查找相应的状态键来管理我们的应用程序的完整状态:

function todoApp(state = {}, action) {
  return {
    todos: todos(state.todos, action),
    visibilityFilter: visibilityFilter(state.visibilityFilter, action)
  }
}

This is basically the whole idea of Redux. Note that we haven’t used any Redux APIs. It comes with a few utilities to facilitate this pattern, but the main idea is that you describe how your state is updated over time in response to action objects.

这就是Redux的整个基本的想法。注意我们到现在都没有用任何Redux的Api。之后会有一些工具去简化这个流程。但是主要的想法还是需要你用action去描述你的state如何更新。

Actions

First, let's define some actions.

首先让我们先定义一些action。

Actions are payloads of information that send data from your application to your store. They are the only source of information for the store. You send them to the store using store.dispatch().

Action是作为信息的载荷讲数据发送到你应用程序的store里面。Action是store信息资源的唯一来源。可以使用store.dispatch()发送。

Here's an example action which represents adding a new todo item:

这里有个实例代表了添加一个todo的条目:

const ADD_TODO = 'ADD_TODO'
{
  type: ADD_TODO,
  text: 'Build my first Redux app'
}

Actions are plain JavaScript objects. Actions must have a type property that indicates the type of action being performed. Types should typically be defined as string constants. Once your app is large enough, you may want to move them into a separate module.

Action是一个纯js对象。Action必须有一个type属性表示哪一种动作将会被执行。类型通常被定一个一个string常量。一旦你的应用程序变得足够大,你可能会想把这些常量分离成一个一个模块。

import { ADD_TODO, REMOVE_TODO } from '../actionTypes'

Note on Boilerplate
You don't have to define action type constants in a separate file, or even to define them at all. For a small project, it might be easier to just use string literals for action types. However, there are some benefits to explicitly declaring constants in larger codebases. Read Reducing Boilerplate for more practical tips on keeping your codebase clean.

注意事项
我们是没要求你一定要把action的类型常量单独分出来啦。因为有些小的应用也许不分出来才是最好的,不分出来还更加简单运行的也很棒。但是如果一旦应用程序变得很大了。分出来是绝对有好处哒。

Other than type, the structure of an action object is really up to you. If you're interested, check out Flux Standard Action for recommendations on how actions could be constructed.

除了类型。其他的action的解构取决于你。如果你感兴趣,可以看Flux Standard Action里面有一些关于怎么写action的很棒的建议。

We'll add one more action type to describe a user ticking off a todo as completed. We refer to a particular todo by index because we store them in an array. In a real app, it is wiser to generate a unique ID every time something new is created.

我们将添加一个描述用户勾选todo为complted的action。我们通过index来引用特定的待办事项,因为我们将它们存储在一个数组中。在真正的应用程序中,每次创建新内容时都要生成唯一的ID。

{
  type: TOGGLE_TODO,
  index: 5
}

It's a good idea to pass as little data in each action as possible. For example, it's better to pass index than the whole todo object.

尽可能少的传数据到action好吧。例如,传index就比传整个todo对象好多了。

Finally, we'll add one more action type for changing the currently visible todos.

最终,我们将添加一个action类型去改变当前可见的todos。

{
  type: SET_VISIBILITY_FILTER,
  filter: SHOW_COMPLETED
}

Action Creators

Action creators are exactly that—functions that create actions. It's easy to conflate the terms “action” and “action creator,” so do your best to use the proper term.

Action Creators应该是我们日常开发中最常用的。而不是直接用纯纯的Action,就是上面那种对象。我们通常会用方法来描述一种Action,因为这样更加灵活。这里是故意区分术语action和action creator的。

In Redux, action creators simply return an action:

在Redux里面,action creators只是简单的返回一个action:

function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}

This makes them portable and easy to test.

这让测试变得变得更加容易。

In traditional Flux, action creators often trigger a dispatch when invoked, like so:

在传统的Flux里边。action creators在执行的时候经常会去触发dispatch,像这样:

function addTodoWithDispatch(text) {
  const action = {
    type: ADD_TODO,
    text
  }
  dispatch(action)
}

In Redux this is not the case.

在Redux里面咱们不这样。

Instead, to actually initiate a dispatch, pass the result to the dispatch() function:

取而代之的是去初始化一个dispatch,然后把结果传到dispatch方法里面:

dispatch(addTodo(text))
dispatch(completeTodo(index))

Alternatively, you can create a bound action creator that automatically dispatches:

或者你可以创建一个酱紫的

const boundAddTodo = text => dispatch(addTodo(text))
const boundCompleteTodo = index => dispatch(completeTodo(index))

Now you'll be able to call them directly:

然后你就可以直接调用他们了

boundAddTodo(text)
boundCompleteTodo(index)

The dispatch() function can be accessed directly from the store as store.dispatch(), but more likely you'll access it using a helper like react-redux's connect(). You can use bindActionCreators() to automatically bind many action creators to a dispatch() function.

dispatch方法可以直接使用store.dispatch()从store里面访问。但更多的情况下你会通过helper访问比如react-redux的connect函数。你可以使用bindActionCreators()去自动的绑定很多的action creatore到一个dispatch方法里。

Action creators can also be asynchronous and have side-effects. You can read about async actions in the advanced tutorial to learn how to handle AJAX responses and compose action creators into async control flow. Don't skip ahead to async actions until you've completed the basics tutorial, as it covers other important concepts that are prerequisite for the advanced tutorial and async actions.

Action creators也可以是异步的并且由副作用。在更高阶的教程去学习如何处理Ajax响应和组合action creators到一个异步控制流里面。请不要跳过基础章节看异步章节,因为跳过了你看不懂啊~

Reducers

Reducers specify how the application's state changes in response to actions sent to the store. Remember that actions only describe the fact that something happened, but don't describe how the application's state changes.

Reducers指定应用程序状态的变化通过响应发送到store里面的action。记住action只是描述了发生了什么。但是并没有描述应用状态会如何变化。

Designing the State Shape

In Redux, all the application state is stored as a single object. It's a good idea to think of its shape before writing any code. What's the minimal representation of your app's state as an object?

在Redux所有的应用程序的状态都存为一个单一的对象。写之前想好形状是非常好的想法。到底怎样才是你的对象的最小表达。

For our todo app, we want to store two different things:

  • The currently selected visibility filter.
  • The actual list of todos.

对于我们的todo应用。我们想要存两种不同的东西:

  • 当前选中过滤的可视列表

  • 实际的todo列表

You'll often find that you need to store some data, as well as some UI state, in the state tree. This is fine, but try to keep the data separate from the UI state.

您经常会发现,您需要在状态树中存储一些数据以及一些UI状态。这很好,但要尽量将数据与UI状态分开。

{
  visibilityFilter: 'SHOW_ALL',
  todos: [
    {
      text: 'Consider using Redux',
      completed: true,
    },
    {
      text: 'Keep all state in a single tree',
      completed: false
    }
  ]
}

Handling Actions

Now that we've decided what our state object looks like, we're ready to write a reducer for it. The reducer is a pure function that takes the previous state and an action, and returns the next state.

现在我们将决定我们的状态对象看起来是什么样子了。我们已经可以开始准备为这个对象写好reducer了。reducer是一个纯方法获取前一个状态和action然后返回下一个状态。

(previousState, action) => newState

It's called a reducer because it's the type of function you would pass to Array.prototype.reduce(reducer, ?initialValue). It's very important that the reducer stays pure. Things you should never do inside a reducer:

之所以称作 reducer 是因为它将被传递给 Array.prototype.reduce(reducer, ?initialValue) 方法。保持 reducer 纯净非常重要。永远不要在 reducer 里做这些操作:

  • 改变参数

  • 执行API调用和路由转换等带有side effects的操作

  • 调用不纯的方法。比如Date.now() 或者 Math.random().

We'll explore how to perform side effects in the advanced walkthrough. For now, just remember that the reducer must be pure. Given the same arguments, it should calculate the next state and return it. No surprises. No side effects. No API calls. No mutations. Just a calculation.

我们会解释什么是side effects在高阶教程里。但是现在,你只需要记住reducer必须是纯的。给定一个相同的参数,reducer应该计算下一个状态然后返回。没有意外。没有side effects。没有API调用。没有变量修改。只是单纯的计算

With this out of the way, let's start writing our reducer by gradually teaching it to understand the actions we defined earlier.

明白了这些之后,就可以开始编写 reducer,并让它来处理之前定义过的 action。

We'll start by specifying the initial state. Redux will call our reducer with an undefined state for the first time. This is our chance to return the initial state of our app:

我们将以指定 state 的初始状态作为开始。Redux 首次执行时,state 为 undefined,此时我们可借机设置并返回应用的初始 state。

import { VisibilityFilters } from './actions'

const initialState = {
  visibilityFilter: VisibilityFilters.SHOW_ALL,
  todos: []
}

function todoApp(state, action) {
  if (typeof state === 'undefined') {
    return initialState
  }

  // For now, don't handle any actions
  // and just return the state given to us.
  return state
}

One neat trick is to use the ES6 default arguments syntax to write this in a more compact way:

如果用ES6的默认参数语法的话我们可以写的更加紧凑些:

function todoApp(state = initialState, action) {
  // For now, don't handle any actions
  // and just return the state given to us.
  return state
}

Now let's handle SET_VISIBILITY_FILTER. All it needs to do is to change visibilityFilter on the state. Easy:

现在可以处理 SET_VISIBILITY_FILTER。需要做的只是改变 state 中的 visibilityFilter。

function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      })
    default:
      return state
  }
}

我们需要注意:

  1. 我们不应该改变state。应该通过Object.assgin()来拷贝一个。Object.assign(state, { visibilityFilter: action.filter })这个也不对。这仍然会改变state参数。你必须将Object.assign的第一个参数设置为空对象({})。当然你也可以用对象结构语法{ ...state, ...newState }来代替。

  2. 在 default 情况下返回旧的 state。遇到未知的 action 时,一定要返回旧的 state。

Handling More Actions

We have two more actions to handle! Just like we did with SET_VISIBILITY_FILTER, we'll import the ADD_TODO and TOGGLE_TODO actions and then extend our reducer to handle ADD_TODO.

我们由两个以上的action需要处理。就像我们处理SET_VISIBILITY_FILTER一样。我们还要引入ADD_TODO和TOGGLE_TODO action然后扩展我们的reducer去处理ADD_TODO。

import {
  ADD_TODO,
  TOGGLE_TODO,
  SET_VISIBILITY_FILTER,
  VisibilityFilters
} from './actions'

...

function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      })
    case ADD_TODO:
      return Object.assign({}, state, {
        todos: [
          ...state.todos,
          {
            text: action.text,
            completed: false
          }
        ]
      })
    default:
      return state
  }
}

Just like before, we never write directly to state or its fields, and instead we return new objects. The new todos is equal to the old todos concatenated with a single new item at the end. The fresh todo was constructed using the data from the action.

和之前一样。我们没有直接把field写入state。取而代之的是我们返回了一个新的对象。新的 todos 对象就相当于旧的 todos 在末尾加上新建的 todo。而这个新的 todo 又是基于 action 中的数据创建的。

Finally, the implementation of the TOGGLE_TODO handler shouldn't come as a complete surprise:

最后,TOGGLE_TODO 的实现也很好理解:

case TOGGLE_TODO:
  return Object.assign({}, state, {
    todos: state.todos.map((todo, index) => {
      if (index === action.index) {
        return Object.assign({}, todo, {
          completed: !todo.completed
        })
      }
      return todo
    })
  })

Because we want to update a specific item in the array without resorting to mutations, we have to create a new array with the same items except the item at the index. If you find yourself often writing such operations, it's a good idea to use a helper like immutability-helper, updeep, or even a library like Immutable that has native support for deep updates. Just remember to never assign to anything inside the state unless you clone it first.

我们需要修改数组中指定的数据项而又不希望导致突变, 因此我们的做法是在创建一个新的数组后, 将那些无需修改的项原封不动移入, 接着对需修改的项用新生成的对象替换。(译者注:Javascript中的对象存储时均是由值和指向值的引用两个部分构成。此处突变指直接修改引用所指向的值, 而引用本身保持不变。) 如果经常需要这类的操作,可以选择使用帮助类 React-addons-update,updeep,或者使用原生支持深度更新的库 Immutable。最后,时刻谨记永远不要在克隆 state 前修改它。

Store

In the previous sections, we defined the actions that represent the facts about “what happened” and the reducers that update the state according to those actions.

前面的章节里。我们定义了action代表实际发生的事情和围绕着action更新state的reducer。

The Store is the object that brings them together. The store has the following responsibilities:

Store是一个连接他们的对象。store有以下的职责:

  • 维护应用的 state
  • 提供 getState() 方法获取 state
  • 提供 dispatch(action) 方法更新 state
  • 通过 subscribe(listener) 注册监听器
  • 通过 subscribe(listener) 返回的函数注销监听器

It's important to note that you'll only have a single store in a Redux application. When you want to split your data handling logic, you'll use reducer composition instead of many stores.

再次强调一下 Redux 应用只有一个单一的 store。当需要拆分数据处理逻辑时,你应该使用 reducer 组合 而不是创建多个 store。

It's easy to create a store if you have a reducer. In the previous section, we used combineReducers() to combine several reducers into one. We will now import it, and pass it to createStore().

如果你有reducer创建store非常简单。在前面的章节里,我们使用combineReducers去将多个 reducer 合并成为一个。现在我们将其导入,并传递 createStore()。

import { createStore } from 'redux'
import todoApp from './reducers'
let store = createStore(todoApp)

You may optionally specify the initial state as the second argument to createStore(). This is useful for hydrating the state of the client to match the state of a Redux application running on the server.

createStore() 的第二个参数是可选的, 用于设置 state 初始状态。这对开发同构应用时非常有用,服务器端 redux 应用的 state 结构可以与客户端保持一致, 那么客户端可以将从网络接收到的服务端 state 直接用于本地数据初始化。

let store = createStore(todoApp, window.STATE_FROM_SERVER)

Dispatching Actions

Now that we have created a store, let's verify our program works! Even without any UI, we can already test the update logic.

现在我们创建了store。我们来验证一下我们的程序是否工作。不需要任何UI我们也可以测试:

mport {
  addTodo,
  toggleTodo,
  setVisibilityFilter,
  VisibilityFilters
} from './actions'

// Log the initial state
console.log(store.getState())

// Every time the state changes, log it
// Note that subscribe() returns a function for unregistering the listener
const unsubscribe = store.subscribe(() =>
  console.log(store.getState())
)

// Dispatch some actions
store.dispatch(addTodo('Learn about actions'))
store.dispatch(addTodo('Learn about reducers'))
store.dispatch(addTodo('Learn about store'))
store.dispatch(toggleTodo(0))
store.dispatch(toggleTodo(1))
store.dispatch(setVisibilityFilter(VisibilityFilters.SHOW_COMPLETED))

// Stop listening to state updates
unsubscribe()

You can see how this causes the state held by the store to change:

你可以看到下面这样的东东:

image

We specified the behavior of our app before we even started writing the UI. We won't do this in this tutorial, but at this point you can write tests for your reducers and action creators. You won't need to mock anything because they are just pure functions. Call them, and make assertions on what they return.

可以看到,在还没有开发界面的时候,我们就可以定义程序的行为。而且这时候已经可以写 reducer 和 action 创建函数的测试。不需要模拟任何东西,因为它们都是纯函数。只需调用一下,对返回值做断言,写测试就是这么简单。

Async Actions

异步Action是一个很有意思的东西。之前我的理解是Redux不好做。这个观点完全是错误的。Redux本身是可以做的。只是一般来说如果用Redux来做会让整个reducer变得相当复杂。也会让reducer变得不纯,reducer追求pure function,异步操作是effect(有副作用的,不纯的)的。并且要处理异步你要维护更多的action。整个Application会看起来非常的复杂和臃肿。

为什么需要更多的action呢。举个例子把一个非常普通的request。他需要处理的action起码有三个。request我们就当这个action是发起请求。request_success当请求成功时候调用的action。request_failed请求失败的时候调用的action。

使用redux本身来做这个会让同步和异步的action都堆积在reducer中,管理起来可能会略微麻烦。而且不容易做测试等等等等。

所以要用到异步的Action的话可以使用Redux-saga。个人很喜欢这个东西。提供了足够多的helper。当然也可以试试dva。但是这超出了本文讨论的范畴。

React Render 相关

The render() method is required.

render函数是必须要有的。

When called, it should examine this.props and this.state and return one of the following types:

当render函数被调用的时候。函数应该检查this.propsthis.state然后返回下列类型中的其中一种:

React elements. Typically created via JSX. An element can either be a representation of a native DOM component (

), or a user-defined composite component ().

React elements。典型的创建方式是通过JSX。一个element可以被代表称一个原生的组件(例如

),或者是用户自定义个组件(例如)。

String and numbers. These are rendered as text nodes in the DOM.

String或者numbers。通常是在渲染text节点在DOM里面的时候。

Portals. Created with ReactDOM.createPortal.

Portals。通过ReactDOM.createPortal创建的元素。 PS: 这个还蛮有用的。看这个例子

null. Renders nothing.

null。什么都不渲染的时候。

Booleans. Render nothing. (Mostly exists to support return test && pattern, where test is boolean.)

Booleans。什么都不渲染的时候。(这个情况大多会发生在return test && 这种写法时候。)

When returning null or false, ReactDOM.findDOMNode(this) will return null.

不管是null或则和false。ReactDOM.findDOMNode(this)函数都会返回null。

The render() function should be pure, meaning that it does not modify component state, it returns the same result each time it’s invoked, and it does not directly interact with the browser. If you need to interact with the browser, perform your work in componentDidMount() or the other lifecycle methods instead. Keeping render() pure makes components easier to think about.

render函数应该是(pure)的,也就是说我们不应该在render函数里面修改组建的state。render函数应该每次执行的时候都返回相同的结果。它不直接与浏览器交互。如果你想与浏览器交互你应该在componentDidMount或者其他的生命周期里面进行。保持render函数的纯度可以让组件更容易理解。

Note
render() will not be invoked if shouldComponentUpdate() returns false.

非常需要注意的一点是。当shouldComponentUpdate方法返回false的时候render函数不会被执行。

Fragments

React支持片段。片段指的是没有被容器包裹的元素。通常直接写的话会报错。但是这样写就不会了

render() {
  return [
    <li key="A">First item</li>,
    <li key="B">Second item</li>,
    <li key="C">Third item</li>,
  ];
}

但是要注意需要添加key噢 不然会有警告。

Since React 16.2.0, the same can also be accomplished using fragments, which don’t require keys for static items:

不过 React16.2.0以后可以不写key了。但是你需要改变一下写法.

render() {
  return (
    <React.Fragment>
      <li>First item</li>
      <li>Second item</li>
      <li>Third item</li>
    </React.Fragment>
  );
}

参考资料

TL;DR

实际上我写这篇的目的是为了理解Redux为何会频繁触发component。Reselect是如何解决这个问题的。后面发现触发更新的其实是react-redux。但是很显然上面的官方文档中我没有找到特别有用能帮助到我的东西。为了让大家所谓的性能问题。我这里写个例子。

组件部分

App.js

import React, { Component } from "react";
import ReactDOM from "react-dom";
import Demo from "./demo"

export default Demo;

demo/index.js

import {Provider} from 'react-redux';
import React from 'react';
import store from './store';
import Posts from './Posts';
import Counter from './Counter';
import './index.css';

const initial = store.getState();

class App extends React.Component {
  render() {
    return (
      <div>
        <h1>Reselect Redux</h1>
        <Posts />
        <Counter />
      </div>
    );
  }
}

export default () => <Provider store={store}><App /></Provider>;

demo/Post.js

import React from 'react';
import {connect} from 'react-redux';

let count = 0;

class Posts extends React.Component {
  render() {
    console.log(`Posts render ${++count}`);
    return (
      <div>
        <h3>Posts</h3>
        <ul>
          {this.props.posts.map(x =>
            <li key={x.id}>
              {`${x.title} - ${x.user.first} ${x.user.last}`}
            </li>
          )}
        </ul>
      </div>
    );
  }
}

const mapState = (state) => {
  const posts = state.postsById
  const users = state.usersById
  const listing = state.postListing
  return {
    posts: listing.map(id => {
      const post = posts[id];
      return {...post, user: users[post.author]}
    })
  }
};

export default connect(mapState)(Posts);

demo/Counter.js

import React from 'react';
import {connect} from 'react-redux';

class Counter extends React.Component {
  componentDidMount() {
    setInterval(() => {
      this.props.increment();
    }, 500);
  }
  render() {
    return (
      <div>
        <h3>Count: {this.props.count}</h3>
      </div>
    );
  }
}

const mapState = (state) => ({count: state.count});
const mapDispatch = {
  increment: () => ({type: 'INCREMENT'}),
};


export default connect(mapState, mapDispatch)(Counter);

Redux部分

demo/store.js

import {createStore} from 'redux';
import reducer from './reducers';
import {getPosts} from './fixtures';

const store = createStore(reducer);

store.dispatch({type: 'RECEIVE_DATA', payload: getPosts()});

export default store;

demo/reducers.js

import {combineReducers} from 'redux';

export const usersByIdReducer = (state = {}, action) => {
  switch (action.type) {
    case 'RECEIVE_DATA':
      const newState = {...state};
      action.payload.users.forEach((user) => {
        newState[user.id] = user;
      });
      return newState;
    default: return state
  }
};

export const postsByIdReducer = (state = {}, action) => {
  switch (action.type) {
    case 'RECEIVE_DATA':
      const newState = {...state};
      action.payload.posts.forEach((post) => {
        newState[post.id] = post;
      });
      return newState;
    default: return state
  }
};

export const postListingReducer = (state = [], action) => {
  switch (action.type) {
    case 'RECEIVE_DATA':
      return action.payload.posts.map(x => x.id);
    default: return state
  }
};

export const counterReducer = (state = 1, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1;
    default: return state;
  }
}

export default combineReducers({
  usersById: usersByIdReducer,
  postsById: postsByIdReducer,
  postListing: postListingReducer,
  count: counterReducer,
});

这个demo是视频里面的小哥提供的。我把它重现了出来。复制粘贴一下就好了。因为我不想单独开个仓库存代码所以贴在上面。如果你喜欢视频教程你可以直接去看小哥哥的视频。里面讲的很清楚。

这个代码很简单就是每500毫秒在Counter组件里面调用一下increment。触发Redux的状态变化。让counter状态不停的加1。另外在Post的render函数里面写了个console.log来确认是Post函数被调用了。

然后运行的结果是。明明Post里面没有用到counter变量却被更新了。打开控制台可以看见。不停的在输出。换句话说不停的在render。这很显然如果比较复杂的组件会带来性能问题。

运行结果

image

Post.js 内心OS: MMP 为什么我在疯狂更新,管我卵事。

那好啊。为什么会这样咧。就是我要探究的问题。到底为什么render函数触发更新了,又为什么用了Reselect修改后的版本就不会触发更新了。Reselect对Redux做了什么,期间有什么py交易。以及Render函数运行的条件是我接下来关心的重点。

Render 函数运行的条件

首先最重要的是这个。在官方文档中我们知道当shouldComponentUpdate方法返回false的时候render函数不会被执行。那么是不是因为这个呢。我粗略的扫了一下redux和reselect的源码,源码里面都没有用到shouldComponentUpdate来控制组件的更新。于是看了下react-redux。ok很快就找了shouldComponentUpdate相关的代码。很明显react-redux通过shouldComponentUpdate做了什么,那我们粗略的看一下源码吧。

PS: 吃饭的时候想了一下**了为什么去看redux的源码。redux本身和react无关。

首先看项目结构。我只看src目录这里用了学长写的工具(可能他已经忘掉了这个工具了)。一开始用的是cmder自带的那个tree,可是我发现体验极差。投奔学长。

- components
    connectAdvanced.js 
    Provider.js

- connect
    connect.js
    mapDispatchToProps.js
    mapStateToProps.js
    mergeProps.js
    selectorFactory.js
    verifySubselectors.js
    wrapMapToProps.js
  index.js

- utils
    PropTypes.js
    shallowEqual.js
    Subscription.js
    verifyPlainObject.js
    warning.js
    wrapActionCreators.js

index.js

东西很少。三个目录compoent,connect,utils。component下面放的是组件,connect是核心的逻辑,utils是会用到的工具类。index.js统一暴露所有主要的接口。

先看看index.js

import Provider, { createProvider } from './components/Provider'
import connectAdvanced from './components/connectAdvanced'
import connect from './connect/connect'

export { Provider, createProvider, connectAdvanced, connect }

Ok 那就先看Provider吧。平时我们都是用包裹组件的。来看看里面写了什么。

import { Component, Children } from 'react'
import PropTypes from 'prop-types'
import { storeShape, subscriptionShape } from '../utils/PropTypes'
import warning from '../utils/warning'

let didWarnAboutReceivingStore = false
function warnAboutReceivingStore() {
  if (didWarnAboutReceivingStore) {
    return  
  }
  didWarnAboutReceivingStore = true

  warning(
    '<Provider> does not support changing `store` on the fly. ' +
    'It is most likely that you see this error because you updated to ' +
    'Redux 2.x and React Redux 2.x which no longer hot reload reducers ' +
    'automatically. See https://github.com/reactjs/react-redux/releases/' +
    'tag/v2.0.0 for the migration instructions.'
  )
}

export function createProvider(storeKey = 'store', subKey) {
    const subscriptionKey = subKey || `${storeKey}Subscription`

    class Provider extends Component {
        getChildContext() {
          return { [storeKey]: this[storeKey], [subscriptionKey]: null }
        }

        constructor(props, context) {
          super(props, context)
          this[storeKey] = props.store;
        }

        render() {
          return Children.only(this.props.children)
        }
    }

    if (process.env.NODE_ENV !== 'production') {
      Provider.prototype.componentWillReceiveProps = function (nextProps) {
        if (this[storeKey] !== nextProps.store) {
          warnAboutReceivingStore()
        }
      }
    }

    Provider.propTypes = {
        store: storeShape.isRequired,
        children: PropTypes.element.isRequired,
    }
    Provider.childContextTypes = {
        [storeKey]: storeShape.isRequired,
        [subscriptionKey]: subscriptionShape,
    }

    return Provider
}

export default createProvider()

很快我们注意到了Provider里面用了context api。prop接收stroe和children两个参数。children就是根元素啦。就是平时用的this.props.children。有一个warnAboutReceivingStore。会在改变store的时候在非生产模式的时候提供警告。Children.only方法是验证是否只有一个child用的。如果不是会报错。如果是的话就会渲染。Provider是createProvider默认参数的实现。

那createProvider也不用看了。是可以给用户自定义的Provider(并没有用过)。createProvider有两个参数storeKey和subKey。不过第二个参数文档里面没有讲怎么用 算了 当作不知道。

最主要的部分应该是这两个部分。

getChildContext() {
    return { [storeKey]: this[storeKey], [subscriptionKey]: null }
}

constructor(props, context) {
    super(props, context)
    this[storeKey] = props.store;
}

现在只知道Provider组件把props.store记录到了this[storeKey]里面。还有getChildContext里面访问[store]的话可以读取到props.store。另外一个subscriptionKey总是为空。

这些东西接下来应该会用到。所以看第三个connectAdvanced吧。connectAdvanced也是组件。更加的复杂。其实是注释好多 因为好长 直接戳进去看吧

嘛 首先看下依赖。hoist-non-react-statics和invariant都是我不知道的东西。好呀既然不知道那就查查看。一下就找到了这个是hoist-non-react-statis。嗯他叫我这个**去看官方的解释。好吧我没有好好看文档。那就看看。这个是做什么的。

简单的说就是有时候给组件加静态方法超爽der。但是如果你用了hoc的话静态方法会丢失。虽然你可以手动将hoc的静态方法指向原组件的静态方法。但是那样太麻烦了。所以你可以用hoist-non-react-statis解决这个问题。简单而又足够高效。

好hoist-non-react-statics知道了那就看看invariant。手动戳进invariant的源码。发现很短有一行这样的注释。

/**
 * Use invariant() to assert state which your program assumes to be true.
 *
 * Provide sprintf-style format (only %s is supported) and arguments
 * to provide information about what broke and what you were
 * expecting.
 *
 * The invariant message will be stripped in production, but the invariant
 * will remain to ensure logic does not differ in production.
 */

嗯大概是用来处理抛出异常时候的一个函数。用法看起来也比较像所以不关心这个东西。看到别人有个copy版本。额 他的描述更清晰了。

A way to provide descriptive errors in development but generic errors in production.

剩下的两个工具类其中PropTypes没什么好说的提取了可以复用的PropType。Subscription的话比较重要。用来订阅Redux的状态的。因为我觉得这个比较重要点开来看看好了。

// encapsulates the subscription logic for connecting a component to the redux store, as
// well as nesting subscriptions of descendant components, so that we can ensure the
// ancestor components re-render before descendants

Emmmm . 英语废表示完全不理解。那就直接看代码吧。嗯 就是普通的订阅者模式。

通过createListenerCollection创建订阅者。createListenerCollection提供了一个闭包函数。有notify,subscribe之类常用的功能。current和next都是数组。每次notify的时候会更新一下current并执行队列里面的回调。OK 继续回到connectAdvanced。

很好往下看发现根本无法理解嘛。那我们就看Connect吧。看看Connect是怎么调用connectAdvanced的。Connect暴露的connect也就是我们平时用的connect方法。

嗯一下子就找到了。connectAdvanced是作为一个Hoc来使用的。也就是说只要我们用了connect。react-redux都会将原组件封装成hoc再暴露出来。这样就使得组件有能力访问redux。

connect的文档可以读一下。虽然平时我们只要state和disptch。

抛开这些东西现在。我们知道reselect是把selector传给第二个参数的,第二个参数是mapDispatchToProps。那么一切都将围绕着它。我们只关心和这个参数挂钩的地方就好了。

和这个挂钩的只有initMapDispatchToProps。

function match(arg, factories, name) {
  for (let i = factories.length - 1; i >= 0; i--) {
    const result = factories[i](arg)
    if (result) return result
  }

  return (dispatch, options) => {
    throw new Error(`Invalid value of type ${typeof arg} for ${name} argument when connecting component ${options.wrappedComponentName}.`)
  }
}

const initMapDispatchToProps = match(mapDispatchToProps, mapDispatchToPropsFactories, 'mapDispatchToProps')

match函数的作用是迭代工厂方法,最终返回结果。Emmmmm 目的不明。暂时不影响阅读。接下去看有一个

return connectHOC(selectorFactory, {
  // used in error messages
  methodName: 'connect',

    // used to compute Connect's displayName from the wrapped component's displayName.
  getDisplayName: name => `Connect(${name})`,

  // if mapStateToProps is falsy, the Connect component doesn't subscribe to store state changes
  shouldHandleStateChanges: Boolean(mapStateToProps),

  // passed through to selectorFactory
  initMapStateToProps,
  initMapDispatchToProps,
  initMergeProps,
  pure,
  areStatesEqual,
  areOwnPropsEqual,
  areStatePropsEqual,
  areMergedPropsEqual,

  // any extra options args can override defaults of connect or connectAdvanced
  ...extraOptions
})

戳进去看就到了connectAdvanced。反正目的是建一个HOC组件Connect。然后这个Connect组件实现了shouldComponentUpdate方法。

shouldComponentUpdate() {
  return this.selector.shouldComponentUpdate
}

shouldComponentUpdate方法围绕着selector依赖着selector的shouldComponentUpdate方法。selector的shouldComponentUpdate长这样子。

function makeSelectorStateful(sourceSelector, store) {
  // wrap the selector in an object that tracks its results between runs.
  const selector = {
    run: function runComponentSelector(props) {
      try {
        const nextProps = sourceSelector(store.getState(), props)
        if (nextProps !== selector.props || selector.error) {
          selector.shouldComponentUpdate = true 
          selector.props = nextProps
          selector.error = null
        }
      } catch (error) {
        selector.shouldComponentUpdate = true
        selector.error = error
      }
    }
  }

  return selector
}

最关键部分在那个if判断里面nextProps !== selector.props || selector.error。如果上次传的prop不一致就会把shouldComponentUpdate设为true。如果一致的话就会是false。虽然没认真找但应该有这么一段。

所以总结一下就是react-redux使用高阶组件Connect来链接Redux。而Connect组件实现了shouldComponentUpdate方法,目的应该是想做优化的。使用Connect链接的组件在state更新的时候都会触发mapState方法。但是因为mapState方法每次返回的对象不一致导致props不一致。而组件被动的更新了。

const mapState = (state) => {
  const posts = state.postsById
  const users = state.usersById
  const listing = state.postListing
  return {
    posts: listing.map(id => {
      const post = posts[id];
      return {...post, user: users[post.author]}
    })
  }
};

使用reselect以后,reselect会在selector返回结果不一致的情况下调用tranfrom函数。但是如果一致的话会返回上次计算的结果。所以组件不会被更新。

reselect - selector library for redux

reselect的灵感来自NuclearJS的getter部分。re-frame的subscriptions和speedskater提议

我们看看一个Selector可以做什么 ? 一下是官方提到的三个重点。

Selectors can compute derived data, allowing Redux to store the minimal possible state.
Selectors are efficient. A selector is not recomputed unless one of its arguments changes.
Selectors are composable. They can be used as input to other selectors.

翻译过来大概就是

  • Selector可以计算一些派生出来的数据,允许Redux存储各种可能的状态。
  • Selector是高效的。一个Selector不会重新计算除非其中一个参数改变了。
  • Selector是可以组合的。一个Selector可以作为参数(输入)到其他的Selector里。

如果对文字不感兴趣也可以直接看视频教程。视频讲解了文字部分的所有内容。

上面的说法其实非常抽象。让我们看一个例子

import { createSelector } from 'reselect'

const shopItemsSelector = state => state.shop.items
const taxPercentSelector = state => state.shop.taxPercent

const subtotalSelector = createSelector(
  shopItemsSelector,
  items => items.reduce((acc, item) => acc + item.value, 0)
)

const taxSelector = createSelector(
  subtotalSelector,
  taxPercentSelector,
  (subtotal, taxPercent) => subtotal * (taxPercent / 100)
)

export const totalSelector = createSelector(
  subtotalSelector,
  taxSelector,
  (subtotal, tax) => ({ total: subtotal + tax })
)

let exampleState = {
  shop: {
    taxPercent: 8,
    items: [
      { name: 'apple', value: 1.20 },
      { name: 'orange', value: 0.95 },
    ]
  }
}

console.log(subtotalSelector(exampleState)) // 2.15
console.log(taxSelector(exampleState))      // 0.172
console.log(totalSelector(exampleState))    // { total: 2.322 }

这个例子是计算商品的总税务。如果无法理解的话也不要紧。我们先看看createSelector的函数体和官方的描述。

createSelector(...inputSelectors | [inputSelectors], resultFunc)

Takes one or more selectors, or an array of selectors, computes their values and passes them as arguments to resultFunc.

createSelector可以接受一个或者一组的inputSelector或者一个inputSelectors作为参数。参数的最后一个总会是一个resultFunc。resultFunc会接收之前的inputSelector参数计算出来的值作为参数。resultFunc是一个回调函数。

使用方法如下

const mySelector = createSelector(
  state => state.values.value1,
  state => state.values.value2,
  (value1, value2) => value1 + value2
)

// You can also pass an array of selectors
const totalSelector = createSelector(
  [
    state => state.values.value1,
    state => state.values.value2
  ],
  (value1, value2) => value1 + value2
)

其他还有更高级的说明在后面写下来。

我们现在回头看最早之前的那个demo。现在已经非常明了。createSelector接受inputSelector返回新的inputSelector。感觉就像这样。

createSelector(...inputSelectors | [inputSelectors], resultFunc) : inputSelector

所以demo里的所有方法变量都是Selector结尾。最前面的两个Selector是取state里面的状态的,是之后用来做参数用的。后面的这一段调用了createSelector方法。

const subtotalSelector = createSelector(
  shopItemsSelector,
  items => items.reduce((acc, item) => acc + item.value, 0)
)

使用Es6的reduce计算了商品的总价值。因为createSelector允许多个Selector作为参数。所以第二步做了这个。

const taxSelector = createSelector(
  subtotalSelector,
  taxPercentSelector,
  (subtotal, taxPercent) => subtotal * (taxPercent / 100)
)

利用了taxPercentSelector和之前定义的subtotalSelector计算了一波总税务。

最后把商品总价和商品总税务合并起来 算出税后商品价格。整个过程非常清晰明确。

export const totalSelector = createSelector(
  subtotalSelector,
  taxSelector,
  (subtotal, tax) => ({ total: subtotal + tax })
)

emmmm 虽然很清晰明确 但是也并非是我们利用他的理由。利用reselect并不是为了这样,利用reselect的真正原因会在下面说到。

接下来这里记录怎么用Reselect的selector。

但是你想知道为什么需要reselect,得看这里

reselect提供的是带有记忆(缓存)能力的Selector。为了展示这一点。我们会用一个最基本的todo例子说明。这个例子取自基于最基础的redux todo list example

containers/VisibleTodoList.js

import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'

const getVisibleTodos = (todos, filter) => {
  switch (filter) {
    case 'SHOW_ALL':
      return todos
    case 'SHOW_COMPLETED':
      return todos.filter(t => t.completed)
    case 'SHOW_ACTIVE':
      return todos.filter(t => !t.completed)
  }
}

const mapStateToProps = (state) => {
  return {
    todos: getVisibleTodos(state.todos, state.visibilityFilter)
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    onTodoClick: (id) => {
      dispatch(toggleTodo(id))
    }
  }
}

const VisibleTodoList = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)

export default VisibleTodoList

In the above example, mapStateToProps calls getVisibleTodos to calculate todos. This works great, but there is a drawback: todos is calculated every time the state tree is updated. If the state tree is large, or the calculation expensive, repeating the calculation on every update may cause performance problems. Reselect can help to avoid these unnecessary recalculations.

上面的例子mapStateToProps调用了getVisibleTodos去计算todos。这工作起来非常的棒。但是有缺点:当每次状态树改变的时候todos都会被重新计算(不相关的状态改变也会触发)。如果状态树很庞大或者计算一次的代价非常昂贵。如果是这样每次重新计算会带来性能问题。Reselect可以帮助你避免掉不必要的重计算。

那么接下来我们将会使用Reselect来改造这个简单的todo demo。

We would like to replace getVisibleTodos with a memoized selector that recalculates todos when the value of state.todos or state.visibilityFilter changes, but not when changes occur in other (unrelated) parts of the state tree.

我们要把之前的getVisibleTodos替换成一个有记忆(缓存)能力的Selector这个Selector重计算只会发生在state.todos或者state.visibilityFilter改变的时候。其他不相关的状态改变都不会触发重计算。

Reselect provides a function createSelector for creating memoized selectors. createSelector takes an array of input-selectors and a transform function as its arguments. If the Redux state tree is mutated in a way that causes the value of an input-selector to change, the selector will call its transform function with the values of the input-selectors as arguments and return the result. If the values of the input-selectors are the same as the previous call to the selector, it will return the previously computed value instead of calling the transform function.

Reselect提供了createSelector方法创建一个可记忆(缓存)的Selector。这个方法以一组Selector函数和一个转换函数(前面提到的resultFunc)作为参数。如果Redux状态树通过突变的方式导致input-selector变化了。那么selector将会调用transfrom方法并把input-selector作为参数再返回结果。如果input-selector的和之前的调用用到的input-selector一样的话。selector将会返回之前计算出来的结果而不是调用transform方法。

Let's define a memoized selector named getVisibleTodos to replace the non-memoized version above:

好了好了 让我们来定义一个可记忆(缓存)的selector并且取名叫做getVisibleTodos去替换之前没有用可记忆(缓存)的版本吧 :

selectors/index.js

import { createSelector } from 'reselect'

const getVisibilityFilter = (state) => state.visibilityFilter
const getTodos = (state) => state.todos

export const getVisibleTodos = createSelector(
  [ getVisibilityFilter, getTodos ],
  (visibilityFilter, todos) => {
    switch (visibilityFilter) {
      case 'SHOW_ALL':
        return todos
      case 'SHOW_COMPLETED':
        return todos.filter(t => t.completed)
      case 'SHOW_ACTIVE':
        return todos.filter(t => !t.completed)
    }
  }
)

是的这个例子和前面的大同小异。只是参数的位置有点不一样其他都大同小异。但是真的运行的时候区别还是蛮大的。

In the example above, getVisibilityFilter and getTodos are input-selectors. They are created as ordinary non-memoized selector functions because they do not transform the data they select. getVisibleTodos on the other hand is a memoized selector. It takes getVisibilityFilter and getTodos as input-selectors, and a transform function that calculates the filtered todos list.

在上面的例子中。getVisibilityFiltergetTodos都算是input-selector。最后一个参数是一个箭头函数。这个参数被认为是transfrom方法。Reselect会在状态改变的时候调用它过滤todo列表。

Vue scoped css VS css module

我们可以看见Vue Loader里面有两种处理样式的方案。一种是Scoped CSS还有一种是CSS Modules。下面来做一些优缺点对比。

Scoped CSS的优点

开箱即用。能避免样式冲突(但是不完全)。

Scoped CSS的缺点

覆盖第三方利用了scoped的插件

引用包含scoped的第三方插件时如若需要修改样式则需要全局修改,而且要注意权重问题,迫不得已再使用!important。

冲突

如果样式和全局的样式重名,依然会导致样式冲突。

需要注意的问题

Scoped 样式不能代替 class。考虑到浏览器渲染各种 CSS 选择器的方式,当 p { color: red } 是 scoped 时 (即与特性选择器组合使用时) 会慢很多倍。
如果你使用 class 或者 id 取而代之,比如 .example { color: red },性能影响就会消除。

这句话的意思是可能有人会觉得有了Scoped就可以直接不写class或者id了。直接给标签写样式。虽然可以但是效率会很低,因为用到了特性选择器

举个例子:

在递归组件中小心使用后代选择器! 对选择器 .a .b 中的 CSS 规则来说,如果匹配 .a 的元素包含一个递归子组件,则所有的子组件中的 .b 都将被这个规则匹配。

选择器性能问题

Google 资深web开发工程师 Steve Souders 对 CSS 选择器的执行效率从高到低做了一个排序:

如何减少 CSS 选择器性能损耗?
Google 资深web开发工程师 Steve Souders 对 CSS 选择器的执行效率从高到低做了一个排序:

1.id选择器(#myid)
2.类选择器(.myclassname)
3.标签选择器(div,h1,p)
4.相邻选择器(h1+p)
5.子选择器(ul < li)
6.后代选择器(li a)
7.通配符选择器(*)
8.属性选择器(a[rel="external"])
9.伪类选择器(a:hover, li:nth-child)

根据以上「选择器匹配」与「选择器执行效率」原则,我们可以通过避免不恰当的使用,提升 CSS 选择器性能。

CSS Modules 优点

不冲突

名字会有postcss生成转成hash。

性能更好一点

因为名字转成hash了,选择器也没有必要了。

多style

可以在.vue文件写很多个style,但是正常人会这样吗?

<style module="a">
  /* 注入标识符 a */
</style>

<style module="b">
  /* 注入标识符 b */
</style>

可以通过js直接访问样式名

<script>
export default {
  created () {
    console.log(this.$style.red)
    // -> "red_1VyoJ-uZ"
    // 一个基于文件名和类名生成的标识符
  }
}
</script>

Composition

可以组合其他的样式。

.className {
  color: green;
  background: red;
}

.otherClassName {
  composes: className;
  color: yellow;
}

composes可以写很多条。但是必须得写在其他CSS规则之前。

CSS Modules的缺点

并非开箱即用

需要手动配置在webpack加css-loader并传入modules:true

// webpack.config.js
{
  module: {
    rules: [
      // ... 其它规则省略
      {
        test: /\.css$/,
        use: [
          'vue-style-loader',
          {
            loader: 'css-loader',
            options: {
              // 开启 CSS Modules
              modules: true,
              // 自定义生成的类名
              localIdentName: '[local]_[hash:base64:8]'
            }
          }
        ]
      }
    ]
  }
}

然后才可以使用

<style module>
.red {
  color: red;
}
.bold {
  font-weight: bold;
}
</style>

新的写法

由于Scoped CSS在CSS Modules之前就出现了,会有更多的解决方案和项目用到。找资料的时候比较方便,CSS Modules是比较后面出来的,写法也不一样,需要一点点的学习成本。

必须使用:class

其实这个也还好,因为样式名是动态生成的,可以理解。和以前直接写class还是有区别的。最糟糕的可能是混着写,因为不免会用到一些三方的CSS。可能在一个组件里会有部分是:class部分直接写的class

使用css module在keyframes中的问题

使用CSS modules处理动画animation的关键帧keyframes,动画名称必须先写。

animation: ani 1s;能正常编译,而animation: 1s ani;则会编译的不符合预期,所以平时养成良好的css参数书写顺序也很重要。

css module父子组件问题

使用module的父组件会在没有使用module的子组件的所有根类上增加hash改变其类名,可能会造成子组件样式应用不上。

如下是没有开启css module子组件的样式:

<style lang="scss">
  .comp{
    color: palegoldenrod;
    p{
      color: black;
    }
  }
  .t {
    color: teal;
  }
  div {
    color: yellow;
  }
</style>

父组件开启css module后编译结果如下:

.comp_2tR6GNan {
  color: palegoldenrod;
}
.comp_2tR6GNan p {
  color: black;
}
.t_39GmF73s {
  color: teal;
}
div {
  color: yellow;
}

可以看到comp和t类都被修改了类名,如果根样式是标签选择器不会受影响。

所以在使用css module的父组件中使用的子组件也要开启css module。

总结

总的来看CSS Modules的坑会少一点。所以选型上我会比较偏向CSS Modules。

相关资料

Git Magic

Note some useful git command in this note.

Hope this can help you , make you life better !!

And thank you read this note. It won't take you for a long time .

Beautify git status

The first command is git status .

Usually we tpye it in terminal:

$ git status

Then we can see:

image

If we add -sb flag:

$ git status -sb

We will get this:

image

git status -sb is more directly than git status !!

Beautify git log

Type this command in your terminal :

git log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit

You will see a awesome output info like this :

image

But this command is too long . We can make an alias

git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"```

Now you can use git lg to instead this command !

git lg

Also you can type git lg -p to show the diff

git lg -p

image

ref: A better git log

Branch Merged

Now i have three branch : feature/balabala , master , qaq , feqture/balabala is same version of master .

git branch -v

Then output:

image

If I want to filter out merged i can type :

git branch --merged master

Now I can see which ones have been merged

image

Also we can filter out no merged branch :

git branch --no-merged

image

Checkout the nearest branch

Type

git checkout -

Output

image

Git Search

Type it we can search info in commit

git show :/query

query is case sensitive

image

Type q to exit.

Some helpful git alias

Alias Command What to Type
git cm git commit git config --global alias.cm commit
git co git checkout git config --global alias.co checkout
git ac git add . -A git commit git config --global alias.ac '!git add -A && git commit'
git st git status -sb git config --global alias.st 'status -sb'
git tags git tag -l git config --global alias.tags 'tag -l'
git branches git branch -a git config --global alias.branches 'branch -a'
git cleanup `git branch --merged grep -v '*'
git remotes git remote -v git config --global alias.remotes 'remote -v'
git lg git log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit -- git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --"

If you are using window , use double quotation marks instead of single quotes.

Checkout File

just git checkout [hash] fileName

Depth 1

using git clone --depth=1 we can only clone the latest commit . It should faster than clone whole project.

such as we clone lodash repo .

git clone --depth=1 [email protected]:lodash/lodash.git

image

We can see only one commit in project .

image

Modify commit

git commit --amend

Global Ignore

Mac

git config --global core.excludesfile ~/.gitignore

Windows

git config --global core.excludesfile %USERPROFILE%\.gitignore

Then just edit ~/.gitignore file , put config . such as the fucking .DS_Store and so on.

# Node
npm-debug.log

# Mac
.DS_Store

# Windows
Thumbs.db

# WebStorm
.idea/

# vi
*~

# General
log/
*.log

# etc...

Reset File

type git reset --hard HEAD^ rollback to pre version , double ^^ is pre pre version . 100 is pre 100 version.

Cherry pick

Apply the changes introduced by some existing commits

Difference git config on folder

We can use include and includeIf to import difference git config by gitdir .

[include]
        path = .github-config
[includeIf "gitdir:~/Desktop/manqian/**"]
        path = .radevio-config

Nwb 食用方式

在这之前我并没有尝试过把组件发布到NPM上提供给他人使用的经历。但是最近我想发布一组件到github。我并不想自己配置webpack然后启动项目。因为那似乎有点麻烦而且小题大做了,我只想写专心写我的组件什么配置环境 guna。好在我在开始写我的webpack配置之前。看了下别人写的react组件。我瞥了一眼作者的的package.json我发现作者使用了nwb创建项目。

A toolkit for React, Preact, Inferno & vanilla JS apps, React libraries and other npm modules for the web, with no configuration (until you need it)

很好。这就是我想要的东西。于是安装开始使用。

nwb new react-component react-scratch
cd react-scratch/
npm start

image

为什么叫react-scratch。是因为我最近在写一个叫react-scratch的组件。当然你喜欢叫别的名字可以叫别的。很快项目建好了。改一下package.json的信息。ok改完了,这就是我的项目了。赶紧打个:tada:init project祝贺一下。

虽然说项目建好了。跑的也非常正常。但是如何构建项目。我心里其实没有一点b数。所以我看了官网的教程。还好这个教程不是很长。没有磨掉我的耐心。

为了照顾我这种不会用的用户。官方表示会通过一个LoadingButton组件的例子告诉我怎么从创建测试到发布的一些列操作。这个LoadingButton要实现三个特性。

  • 首先第一个是要可以接受loading的prop。这个属性代表了按钮正在做什么。
  • 在loading的状态的时候,按钮会被disabled 掉。避免多次提交。
  • 按钮默认使用type="button"而不是type="submit"`。这可能会让你有一点点惊讶。

创建项目

把我刚刚创建的项目忘掉吧。我们重新开坑。使用nwb new命令创建一个新的React component 项目。

nwb new react-component react-loading-button

运行这个会问你几个问题。你可以使用-f或者--force跳过 。

问题

这一部分就不翻译了吧。一般来说都看得懂。自己填吧。你可以一直按回车。但是如果你想用UMD方式构建的话,你需要注意最后一个问题。填上你想暴露的全局变量名。

? Which global variable name should the UMD build set? ReactLoadingButton

注意哦: nwb会创建一个开发版的react-loading-button.js和一个生产版的react-loading-button.min.js。UMD构建的文件会放在/umd文件夹里面。

项目结构

在我们愉快的构建完项目后会看见这么一个项目结构。构建后会安装reactreact-dom依赖到node_modules里。

react-loading-button/
  .gitignore
  .travis.yml
  CONTRIBUTING.md
  nwb.config.js
  package.json
  README.md
  demo/
    src/
      index.js
  node_modules/
  src/
    index.js
  tests/
    .eslintrc
    index-test.js

demo/: 包含一个React应用程序,您可以使用nwb的dev server来开发您的模块。
node_modules/: emmmm 这个你懂的
nwb.config.js: nwb的配置文件
src/: 组件的源码
tests/: nwb会运行test目录下面的测试文件。这些测试文件名后缀可以是.test,-test.js或者.spec.js。nwb给我们提供了一个最基础的测试例子在这个项目下。


cd到项目目录我们可以开始我们的示例组件了:

cd react-loading-button/

npm run

package.json里面配置了好多个我们可以在开发中使用npm run运行的script:

Command Description
npm start 给demo app启动dev server
npm test 跑测试
npm run test:coverage 跑测试在生成个覆盖率到coverage/目录
npm run test:watch 在每次修改保存的时候都跑测试
npm run build 在发布到npm之前的准备
npm run clean 删除构建的资源

npm run clean

npm run clean具体是删掉哪些资源呢。看了下package.json里面写的script是nwb clean-module && nwb clean-demo。用nwb --help看了一下这两条分别是清空demo/dist和删掉 coverage/, es/, lib/umd/文件夹用的。

运行demo app

项目结构里面包含了一个demo app。demo app在demo/src/index.js文件夹里。

注意: 如果你不需要这个demo app , 你可以删掉这个目录。

运行pm start将会给这个demo app开启dev server。每次您对demo app或组件进行更改(保存)时,都会刷新当前的编译状态。

image

如果有错误。将会同时展现在控制台和浏览器之中。所以你在开发中不会错过错误信息。

控制台的错误

image

浏览器上的错误

image

通过热重起部署组件会获得非常快的反馈(指的是页面上的,不需要刷新什么的)。

If you're into README Driven Development, it also provides a place to play with your component's API before you've built anything, and tinker with it live as you implement.

这一段不是很懂。不太理解这个基于README的开发模式。

让我们开始想象我们的LoadingButton会被怎么样使用吧。

import React, {Component} from 'react'
import {render} from 'react-dom'

import LoadingButton from '../../src'

class Demo extends Component {
  state = {loading: false}

  handleToggleLoading = () => {
    this.setState({loading: !this.state.loading})
  }

  render() {
    return <div>
      <h1>react-loading-button Demo</h1>

      <h2>Static</h2>
      <LoadingButton>Load</LoadingButton>
      <LoadingButton loading>Loading</LoadingButton>

      <h2>Dynamic</h2>
      <LoadingButton loading={this.state.loading}>
        {this.state.loading ? 'Loading' : 'Load'}
      </LoadingButton>
      <button type="button" onClick={this.handleToggleLoading}>
        Toggle Loading
      </button>
    </div>
  }
}

render(<Demo/>, document.querySelector('#demo'))

Once your component is developed, the demo app falls back to its primary purpose of creating a demo you can deploy when publishing your code without having to build and publish separately, as component builds and demo bundles are built from the same source at the same time.

实现。

以下是Loading组件的实现:

import t from 'prop-types'
import React, {Component} from 'react'

class LoadingButton extends Component {
  static propTypes = {
    disabled: t.bool,
    loading: t.bool,
    type: t.string,
  }
  static defaultProps = {
    disabled: false,
    loading: false,
    type: 'button',
  }
  render() {
    let {children, disabled, loading, type, ...props} = this.props
    if (loading) {
      disabled = true
    }
    return <button disabled={disabled} type={type} {...props}>
      {children}
    </button>
  }
}

export default LoadingButton

image

测试

咦 略过

构建和发布

终于到我最感兴趣的章节了

nwb提供了一个默认设置,它可以让源代码库免受建立资源的干扰(这也可能会让潜在的贡献者感到困惑)并使您的代码作为标准Node.js开发设置的一部分使用,通过模块打包器并通过<script>标记直接在浏览器中使用。

准备发布

npm run build将准备发布,创建组件:

  • 一个CommonJs会被构建在lib/目录里面
  • 一个ES6模块会被构建在es/里(默认会构建无需配置)
  • UMD开发版和生产版都会构建在umd/目录里面。

CommonJs构建使用了add-module-exports插件。避免了使用require()的时候还要加个.default。

任何有propTypes声明的组件或者无状态的函数时组件都会被包裹在if (process.env.NODE_ENV !== 'production')环境之中。所以它们会自动从应用程序的生产版本中剥离。

默认情况下,nwb还将在demo / dist /中创建演示React应用程序的生产版本,你随时可以把这个demo部署在你想要放的地方(例如Surge,Github Page的等)

image

发布到npm

一旦你写完了项目。你就可以用npm publish发布你的应用。就是这么的简单...

好了 看完了。 其他都不是我特别感兴趣的部分了。。 要用到的时候再说吧

Java 泛型

Java 1.5发行版本添加了泛型(Generic)。没有泛型之前,从集合中读取到的每一个对下那个都必须进行转化。如果有人不小心插入了类型错误的对象,在运行时就会出错。

Jsp 复习

Jsp 全称是 Java Server Page。它和PHPASPASP.NET 等语言类似,运行在服务端的语言。

是Sun公司推出来的东西。JSP 技术是以 Java 语言作为脚本语言的,就是说它可以直接在里面写java代码。也因为它这个特性即是缺点也是优点。优点是想写啥写啥,缺点是乱乱的。和js,html,css混在了一起。

JSP的性能据说还是不错的相比于其他的一些模板语言。

JSP是Java EE的一部分。

// 上次写jsp也快两年了... 为了面向公司学习于是得复习一下。老实说基本是照搬RUNOOB的。自己只做了一部分改动

React render prop 和 Vue slot-scope

之前有看过render prop或者render children的文章。第一次看是看尤雨溪推特发的那条。楼下有人问在vue里面有类似render prop这样的操作吗? 尤雨溪给的答案是slot-scope。 当时也看不懂他们在聊啥,也不是很理解render prop的概念,文章也只是虽翻遍了两下就close了。只记得作者好像是个光头,也叫Mj,不过不是King of Pop。

之前文章没认真看也不知道场景。有一天在搜不知道什么的时候搜到了一个叫做react-media的库。看了一下Usage被惊艳到了,但是又感觉似曾相识。才想起来是之前看的文章,再看这个库的作者就是那个光头,mj 。

我们先看一下库的用法吧。

import React from "react";
import Media from "react-media";

class App extends React.Component {
  render() {
    return (
      <div>
        <Media query="(max-width: 599px)">
          {matches =>
            matches ? (
              <p>The document is less than 600px wide.</p>
            ) : (
              <p>The document is at least 600px wide.</p>
            )
          }
        </Media>
      </div>
    );
  }
}

通过matches 的true/false来决定显示哪一种视图。代码真的非常清晰。与以往的jsx写法不一样。这里的children是直接传的一个function过去。这就是render children。还有一种是render prop

import React from "react";
import Media from "react-media";

class App extends React.Component {
  render() {
    return (
      <div>
        <Media
          query="(max-width: 599px)"
          render={() => <p>The document is less than 600px wide.</p>}
        />
      </div>
    );
  }
}

它们基本上没有差异只是叫法上有略微的差异。本质上children也是个prop。所以render children也算是render prop

render prop在社区慢慢的变的热门了起来。React 16.3的新的Context Api就用到了这一特性。通过ProviderConsume配合使用可以替代一些Redux的工作。文档在这里。React官方文档也有render-props的介绍。写的非常的清晰。

我在这里用react-media描述一下工作原理。主要我们看render部分的代码

render() {
const { children, render } = this.props;
const { matches } = this.state;

return render
  ? matches ? render() : null
  : children
    ? typeof children === "function"
      ? children(matches)
      : !Array.isArray(children) || children.length // Preact defaults to empty children array
        ? matches ? React.Children.only(children) : null
        : null
    : null;
}

holy shit,这段三元。我整理一下。顺便发个PR

render() {
  const { children, render } = this.props;
  const { matches } = this.state;
  if (render && matches) {
    return render ()
  }
  else if (children && typeof children === "function") {
    return children(matches)
  }
  else if ((!Array.isArray(children) || children.length) && matches) {
    return React.Children.only(children)
  }
  return null
}

原理就是将prop作为functional component。把父(React-Media)组件的状态传下去。**render prop相较于之前的HOC会更加的灵活和易懂。并且能做到HOC能做到的所有事情。并且不会有HOC的缺点。**比如说:

  1. prop的命名冲突,如果两个HOC的prop的参数名一样会导致前者的prop被覆盖掉。并且React不会告诉你被覆盖了。如果使用了render prop,不会自动合并参数,所以也不会存在参数被覆盖的问题。

  2. 我们很难知道是哪个HOC组件提供了哪些状态作为prop。如果使用了render prop我们可以直接看出哪些参数来自哪个组件。

Vue-slot scope

render prop有异曲同工之妙,可能是社区的共识。只需要在slot上bind一个值就可以直接在子组件使用slot-scope获取到然后使用。

参考资料

React-in-patterns 笔记

React-in-patterns 我个人很喜欢这本。作者还是蛮风趣幽默的。同样English不是native language,人家可以写书,我只能看他的书 ¯(ツ)/¯

Also notice that English is not my native language. If you see a typo or something sounds weird please contribute here github.com/krasimir/react-in-patterns. If you are reading from a printed version of this book then feel free to use a pen ¯(ツ)/¯

书的知识点围绕着React的各种Pattern,不会很枯燥,倒不如说很有趣。虽然都非常主观,围绕做作者的视角。以下笔记不全出自React-in-patterns,而是在看了React-in-paterns之后想到的。更多的是我自己额外的一些研究和拓展。

Event handlers

我们在看官方文档的时候应该都有见过官方说过我们可以需要通过bind(this)来绑定事件,如果我们想访问component的this。比如一个Button组件。

它可能是这么写的

class Button extends React.Component {
   constructor (props) {
     super(props);
     this.state = { message: 'React in patterns' };
   }
  
  handlerClick () {
     console.log(this.state.message)
  }   

   render () {
     return (
       <button onClick={ this.handlerClick.bind(this) }>clike me</buton>
    )
  }
}

并且它也可能是这样的

class Button extends React.Component {
   constructor (props) {
     super(props)
     this.state = { message: 'React in patterns' }
     this.handlerClick = this.handlerClick.bind(this)
   }
  
  handlerClick () {
     console.log(this.state.message)
  }   

   render () {
     return (
       <button onClick={ this.handlerClick }>clike me</buton>
    )
  }
}

它们两个谁比较好,其实很显然当然是后者,因为在列表渲染多次重绘的时候,前者会不停的调用bind函数。后者不会只会做一次。

除了上面的情况以外。在构造器里面写bind能让我们使用一个方法处理多种输入。

class Form extends React.Component {
  constructor(props) {
    super(props);
    this._onNameChanged = this._onFieldChange.bind(this, 'name');
    this._onPasswordChanged = this._onFieldChange.bind(this, 'password');
  }
  render() {
    return (
      <form>
      <input onChange={ this._onNameChanged } />
      <input onChange={ this._onPasswordChanged }
      />
      </form>
    );
  }
  _onFieldChange(field, event) {
    console.log(`${ field } changed to ${ event.target.value }`);
  }
};

HOC

HOC的确很强大社区里也很流行。我个人对这个是有偏见的,我认为它不是最佳的实现。举个我不喜欢的原因,在React的devtools里面,高度组合过的HOC组件嵌套层级真的很深到了惨不忍睹的境界。

社区里最常用的HOC库应该是recompose。之前也写过一篇关于recompose的笔记。感兴趣的可以看一下。最基本的HOC的例子如下:

var enhanceComponent = (Component) =>
  class Enhance extends React.Component {
    render() {
      return (
        <Component {...this.props} style={{ border: '1px solid red' }} />
      )
    }
  };

var OriginalTitle = () => <h1>Hello world</h1>;
var EnhancedTitle = enhanceComponent(OriginalTitle);

class App extends React.Component {
  render() {
    return <EnhancedTitle />;
  }
};

这个例子展示的是给OriginTitle组件加一层外边框。

Function as a children, render prop

这一个之前很想写的一篇也没有写闲置了。在写那篇之前这里先整理一下吧。Function as children这个我认为是React里很强大的一个概念。Function as children或者是render prop都只是写法上的不同,本质上做的还是一件事情。

function TodoList({ todos, render }) {
  return (
    <section className='main-section'>
      <ul className='todo-list'>{
        todos.map((todo, i) => (
          <li key={ i }>{ render(todo) }</li>
        ))
      }</ul>
    </section>
  );
}

return (
  <TodoList
    todos={ todos }
    render={
      todo => isCompleted(todo) ?
        <b>{ todo.label }</b> : todo.label
    } />
);

另一个版本

function TodoList({ todos, render }) {
  return (
    <section className='main-section'>
      <ul className='todo-list'>{
        todos.map((todo, i) => (
          <li key={ i }>{ children(todo) }</li>
        ))
      }</ul>
    </section>
  );
}

return (
  <TodoList
    todos={ todos }>
    {
      todo => isCompleted(todo) ?
        <b>{ todo.label }</b> : todo.label
    }
 </TodoList>
);

他们其实都是一样的在做一样的事情。TodoList 完全不知道 label 和 status 属性。行为完全由父组件自定义。

我们可以抽出行为做出更为高级的抽象。React-Media就是一个很好的例子。

import React from "react";
import Media from "react-media";

class App extends React.Component {
  render() {
    return (
      <div>
        <Media query="(max-width: 599px)">
          {matches =>
            matches ? (
              <p>The document is less than 600px wide.</p>
            ) : (
              <p>The document is at least 600px wide.</p>
            )
          }
        </Media>
      </div>
    );
  }
}

还有其他的例子比如说一个权限验证的组件。如果用户具有读取产品列表的权限,那么我们便渲染 ProductList 。

<Authorize
  permissionsInclude={[ 'read:products' ]}
  render={ () => <ProductsList /> } />

Flux

Fulx并非react特有。首先我们来看官网

Flux is the application architecture that Facebook uses for building client-side web applications. It complements React's composable view components by utilizing a unidirectional data flow. It's more of a pattern rather than a formal framework, and you can start using Flux immediately without a lot of new code.

Flux是FaceBook用来构建客户端应用的架构。Flux通过使用单向数据流来补充React的可组合视图组件。它更多的是一种模式,而不是一个正式的框架,您可以立即开始使用Flux,而无需大量的新代码。

视频很有意思。 讲演的人是Jing Chen。**人。这一张视频截图说明了传统MVC架构存在的问题。但是评论区也有表示FB提供的MVC的图是错误的,并且戏称Jing的做法完全不对,FB又一次发明了MVC,只是把他叫做了MVC。如果你对撕逼感兴趣转战评论区和reddit,还蛮有意思的。

image

好了言归正传。这个图的确有些问题的本人也是承认的。

Yeah, that was a tricky slide [the one with multiple models and views and bidirectional data flow], partly because there's not a lot of consensus for what MVC is exactly - lots of people have different ideas about what it is. What we're really arguing against is bi-directional data flow, where one change can loop back and have cascading effects.

但是是不妨碍我们理解。就照着Jing的话来说吧。那么上面的传统MVC架构关系非常复杂,MV双向的箭头意味着Model会改变View,同时View也会改变Model,这样在应用变得越来越复杂,特性越来越多的时候我们会非常难以追踪应用的变化状态。

这里需要补充一个例子。我个人认为我上面写的很不专业这一部分会搁置,直到找到足够多的资料。

ok 最近找到了资料其实MVC这种设计模式一直都是有很多变种,并且一直都不是很纯粹,时间证明了MVC 并不是web开发的最好实践。

一个更好的实践是MVVM。

参考资料

怎样才算是美观的代码

好的源代码应当"看上去养眼"。确切的说,有三条原则:

  • 使用一致的布局,让读者很快就习惯这种风格。
  • 让相似的代码上看去相似。
  • 把相关的代码行分组,形成代码块。

为什么我们要关注代码的审美

假设你不得不用这个类:

class StatsKeeper{
public:
// A class for keeping track of a series of doubles
        void Add(double d); // and methods for quick statistics about them
        private: int count;          /* how many so    far
        */ public:
                double Average();
    private:   double minium;
    list<double>
      past_items
      ;double maximum;
};

相对于下面这个更整洁的版本,你可能要花更多的时间来理解上面的代码:

// A class for keeping track of a series of doubles
// and methods for quick statistics about them
class StatsKeeper{
    public:
        void Add(double d); 
        double Average();

    private:
        list<double> past_items;  
        int count;          //how many so far
        double minium;
        double maximum;
};

很明显,使用从审美角度来讲让人愉悦的代码更容易让人理解。试想一下,你编程的时间大部分时间都花在看代码上! 浏览代码的速度越快,人们就越容易使用它。

重新安排换行来保持一致和紧凑

假设你在写Java代码来评估你的程序在不同的网络连接速度下的行为。你有一个TcpConnectionSimulator类,它的构造函数有4个参数:

  1. 网络连接的速度(Kbps)
  2. 平均延时(ms)
  3. 延时的"抖动"(ms)
  4. 丢包率(ms)

你的代码需要3个不同的TcpConnectionSimulator示例:

public class PerfomanceTester{
    public static final TcpConnectionSimulator wifi = new TcpConnectionSimulator(
        500, /* Kbps */
        80, /* millisecs latency */
        200, /* jitter */
        1 /* packet loss % */);

    public static final TcpConnectionSimulator t3_fiber = 
        new TcpConnectionSimulator(
            45000, /* Kbps */
            10, /* millisecs latency */
            0, /* jitter */
            0 /* packet loss % */);

    public static final TcpConnectionSimulator cell = new TcpConnectionSimulator(
        100, /* Kbps */
        400, /* millisecs latency */
        250, /* jitter */
        5 /* packet loss % */);
}

这段示例代码需要很多额外的换行来满足每行80个字符的限制(这是你们公司的编码规范)。遗憾的是,这使得t3_fiber的定义看上去和它的另据不一样。这段代码的"剪影"看上去很怪,它毫无理由的让t3_fiber很突兀。这违反了"相似的代码看上去很相似"这条原则。

为了让代码看上去更一致,我们可以引入更多换行(同时还可以让注释对其)

public class PerfomanceTester{
    public static final TcpConnectionSimulator wifi = 
        new TcpConnectionSimulator(
            500, /* Kbps */
            80, /* millisecs latency */
            200, /* jitter */
            1 /* packet loss % */);

    public static final TcpConnectionSimulator t3_fiber = 
        new TcpConnectionSimulator(
            45000, /* Kbps */
            10, /* millisecs latency */
            0, /* jitter */
            0 /* packet loss % */);

    public static final TcpConnectionSimulator cell =
        new TcpConnectionSimulator(
            100, /* Kbps */
            400, /* millisecs latency */
            250, /* jitter */
            5 /* packet loss % */);
}

这段代码有优雅一致的风格,并且很容易从头到尾快速浏览。但遗憾的是,它占用了更多纵向的空间。并且它还把注释重复了3遍。

下面是写这个类的更紧凑的方法。

public class PerfomanceTester{
    // TcpConnectionSimulator(throughput , latency , jitter , packet_lose)
    //                           [kbps]     [ms]      [ms]     [percent]
    public static final TcpConnectionSimulator wifi = 
        new TcpConnectionSimulator(500,80,200,1);

    public static final TcpConnectionSimulator t3_fiber = 
        new TcpConnectionSimulator(45000,10,0,0);

    public static final TcpConnectionSimulator cell =
        new TcpConnectionSimulator(100,400,250, 5;
}

我们把注释挪到了上面,然后把所有的参数都放在一行上。现在尽管注释不再紧挨相邻的每个数字,但"数据"现在排成更紧凑的一个表格。

用方法来整理不规则的东西

假设你有一个个人数据,它提供了下面这个函数:

// Turn a partial_name like "Doug Adams" into "Mr. Douglas Adams".
// If not possible , 'error' is filled with an explanation.
string ExpandFullName(DatabaseConnection dc,string partial_name,string* error);

并且这个函数由一系列的例子来测试:

DatabaseConnection database_connection;
string error;
assert(ExpandFullName(database_connection,"Doug Adams",&error)
    == "Mr. Douglas Adams");
assert(ExpandFullName(database_connection,"Jake Brown",&error)
    == "Mr. Jacob Brown III");
assert(error == "");
assert(ExpandFullName(database_connection,"No Such Huy",&error) == "");
assert(error == "no match found");
assert(ExpandFullName(database_connection,"John",&error) == "");
assert(error == "more than one result");

这段代码没什么美感可言。有些行长的都换行了。也没有一致的风格。

对于这种情况,重新布置换行也竟只能做到这样。更大的问题是这里有很多重复的串,例如"assert(ExpandFullName(database_connection...))",其中还有很多的"error"。要是真的想改进这段代码,需要一个辅助方法。就像这样:

CheckFullName("Doug Adams","Mr. Douglas Adams","");
CheckFullName("Jake Brown","Mr. Jacob Brown III""");
CheckFullName("No such Guy","","no match found");
CheckFullName("John","","more than one result");

现在,很明显这里有4个测试,每个使用了不同的参数。尽管所有的"脏活"都放在了CheckFullName()中,这个函数也没有那么糟糕:

void CheckFullName(string partial_name,
                   string expected_full_name,
                   string expected_error) {
     // database_connnection is now a class member
     string error;
     string full_name = ExpandFullName(database_connection,partial_name,&error);
     assert(error == expected_error);
     assert(full_name == expected_full_name); }

尽管我们的目的仅仅是让代码更有美感,但这个动词同时有几个附带的效果:

  • 它消除了原来代码中大量的重复,让代码变得更紧凑。
  • 每个测试用例重要的部分( 名字和错误字符串 )现在都变得很直白。以前,这些字符串是混杂在像database_connection和error这样的标识之间的,这使得一眼看全这段代码变得很难。
  • 现在添加新测试应当更简单。

这个故事想要传达的寓意是让代码"看上去漂亮"通常会带来不限于表面层次的改进,它可能会帮你把代码的结构组的更好。

在需要时使用列对齐

整齐的边和列能让读者更轻松地浏览文本。

有时你可以借用"列对齐"的方法让代码易读。例如,在前一部分中,你可以用空白来把CheckFullName()的参数排成:

CheckFullName("Doug Adams" , "Mr. Douglas Adams"  , "");
CheckFullName("Jake Brown" , "Mr. Jacob Brown III""");
CheckFullName("No such Guy", ""                   , "no match found");
CheckFullName("John"       , ""                   , "more than one result");

在这段代码中,很容易区分出ChecckFullName()的第二个和第三个参数。下面是一个简单的例子,它有一大组变量定义:

# Extract POST parameters to local variables
details  = request.POST.get('details')
location = request.POST.get('location')
phone    = equest.POST.get('phone')
email    = request.POST.get('email')
url      = request.POST.get('url')

你可能注意到了,第三个定义有个拼写错误(把request写成了equest)。当所有的内容都这么整齐的排列起来时,这样的错误就很明显。

在wget数据库中,可用的命令行选项(有一百多项)这样列出:

commands[] = {
    ...
    { "timeout",          NULL,                 cmd_spec_timeout },
    { "timestamping" ,    &opt.timestamping,    cmd_boolean },
    { "tries",            &opt.ntry,            cmd_number_inf  },
    { "useragent",        NULL,                 cmd_spec_useragent },
    ...
}

这种方式使行这个列表很容易快读和从一列跳到另一列。

应该使用列对齐吗

列对齐的好处是"让相似的代码看起来相似"。但是不可避免的在建立和维护的时候工作量很大。如果再不花费非常多的功夫的时候,可以尝试。如果工作量很大。那么可以不这么做。

选一个有意义的顺序,始终一致地使用它

在很多的情况下,代码的顺序不会影响程序的正确性。例如,下面的5个变量定义可以写成任意的排序:

# Extract POST parameters to local variables
details  = request.POST.get('details')
location = request.POST.get('location')
phone    = equest.POST.get('phone')
email    = request.POST.get('email')
url      = request.POST.get('url')

在这种情况下,不要随机地排序,把它们按有意义的方式排列会有帮助。下面是一些想法:

  • 让变量的顺序对应的HTML表单中的字段的顺序相匹配。
  • 从"最重要"到"最不重要"排序。
  • 从字母顺序排序。

无论使用什么顺序,你在代码中应当始终使用这一顺序。如果后面改变了这个顺序。那会让人很困惑。

按声明按块组织起来

我们的大脑很自然地会按照分组和层次结构来思考,因此你可以通过这样的组织方式来帮助读者快速地理解你的代码。

例如下面是一个签单服务器类C++类,这里有它所有方法的声明:

class ForntendServer{
    public:
      ForntendServer();
      void ViewProfile(HttpRequest* request);
      void OpenDatabase(string location , string user);
      void SaveProfile(HttpRequest* request);
      string ExtractQueryParam(HttpRequest* request , string param);
      void ReplyOk(HttpRequest* request, string html);
      void FindFriends(HttpRequest* request);
      void ReplyNotFound(HttpRequest* request, string error)
      void CloseDatabase(string location);
      ~FrontendServer();
}

这不是很难看的代码。但是可以肯定这样的布局不会对读者更快地理解所有的方法有什么帮助。不要把所有的方法都放到一个巨大的代码块中,应当按逻辑把它们分成组,像以下这样。

class ForntendServer{
    public:
      ForntendServer();
      ~FrontendServer();

      // Handlers      
      void ViewProfile(HttpRequest* request);
      void SaveProfile(HttpRequest* request);
      void FindFriends(HttpRequest* request);
      
      // Request/Reply Utilities
      string ExtractQueryParam(HttpRequest* request , string param);
      void ReplyOk(HttpRequest* request, string html);
      void ReplyNotFound(HttpRequest* request, string error)

      // Database Helpers
      void OpenDatabase(string location , string user);
      void CloseDatabase(string location);
}

这个版本容易理解多了。他还更容易读,尽管代码行数更多了。原因是你可以快速的找出4个高层次段落,然后在需要时阅读每个段落的具体内容。

因为同样的原因,代码也应当分成"段落"。例如,没有人会喜欢读下面这样一大块代码:

# Import the user's email contacts, and match them to users in our system.
# Then display a list of those users that he/she isn;t already friends with.
def suggest_new_friends(user, email_password):
    friends = user.friends()
    firend_emails = set(f.email for f in friends)
    contacts = import_contacts(user.email, email_password)
    contact_emails = set(c.email for c in contacts)
    non_friend_emails = set(contact_email - friend_emails)
    suggested_friends = User.objects.select(email_in = none_friend_emails)
    display['user'] = user
    display['friends'] = friends
    display['suggested_friends'] = suggested_friends
    return render("suggested_friends.html",display)

可能看上去并不明显,但这个函数会经过数个不同的步骤。因此,把这些行代码分成段落会特别有用:

def suggest_new_friends(user, email_password):
    # Get the user's friends' email addresses.
    friends = user.friends()
    firend_emails = set(f.email for f in friends)

    # Import all email addresses from this user's email account.
    contacts = import_contacts(user.email, email_password)
    contact_emails = set(c.email for c in contacts)

    # Find matching users that they aren't already friends with.
    non_friend_emails = set(contact_email - friend_emails)
    suggested_friends = User.objects.select(email_in = none_friend_emails)

    ## Display these lists on the page.
    display['user'] = user
    display['friends'] = friends
    display['suggested_friends'] = suggested_friends

    return render("suggested_friends.html",display)

请注意,我们还给每个段落加了一条总结性的注释。这也会帮助读者浏览代码。

个人风格与一致性

有相当一部分审美选择可以归结为个人风格。例如,类定义的大括号应该放在哪里:

class Logger{
    ...
};

还是:

class Logger
{
    ...
};

选择一种风格而非另一种,不会真的影响到代码的可读性。但如果把两种风格混在一起,就会对可读性有影响了。

在日常的开发里应该遵循团队的风格。

recompose 使用方法

因为公司用到了。所以特地学习一下用法。在此记录一下。

recompose官方对自己的定位如下:

A React utility belt for function components and higher-order components.

一个用来把普通方法和高阶组件组合起来的工具。

Think of it like lodash for React.

可以认为他是React版的lodash。

这里有一个最基础的demo。实现了通过按钮控制count的增减。

const { withStateHandlers } = Recompose;

const Counter = ({ count, increment, decrement }) =>
  <div>
    Count: {count}
    <div>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  </div>;

const App = withStateHandlers(
  {
    count: 0
  },
  {
    increment: ({ count }) => () => ({ count: count + 1 }),
    decrement: ({ count }) => () => ({ count: count - 1 })
  }
)(Counter);

ReactDOM.render(<App />, document.getElementById("container"));

这里完完全全的把组件的状态和功能分离了出来。如果不使用recompose我们可能会这样写。

const Counter = ({ count, increment, decrement }) => (
  <div>
    Count: {count}
    <div>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  </div>
);

class App extends React.Component {
  constructor (props) {
    super(props);
    this.state = { count: 0 }
    this.incr = this.increment.bind(this)
    this.decr = this.decrement.bind(this)
  }
  increment () {
    this.setState({ count: ++this.state.count })
  }
  decrement () {
    this.setState({ count: --this.state.count })
  }
  render () {
    return (<Counter count={ this.state.count } 
                     increment = { this.incr } 
                     decrement = { this.decr } >
           </Counter>)
  }
}

但是写成这样子不得不说实在有点麻烦。源码的话思路差不多

好了 有了最基本的用法 我们再看看recompose给我们带来了什么其他的。

除了上面那个withState的例子以外还有一个withReducer的例子。withReducer只是一种Redux-style风格的写法和真正的Redux无关。

const counterReducer = (count, action) => {
  switch (action.type) {
  case INCREMENT:
    return count + 1
  case DECREMENT:
    return count - 1
  default:
    return count
  }
}

const enhance = withReducer('counter', 'dispatch', counterReducer, 0)
const Counter = enhance(({ counter, dispatch }) =>
  <div>
    Count: {counter}
    <button onClick={() => dispatch({ type: INCREMENT })}>Increment</button>
    <button onClick={() => dispatch({ type: DECREMENT })}>Decrement</button>
  </div>
)

这两种写法你喜欢哪一种取决于你。

你可能注意到了enhance这个单词频繁出现。enhance的意思是增强表示被recompose增强过的组件。

recompose 还封装了很多常用的React模式。比如

const enhance = defaultProps({ component: 'button' })
const Button = enhance(componentFromProp('component'))

<Button /> // renders <button>
<Button component={Link} /> // renders <Link />

这样就可以让Button组件随意选择需要用到的类型。默认是button,可以变成其他的标签比如a,li等等。

除此之外还有withContext。让我们看看React官网提供的标准的Context写法。

import PropTypes from 'prop-types';

class Button extends React.Component {
  render() {
    return (
      <button style={{background: this.context.color}}>
        {this.props.children}
      </button>
    );
  }
}

Button.contextTypes = {
  color: PropTypes.string
};

class Message extends React.Component {
  render() {
    return (
      <div>
        {this.props.text} <Button>Delete</Button>
      </div>
    );
  }
}

class MessageList extends React.Component {
  getChildContext() {
    return {color: "purple"};
  }

  render() {
    const children = this.props.messages.map((message) =>
      <Message text={message.text} />
    );
    return <div>{children}</div>;
  }
}

MessageList.childContextTypes = {
  color: PropTypes.string
};

如果使用withContext。把MessageList改成这样子。

class MessageList extends React.Component {
  render() {
    const children = this.props.messages.map((message) =>
      <Message text={message.text} />
    );
    return <div>{children}</div>;
  }
}

然后提供一个storeprovide里面。就会动态生成组件。此时的MessageList不在是写死的了。你甚至可以变成其他的颜色之类的。只要你传了store

const provide = store => withContext({color: PropTypes.string}, () => ({ ...store }))

举个例子

const EnMessageList= () => <MessageList messages={[{text: 'hello'},{text: 'world'}]}></MessageList>
const Purple = provide({ color: "purple" })(EnMessageList)
const Green = provide({ color: "green" })(EnMessageList)

defaultPropscomponentFromProp的源码非常简单。

withContext也非常简单。就是给组件添加getChildContext方法和增加childContextTypes属性用来检测类型。

原组件只接受一个name。但是我们想在name上做一点其他的事情。这时候我们可以用mapProps

class Name extends Component {
  render () {
    return <div>{this.props.name}</div>
  }
}

const FullName = mapProps(({firstName, lastName}) => {
  return { name:  `${firstName} · ${lastName}`}
})(Name)

const Example = () => <FullName firstName="Gavin" lastName="Phang"></FullName>

这样就增强了原组件的表达能力。

接下来是withProps。他可以说是mapProps的增强。后者只接受方法,而withProps支持接受方法和对象。我们可以直接将上面的mapProps替换成withProps。我们看他的源码。做法就是调用了mapProps然后再把input传到组件里。input可以是方法或者对象。

那我们替换一下。

class Name extends Component {
  render () {
    return <div>{this.props.name}</div>
  }
}

const FullName = withProps(({firstName, lastName}) => {
  return { name:  `${firstName} · ${lastName}`}
})(Name)

const Example = () => <FullName firstName="Gavin" lastName="Phang"></FullName>

另一种直接使用对象的写法。

class Name extends Component {
  render () {
    return <div>{this.props.name}</div>
  }
}

const FullName = withProps({ name:  `Gavin · Phang`})(Name)

const Example = () => <FullName></FullName>

效果一样但是第二种写死了。

接下来是withPropsOnChange。我们来看一个场景。这个按钮点击的话state就会改变。然后传递到子组件的props也会变。此时子组件会被重新渲染。

class Name extends Component {
  render() {
    return <div>{this.props.name}</div>;
  }
}

class Change extends Component {
  constructor(props) {
    super(props);
    this.handleClick = this.click.bind(this);
    this.state = { name: "abc" };
  }

  click() {
    this.setState({ name: "cba" });
  }

  render() {
    return (
      <div>
        <Name name={this.state.name} />
        <button onClick={this.handleClick}>change</button>
      </div>
    );
  }
}

但是如果我们不想每次都变化只想某个属性变化的时候才重新渲染怎么做呢。答案就是用withPropsOnChangewithPropsOnChange接收的参数如下。

withPropsOnChange(
  shouldMapOrKeys: Array<string> | (props: Object, nextProps: Object) => boolean,
  createProps: (ownerProps: Object) => Object
): HigherOrderComponent

shouldMapOrKeys可以接受数组或者一个返回boolean的方法。createProps参数只能传方法并且返回一个Ojbect。

这里我们做个实验。我们增加FullName组件。

const FullName = withPropsOnChange([""], ({ name }) => {
  return { name };
})(Name);

然后改变原来的Change组件的render函数。

class Change extends Component {
  constructor(props) {
    super(props);
    this.handleClick = this.click.bind(this);
    this.state = { name: "abc" };
  }

  click() {
    this.setState({ name: "cba" });
  }

  render() {
    return (
      <div>
        <FullName name={this.state.name} />
        <button onClick={this.handleClick}>change</button>
      </div>
    );
  }
}

此时点击的话 我们会发现FullName不会改变了。如果我们给数组传name

const FullName = withPropsOnChange(["name"], ({ name }) => {
  return { name };
})(Name);

这时候会重新渲染。如果我们传方法的话可以更加自由的控制。决定哪些需要情况需要被重新渲染。

defaultProps这个相当有用。假设有一个默认是白色的按钮。

class Button extends Component {
  render() {
    return (
      <button style={{ background: this.props.color || "#FFFFFF" }}>
        hello
      </button>
    );
  }
}

我们想要一个其他颜色的按钮只需要调用一下defaultProps方法就好了。

const newButton = defaultProps({ color: "#FAC123" })(Button);

flattenProp好像有卵用好像没卵用的一个方法。用起来类似解构。

class Span extends Component {
  render() {
    return (
      <span
        style={{
          background: this.props.color || "#FFFFFF",
          border: `1px solid ${this.props.border}`
        }}
      >
        my background is {this.props.color} , and my border is {this.props.border}
      </span>
    );
  }
}

const enhance = compose(
  withProps({
    style: {
      color: "pink",
      border: "green"
    }
  }),
  flattenProp("style")
);

此时这个enhance的组件可以接收style并解构这个style。但是好像不如直接解构来的方便。

有的时候可以让组件看起来相当清晰。但是没什么卵用感觉

// The `post` prop is an object with title, author, and content fields
const enhance = flattenProp('post')
const Post = enhance(({ title, content, author }) =>
  <article>
    <h1>{title}</h1>
    <h2>By {author.name}</h2>
    <div>{content}</div>
  </article>
)

withStatewithHandle算是比较有用的方法。可以用compose把两个函数组合起来。但是实际上写起来还挺麻烦。看这里。不如用更高阶的把他俩组合在一起的函数withStateHandlers。就是文中一开始提到的那个。

这个方法接收的参数如下

withStateHandlers(
  initialState: Object | (props: Object) => any,
  stateUpdaters: {
    [key: string]: (state:Object, props:Object) => (...payload: any[]) => Object
  }
)

一个简单的使用例子

const Counter = withStateHandlers(
  ({ initialCounter = 0 }) => ({
    counter: initialCounter,
  }),
  {
    incrementOn: ({ counter }) => (value) => ({
      counter: counter + value,
    }),
    decrementOn: ({ counter }) => (value) => ({
      counter: counter - value,
    }),
    resetCounter: (_, { initialCounter = 0 }) => () => ({
      counter: initialCounter,
    }),
  }
)(
  ({ counter, incrementOn, decrementOn, resetCounter }) =>
    <div>
      <p>{ counter }</p>
      <button onClick={() => incrementOn(2)}>Inc</button>
      <button onClick={() => decrementOn(3)}>Dec</button>
      <button onClick={resetCounter}>Reset</button>
    </div>
)

branch结合其他函数蛮有用的。用法如下

branch(
  test: (props: Object) => boolean,
  left: HigherOrderComponent,
  right: ?HigherOrderComponent
): HigherOrderComponent

举个例子

const Green = WrappedComponent => props => {
  return (
    <div style={{ color: "white", background: "#51cf66", padding: 8 }}>
      <WrappedComponent {...props} />
    </div>
  );
};

const Red = WrappedComponent => props => {
  return (
    <div style={{ color: "white", background: "#ff6b6b", padding: 8 }}>
      <WrappedComponent {...props} />
    </div>
  );
};

const Text = _ => <div>Ass We Can</div>;

const BC = branch(
  ({ color }) => {
    return color === "green";
  },
  Green,
  Red
)(Text);

const ba = _ => <BC color={"green"} />;

例子是 如果颜色不是绿色的 那就会选用红色的

代码命名上的思考

无论是命名变量、函数还是类,都可以使用很多相同的原则。我们喜欢把名字当做一条小小的注释。尽管空间不算很大,但选择一个好名字可以让它承载很多信息。

我们在程序中见到的很多名字都很模糊,例如tmp。就算是看上去合理的词,如size或者get,也都没有装入很多信息。

我们应该选择专业的词。

  • 避免泛泛的名字(或者说要知道什么时候使用它)。
  • 用具体的名字代替抽象的名字。
  • 使用前缀或后缀来给名字附带更多信息。
  • 决定名字的长度。
  • 利用名字的格式来表达含义。

选择专业的词

“把信息装入名字中”包括要选择非常专业的词,并且避免使用“空洞”的词。
例如,“get”这个词就非常不专业,例如在下面的例子中:

def GetPage(url): ...

“get”这个词没有表达出很多信息。这个方法是从本地的缓存中得到一个页面,还是从数据库中,或者从互联网中?如果是从互联网中,更专业的名字可以是FetchPage()或者DownloadPage()。

下面是一个BinaryTree类的例子:

class BinaryTree
{ 
    int Size();
    ...
}

你期望Size()方法返回什么呢?树的高度,节点数,还是树在内存中所占的空间?

问题是Size()没有承载很多信息。更专业的词可以是Height()、NumNodes()或者MemoryBytes()。

另外一个例子,假设你有某种Thread类:

class Thread
{ 
    void Stop();
    ...
}

Stop()这个名字还可以,但根据它到底做什么,可能会有更专业的名字。例如,你可以叫它Kill(),如果这是一个重量级操作,不能恢复。如果只是停止的话你可以叫它Pause(),如果有方法恢复的话可以创建一个Resume()方法。

找到更有表现力的词

要勇于使用同义词典或者问朋友更好的名字建议。英语是一门丰富的语言,有很多词可以选择。

下面是一些例子,这些单词更有表现力,可能适合你的语境:

单词 其他的选择
send deliver、 dispatch、announce、distribute、route
find search、extract、locate、recover
start launch、create、begin、open
make create、set up、build、generate、compose、add、new

但别得意忘形。在PHP中,有一个函数可以explode()一个字符串。这是个很有表现力的名字,描绘了一幅把东西拆成碎片的景象。但这与split()有什么不同?(这是两个不一样的函数,但很难通过它们的名字来猜出不同点在哪里。)

避免像tmp和retval这样泛泛的名字

使用像tmp、retval和foo这样的名字往往是“我想不出名字”的托辞。与其使用这样空洞的名字,不如挑一个能描述这个实体的值或者目的的名字。

例如,下面的JavaScript函数使用了retval:

var euclidean_norm = function (v) {
var retval = 0.0;
for (var i = 0; i < v.length; i += 1)
retval += v[i] * v[i];
return Math.sqrt(retval);
};

当你想不出更好的名字来命名返回值时,很容易想到使用retval。但retval除了“我是一个返回值”外并没有包含更多信息(这里的意义往往也是很明显的)。

好的名字应当描述变量的目的或者它所承载的值。在本例中,这个变量正在累加v的平方。因此更贴切的名字可以是sum_squares。这样就提前声明了这个变量的目的,并且可能会帮忙找到缺陷。例如,想象如果循环的内部被意外写成

retval += v[i];

如果名字换成sum_squares这个缺陷就会更明显:

sum_squares += v[i]; //我们要累加的"square"在哪里?缺陷!

然而,有些情况下泛泛的名字也承载着意义。让我们来看看什么时候使用它们有意义

tmp

请想象一下交换两个变量的经典情形:

if (right < left) {
    tmp = right;
    right = left;
    left = tmp;
}

在这种情况下,tmp这个名字很好。这个变量唯一的目的就是临时存储,它的整个生命周期只在几行代码之间。tmp这个名字向读者传递特定信息,也就是这个变量没有其他职责,它不会被传到其他函数中或者被重置以反复使用。但在下面的例子中对tmp的使用仅仅是因为懒惰:

String tmp = user.name();
tmp += " " + user.phone_number();
tmp += " " + user.email();
...
template.set("user_info", tmp);

尽管这里的变量只有很短的生命周期,但对它来讲最重要的并不是临时存储。用像user_info这样的名字来代替可能会更具描述性。

在下面的情况中,tmp应当出现在名字中,但只是名字的一部分:

tmp_file = tempfile.NamedTemporaryFile()
...
SaveData(tmp_file, ...)

请注意我们把变量命名为tmp_file而非只是tmp,因为这是一个文件对象。想象一下如果我们只是把它叫做tmp:

SaveData(tmp, ...)

只要看看这么一行代码,就会发现不清楚tmp到底是文件、文件名还是要写入的数据。

循环迭代器

像i、j、i t e r和i t等名字常用做索引和循环迭代器。尽管这些名字很空泛,但是大家都知道它们的意思是“我是一个迭代器”(实际上,如果你用这些名字来表示其他含义,那会很混乱。所以不要这么做!)

for (int i = 0; i < clubs.size(); i++)
    for (int j = 0; j < clubs[i].members.size(); j++)
        for (int k = 0; k < users.size(); k++)
            if (clubs[i].members[k] == users[j])
                cout << "user[" << j << "] is in club[" << i << "]" << endl;

在if条件语句中,members[]和users[]用了错误的索引。这样的缺陷很难发现,因为这一行代码单独来看似乎没什么问题:

if (clubs[i].members[k] == users[j])  // 此处错误指的是members[k]应该为members[j],而users[j]应该为users[k]

在这种情况下,使用更精确的名字可能会有帮助。如果不把循环索引命名为(i、j、k),另一个选择可以是(club_i、members_i、user_i)或者,更简化一点(ci、mi、ui)。这种方式会帮助把代码中的缺陷变得更明显:

if (clubs[ci].members[ui] == users[mi]) #缺陷!第个字母不匹配。

如果用得正确,索引的第一个字母应该与数据的第一个字符匹配:

if (clubs[ci].members[mi] == users[ui]) #OK。首字母匹配。

对于空泛名字的裁定

如你所见,在某些情况下空泛的名字也有用处

很多时候,仅仅因为懒惰而滥用它们。这可以理解,如果想不出更好的名字,那么用个没有意义的名字,像foo,然后继续做别的事,这很容易。但如果你养成习惯多花几秒钟想出个好名字,你会发现你的“命名能力”很快提升。

用具体的名字代替抽象的名字

在给变量、函数或者其他元素命名时,要把它描述得更具体而不是更抽象。

例如,假设你有一个内部方法叫做ServerCanStart(),它检测服务是否可以监听某个给定的TCP/IP端口。然而ServerCanStart()有点抽象。CanListenOnPort()就更具体一些。这个名字直接地描述了这个方法要做什么事情。下面的两个例子更深入地描绘了这个概念。

例子 DISALLOW_EVIL_CONSTRUCTORS

这个例子来自Google的代码库。在C++里,如果你不为类定义拷贝构造函数或者赋值操作符,那就会有一个默认的。尽管这很方便,这些方法很容易导致内存泄漏以及其他灾难,因为它们在你可能想不到的“幕后”地方运行。

class ClassName {
    private:
        DISALLOW_EVIL_CONSTRUCTORS(ClassName);
    public: ...
};

这个宏定义成:

#define DISALLOW_EVIL_CONSTRUCTORS(ClassName) \
    ClassName(const ClassName&); \
    void operator=(const ClassName&);

通过把这个宏放在类的私有部分中,这两个方法是私有的,所以不能用它们,即使意料之外的使用也是不可能的。

然而DISALLOW_EVIL_CONSTRUCTORS这个名字并不是很好。对于“邪恶”这个词的使用包含了对于一个有争议话题过于强烈的立场。更重要的是,这个宏到底禁止了什么这一点是不清楚的。它禁止了operator=()方法,但这个方法甚至根本就不是构造函数!

这个名字使用了几年,但最终换成了一个不那么嚣张而且更具体的名字:

#define DISALLOW_COPY_AND_ASSIGN(ClassName) ...

例子 --run_locally (本地运行)

我们的一个程序有个可选的命令行标志叫做--run_locally。这个标志会使得这个程序输出额外的调试信息,但是会运行得更慢。这个标志一般用于在本地机器上测试,例如在笔记本电脑上。但是当这个程序运行在远程服务器上时,性能是很重要的,因此不会使用这个标志。

你能看出来为什么会有--run_locally这个名字,但是它有几个问题:

  • 团队里的新成员不知道它到底是做什么的,可能在本地运行时使用它(想象一下),但不明白为什么需要它。

  • 偶尔,我们在远程运行这个程序时也要输出调试信息。向一个运行在远端的程序传递--run_locally看上去很滑稽,而且很让人迷惑。

  • 有时我们可能要在本地运行性能测试,这时我们不想让日志把它拖慢,所以我们不会使用--run_locally。

这里的问题是--run_locally是由它所使用的典型环境而得名。用像--extra_logging这样的名字来代换可能会更直接明了。

但是如果--run_locally需要做比额外日志更多的事情怎么办?例如,假设它需要建立和使用一个特殊的本地数据库。现在--run_locally看上去更吸引人了,因为它可以同时控制这两种情况。

但这样用的话就变成了因为一个名字含糊婉转而需要选择它,这可能不是一个好主意。更好的办法是再创建一个标志叫--use_local_database。尽管你现在要用两个标志,但这两个标志非常明确,不会混淆两个正交的含义,并且你可明确地选择一个。

为名字附带更多信息

我们前面提到,一个变量名就像是一个小小的注释。尽管空间不是很大,但不管你在名中挤进任何额外的信息,每次有人看到这个变量名时都会同时看到这些信息。

因此,如果关于一个变量有什么重要事情的读者必须知道,那么是值得把额外的“词”添加到名字中的。例如,假设你有一个变量包含一个十六进制字符串:

string id; // Example: "af84ef845cd8"

如果让读者记住这个ID的格式很重要的话,你可以把它改名为hex_id。

带单位的值

如果你的变量是一个度量的话(如时间长度或者字节数),那么最好把名字带上它的单位。

例如,这里有些JavaScript代码用来度量一个网页的加载时间:

var start = (new Date()).getTime(); // top of the page
...
var elapsed = (new Date()).getTime() - start; // bottom of the page
document.writeln("Load time was: " + elapsed + " seconds");

这段代码里没有明显的错误,但它不能正常运行,因为getTime()会返回毫秒而非秒。

通过给变量结尾追加_ms,我们可以让所有的地方更明确:

var start_ms = (new Date()).getTime(); // top of the page
...
var elapsed_ms = (new Date()).getTime() - start_ms; // bottom of the page
document.writeln("Load time was: " + elapsed_ms / 1000 + " seconds");

除了时间,还有很多在编程时会遇到的单位。下表列出一些没有单位的函数参数以及带单位的版本:

函数参数 带单位的参数
Start(int delay) delay → delay_secs
CreateCache(int size) size → size_mb
ThrottleDownload(float limit) limit → max_kbps
Rotate(float angle) angle → degrees_cw

附带其他重要属性

例如,很多安全漏洞来源于没有意识到你的程序接收到的某些数据还没有处于安全状态。在这种情况下,你可能想要使用像untrustedUrl或者unsafeMessageBody这样的名字。在调用了清查不安全输入的函数后,得到的变量可以命名为trustedUrl或者safeMessageBody。

下表给出更多需要给名字附加上额外信息的例子:

情形 变量名 更好的名字
一个“纯文本”格式的密码,需要加密后才能进一步使用 password plaintext_password
一条用户提供的注释,需要转义之后才能用于显示 comment unescaped_comment
已转化为UTF-8格式的html字节 html html_utf8
以“url方式编码”的输入数据 data data_urlenc

但你不应该给程序中每个变量都加上像unescaped_或者_utf8这样的属性。如果有人误解了这个变量就很容易产生缺陷,尤其是会产生像安全缺陷这样可怕的结果,在这些地方这种技巧最有用武之地。基本上,如果这是一个需要理解的关键信息,那就把它放在名字里。

匈牙利表示法

匈牙利表示法是一个在微软广泛应用的命名系统,它把每个变量的“类型”信息都编写进名字的前缀里。下面有几个例子:

名字 含义
pLast 指向某数据结构最后一个元素的指针(p)
pszBuffer 指向一个以零结尾(z)的字符串(s)的指针(p)
cch 一个字符(ch)计数(c)
mpcopx 在指向颜色的指针(pco)和指向x轴长度的指针(px)之间的一个映射(m)

名字应该有多长

当选择好名字时,有一个隐含的约束是名字不能太长。没人喜欢在工作中遇到这样的标识符:newNavigationControllerWrappingViewControllerForDataSourceOfClass

名字越长越难记,在屏幕上占的地方也越大,可能会产生更多的换行。

另一方面,程序员也可能走另一个极端,只用单个单词(或者单一字母)的名字。那么如何来处理这种平衡呢?如何来决定是把一变量命名为d、days还是days_since_last_update呢?

这是要你自己要拿主意的,最好的答案和这个变量如何使用有关系,但下面还是提出了一些指导原则。

在小的作用域里可以使用短的名字

当你去短期度假时,你带的行李通常会比长假少。同样,“作用域”小的标识符(对于多少行其他代码可见)也不用带上太多信息。也就是说,因为所有的信息(变量的类型、它的初值、如何析构等)都很容易看到,所以可以用很短的名字。

if (debug) {
    map<string,int> m;
    LookUpNamesNumbers(&m);
    Print(m);
}

尽管m这个名字并没有包含很多信息,但这不是个问题。因为读者已经有了需要理解这段代码的所有信息。

然而,假设m是一个全局变量中的类成员,如果你看到这个代码片段:

LookUpNamesNumbers(&m);
Print(m);

这段代码就没有那么好读了,因为m的类型和目的都不明确。

因此如果一个标识符有较大的作用域,那么它的名字就要包含足够的信息以便含义更清楚。

首字母缩略词和缩写

程序员有时会采用首字母缩略词和缩写来命令,以便保持较短的名字,例如,把一个类命名为BEManager而不是BackEndManager。这种名字会让人费解,冒这种风险是否值得?

在我们的经验中,使用项目所特有的缩写词非常糟糕。对于项目的新成员来讲它们看上去太令人费解和陌生,当过了相当长的时间以后,即使是对于原作者来讲,它们也会变得令人费解和陌生。

所以经验原则是:团队的新成员是否能理解这个名字的含义?如果能,那可能就没有问题。

例如,对程序员来讲,使用eval来代替evaluation,用doc来代替document,用str来代替string是相当普遍的。因此如果团队的新成员看到FormatStr()可能会理解它是什么意思,然而,理解BEManager可能有点困难。

丢掉没用的词

有时名字中的某些单词可以拿掉而不会损失任何信息。例如,ConvertToString()就不如ToString()这个更短的名字,而且没有丢失任何有用的信息。同样,不用DoServeLoop(),ServeLoop()也一样清楚。

利用名字的格式来传递含义

对于下划线、连字符和大小写的使用方式也可以把更多信息装到名字中。例如,下面是一些遵循Google开源项目格式规范的C++代码:

static const int kMaxOpenFiles = 100;
    class LogReader {
        public:
            void OpenFile(string local_file);
        private:
            int offset_;
            DISALLOW_COPY_AND_ASSIGN(LogReader);
};

对不同的实体使用不同的格式就像语法高亮显示的形式一样,能帮你更容易地阅读代码。

该例子中的大部分格式都很常见,使用CamelCase(驼峰命名)来表示类名,使用lower_separated(分开的小写字母)来表示变量名。但有些规范也可能会出乎你的意料。

例如,常量的格式是kConstantName而不是CONSTANT_NAME。这种形式的好处是容易和#define的宏区分开,宏的规范是MACRO_NAME。

类成员变量和普通变量一样,但必须以一条下划线结尾,如o f f s e t_。刚开始看,可能会觉得这个规范有点怪,但是能立刻区分出是成员变量还是其他变量,这一点还是很方便的。例如,如果你在浏览一个大的方法中的代码,看到这样一行:

stats.clear();

你本来可能要想“stats属于这个类吗?这行代码是否会改变这个类的内部状态?”如果用了member_这个规范,你就能迅速得到结论:“不,stats一定是个局部变量。否则它就会命名为stats_。”

其他的一些规范

根据项目上下文或语言的不同,还可以采用其他一些格式规范使得名字包含更多信息。

例如,在《JavaScript:The Good Parts》( Douglas Crockford, O’Reilly, 2008)一书中,作者建议“构造函数”(在新建时会调用的函数)应该首字母大写而普通函数首字母小字:

var x = new DatePicker(); // DatePicker() is a "constructor" function
var y = pageHeight(); // pageHeight() is an ordinary function

下面是另一个JavaScript例子:当调用jQuery库函数时(它的名字是单个字符$),一条非常有用的规范是,给jQuery返回的结果也加上$作为前缀:

var $all_images = $("img"); // $all_images is a jQuery object
var height = 250; // height is not

在整段代码中,都会清楚地看到$all_images是个jQuery返回对象。

下面是最后一个例子,这次是HTML/CSS:当给一个HTML标记加id或者class属性时,下划线和连字符都是合法的值。一个可能的规范是用下划线来分开ID中的单词,用连字符来分开class中的单词。

<div id="middle_column" class="main-content"> ...

是否要采用这些规范是由你和你的团队决定的。但不论你用哪个系统,在你的项目中要保持一致。

自动装箱的一点小坑

首先要知道Java的类型系统有两部分组成。

一种是基本(primitive)类型,如int,double,boolean等。另一种是引用类型(reference type),如String,List那些。基本类型对应的装箱类型叫做装箱基本类型(boxed primitive)

原始类型最明显的地方就是不能用.操作符。因为它们并不继承自Object。并不是对象。有一点要提的是泛型只支持引用类型不支持原始类型。也就是说你在<>里要写装箱基本类型

Vue.js 实用技巧

这里有一个Input Group组件。最直接的写法是这样子(节选自我的项目里的破代码)。

<template>
  <div class="input-group">
    <label>{{ label }}</label>
    <input ref="input"
                @input="$emit('input',$event.target.value)"
                :value="value"
                :placeholder="placeholder"
                :maxlength="maxlength"
                :minlength="minlength"
                :name="name"
                :form="form"
                :value="value"
                :disabled="disabled"
                :readonly="readonly"
                :autofocus="autofocus">
  </div>
</template>

<script>
export default {
  name: 'InputGroup',
  props: ['label', 'type', 'placeholder', 'maxlength', 'minlength', 'name', 'form', 'value', 'disabled', 'readonly', 'autofocus']

}
</script>

如果我们还需要传原生组件上的属性,例如name,id以及各种校验属性和事件,那么重复代码就会非常多。其实没有必要弄得这么复杂。Vue有个$props。如果结合v-bind可以简写成下面这样。不需要每个属性都写上:arrt=...这样的格式。

<template>
  <div class="input-group">
    <label>{{ label }}</label>
    <input ref="input"
               @input="$emit('input',$event.target.value)"
               v-bind="$props">
  </div>
</template>

<script>
export default {
  name: 'InputGroup',
  props: ['label', 'type', 'placeholder', 'maxlength', 'minlength', 'name', 'form', 'value', 'disabled', 'readonly', 'autofocus']
}
</script>

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.