使用 Vue.js 创建反应式数据管道

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的计算特性的潜力,并使我的想象力像疯狂的想法。

Published At
Categories with 技术
Tagged with
comments powered by Disqus