Skip to content

Discussion of Scoped Mixins APIΒ #60

Closed
@likern

Description

@likern
  • Start Date: 2019-06-24
  • Target Major Version: 3.x
  • Reference Issues: (fill in existing related issues, if any)
  • Implementation PR: (leave this empty)

Note

I opened this Issue to discuss ideas of scoped mixins API and gather ideas and feedback about it. The main idea is, instead of completely change the way how components are written, update mixins semantic to make it more composable and reusable.

Summary

Unify logic-composition patterns under scoped mixins.

Basic example

export default {
  data() {
    return {
      value: 0
    };
  },
  methods: {
    increment() {
      this.value++
    }
  },
  computed: {
    plusOne: function () {
      return this.value + 1
    }
  },
  watch: {
    value: function (newVal, old) {
      console.log(`count * 2 is ${newVal * 2}`)
    }
  },
  mounted() {
    console.log(`mounted`)
  }
}
<template>
  <div>
    <span>count is {{ count.value }}</span>
    <span>plusOne is {{ count.plusOne }}</span>
    <button @click="increment">count.value++</button>
  </div>
</template>

<script>
import Count from "@/mixins/Count";

export default {
  mixins: {
    count: Count
  },
  data() {
    return {
    };
  }
}
</script>

Motivation

Logic Composition

One of the key aspects of the component API is how to encapsulate and reuse logic across multiple components. With Vue 2.x's current API, there are a number of common patterns we've seen in the past, each with its own drawbacks. These include:

  • Mixins as array of mixin objects (via the mixins option)
  • Higher-order components (HOCs)
  • Renderless components (via scoped slots)

Mixins drawbacks

Mixins in its current state have several major drawbacks making it difficult to reason about them and compose.

  • One of the main complains about mixins is it's difficult to figure out sources the properties come from. When several mixins are used it becomes problematic to understand which mixin contains property and how properties are grouped by mixins.
  • Namespace clashing. Mixins can potentially clash on property and method names.
  • Unexpected merging strategy for mixins
  • Order of mixins in array is important and mixins can overwrite each other's functionality
<template>
  <h1>Problem 1 with mixins</h1>
</template>

<script>
// Mixins are composed in unexpected way
// Why is called only secondMixin and why it is called twice?
const firstMixin = {
  created: function() {
    this.hello();
  },
  methods: {
    hello: function() {
      console.log("I'm from first mixin!");
    }
  }
};

const secondMixin = {
  created: function() {
    this.hello();
  },
  methods: {
    hello: function() {
      console.log("I'm from second mixin!");
    }
  }
};

export default {
  name: "problem1",
  mixins: [firstMixin, secondMixin]
};
</script>

The new scoped-mixins API presents a clean and flexible way to compose logic inside and between components without any of these drawbacks. This can be achieved by extracting code related to a piece of logic into what we call a "scoped mixin" which can be easily injected into component. Here is an example of using a scoped mixin to extract the logic of listening to the mouse position:

export default {
  data() {
    return {
      x: 0,
      y: 0
    };
  },
  methods: {
    update(e) {
      this.x = e.pageX
      this.y = e.pageY
    }
  },
  mounted() {
    window.addEventListener('mousemove', this.update)
  },
  unmounted() {
    window.removeEventListener('mousemove', this.update)
  }
}
// in consuming component

<template>
  <div>{{ mouse.x }} {{ mouse.y }} {{ other.z }}</div>
</template>

<script>
import mouseMixin from "./mixins/mouse"
import otherMixin from "./mixins/other"

export default {
  name: "app",
  mixins: {
    mouse: mouseMixin,
    other: otherMixin
  }
};
</script>

Note in the example above:

  • Properties exposed to the template have clear sources since they are nested object properties, where parent object is used as namespace;
  • Mixin objects can be registered at arbitrary name (used as namespace) so there is no namespace collision;
  • There are no unnecessary component instances created just for logic reuse purposes.

Type Inference

TypeScript type inference support is out of scope of this RFC. This RFC solely focuses on better logic composability / reusability and tries to be better mixins / HOC / renderless components alternative.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions