有必要将Vue项目重构到Typescript吗?

Vue对Typescript的支持

与React不同的是,Vue的组件在运行时是动态生成的,换句话说就是在非runtime环境是无法知道具体变量的类型的,而React尤其是使用了ES6class作为组件的React在做静态类型检查的时候会有天生的优势,也就是这个原因导致tsx的支持非常的迅速。
虽然Vue在2.5.0版本中为Vuevue-routevuex等库加入了大量的type declarations,但是Typescript依然无法轻易的推断出Vue中用到的基于对象的Api中的this,换个通俗点的说法就是以前用Typescript难以推断用Object的写法写出的Vue组件中的this

vue-class-component

为了解决这个问题,Vue官方推出了一个名为vue-class-component的库,该库提供了一个名为Component的装饰器,目的在于能让开发者用class的语法去写Vue组件。
大致会发生如下改变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 改造前
<script>
import someComponent from 'path/to/component'
export default {
props: { propsA: { type: String, required: true } },
component: { someComponent },
data() {
return {
dataA: '',
}
},
computed {
computedA() { return this.dataA + 'wahaha' }
}
methods: {
foo () {
alert(this.dataA)
}
}
mounted () {
this.foo()
}
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 改造后
<script lang='ts'>
import SomeComponent from 'path/to/component'
import Vue from 'vue'
import Component from 'vue-class-component'
@Component({
props: { propsA: { type: String, required: true } },
component: { someComponent }
})
export default class Foo extends Vue {
propMessage!: string
dataA: string = ''
get computedA(): string { return this.dataA + 'wahaha' }
foo () {
alert(this.dataA)
}
mounted () {
this.foo()
}
$refs!: {
SomeComponent: SomeComponent
}
}
</script>

看起来是不是很熟悉很简单,但是实际上不是这样的,从官方的例子我们能看出该库有了如下改变规则

  • 将class的成员方法直接作为组件的methods
  • 与生命周期钩子同名的成员方法将作为生命周期函数
  • 组件内部成员变量作为响应式数据,同时可以声明类型
  • 计算属性改为get属性
  • 使用了$refs占位符的动态组件,需要声明类型
  • props也需要声明类型

如果仔细一点,你会发现,我的props在两个地方被声明了类型。不难看出,作为装饰器的参数的props其实是vue组件本身自带的类型检查,而在class内部声明的类型,是交给typescript做类型检查的。
这意味着,我们在重构代码时需要做一些抉择:究竟是否放弃Vue本身的类型检查。

同时,vue组件的选项远不止上述代码展示的这么点,vue-class-component的官方文档中这样写道:

For all other options, pass them to the decorator function.

刚看到这里的时候我以为官方已经为开发者做好了一切,然而在继续翻阅文档和源码的过程中我发现并非这样,官方提供了一种自己createDecorator的方法,也就是自己动手的意思。
好在有一个名为vue-property-decorator的库已经在vue-class-component的基础上封装了常用的vue组件选项,这个将在下文中讨论。

当然也有人会好奇,用新的类的写法,能否像react一样将类的方法写成箭头函数的形式呢。很遗憾依然不行,箭头函数中的this依然没有指向vue实例,这意味着在箭头函数内部使用this.dataName这种方式来修改响应式数据是不会生效的。

vue-property-decorator

vue-property-decorator 为开发者提供了7中装饰器,他们分别是

  • @Emit
  • @Inject
  • @Model
  • @Prop
  • @Provide
  • @Watch
  • @Component

体验了一番下来,让我最感到奇怪的是@Emit这个装饰器,例如我原本要emit一个click事件

1
2
3
4
5
6
7
8
// 按照原本vue的写法
onFooClick(foo) {
this.$emit('click', foo)
}
// 使用了装饰器后
// onFooClick的参数foo 是$emit的第二个参数
@Emit('click')
onFooClick(foo: any) {}

但是如果foo的来源其实是组件内部的响应式数据,原本的写法只需要将参数去掉,$emit的第二个参数改为this.foo,即:

1
2
3
4
5
6
7
8
// 按照原本vue的写法
onFooClick() {
this.$emit('click', this.foo)
}
// 使用了装饰器后
// 需要在调用onFooClick的地方将this.foo作为参数传给onFooClick
@Emit('click')
onFooClick(foo: any) {}

并且,@Prop这个装饰器并没有解决Vue自带的类型检查和typescript类型检查的矛盾,该装饰器依然可以接受vue类型的写法。
这意味着如果你想进行静态类型检查+运行时的类型检查,需要写两套规则不太一样的类型。

1
2
@Prop([String, Boolean])
someProp: string | boolean

Declaration

Vue有不少插件会在Vue实例下面挂在新的方法,而官方提供的Vue类型生命中是肯定没有这些方法的,为了解决这个问题,Vue官方推荐使用module augmentation来帮助Vue补充新的类型声明。
从工程上看,这个举动确实能帮助类型检查,但是回到现实情况中,一般我们会在项目的入口文件中给vue注入挂载各种插件(即在index.js或者main.js之类的入口文件调用Vue.use()),基本不会存在调用的时候Vue没有挂载该方法的情况,然而Vue下挂载属性一般是将其用作全局状态来使用的,同时vue也提供vuex这样的全局状态管理库,直接把状态挂载vue实例下的情况并不多见,再加上如果是服务端渲染的项目,这类side effect更应该交由vuex来管理。不过话说回来,防范于未然总是好的。
上面这句话不一定正确,先划掉。

Reactive

响应式的数据是vue的特色之一,但是也导致了使用了响应式数据的计算属性/方法无法有typescript来推断类型,需要自己手动标注。不能使用类型推断而需要自己一个个手动标注,如果是比较大的项目,很有可能出现很多变量都是any类型(是的我就喜欢这样干),那这样引入ts也就毫无意义了

简单总结

随着vue版本更新,vue项目也能ts带来的诸多好处,但是并不是每一个团队都能在紧张刺激的产品迭代期间挤出时间进行这样的重构。相比这些好处,重构的工程量究竟会有多大,是否真的有必要这样做是一个值得思考的问题。

Reason简介

Reason是什么

Reason是facebook团队开发的OCaml的一种新语法,其目的是给OCaml类似JavaScript的语法以便JavaScript程序员使用,并通过BuckleScript编译成JavaScript。
同时ES新特性的很多草案是从OCaml中引入的,比如管道操作符 |>,直接使用Reason能提前爽到这些东西(官方称这些特性在ECMAScript中甚至排到了ES2030),并且Reason也能享受到Javascript和OCaml的生态,例如你依然能够使用npm/yarn,或者opam。在构建应用方面,也有ReasonReact等库。

特性

  • 类型有着100%的覆盖率,能自动推断类型,一旦编译,类型会保证是明确的
  • 在大多数是纯函数(pure)、不可变(immtuable)、函数式(functional)的同时,有可选的副作用(side-effect)和数据突变(mutation)
  • 编译快速且产生的体积小
  • 能转换使用js的代码,同时依然能用js的生态系统

与JavaScript语法的联系

虽然Reason很大程度上是提供给JavaScript开发人员使用的,但实际上其很多语法设计是基于OCaml,在实际使用中有不少地方值得注意。

句末的分号

JavaScript中句末分号是可选的,Reason中分号有自己的规定,有些地方必须有分号,有些地方不可以用分号,并且是有着意义的。

1
2
3
4
5
6
7
8
9
/* OCaml syntax*/
let a = 1 in
let b = 2 in
a + b
/* Reason syntax */
let a = 1;
let b = 2;
a + b;

类型

JavaScript是典型的弱类型语言,也出现过TypeScript或者Flow等语言或工具来增强JavaScript的类型。在这一点上,Reason本身是能自动推断类型的,同时也可以使用:去标注类型,例如let foo: int = 10;。这种形式和TS、flow非常相似。
不过Reason没有泛型的概念, 要想声明类似一个又有int又有float的tuple需要这样

1
2
type myTuple = (int, float, int);
let realMyTuple: myTuple = (1, 1.1, 2);

Reason的list的元素必须为同一类型,所以不需要List<String>这种操作了。

let关键字

JavaScript在ES6中引入的let关键字用于声明块级作用域的变量,相同的是Reason中也有块级作用域这一概念,但是在Reason中let强调的是绑定(binding)这一概念,let绑定的变量是immtuable的.这意味这声明变量以后无法简单的直接修改值(类似es6的const关键字)。要想类似es6的let关键字声明可变变量的话,可以使用ref关键字,

1
2
3
4
5
6
let foo = "bar";
Js.log(foo); /* bar */
foo = "foo"; /* Error:The value foo is not an instance variable */
let x = ref(5); /* similar to let x = 5 is ES6 */
x := x^ + 1; /* similar to let x = x + 1 */

注释

Reason中不能使用//单行注释,请使用/* */

单引号和双引号

在JavaScript中没有字符和字符串的区别,单双引号区别也基本只是在转义上。
Reason中有字符串(String)和字符(Characters)的区别,字符串使用双引号,字符使用单引号。

+操作符的使用

在JavaScript中+不仅是加法运算,同时也可以是链接字符串的操作符,而且有着比如”1”+2 = “12”这种很神奇的类型转换的问题。
而在Reason中,单独的+用于整数加法运算,浮点数的加法运算会使用+.,链接字符串使用++
Reason中其他运算符,整数型和浮点型也是不同的操作符,浮点型的操作符会多一个.

===、==和布尔值

JavaScript中两种等的区别为是否严格相等,===除了会去比较值以外还会去比较类型和引用。
Reason中的==叫做physical equal,===叫做 referential equal,==会去深检查数据结构,===只会检查引用(浅检查, 和js的===是类似的)。

1
2
3
4
Js.log((1,2) == (1,2)); /* true */
Js.log((1,2) === (1,2)); /* false */
let someTuple = (1,2);
Js.log(someTuple === someTuple); /* true */

实际上在Reason通过bucklescript编译成JavaScript的后的代码里,==会被编译成Caml_obj.caml_equal()===依然是===
Reason的布尔值true,false表面上和JavaScript的布尔值是一回事,但在编译中会被编译为Js.true_Js.false_

null undefined

JavaScript中很有名的null和undefined,null表示不存在,undefined表示未定义,在Reason中统统用None代替了。

数据结构类型

JavaScript是基于原型的语言,所有类型都能追溯到Object.prototype,这导致无论是Array还是Object都在一定程度上有着相似性,比如Array和Object都是mutable的,这也是我们需要immtuable.js的原因之一。同时ES6中引入了迭代器(iterator)的概念,Array,Map,Set等都是基于迭代器实现的。
而Reason中的数据结构多种多样,其中最类似JavaScript里Object的Record则是默认immutable的。用[]创建的数据结构在Reason中是List,是immtuable的,创建Array则需要使用[||]
需要注意的是,Reason的list里的元素必须为同一类型,Array为定长,这点与JavaScript有很大区别。

函数

JavaScript中使用function关键字声明函数,在ES6中引入了箭头函数(Arrow Function)。Reason中没有function关键字,使用=>来声明函数,装参数的括号不可以省略,参数可以使用~来标注到对应的参数名(像是React中给某个组件传递props然后调用该组件的this.props.名字去获取props的值)。
由于Reason是函数式的,没有return关键字,函数都可以部分调用,很方便科里化(currying),例如

1
2
3
4
5
6
7
let add = (x, y) => x + y;
let process = add(1);
let firstAnswer = process(2); /* firstAnswer = 3 */
let secondAnswer = process(3); /* firstAnswer = 4 */
/* 实际上add是类似于如下方式声明的 */
let add = (x) => (y) => x + y;

因此要注意的是,调用函数时传递的参数少于函数参数的个数时并不是不传递参数而是部分调用该函数,要想实现类似JavaScript中可选参数的话需要使用?来标注可选参数。

循环

在没有Array.map等函数或者迭代器的年代要用原生js遍历数组得老老实实写for循环,不过在Reason中for循环非常简单

1
2
3
for (i in 0 to 10) {...}
/* 还能倒着来,没想到吧 */
for (i in 10 downto 0) {...}

while循环则是大致相同

解构(Destructuring)

ES6的解构为书写代码带来了很大的便利,同时如果用的非常熟练,能写出很多简介明了的代码,Reason中同样支持了解构,并且大致与ES6的解构相似。非要说不一样的话那就是多了元组(tuple)的解构

1
2
let myTuple = ("やじゅう", "せんぱい");
let (yj, snpi) = myTuple;

switch

二者的switch都是拿来替代大量的if/else结构,不过Reason的switch功能结合Variant和Pattern Matching使用会变得非常强大,能极大程度的让代码变得简洁优雅,不过这里只是比较与JavaScript的异同,就不展开讲了。

异常

JavaScript能使用throw抛出异常或者try catch finally结构来处理异常,Reason中使用raise来抛出异常,使用try foo { |Err => something}来处理异常,不过要注意没有finally。

JSX

JSX能够使用类似html标签的语法来使用组件,Reason依然继承了这个特点,但在某些语法上有略微不同。
JSX中props如果只写props名字,则会被当作true,比如<myInput checked />, 而在Reason中这会被解释为<myInput checked={checked} />。这一点被称为Argument punning。另外,如果是使用jsx来开发react应用的话,已经有ReasonReact了。

转化为js

Reason可以使用[%%bs.raw {||}][%bs.raw {||}]来将执行js的代码(这里的一个%和两个%区别很大,暂不展开讲)。

总结

Reason有着大量OCaml优雅语法的优点,同时对JavaScript开发者非常友好,相应的生态也有非常好的支持,对于爱折腾的个人开发者来说不失为一个有趣好玩的新鲜事物。

Async/Await胜过Promise的6个理由(自译)

原文链接

In case you missed it, Node now supports async/await out of the box since version 7.6. If you haven’t tried it yet, here are a bunch of reasons with examples why you should adopt it immediately and never look back.

如果你错过了它,Node7.6现在已经支持async/await。如果你至今还没有试过,本文将会有许多理由和例子来告诉你为什么你应该马上使用async/await并且再也不回换回之前的写法。

Async/await 101
For those who have never heard of this topic before, here’s a quick intro

  • Async/await is a new way to write asynchronous code. Previous options for asynchronous code are callbacks and promises.
  • Async/await is actually built on top of promises. It cannot be used with plain callbacks or node callbacks.
  • Async/await is, like promises, non blocking.
  • Async/await makes asynchronous code look and behave a little more like synchronous code. This is where all its power lies.

Async/await基础

这里给没有从没用过async/await的读者进行一个简单的介绍

  • async/await是一种书写异步代码的新方法,在这之前使用回调函数和promise来设置异步代码。
  • async/await实际上是在promise的基础上构建的,他不能和纯回调函数(plain callbacks)和节点回调函数(node callbacks)一起使用。
  • async/await很像promise,是非阻塞的。
  • async/await让异步代码的外观和行为像同步代码。这就是他厉害的地方。

Syntax
Assuming a function getJSON that returns a promise, and that promise resolves with some JSON object. We just want to call it and log that JSON, then return “done”.
This is how you would implement it using promises

语法

假设一个函数getJSON返回一个promise,并且该promise会去reslove一些JSON对象,我们只是像简单的调用它并且打印该JSON,之后再返回"done"

你可能用promise这样去实现

1
2
3
4
5
6
7
const makeRequest= () =>
getJSON()
.then(data => {
console.log(data)
return "done"
})
makeRequest()

And this is how it looks with async/await

用async/await会长这样

1
2
3
4
5
6
const makeRequest = async () => {
console.log(await getJSON())
return "done"
}
makeRequest()

There are a few differences here

  1. Our function has the keyword async before it. The await keyword can only be used inside functions defined with async. Any async function returns a promise implicitly, and the resolve value of the promise will be whatever you return from the function (which is the string “done” in our case).
  2. The above point implies that we can’t use await in the top level of our code since that is not inside an async function.
  3. await getJSON() means that the console.log call will wait until getJSON() promise resolves and print it value.

有几点不同

  1. 我们的函数前面有一个关键字asyncawait关键字只能在async定义的函数内部使用,任何async函数返回一个隐式的promise,并且这个promise的resolve将会是这个函数中的return(在上例中是字符串"node")。
  2. 前面一点暗示了我们不能在代码的最外层使用await,因为那不再一个async函数内部。

    1
    2
    3
    4
    5
    6
    7
    // this will not work in top level
    // await makeRequest()
    // this will work
    makeRequest().then((result) => {
    // do something
    })
  3. await getJSON()意味着console.log()的调用会等到getJSON()resolve之后。

Why Is It better?
1.Concise and clean
Look at how much code we didn’t write! Even in the contrived example above, it’s clear we saved a decent amount of code. We didn’t have to write .then, create an anonymous function to handle the response, or give a name data to a variable that we don’t need to use. We also avoided nesting our code. These small advantages add up quickly, which will become more obvious in the following code examples.

为什么async/await更好?

1. 简洁干净

看一看我们少写了多少代码!甚至在上面故意这样写的例子里,显然我们节省了大量代码。我们不用写.then,不用创建一个匿名函数来处理response,或者是给data赋值给一个我们不需要的变量,我们也避免了嵌套代码。这些小的优点汇聚起来,将在接下来的例子中更加明显。

2.Error handling
Async/await makes it finally possible to handle both synchronous and asynchronous errors with the same construct, good old try/catch. In the example below with promises, the try/catch will not handle if JSON.parse fails because it’s happening inside a promise. We need to call .catch on the promise and duplicate our error handling code, which will (hopefully) be more sophisticated than console.log in your production ready code.

2. 异常处理

async/await 终于让使用同样的结构,好并且成熟的try/catch来处理同步和异步异常成为可能。在下面使用promise的例子中,当JSON.parse失败时,try/catch不会去处理异常,因为错误在promise内部产生,我们需要在promise上调用.catch并重复你异常处理的代码。这用法看起来会比在你的成品代码里使用console.log更加成熟。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const makeRequest = () => {
try {
getJSON()
.then(result => {
// this parse may fail
const data = JSON.parse(result)
console.log(data)
})
// uncomment this block to handle asynchronous errors
// .catch((err) => {
// console.log(err)
// })
} catch (err) {
console.log(err)
}
}

Now look at the same code with async/await. The catch block now will handle parsing errors.

现在来看一下相同的代码使用async/await,catch代码块将会处理解析异常。

1
2
3
4
5
6
7
8
9
const makeRequest = async () => {
try {
// this parse may fail
const data = JSON.parse(await getJSON())
console.log(data)
} catch (err) {
console.log(err)
}
}

3.Conditionals
Imagine something like the code below which fetches some data and decides whether it should return that or get more details based on some value in the data.

3. 条件语句

想象一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const makeRequest = () => {
return getJSON()
.then(data => {
if (data.needsAnotherRequest) {
return makeAnotherRequest(data)
.then(moreData => {
console.log(moreData)
return moreData
})
} else {
console.log(data)
return data
}
})
}

Just looking at this gives you a headache. It’s easy to get lost in all that nesting (6 levels), braces, and return statements that are only needed to propagate the final result up to the main promise.
This example becomes way more readable when rewritten with async/await.

(上面的代码)只是看着就会头痛。很容易让人迷失在嵌套(6层),支架(?)和返回由主要promise来产生最终结果所需要的声明之中。
这个例子实现了一种用async/await重写的更可读的方法

1
2
3
4
5
6
7
8
9
10
11
const makeRequest = async () => {
const data = await getJSON()
if (data.needsAnotherRequest) {
const moreData = await makeAnotherRequest(data);
console.log(moreData)
return moreData
} else {
console.log(data)
return data
}
}

4.Intermediate values
You have probably found yourself in a situation where you call a promise1 and then use what it returns to call promise2, then use the results of both promises to call a promise3. Your code most likely looked like this

4. 中间值

你可能发现你经常遇到这样的情况:你调用一个promise1然后用它的返回值去调用promise2然后用它的返回值去调用promise3.你的代码看起来会是这样

1
2
3
4
5
6
7
8
9
10
11
const makeRequest = () => {
return promise1()
.then(value1 => {
// do something
return promise2(value1)
.then(value2 => {
// do something
return promise3(value1, value2)
})
})
}

If promise3 didn’t require value1 it would be easy to flatten the promise nesting a bit. If you are the kind of person who couldn’t live with this, you could wrap both values 1 & 2 in a Promise.all and avoid deeper nesting, like this

如果promise3不依赖value1,那么退出promise会简单一些。如果你是那种不能忍受这样写的人,你可以把value1和2都包在一个Promise.all里,同时也避免了更深的嵌套,就像这样

1
2
3
4
5
6
7
8
9
10
11
const makeRequest = () => {
return promise1()
.then(value1 => {
// do something
return Promise.all([value1, promise2(value1)])
})
.then(([value1, value2]) => {
// do something
return promise3(value1, value2)
})
}

This approach sacrifices semantics for the sake of readability. There is no reason for value1 & value2 to belong in an array together, except to avoid nesting promises.
This same logic becomes ridiculously simple and intuitive with async/await. It makes you wonder about all the things you could have done in the time that you spent struggling to make promises look less hideous.

这个方法牺牲了语法来获得可读性,没有理由让value1value2都属于同一个数组,除非是为了避免嵌套promise。
相同的逻辑用async/await将变得非常简单并且浅显易懂。这会让你怀疑你能用原本花费在绞尽脑汁让promise看起来不那么丑的时间来完成所有事情。

1
2
3
4
5
const makeRequest = async () => {
const value1 = await promise1()
const value2 = await promise2(value1)
return promise3(value1, value2)
}

5.Error stacks
Imagine a piece of code that calls multiple promises in a chain, and somewhere down the chain an error is thrown.

5. 异常栈

想象一片链式调用了多个promise的代码,在调用的某处抛出了异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const makeRequest = () => {
return callAPromise()
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => {
throw new Error("oops");
})
}
makeRequest()
.catch(err => {
console.log(err);
// output
// Error: oops at callAPromise.then.then.then.then.then (index.js:8:13)
})

