Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mutable props with model option #165

Open
KorHsien opened this issue Apr 27, 2020 · 0 comments
Open

Mutable props with model option #165

KorHsien opened this issue Apr 27, 2020 · 0 comments

Comments

@KorHsien
Copy link

Motivation

v-model comes in handy when we have form inputs in our components, it has a unified interface and allows us to have a same simpler mental model when dealing with various types of inputs.

When it comes to creating custom inputs, however, it's a little cumbersome, we have to think of and deal with it as a combo of props and events.

With the composition API, I found myself writing something like this:

export const propToModel = (props, emit, key) => computed({
  get: () => props[key],
  set: (val) => emit(`update:${key}`, val),
})
export default {
  template: '<input type="number" v-model.number="count" >',
  props: {
    count: Number,
  },
  emits: ['update:count'],
  setup(props, { emit }) {
    const count = propToModel(props, emit, 'count')

    // some other stuff

    return {
      count,
    }
  },
}

This abstract the combo behind a Ref, so instead of emitting events, we could just mutating the Ref.

When we wrap form inputs, we could use the Ref in v-model directly. This is convenient and minimize the burden of extracting components.

It might seem not much of a difference, but it is more aligned with the composition API and the mental model shifts a little bit.

Once the model props become mutable Refs, general composition functions could easily applied to them. For example, this general useDebounce composition function could be used with model props directly:

export const useDebounce = (ref, wait = 200) => computed({
  get: () => ref.value,
  set: debounce((val) => { ref.value = val }, wait),
})

Proposed Solution

Add a boolean typed model option to props:

  1. The default value is false, which has no effect;
  2. When it set to true, the prop will be mutable, every mutation on the prop emits an update event for the prop.

Basic Example

export default {
  template: `
    <input type="number" v-model.number="count" >
    <button @click="reset" >Reset</button>
  `,
  props: {
    count: {
      type: Number,
      model: true,
    },
  },
  setup(props) {
    const count = useDebounce(toRef(props, 'count'))

    return {
      count,
    }
  },
  methods: {
    reset() {
      this.count = 0
    },
  },
}

Additional Thoughts

Object Props

When an object passed as a prop, the child component get the reference of the original object, thus it can be mutated.

Now it appears to us that the model option controls the mutability of props, it seems natural that the model option would also control the mutability of the objects passed in.

So if we adopt this idea, the prop with a model option set to false would have a readonly reference to the original object.

This will enforce the mutability of props being explicitly stated, so instead of asking "Should we mutate the object props?" maybe we should ask "Is this prop a model?"

One Step Further

Since we are abstracting away the combo of props and events, would it be a good idea to just build the v-model feature upon the reactivity system?

Currently any Ref referred in templates would be automatically unwrapped. But we could pass a Ref as is to the child component in hand-writing render function, and the child component could use it just as normal Refs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant