joedski / vue-flyd

Use Flyd in Vue! If some reaction is good, more is better, right?
2 stars 0 forks source link

Typescript Support? #3

Open joedski opened 5 years ago

joedski commented 5 years ago

The way source() works doesn't really lend itself to Typescript component typing. How to resolve that?

Example

export default {
  mixins: [StreamsMixin],

  streams: {
    sources: {
      labelProp: 'labelProp',
      combinedProp() {
        return this.a + this.b
      },
      propWithOptions: {
        watch: 'otherProp',
        deep: true,
      },
      // No $watch, just a stream.
      // TODO: How to get a stream type?
      buttonClicks: null,
      // Maybe just an object with `type`?
      otherClicks: { type: MouseEvent },
    },
    sinks(sources) {
      const clickCount = sources.buttonClicks.pipe(flyd.scan(acc => acc + 1, 0))
      const clickCountLabel = flyd.combine(
        (clickCount, clickCountLabel, self, changed) => {
          return `${clickCount()} ${clickCountLabel()}`
        },
        [clickCount, sources.labelProp]
      )

      return {
        clickCount,
        clickCountLabel,
      }
    },
  },
}
joedski commented 5 years ago

Thinking about it, we don't actually care how the sources are created, only what their types are. We could just do something like this:

export default {
  mixins: [StreamsMixin],

  streams: {
    sources({ fromWatch }) {
      // Watch a prop or data named 'label'.
      const labelProp = fromWatch('label')
      // Watch the result of combining two props.
      const combinedProp = fromWatch(() => this.a + this.b)
      // Watch options.
      const deepProp = fromWatch('someObject', { deep: true })
      // Plain streams if we're just shoving things in from events.
      const buttonClicks = flyd.stream()

      return { labelProp, combinedProp, deepProp, buttonClicks }
    },
    // the sources arg here then has the type of the return value
    // of the sources function above.
    sinks(sources) {
      const clickCount = sources.buttonClicks.pipe(flyd.scan(acc => acc + 1, 0))
      const clickCountLabel = flyd.combine(
        (clickCount, clickCountLabel, self, changed) => {
          return `${clickCount()} ${clickCountLabel()}`
        },
        [clickCount, sources.labelProp]
      )

      return {
        clickCount,
        clickCountLabel,
      }
    },
  },
}

where fromWatch is just a convenience function that does this:

const fromWatch = (watchHandler, watchOptions) => {
  const stream = flyd.stream()
  this.$watch(watchHandler, next => stream(next), { immediate: true, ...watchOptions })
  return stream
}

This gives us the ability to infer a type for sources based on what's written, without saying anything about the implementation. Excellent.