The error stack returned from a promise chain gives no clue of where the error happened. Even worse, it’s misleading; the only function name it contains is callAPromise which is totally innocent of this error (the file and line number are still useful though).
However, the error stack from async/await points to the function that contains the error

异常栈由promise链返回,不知道到底是哪里发生了异常。甚至会出更坏的情况:误导。只包含唯一的函数名callAPromise,它在这个异常中完全错误(虽然文件和行号依然能用)。
然而,由async/await得到的异常栈会指向包含错误的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const makeRequest = async () => {
await callAPromise()
await callAPromise()
await callAPromise()
await callAPromise()
await callAPromise()
throw new Error("oops");
}
makeRequest()
.catch(err => {
console.log(err);
// output
// Error: oops at makeRequest (index.js:7:9)
})

This is not a huge plus when you’re developing on your local environment and have the file open in an editor, but it’s quite useful when you’re trying to make sense of error logs coming from your production server. In such cases, knowing the error happened in makeRequest is better than knowing that the error came from a then after a then after a then

当你在本地环境,用编辑器开着文件开发,对你而言不是一个很大的好事,但是当你想从生产服务器搞清楚错误输出的话,这将会非常有用。这种情况下,知道异常出现在makeRequest里比知道异常从一堆then里好了不少。

6.Debugging
Last but not least, a killer advantage when using async/await is that it’s much easier to debug. Debugging promises has always been such a pain for 2 reasons
1.You can’t set breakpoints in arrow functions that return expressions (no body).

