pablohpsilva/vuejs-component-style-guide

Avoid this.$refs needs context

kartsims opened this issue ยท 14 comments

While reading the paragraph about avoiding this.$refs I thought it wasn't clear enough that you mean "avoid it to access subcomponents".

this.$refs can also be used to access any DOM element's native API and in this case it is pretty useful and can't be done otherwise.

Would you prefer a PR ?

Hello @kartsims ! Thank you for this issue.

OK, help me out.

What I meant is that instead using this.$refs on subcomponents, the subcomponent should provide a good API (props + events). That's why I said "avoid" instead of "don't".

I'm not against the use of refs to access DOM elements.

How could we write this in a more specific way?

After trying to change the paragraph I came to the conclusion that it just doesn't belong here. Not like this anyway

Using this.$refs is a hacky way to overcome a component's poor API.

It is still interesting to mention it but maybe in a more general way, at the end of the style guide, in a paragraph like "Improving existing components" or "When should you change the component itself"... Not fan of either title but you get the idea.

I believe it would be interesting to mention specific cases. Examples :

1 - Accessing a subcomponent through this.$refs is a good example of a poor component's API. They should never be accessed via this.$refs.xxx as it violates the main rule of component based frameworks : props down, events up. This shortcut is a hacky way to highlight caveats in a component's API.

2 - Using a .sync props modifiers on a component is another example of disregarding the "props down, events up" rule. Your component should expose an event when the specific prop's value is updated or offer another way of doing so.

I am sure there are plenty of other examples you could think of. The times when you hesitated and thought "hmm maybe this is not standard, there must be a better way"

What do you think about changing it to:


Avoid this.$refs

Vue.js supports components to have access to other components and elements context via ref attribute. That attribute will provide an accessible way through this.$refs to other components/elements context. The need to access other componets context via this.$refs is a hack. In most cases this.$refs just means a component lacks a good API.

Why?

  • A vue component, like any component, must work in isolation. If a component does not support all the access needed, it was bad designed/implemented.

How?

  • Create a good component API;
  • Check all the props to see if something is missing. If it is, create an issue or enhance the component yourself;
  • Check all the events. In most cases developers forget that Child-Parent communication (events) is needed, that's why they only remember the Parent-Child communication (using props);
  • Props down, events up! Upgrade your component when requested with a good API and isolation as goals.
<range :max="max"
  :min="min"
  @current-value="currentValue"
  :step="1"></range>


<!-- avoid -->
<template>
  <range :max="max"
    :min="min"
    ref="range"
    :step="1"></range>
</template>

<script>
  export default {
    // ...
    methods: {
      getRangeCurrentValue() {
      	return this.$refs.range.currentValue;
      },
    },
    // ...
  };
</script>

I agree this.$refs is not always a bad practice. See this API for example: http://quasar-framework.org/components/modal.html

I think it's pretty clean to access modal open and close method. How would you do it otherwise?
Using a bool prop maybe? I think it wouldn't be as nice.

Use $refs wisely, as quick access to dom objects in complex components as Modals. Yep.

I never said its a bad practice @Elfayer . I said its better to avoid it. :)

The example you just shown its a good example of when use this.$refs. It makes sense and it looks super clean.

Check my last comment to see a bad use of this.$refs. Why is it bad? Because that value could be emitted. It makes sense and the component would be isolated.

What do you think @Elfayer ?

@pablohpsilva I think this.$refs should be mentioned as a useful tool, but people should use it only when required. It could mean that the child component API is wrong.
Maybe just add a reminder: Use this.$refs as last resort, most of the time properties and events will be sufficient to a strong API.

We are talking about using this.$refs on components, right?
On basic HTML elements it's just fine to allow DOM manipulation. Isn't it? (still better than $ or document.getElement[...]/document.queryElement(), those should never appear in a VueJS code, from my point of view)
Although with data-binding DOM manipulation should not happen often.

Edit: Or maybe just mention to use it when you know why you use it, not just as a shortcut.

Also maybe add this image?
http://i.stack.imgur.com/G1YWd.png
It comes from The Progressive Framework slides of Evan You.

Yeap! That's exactly it @Elfayer .

Guys, help me out writing this style guide:


Use this.$refs as last resort

