Vue可以做很多事情,当然,其中之一是作为您的应用程序的视图层。事实上,我认为这是Vue的唯一预期目的,但我知道其他一些实际上相当干净的东西。
现在,这里有很多代码,我会尽我所能解释它,但这可能有点难理解。
我们将瞄准的预期用途将是这样的东西:
1import { ReactivePipeline } from './reactive-pipeline';
2
3const sourceArray = ['e', 'x', 'a', 'm', 'p', 'l', 'e'];
4
5// Create a new pipeline.
6new ReactivePipeline(sourceArray)
7// Make all letters uppercase.
8.pipe(array => array.map(letter => letter.toUpperCase()))
9// Join the array into a string.
10.pipe(array => array.join(''))
11// Log any errors.
12.error(e => {
13 console.error(e)
14})
15// Start the pipeline and listen for changes.
16.subscribe(result => {
17 // Whenever the output changes, log it.
18 console.log(result) // EXAMPLE
19});
现在,每当原始数组发生变化时,对.subscribe 的回调将输出通过管道运行该数组的结果。
创建班级
所有你需要的是Vue安装为依赖性. 如果你使用正常需要(),它在节点下运行很好。
代码智慧的第一步是创建一个简单的类,具有几个函数。
1[label reactive-pipeline.js]
2import Vue from 'vue';
3
4export class ReactivePipeline {
5 constructor (sourceData) {
6 this._source = sourceData;
7 this._tracker = null;
8 this._transformers = [];
9 this._subscribeHandler = function() {};
10 this._errorHandler = function(e) { throw e };
11 }
12
13 pipe (transformer) {
14 this._transformers.push(transformer);
15 return this;
16 }
17
18 subscribe (callback) {
19 this._subscribeHandler = callback;
20 this.setupComponent();
21 return this;
22 }
23
24 error (callback) {
25 this._errorHandler = callback;
26 return this;
27 }
28
29 setupComponent () {
30 // ... We'll flesh this out next.
31 }
32}
实际上,我们所做的就是创建一堆函数,这些函数会收集数据并将其存储在类中以便通过setupComponent()
进行使用。
实际上,对于我们在这里试图实现的东西,它有点过于复杂(我们可以使用单个观察器而没有计算属性),但这种方法将允许您为Vue的依赖追踪系统添加支持,以缓存计算属性,而不是在依赖变化时重新启动整个东西。
1[label reactive-pipeline.js]
2...
3setupComponent () {
4 // Get everything in this closure so we can access it from inside the computed handlers.
5 const source = this._source;
6 const transformers = this._transformers;
7 const subscribeHandler = this._subscribeHandler;
8 const errorHandler = this._errorHandler;
9
10 const computed = {};
11
12 // Populate computed properties object with transformer function wrappers.
13 transformers.forEach((transformer, index) => {
14 // Create a named computed property for each transformer.
15 // These can't be arrow functions, as they need to be bound to the generated component.
16 computed[`transformer_${index}`] = function() {
17 try {
18 // Run each transformer against the previous value in the chain.
19 return transformer(index === 0 ? this.source : this[`transformer_${index - 1}`]);
20 } catch (e) {
21 // Handle any errors.
22 errorHandler(e);
23 }
24 }
25 })
26
27 // Create an "output" computed property that simply serves as the last one in the chain.
28 computed['output'] = function() {
29 return this[`transformer_${transformers.length - 1}`];
30 }
31
32 // Here's where the magic happens.
33 // Create a new Vue component with the source data in it's data property.
34 // (This makes it observable.)
35 const PipelineComponent = Vue.extend({
36 data() {
37 return {
38 source: this._source
39 }
40 },
41
42 // We need one watcher to "use" the final computed property and cause the chain to update.
43 watch: {
44 // I do realize we could've just put the entire transformer chain in here, but that would be boring.
45 source () {
46 subscribeHandler(this.output);
47 }
48 },
49
50 computed,
51 });
52
53 // Now, initialize the component and start the transformation chain going.
54 this._tracker = new PipelineComponent();
55
56 return this;
57}
58...
一旦完成,您应该能够以文章开始时所示的方式使用它。
现在的所有人:**
反动式管道类...
1[label reactive-pipeline.js]
2import Vue from 'vue';
3
4export class ReactivePipeline {
5 constructor (sourceData) {
6 this._source = sourceData;
7 this._tracker = null;
8 this._transformers = [];
9 this._subscribeHandler = function() {};
10 this._errorHandler = function(e) { throw e };
11 }
12
13 pipe (transformer) {
14 this._transformers.push(transformer);
15 return this;
16 }
17
18 subscribe (callback) {
19 this._subscribeHandler = callback;
20 this.setupComponent();
21 return this;
22 }
23
24 error (callback) {
25 this._errorHandler = callback;
26 return this;
27 }
28
29 setupComponent () {
30 // Get everything in this closure so we can access it from inside the computed handlers.
31 const source = this._source;
32 const transformers = this._transformers;
33 const subscribeHandler = this._subscribeHandler;
34 const errorHandler = this._errorHandler;
35
36 const computed = {};
37
38 // Populate computed properties object with transformer function wrappers.
39 transformers.forEach((transformer, index) => {
40 // Create a named computed property for each transformer.
41 // These can't be arrow functions, as they need to be bound to the generated component.
42 computed[`transformer_${index}`] = function() {
43 try {
44 // Run each transformer against the previous value in the chain.
45 return transformer(index === 0 ? this.source : this[`transformer_${index - 1}`]);
46 } catch (e) {
47 // Handle any errors.
48 errorHandler(e);
49 }
50 }
51 })
52
53 // Create an "output" computed property that simply serves as the last one in the chain.
54 computed['output'] = function() {
55 return this[`transformer_${transformers.length - 1}`];
56 }
57
58 // Here's where the magic happens.
59 // Create a new Vue component with the source data in it's data property.
60 // (This makes it observable.)
61 const PipelineComponent = Vue.extend({
62 data() {
63 return {
64 source: this._source
65 }
66 },
67
68 // We need one watcher to "use" the final computed property and cause the chain to update.
69 watch: {
70 // I do realize we could've just put the entire transformer chain in here, but that would be boring.
71 source () {
72 subscribeHandler(this.output);
73 }
74 },
75
76 computed,
77 });
78
79 // Now, initialize the component and start the transformation chain going.
80 this._tracker = new PipelineComponent();
81
82 return this;
83 }
84}
...和使用:
1[label main.js]
2import { ReactivePipeline } from './reactive-pipeline';
3
4const sourceArray = ['e', 'x', 'a', 'm', 'p', 'l', 'e'];
5
6// Create a new pipeline.
7new ReactivePipeline(sourceArray)
8// Make all letters uppercase.
9.pipe(array => array.map(letter => letter.toUpperCase()))
10// Join the array into a string.
11.pipe(array => array.join(''))
12// Log any errors.
13.error(e => {
14 console.error(e)
15})
16// Start the pipeline and listen for changes.
17.subscribe(result => {
18 // Whenever the output changes, log it.
19 console.log(result) // EXAMPLE
20});
BOOM! RxJS 风格的反应式数据管道与 Vue.js 在 100 SLoC 以下!
只需擦它,RxJS 是 ~140 kB 缩小,而Vue 是大约 60 kB. 所以你可以有一个视图框架 和 自己的自定义可观察系统在不到一半的RxJS的大小。
认可
我非常感谢(Anirudh Sanjeev)的作品(LINK0),他第一次打开了我的眼睛,看到Vue的计算特性的潜力,并使我的想象力像疯狂的想法。