6. Debugging

最后但同样重要的一点,async/await的杀手锏是它debug起来更加简单,promise进行debug有两个导致他异常痛苦的原因:

  1. 你不能在一列返回表达式的函数上打断点。

    1. If you set a breakpoint inside a .then block and use debug shortcuts like step-over, the debugger will not move to the the following .then because it only “steps” through synchronous code.
      With async/await you don’t need arrow functions as much, and you can step through await calls exactly as if they were normal synchronous calls.
  2. 如果你在.then代码块内部打断点,并且用了bebug的快捷方式,比如step-over,debugger不会执行下一个.then因为他只能在同步代码中执行。
    用async/await的话你不需要一列函数,并且你可以单步调试await来像普通同步代码一样调用。

In Conclusion
Async/await is one of the most revolutionary features that have been added to JavaScript in the past few years. It makes you realize what a syntactical mess promises are, and provides an intuitive replacement.

总结

async/await是近几年javascript新增的最具革命性的特性之一。它能让你实现与杂乱的promise等价的功能,同时提供了直接了当的替换方案。

Concerns
Some valid skepticism you might have about using this feature
It makes asynchronous code less obvious: Our eyes learned to spot asynchronous code whenever we see a callback or a .then, it will take a few weeks for your eyes to adjust to the new signs, but C# had this feature for years and people who are familiar with it know it’s worth this minor, temporary inconvenience.
Node 7 is not an LTS release: Yes, but node 8 is coming next month, and migrating you codebase to the new version will most likely take little to no effort.