Vue.js supports components to have access to other components and elements context via ref attribute. That attribute will provide an accessible way through this.$refs to other components/elements context. The need to access other componets context via this.$refs is a hack and in most cases this.$refs just means a component lacks a good API.

Why?

  • A vue component, like any component, must work in isolation. If a component does not support all the access needed, it was bad designed/implemented.
  • this.$refs is a useful tool, but you should know when to use it. See the How? below.

How?

  • Create a good component API;
  • Check all the props to see if something is missing. If it is, create an issue or enhance the component yourself;
  • Check all the events. In most cases developers forget that Child-Parent communication (events) is needed, that's why they only remember the Parent-Child communication (using props);
  • Props down, events up! Upgrade your component when requested with a good API and isolation as goals;
  • Using this.$refs on components should be used when props and event options are exhausted and having it makes sense (see the example below);
  • Using this.$refs to access DOM elements (instead of doing jQuery, document.getElement*, document.queryElement) is just fine, when the element can't be manipulated with data bindings.
<range :max="max"
  :min="min"
  @current-value="currentValue"
  :step="1"></range>

<!--good example of when to use this.$refs -->
<q-modal ref="basicModal">
  <h4>Basic Modal</h4>
  <button class="primary" @click="$refs.basicModal.close()">Close</button>
</q-modal>


<!-- avoid accessing something that could be emitted -->
<template>
  <range :max="max"
    :min="min"
    ref="range"
    :step="1"></range>
</template>

<script>
  export default {
    // ...
    methods: {
      getRangeCurrentValue() {
      	return this.$refs.range.currentValue;
      },
    },
    // ...
  };
</script>

@pablohpsilva I added as much details as I could, keep what you like ;)
I made italic everything I changed.


Use this.$refs with caution

Vue.js supports components to have access to other components and basic HTML elements context via ref attribute. That attribute will provide an accessible way through this.$refs to a component or DOM element context. In most cases, the need to access other components context via this.$refs could be avoided. This is why you should be careful when using it to avoid wrong component APIs.

Why?

  • A vue component, like any component, must work in isolation. If a component does not support all the access needed, it was badly designed/implemented.
  • Properties and events should be sufficient to most of your components.

How?

  • Create a good component API;
  • Always focus on the component purpose out of the box;
  • Never write specific code. If you need to write specific code inside a generic component, it means its API isn't generic enough or maybe you need a new component to manage other cases;
  • Check all the props to see if something is missing. If it is, create an issue or enhance the component yourself;
  • Check all the events. In most cases developers forget that Child-Parent communication (events) is needed, that's why they only remember the Parent-Child communication (using props);
  • Props down, events up! Upgrade your component when requested with a good API and isolation as goals;
  • Using this.$refs on components should be used when props and events are exhausted and having it makes sense (see the example below);
  • Using this.$refs to access DOM elements (instead of doing jQuery, document.getElement*, document.queryElement) is just fine, when the element can't be manipulated with data bindings or for a directive.
<!-- good, no need for ref -->
<range :max="max"
  :min="min"
  @current-value="currentValue"
  :step="1"></range>
<!-- good example of when to use this.$refs -->
<modal ref="basicModal">
  <h4>Basic Modal</h4>
  <button class="primary" @click="$refs.basicModal.close()">Close</button>
</modal>
<button @click="$refs.basicModal.open()">Open modal</button>

<!-- Modal component -->
<template>
  <div v-show="active">
    <!-- ... -->
  </div>
</template>

<script>
  export default {
    // ...
    data() {
        return {
            active: false,
        };
    },
    methods: {
      open() {
      	this.active = true;
      },
      hide() {
      	this.active = false;
      },
    },
    // ...
  };
</script>
<!-- avoid accessing something that could be emitted -->
<template>
  <range :max="max"
    :min="min"
    ref="range"
    :step="1"></range>
</template>

<script>
  export default {
    // ...
    methods: {
      getRangeCurrentValue() {
      	return this.$refs.range.currentValue;
      },
    },
    // ...
  };
</script>

I like this version, maybe we could continue the discussion (if need be) in a PR ?

I liked it too and agree with @kartsims . @Elfayer could you please make that change on a PR for us?
:D

@pablohpsilva I'll try, but it'll be my first commit ever on GitHub, so I hope I'm not going to break anything. =/

Please tell me if I made any mistake in the process.

Nope @Elfayer :D
PR accepted.