关注点

你需要了解关于这个特性的一些有意义的质疑

  • 它会让异步代码更加不显眼:我们的眼睛习惯了当看到回调或者.then进而关注异步代码,这将会花费你几周来让你的眼睛适应新写法,但是C#许多年前就有这个特性,并且熟悉这种写法的人知道这种写法带来的好处远远值得去克服这些很小的麻烦。
  • node7还不是LTS版本:是的,但是node8下个月(原文发布于2017年3月26日)就要出了。迁移你的代码库到新版本将很可能不需要花费太大力气。

我不懂的js集合

在公司实习的时候学到了不少新东西,不如简单记录一些

1 取消异步请求带来的思考

最近实习的时候遇到了这样的一个需求,发送请求A之后,浏览器还有可能异步发送相同接口的请求A1,由于服务端计算的时间长短可能不一样,导致返回的顺序不一定与请求发送的顺序一致,导致界面显示的数据与最后一次请求所期望的结果不一样。

  1. 甩锅给服务端
    让服务端每次返回都带上一个id,通过id去匹配返回的结果是否为最后一次查询所预期的。我想这样能解决问题,但是隐约感觉到能在不修改服务端代码的情况下解决问题,于是就开始瞎折腾。
  2. 尝试用debounce
    最开始没有深入思考这个问题,简单的认为用debounce来保证短时间内多次操作只会发送一次请求。后来发现并不是那么简单,在debounce设置的延迟内服务端可能还没返回结果,若是此时浏览器又发送了新的请求,依然会出现最开始的问题。
  3. 发送请求后将对应的界面disable掉
    可以在发送请求,返回结果之前将对应触发请求的界面给disable掉,但是我想这样有两个缺点:1.对用户不够友好,一个误操作可能就要等到返回结果才能重新操作。2.并不是通用的解决方法,只适用于用户操作触发事件的情况。
  4. 不知道叫什么的解决方法
    想了很久以后决定去问问导师,导师告诉我他之前也遇到过这样的问题,给我讲了一下他的大致思路:

    • 将原方法外面包装两层,设置一个cancelHandler,初始为null,每次发送请求会返回一个把isCancel变量置为ture的函数,将这个函数赋值给cancelHandler,只要cancelHandler不为null(说明之前发送过请求了),就执行cancelHandler来将isCancel置为ture。之后再执行第二层包装的方法。
    • 第二层方法先创建一个变量isCancel并初始化为false,该变量用于判断是否处理response。然后创建了一个succsessCallback函数,该函数会作为原本发送请求的方法(成功时)的回调函数,判断isCancel的值来决定处不处理response。之后创建了一个cancaelCallback函数作为第二层包装的返回值,也就是之前那一步里赋值给cancelHandler的函数。之后在执行原本的请求,回调调用successCallback函数来判断isCancel以决定是否对response进行处理。最后第二层包装返回cancelCallback函数。
    • 当第一个请求发送之后,第二个请求事件触发时,此时cancelHandler不为null,会去将isCancel置为ture,这样就不会处理第一次请求的response。
    • 前面一大段简单点说就是根据有没有新的请求事件触发来决定是否处理上一次请求的response。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    //在一个React组件的内部
    class Example extends React.Component {
    constructor{
    //balabala
    this.cancelHandler = null; //初始化cancelHandler
    }
    //包在最外层的方法,触发发送请求时,调用此方法
    startFetchData (params) {
    this.cancelHandler && this.cancelHandler();
    this.cancelHandler = this.fetchData(params);
    }
    //第二层包装,其中realFetchData()是最开始去发送请求的方法
    fetchData (params) {
    let isCancel = false;
    let successCallback = (res) => {
    if(isCancel) return;
    //接下来进行原本对response进行的操作
    //...
    };
    let cancelCallback = () => {
    isCancel = true;
    };
    realFetchData(params).then(successCallback);
    return cancelCallback;
    }
    //实际会这样调用
    onSomethingClick () {
    //...
    this.startFetchData(params);
    }
    }

    于是我很顺利的就解决了这个问题。但是这样还是有着一些缺点和值得思考的点,比如依然会发送一次请求,比如如何进行异常处理最合适,能不能用async/await让代码看起来更简单等等。

  5. redux-sage
    (偷懒直接拿来官方文档的例子改了改)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    import { take, put, call, fork, cancel, cancelled } from 'redux-saga/effects';
    import { delay } from 'redux-saga';
    import * as Api from './api';
    import {action} from './action';
    function* bgSync() {
    try {
    while (true) {
    yield put(actions.requestStart())
    const result = yield call(Api.fetchData)
    yield put(actions.requestSuccess(result))
    yield call(delay, 5000)
    }
    } finally {
    if (yield cancelled())
    yield put(actions.requestFailure('Sync cancelled!'))
    }
    }
    function* main() {
    while ( yield take(START_BACKGROUND_SYNC) ) {
    // starts the task in the background
    const bgSyncTask = yield fork(bgSync)
    // wait for the user stop action
    yield take(STOP_BACKGROUND_SYNC)
    // user clicked stop. cancel the background task
    // this will cause the forked bgSync task to jump into its finally block
    yield cancel(bgSyncTask)
    }
    }
  6. redux-observable
    redux-observable的takeUtils()能取消发送的请求,
    同时能使用rxjs的一些运算符,还能异常处理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    import * as Api from './api';//假设我们的api请求函数返回的都是Promise
    import 'rxjs';
    import { Observable } from 'rxjs/Observable';
    //Action type
    const FETCH_DATA = 'FETCH_DATA';
    const FETCH_DATA_FULFILLED = 'FETCH_DATA_FULFILLED';
    const FETCH_DATA_FAILURE = 'FETCH_DATA_FAILURE';
    const FETCH_DATA_CANCELLED = 'FETCH_DATA_CANCELLED';
    //Action creator
    const fetchData = (params) => ({type: FETCH_DATA, payload: params});
    const fetchDataFullfilled = payload => ({type: FETCH_DATA_FULFILLED, payload});
    const fetchDataFailure = error => ({type: FETCH_DATA_FAILURE, payload: error});
    const cancelFetchData = () => ({type: FETCH_DATA_CANCELLED});
    //Epic
    export const fetchDataEpic = action$ =>
    action$.ofType(FETCH_DATA)
    .mergeMap(action =>
    Observable.fromPromise(Api.fetchDataAjax({...action.payload}))
    .map(response => fetchDataFullfilled(response))
    .takeUntil(action$.ofType(FETCH_DATA_CANCELLED))
    .catch(error => Observable.of(fetchDataFailure(error)))
    );

Preact Virtual DOM算法探究

Preact简介


Preact是一个React轻量化的库,整个库大小只有3kb,相比其他react轻量化的方案,如25kb的react-lite,Preact在体积上有着很大的优势。
Preact有着和React相似的API,大部分情况下能兼容React的组件,只需要绑定一个preact-compat兼容层就能解决很多兼容问题。类似React的生态圈,Preact自己也有着规模不小的生态圈。
同时官网也宣称Preact有着一个简单却高效的diff算法实现使他成为最快的虚拟DOM框架之一,官网也号称自己可能是最薄的一层虚拟DOM实现。接下来本文将简单探究一下preact的虚拟DOM算法, 同时对比一下于React的Vitrual DOM算法的区别

Preact的Virtual DOM实现

React使用React.createElement()来实现js转换为Virtual DOM的过程,Preact在这里用了自己的实现方式。
Preact的Virtual DOM实现使用了babel-plugin-transform-react-jsx,通过pragma注入一个函数preact.h,实现类似下面的转换

1
2
3
4
5
/** @jsx h */
let foo = <div id='foo'>Hello</div>
//After babel transpilation
var foo = h('div', {id: 'foo'}, 'Hello');

Preact利用Preact.h()函数来将转换后的js生成Virtual DOM,之后再利用自己的Virtual DOM算法将其转换为真实的DOM。
Preact.h()这个函数为hyperscript的一个简化版,有兴趣的读者可以去了解完整的hyperscript。

Preact的Virtual DOM算法

创建节点

h函数接受babel转换后的代码,生成一系列的VNode(也就是Virtual DOM),之后执行render()函数,例如

1
render(h(Foo), document.getElementById('app'))

h()函数会将Foo转为如下的VNode

1
2
3
4
{
"nodeName": "Foo",
"children": []
}

之后会根据VNode.nodeName的类型分为以下几种情况

为组件创建VNode的实例

如果当前这个VNode.nodeName是一个组件,且还未被实例化,则会先去创建这个组件的实例,然后去先后调用这个组件的componentWillMountrender生命周期,其中render()会多次调用h()函数,最终让VNode变成类似下面的结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"nodeName": "div",
"children": [
{
"nodeName": "input",
"attribute": {
"type": "text",
"onChange": "",
"placeholder": "input here"
},
"children": []
},
{
"nodeName": "List",
"attribute": {
"items": [
"foo",
"bar",
]
},
"children": []
}
]
}

需要注意的是,上面的节点的第二个子节点为一个组件,在这里并没有被转换。
render()函数执行完后会又重复根据VNode.nodeName类型进行判断

与组件相关的函数一共有四个,分别为

  • setComponentProps(): 为组件设立props
  • renderComponent(): 渲染一个组件,在这个函数内部会触发一些生命周期的钩子以及接收高阶组件
  • buildComponentFromVNode(): 将来自VNode的组件变为真实DOM
  • unmoumtComponent(): 将组件从DOM树中移除,并回收它
为非组件直接创建真实DOM节点

当VNode.nodeName为非组件(即HTML标签),且还没有相同的真实DOM节点已经被渲染,则会去用document.createNode(node)创建新的节点。
如果这个节点还有子节点的话,则会循环这个过程来重复创建子节点。

子节点添加到父节点

当上一步中判断节点没有子节点时,会接着判断该节点是否有父节点,若有父节点则会调用父节点的appendChild(childNode)方法来将子节点添加到父节点。

创建完子节点A(A无子节点)后并不会立即去创建子节点B,而是会先将子节点A添加到父节点,再去创建子节点B

处理子组件

当子节点为一个组件时,流程与前面类似,先为该组件创建VNode,再对其子节点进行前面流程的操作。
最终处理完该组件(以前面的List组件为例)的VNode类似如下结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
"nodeName": "ul",
"children": [
{
"nodeName": "li",
"attribute": {
"key": "foo"
},
"children": [
"foo"
],
"key": "foo"
},
{
"nodeName": "li",
"attribute": {
"key": "bar"
},
"children": [
"bar"
],
"key": "bar"
}
]
}

结束处理

多次重复前面的流程,所有的节点以及挂载之后(即当前处理的VNode没有父节点),会调用componentDidMount函数,结束处理。

在这之后,组件会保持一个对真实DOM的引用(refs),引用会用于更新和对比,能避免重复创建相同的节点。

删除节点

删除节点的过程中,涉及到了组件的更新、删除节点和组件的卸载

更新VNode

在判断完VNode.nodeName为组件并且该组件以及存在后,会去先后调用componentWillReceiveProps, shouldComponentUpdatecomponentWillUpdate的生命钩子,之后再去调用render

如果在这个流程中shouldComponentUpdate返回了false,则会直接跳过componentWillUpdaterender,这也就是shouldComponentUpdate用于优化react/preact性能的原理。

引用真实DOM

先前已经创建过的组件会保持对真实DOM的引用,在更新后,他的每一个属性都会和真实DOM的属性比较,如果是没有变化,则会跳过这个节点,去处理下一个节点,从而实现避免重复创建。

完全删除节点

当节点找不到与之相同的真实DOM,也不在创建节点的情况下,则会去删除(remove)该节点。
该节点不为组件的情况下,其父节点会简单的调用removeChild(node)来删除节点,在这之后会触发ComponentDidUpdate生命钩子。

卸载组件

类似上面完全删除节点的情况,如果即将删除的为一个组件,则会先调用ComponentWillUnMount,之后去调用component.unmoumtComponent(),删除完之后调用ComponentDidMounted

DOM Diff

上面的Virtual DOM算法在具体实现中会多次用到DOM Diff算法进行对比。
在探索Preact的DOM Diff算法之前,先简单了解一下React的DOM Diff算法:

React的DOM Diff算法

React的DOM Diff算法在标准Diff算法的基础上改进,将时间复杂度从O(n^3)减少到了O(n),能做到这样是因为React的diff算法基于了两点假设:

1.Two elements of different types will produce different trees.
两个不同类型的元素会生成不同的DOM树
2.The developer can hint at which child elements may be stable across different renders with a key prop.
开发者可以用给子元素加上key这个prop来让其能在多次渲染时保持稳定
(关于第二点,网上部分资料给出的为 对于同一层次的一组子节点,它们可以通过唯一的id进行区分)

其DOM Diff算法主要分为三类情况,tree diff,component diff和element diff。
(关于React DOM Diff的文章很多,本文就不在这里赘述)

Preact的DOM Diff算法

阅读过源码的读者不难发现,前面提到的render()函数本质上是调用了diff函数,而diff函数的具体实现中会涉及到以下函数

  • idiff()diff()主要会调用这个函数,去进行具体的对比
  • diffAttributes():对比节点的具体属性
    (待补完)

    关于具体的性能测试

    REPAINT RATE CHALLENGE
    UI Benchmark

GraphQL+Relay初探

GraphQL简介


GraphQL是Facebook开源的一套用于API的查询语言和runtime,其比较明显的特色在于用一次请求拿到所有想要的数据,并且返回的查询结果与想要查询的格式一致。同时也强调了强类型,减少冗余。

Query/Mutation

(后文大部分例子皆来自/魔改自官方文档)
拿一个如下的简单的query来分析,这里的hero,name或者friends叫做field,field会返回type中定义的类型。

  • field能通过例如hero(name: "R2-D2")这样的方式来添加参数
  • 多个同级同类型的field能通过例如empireHero: hero 来指定别名
  • 多个filed要查询相同类型的数据时,可以用碎片(fragments)来复用相同的查询条件
  • 当遇到多个查询需要带上不同的参数时,可以使用变量
  • 可以用指令(directive)去动态的改变query的结构和形状,如@include(if: Boolean) @skip(if: Boolean)

    1
    2
    3
    4
    5
    6
    7
    8
    query Hero($episode: Episode, $withFriends: Boolean!) {
    hero(episode: $episode) {
    name
    friends @include(if: $withFriends) {
    name
    }
    }
    }
  • GraphQL不只是提供了获得数据的方式,也提供了Mutation去修改服务端的数据

    1
    2
    3
    4
    5
    6
    mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
    createReview(episode: $ep, review: $review) {
    stars
    commentary
    }
    }
  • GraphQL的schemas能定义接口和结合(union)的type,因此当query中的field在schema中对应这些type时,你需要内联碎片(inline fragments)去获取混合(concrete)type之下的数据。

  • 当不知道服务器返回的type时,可以用meta field去代替,即在field前加上__
    例如 我们要查询带有’an’的数据,同时也不知道返回的数据的type,如果这个返回的数据的type是Droid,则会同时返回primaryFunction,如果为Human类型,则会返回height。此外返回结果里的__typename就会是这个查询结果的type的名字了(此例中可能为Human、Droid或者Starship)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    {
    search(text: "an") {
    __typename
    ... on Human {
    height
    }
    ... on Droid {
    primaryFunction
    }
    ... on Starship {
    name
    }
    }
    }

Types/Schemas

上一节中提到了许多次type,type即我们想要查询的field的类型,用于描述我们想要的数据。属于schema中最基础的一类

每一个GarphQL Service定义了一系列的type用于完全描述一系列可能会被查询到的数据。当query来的时候,GraphQL Service会根据schema去验证并执行。

GraphQL Service可以用任何语言去编写。同时还有一种叫做”GraphQL schema language”的语言用于描述schema。

一个简单的定义名为Character的type的例子

  • name,appearsIn和length为该type的field
  • field的type后面的!表示该field不可为null
  • appearsIn: [Episode]表示对应这个appearsIn对应的是一个Episode类型的数据的数组
  • schema中的参数必须有名字,等号前的为参数的type,后面的为默认值
  • 一些基本的type:Int Float String Boolean ID

    1
    2
    3
    4
    5
    type Character {
    name: String!
    appearsIn: [Episode]!
    height(unit: LengthUnit = METER): Float
    }
  • 纯量(scalar):经常作为query的叶结点,例如最前面的query例子里的name。可以用scalar去声明一个纯量 scalar Date

  • 枚举(enum):枚举是一种特殊的纯量,他被限制为一个特定允许值的集合。这个特性可以拿来做验证或者利用一个有限集合的值来在type系统之间传送,可以用如下方式声明,Episode一定会为NEWHOPE、EMPIRE、和JEDI其中之一

    1
    2
    3
    4
    5
    enum Episode {
    NEWHOPE
    EMPIRE
    JEDI
    }
  • 接口(interface):像许多强类型语言一样,GraphQL也提供了接口,即包含了type必须实现的、确定的、field的集合的抽象的type。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    interface Character {
    id: ID!
    name: String!
    friends: [Character]
    appearsIn: [Episode]!
    }
    type Human implements Character {
    id: ID!
    name: String!
    friends: [Character]
    appearsIn: [Episode]!
    starships: [Starship]
    totalCredits: Int
    }
  • 结合(union):结合和接口非常类似,只是没有具体说明type之间的共同field,即没有将type的具体属性抽象出来

    1
    union SearchResult = Human | Droid | Starship
  • 输入(input):当你可能要传递整个对象(例如参数为一个对象时),需要指定这个对象的type,就可以使用input。简单的把type换成input即可

    1
    2
    3
    4
    input ReviewInput {
    stars: Int!
    commentary: String
    }

上面就是GraphQL中一些基础概念,更加进阶的概念会在以后的文章中提到。

GraphQL对比RESTful

一万个人心中有一万种RESTful,RESTful虽然简单易理解,但是在深入开发的过程中其难扩展的缺点逐渐暴露了出来。

例如前面官网上的例子,GraphQL会直接返回这样的结果

1
2
3
4
5
6
7
8
9
10
11
12
{
"data": {
"hero": {
"name": "Luke Skywalker"
"friends": [
{ "name": "Han Solo" },
{ "name": "R2-D2" },
{ "name": "Leia Organa" },
]
}
}
}

而RESTful,可能有人会这样写

1
2
3
GET /hero/
#之后遍历返回的信息,并发送新请求
GET /hero/加上前一次查询返回的name/friends/

或者

1
GET /heros?extend_query=friends

这种情景下,RESTful可能会去发送多次请求,或者把请求魔改得不直观。GraphQL恰恰能很好的解决这类问题。

同时我们也看到了,GraphQL在一定程度上,查询请求是不依赖url的,这也许意味着当系统要添加新的功能,同时需要的数据能用之前的数据组装,就不需要新的后台接口了。

此外,若是后台能将数据拆分,提供大量的原子数据接口的话,那么对于产品版本迅速迭代将是一件非常有意义的事情。

Relay简介


Relay是一个用于构建数据驱动的React应用的框架,结合GraphQL可以实现用GraphQL声明需要的数据,Relay指明在什么时机用什么方法去获取数据。
Relay还能综合所有query,利用高效的网络请求去获取你恰好需要的数据,也能结合GraphQL的mutation去修改客户端/服务端上的数据,并且同时提供了自动化的连贯的数据更新机制和错误处理机制。

vue自定义指令实现Highlight.js高亮代码

在Vue项目中直接引入highlight.js会遇到一些问题,官方论坛上的一篇帖子给出了出现这种现象的原因,即vue-router在改变路由的时候会清除渲染,并移除事件
参考了答案后,得出了用vue的自定义指令(directive)解决问题的方法:

1
2
3
4
5
6
import hljs from 'highlight.js'
//还需要引入hljs的主题css,此处略过
Vue.directive('highlight', function () {
let blocks = this.el.querySelectorAll('pre code')
Array.prototype.forEach.call(blocks, hljs.highlightBlock)
})

补充:Vue 2中,自定义指令的API发生了一些变化,需要用这样的写法:

1
2
3
4
5
6
Vue.directive('highlight', {
bind: function (el) {
let blocks = this.el.querySelectorAll('pre code')
Array.prototype.forEach.call(blocks, hljs.highlightBlock)
}
})

设置好自定义指令后,在标签里使用指令

1
2
3
4
5
6
<div v-highlight>
<pre>
<code class='可以在这里手动指定语言'>
</code>
</pre>
</div>

补充2: 利用上面的原理,我写了一个简单的插件vue-hljs,安装完并引入依赖以后,简单的Vue.use(vueHljs)就可以使用v-highlight去高亮代码了。

The State of Vue(自译)

2.0 is now in RC!

We announced Vue 2.0 back in April,
and today I am very excited to release the first release candidate for Vue 2.0!
From this stage on we will be in API freeze and there will be no more breaking changes before official release.

2.0 正在候选发布(RC)!

我们在四月的时候宣布了Vue 2.0的消息,
并且在今天我将非常激动地发布Vue 2.0的第一个RC版本!
从这个阶段起我们将会API冻结,并且在官方版本之前将会有更多突破性的改动.

All the official supporting libraries, e.g. vue-router, vuex, vue-loader & vueify have all been updated to work with Vue 2.0.
This means the 2.0 stack is technically complete — the only thing we are waiting for is documentation and release-related logistics.
Despite that, we have prepared a Vue 2.0 RC Starter Resources guide to help those feeling adventurous jump right into 2.0 today.

所有官方支持的库,例如vue-router,vuex,vue-loader和vueify都已经升级到能和Vue 2.0一起使用。
这意味着2.0在技术层面已经完善——我们唯一在等待的就是文档和发布相关的后续工作
尽管如此,我们准备了Vue 2.0 RC Starter Resources
指南来帮助那些想要探险的人跳入2.0的大坑。

Vue.js in the Industry

I recently answered the question “How Popular is Vue.js in the Industry?” on Quora.
You can read about the full answer here, but here are some highlights:

  • Based on the combined metrics including Google Trends, GitHub star history & stats.js.org statistics,
    Vue.js has consistently been one of the fastest growing libraries in the past few months and there’s currently no sign of slowing down.
  • 1 million+ downloads on NPM at 125k~150k per month
  • 1.5 million page views & 150k monthly unique visitors on vuejs.org
  • 36,000+ weekly active vue-devtool users
  • Used by great open source projects: Laravel, GitLab, PageKit & more.
  • Strong international adoption: biggest Chinese public companies (Alibaba, Baidu, Tencent), biggest Chinese unicorns (private companies with $1B+ valuation — Xiaomi, Didi Chuxing, DJI, Ele.me), established enterprises in Japan/UK (Line, Nintendo, Sainsbury’s)

业界的Vue.js

我最近在Quora上回答了“Vue在业界有多流行?”的问题. 你可以在这里看到全部的答案, 当然你也可以只看以下几点:

  • 根据结合了包括 Google Trends, Github star历史记录以及 stats.js.org统计数据在内的度量结果, Vue.js在过去几个月内连续保持增长最快速的库之一,并且现在毫无减弱的迹象.
  • NPM上有100万次以上的下载, 每月12.5到15万.
  • vuejs.org访问量达150万, 每月有15万不同的访客.
  • vue-devtool 每月有36万以上的活跃用户.
  • 被大型开源项目所用:例如 Laravel, GitLab, PageKit 等等.
  • 国际上广为采用: 例如 中国最大的公司(阿里巴巴,腾讯,百度),中国最大的独角兽公司(具有一亿以上资产的私有公司——小米,滴滴出行,大疆,饿了么),设立在日本/英国的企业(Line,任天堂,森宝利)

Patreon Campaign Going Strong

The Vue.js patreon campign now has over $8,000 monthly pledge from the community and sponsors.
It is absolutely amazing to be able to work full-time on an open source project that I am genuinely passionate about, and for that I am forever grateful to all the supporters.

Patreon Campaign 让我们更加壮大

Vue.js patreon campign现在已经有了每月从社区和赞助者处得到的$8,000的赞助.
能全职投入到一个我真正为之狂热的开源项目中当然是令人惊讶的, 因此我永远感谢那些支持我的人.

I think the Vue.js patreon campaign is an interesting experiment on sustainable financial models for open source projects that demand serious commitment: it’s open source not as the by-product of a commercial company trying to help with recruiting — the only goal of the project is to create something that help more people get more things done in an easier and faster way. It’s sustained not by revenue that possibly has nothing to do with the project itself, but rather directly by those who use it in their everyday work. To be honest, I don’t think this is a model that is easy to pull off (for that I am very lucky), but I do hope it can serve as a valuable case study for those who are interested in more sustainable open source work.

我认为Vue.js patreon campign 是一个对需要严肃承诺的开源项目的可持续金融模型上有趣的实验: 它不是因为作为商业公司用来招聘的副产品而开源——这个项目的唯一目标就是创造一些能够帮助更多的人用更简单快捷的方法做更多事情的东西. 它可持续不是因为那些可能对项目本身无益的收入,而是直接因为那些每天都在工作中都在用到它的人.讲真, 我不认为这是一个能轻松推广的模型(所以说我很幸运), 但我衷心希望它能够作为一个对致力于更加可持续开源项目的人来说有价值的案例而服务.


Powered by Hexo and Hexo-theme-hiker

Copyright © 2013 - 2018 香香鸡的小窝 All Rights Reserved.

SunskyXH hold copyright