Skip to content

Instantly share code, notes, and snippets.

@brianboyko brianboyko/README.md
Last active Aug 7, 2019

Embed
What would you like to do?
Vue Extended Styleguide for Vue-Function-API / Vue 3.0 Proposal

Important: This is in the RFC stage. Please comment if you notice typos, code flaws, or even disagree with the conclusions.

I'm still getting used to vue-function-api and haven't had a chance to try it out extensively. In other words, I might be completely wrong on everything. That's okay.

Brian Boyko's Vue.js Style Guide for use with the Vue-Function-API

Guide for developing Vue.js applications for the proposed Vue 3 function API (or the vue-function-api plugin).


This is a living document

If you've been linked to this document from another source, please keep in mind that this is a living document. I do read the comments, and that's the best place to give suggestions, comments, corrections, or ideas for how to expand the guide. Vue 3.0 is still in RFC stage and this document will try to keep up with the changes as best as I can.

Who is this for?

This extended style guide is an extension to the official Vue style guide for Vue version 2 at https://vuejs.org/v2/style-guide/, with two main goals:

  • To provide updates to the styleguide for use with the vue-function-api plugin (which is seriously being considered in an RFC for the base of Vue 3.0)
  • To provide even more conventions to enable large organizations to write standardized Vue code that will scale.

Why are you writing this?

Vue.js is an amazing framework. It's easier to use than React and Angular, provides, through .vue files, a great way to organize responsibilities for your applications.

It's got an easy to use way of combining templates with observable values and a reactive paradigm - when the value changes, the page updates. Neat!

The ease-of-use of Vue, however, is also in many ways, it's downfall, and if not treated carefully, can result in an inability to scale or modify applications.

This is because Vue is extremely flexible, and gives you multiple ways to alter the state of your application and it's components. There's the standard observables, passed-down-props, sync/emit patterns, event busses, Vuex, modifying the Vue instance's root, accessing this.$parent, watchers, mutators... in other words, if you can think of a way to modify a value in Vue, that idea probably works.

That means that if you want to have consistancy and you want to be able to pass off code from programmer to programmer in teams, you need to adopt additional conventions which provide constraints on how to solve certain problems that can be solved in a number of different ways.

Why are you using Typescript, and not Javascript, in your examples?

There's three reasons. First, I recommend using Typescript as best practices, it'd be hypocritical to suggest otherwise.

Secondly, I love Javascript's dynamic typing, as it's one of the reasons it's easy to use and to learn, especially when starting out. But these best practices guides are aimed at a more experienced audience, and I would have to say that if you are going beyond small toy projects and are responsible for software in an enterprise, organization, or business, especially if you are working with a team, I cannot emphasize how much more time you will save and how much more stable your code will be if you use type checking of some sort.

I come to this as a convert - I truly believed Typescript was garbage until I was forced to use it for a client's site - and I was able to start getting to work immediately, taking a lot less time to learn the existing codebase, because everything was self-documenting.

You won't find fancy generics or anything like that in the code samples below, and I don't think there's anything wrong with the typescript "any" being used in your code - but TS really does what it says on the tin: "Javascript that scales."

I'll try to annotate some of the typescript more than I normally would in comments, I hope I write my typescript in a way that a JS-only programmer can read and understand.

Third: In a previous version of this styleguide, designed for Vue 1.0/2.0, I recommended using prop type validation, for which Vue has some amazing built-in tools. But if you use Typescript, it can supercede prop-type validation and actually catch prop-type errors at compilation time, not just at runtime.

If you need to sell your team or your client on typescript, just give them this list:

  • IDEs like VSCode that support typescript limits the amount of googling and file-switching you have to do.
  • You get fewer errors/bugs at runtime because you've caught potential errors/bugs at compile time.
  • You won't have to write a whole bunch of unit tests to ensure you're getting/recieving/handling different types.
  • You won't "break" code as often as you refactor a function that another function relies on, meaning you won't be as afraid to clean up code that needs cleaning.
  • Even if you're rushed and have to resort to using "any" as a type more often than you should, there's absolutely nothing wrong with that and it leaves room for improvement.

The next developer that takes over ownership of your code will thank you (or at least curse you less) for using Typescript.

A note on observables.

Most of Vue's ease-of-use is due to the use of Observables - a pattern that triggers re-renders and other function calls with the reassignment of a variable.

Under the hood, it's using get() and set() methods, part of the core functionality of Javascript. get() and set() are very powerful. They are also, in my opinion, very dangerous, as the reassignment or access of a property via get() or set() will execute additional code other than the read from memory or write from memory - what functional programmers call with disdain "side effects" (Ooohh... scary ghost noises)

In this case, the side-effect is that Vue will re-render the page where it changes. Which is what we want. But it violates the principle of least surprise - you expect "=" to only do assignment, because it's used for assignment 99% of the time. Using set() changes what the operator "=" does and if you can't see how that can be a problem...

As an aside: React handles this by using the React.setState() method, rather than using set(), which is a design choice for handling reactivity without observables. But that's React.

In Vue, these observables are everywhere and for the most part they are all accessible to the programmer. You can directly access a parent component through this.$parent. You can access a vuex state through this.$state. But just because you can doesn't mean you should, as those assignments can come from anywhere and result in changes anywhere in your application, creating very tightly coupled, fragile code.

What we're trying to do is avoid design patterns that increase fragility, and find a standard way to do things - for example, using the sync/emit pattern instead of accessing $parent directly, or using mapActions and mapGetters for Vuex instead of accessing $state.

Section 1: The Basics

Vue.js has put out an official style guide and we should consider this document an extension of that one. While this shouldn't be an exhaustive list, here are some key takeaways from that document:

  • Always use key with v-for.
  • Use Vue Single File Components
  • No more than one component per file.
  • Use PascalCase for naming component files. (i.e. "MyComponent.vue" not "myComponent.vue" or "my-component.vue")
  • Base components (that is, a "dumb" component that has no interactivity or internal state and just displays data) should begin with the prefix "Base". (As in : "BaseButton.vue", "BaseTable.vue")
  • Components that are created and used only once (singletons) should be prefixed with "The", as in "TheHeading.vue, "TheSidebar.vue"
  • Child components tightly coupled with their parent should include the parent component name as a prefix. For example, "TodoList.vue" has children "TodoListItem.vue" & "TodoListAddButton.vue" Better still, put them all in a "TodoList" folder.

Here's some more takeaways that might need some examples:

  • Directives and template expressions should only contain the most simple of javascript commands.

The main reason for this is readability. It makes more sense to think of keeping all the "hard logic' in the <script> tags, rather than in the template - you don't expect the template to change things, in other words, and when you debug, it'll be a while before you even think to look at code in the tag.

Bad:

<template>
  <div>
    <app-some-component
      @click="val => val.reduce((pv, cv) => {
      pv[cv.id] = cv;
      return pv; 
    }, {})"
    >
    </app-some-component>
    {{ fullName.split(' ').map(function (word) { return word[0].toUpperCase() +
    word.slice(1) }).join(' ') }}
  </div>
  <template></template
></template>

Good:

<template>
  <div>
    <app-some-component @click="normalize"></app-some-component>
    {{wordmangler}}
  </div>
</template>

// component
<script lang="ts">
import {Vue} from 'vue';
import {computed, value} from 'vue-function-api';
export default {
    setup(){
      const fullName = value("any string"); // TS infers: ValueWrapper<string>
      const wordMangler = computed(() => fullName.value.split(' ').map((word: string) => {
          return word[0].toUpperCase() + word.slice(1)
        }).join(' '))
      const normalize = (event: any) => event.target.value.reduce(
        (pv: any, cv: {id: string, [key: string]: any})) => {
          pv[cv.id] = cv;
          return pv; 
        }, {}) // TS infers ComputedWrapper<string>
      return {fullName, wordMangler, normalize}; 
    }
  }
</style>

Better:

// template
<template>
  <app-some-component @click="normalize">
    {{wordmangler}}
  </app-some-component>
</template>

// javascript
<script lang="ts">
  import {Vue} from 'vue';
  import {computed, value} from 'vue-function-api';

  type Dictionary = {[key:string]: any}; // normal object, we're just checking that all keys are strings and not symbol/number
  type EntryWithId = Dictionary & {id: string}; // same as above but it MUST contain an id key with a string value. 
  
  const mangleWord = (fullName: string): string => 
    fullName.split(' ')
      .map((word: string) => `${word[0].toUpperCase()}${word.slice(1)}`)
      .join(' ')) // normal ordinary string-to-stringfunction.

  const normalizeData = (event: any): Dictionary =>    
    event.target.value.reduce((
      pv: Dictionary, 
      cv: EntryWithId,
    ) => {
      pv[cv.id] = cv;
      return pv;
    }, {}); 

  export default {
    setup(){
      const fullName = value("Full Name");
      const wordmangler = computed(() => mangleWord(fullName.value));
      return {
        fullName, wordmangler, normalizeData
      }
    }
  }
</script>
  • Don't mutate props directly in a child component. They can still be mutated, but they should use the .sync/emit pattern.

With .sync/emit (and required props), you can allow even your dumb components to have interactivity while still allowing parent components to dispatch actions.

Bad

interface ITodo {
  id: string
  text: string
}
// the todo item is getting passed in as a prop. If you're using Vue without typescript, you can check that the Todo matches
// the expected structure by using prop validation instead of typescript interfaces.  
export default {
  name: "TodoItem",
  setup(({todo}): {todo: ITodo}) {
    const removeTodo = () => {
      var vm = this;
      vm.$parent.todos = vm.$parent.todos.filter((todo: ITodo) {
        return todo.id !== vm.todo.id;
      });
    }
    return {
      removeTodo, todo
    }
  },
  template: `
    <span>
      {{ todo.text }}
      <button @click="removeTodo">
        X
      </button>
    </span>
  `
}

Good

interface ITodo {
  id: string
  text: string
}
export default {
  name: "TodoItem",
  setup(({todo}: ITodo) => ({todo})),
  template: `
    <span>
      {{ todo.text }}
      <button @click="$emit('update:delete', todo.id)">
        X
      </button>
    </span>
  `
}

Example

// parent template // syntax 1
<app-child-component foo="foo" @update:foo="val => foo = val"/>
// syntax 2
/* OR: <app-child-component :foo.sync="foo"/> */
// syntax 2 is syntactic sugar for syntax 1, they are identical. 
// child
<script>
  // ...
  setup((props, context) => {
    const changeFoo = (newFoo: any) => context.emit('update: foo', newFoo);
    return {changeFoo}; 
  })
</script>
>

Section 2: Advanced

Following these guidelines should help you organize your apps on a more macro scale.

These are the rules that I've come up with - and your mileage my vary. While the above "style guide" deals more with how to write Vue code, these are general tips for organizing any SPA application, but just written with vue in mind.

NEVER modify the vue prototype.

  • NEVER modify the Vue prototype.
  • If you HAVE to modify the prototype, extend the Vue class and modify THAT prototype.
  • NEVER store extra constant values on the Vue prototype.

First, there's just a general practice of not modifying prototypes in general. It's already bad practice to extend native prototypes (monkey-patching), but as a general rule, programmers who use Vue (or React, or Angular, or any other "big standard") expects Vue to work the same way in every program.

So put yourself in the position of the programmer who is new to Vue, but not to programming. A developer decides to put all the methods that fetch data in Vue.prototype.$API. A follow-up programmer would then have the following problems:

  1. What if I need to use the API to make a call outside of a component (say in a Vuex module?)
  2. Where is Vue.prototype.$API defined?
  3. Is it all defined in one place or is it extended in different places over the program in runtime?
  4. Is this.$API a native method to Vue? It's got the magic $ to indicate it is. If it doesn't have that $, then is this.API an observable, like other items data() returns?
  5. Why does this.$API work in Vue Program A, but not in Vue Program B?

Notably, this doesn't apply to plugins designed to work with Vue as extendable libraries published on NPM and imported via a Vue.use(), like Vuex and Vue Router, as they're designed to extend capabilities in a known and predictable way, and need access to the root Vue instance in order to do thier job.

By definition, when you add a method to a prototype, you tightly couple the method you've created to the prototype, and it can be difficult to untangle.

Vuex

You may not need Vuex. Especially with the new function API, it's much easier to create an event bus that can be used in multiple places in your application. But Vuex still has a lot of advantages in organizing data, including loggers, asynchronous action dispatchers, etc.

Keep in mind that Vuex is not the only state manager out there. It is possible to use Redux with Vue, and it's rare, but you may want to use Redux instead of Vuex as your state manager despite the added difficulty if these concepts are true:

  • You want to dispatch an action that will change multiple reducers, even among modules.

One of the things about Redux is that every action goes through every reducer, so you can listen for a particular type in multiple reducers and make multiple updates to the store. Conversely, with switch fallthroughs, you can have more than one type execute the same code. This is possible in Vuex but Redux made support for this part of the design decisions, Vuex treats that as an execeptional case, as typically an action will go through only ONE mutator. That mutator may make multiple updates to the state, but if you have a lot of interrelated actions and reducers - you might want to consider Redux.

So - caveats aside, Vuex is pretty damn sweet, and one of the things it does better than Redux is having a unified place to define getters (rather than each component having it's own "mapStateToProps", like in Redux).

As a general rule:

  • If your data is coming from an external state (an API) and you want to be sure that the data only changes when the information on the backend changes, Vuex is a great tool. In other words - use Vuex for data that comes from the API. API data is typically the kind of data used in multiple places in an application, but keeping all the API data in a single place (the Vuex store) will help you when tracking down where the data is made.

While the store might contain information about the state of your front-end application, if you keep all your API calls in actions, and those actions then commit to the mutator, and thus to the state, you can ensure that A) all components are pulling from the single source of truth, and that no data will be duplicated, B) your front-end store will stay synced with your back-end data, C) your application will update automatically as soon as the API call resolves.

  • If you have a stateful bit of information used by the entire application - (a JWT token or login data, say), Vuex is a great way to ensure that you can pass that information to where it's needed and modify it in a controlled way.

Vuex stores have four main parts: the state object itself (the store), getters, actions, and mutations.

Store:

  • Don't use Immutable.js

As much as I love Immutable.js, and recommend it for Redux-based applications, and would LOVE Vuex support for it, Vuex is designed to work with mutations of standard JS objects efficiently and effectively. Since JS normally passes objects by reference, Redux may not recognise a mutated object as different and fail to trigger the change. Vuex does, by design.

  • Whenever you can, normalize data that comes in array form, unless the order is important (such as when you're getting the top 20 most recent pages from your API.)

Even if the data has come back from the API as an array of objects, AND your view needs to display it as an array mapped over with a v-for directive, you should consider storing it in the store as an object of objects, by some identifying key, and then just denormalizing the data back again inside the getter.

Bad:

interface IPartner {
  name: string,
  id: string,
}
interface IState {
  partnersList: IPartner[],
  selectedPartner: IPartner
}
const initialState: IState = {
  partnersList: [], // array of all partners
  selectedPartner: {} // current partner object.
};

Good:

interface IPartner {
  name: string,
  id: string,
}
interface IState {
  partnersList: {id: IPartner},
  selectedPartnerId: string
}
const initialState: IState = {
  partnersList: {}, // object with all partners keyed by ID
  selectedPartnerId: `` // a string contining the ID of the current partner.
};

There are a couple of advantages to this. First, you can easily use ES6+'s built in Object.keys, Object.values, and Object.entries methods to get an array back from any object, but it's a bit more complicated to go the other way around.

Second, as an algorithm, retrieiving something from an object by key is O(1), compared to O(n) if you use searches or filters to find the same data. It's not much of an overhead, but it adds up.

Third, listing by key allows you to be able to access the correct data even as the array changes length.

Take for example, the information about partners. Each partner has it's own entry in the database, this comes back to us in the form of an array. These are stored in the vuex store as "partnersList". You could switch between partners by searching for some criteria, then making a copy of the current partner in a currentPartner object in the store, but that not only is a slow method, it needlessly copies data.

Instead, normalize the partner list by creating an object where each key is the partnerId. Then, just store the currentPartnerId as a primitive string (or number) and access the keys in partnerList that way.

Or better still... use a Vuex getter function that takes an ID and returns the bit of data you need.

Getters:

A getter is a small function which takes the state as it's single parameter and returns some aspect of the state.

  • It's a bad pattern to access the state directly, as you don't want to accidentally change the (observable) vuex store in the component. Instead, use a getter to get the specific data you need. This also allows you more granularity in the type of data you need.

So that, for example, instead of needing to reference this.$store.partnersList[this.$store.currentPartnerId] in your component, you can define a getter to do that for you automatically.

Example:

const getters = {
  partnersList: state => state.partnersList, // gets the partner list.
  // returns a function which returns any partner by id.
  getPartnerById: state => id => state.partnersList[id],
  // gets the current partner id.
  currentPartnerId: state => state.currentPartnerId,
  // gets the current partner object
  // (even though that object isn't stored directly in the state)
  currentPartner: state => state.partnersList[state.currentPartnerId]
};
  • Getters should not mutate data, nor should they have side-effects. (indeed, the purpose of a getter is to provide a "safer" way to access the vuex store values). They should only return the data you need.

  • Getters are great for taking the data from the store then formatting it the way you need it before handing it off to the Vue component. This keeps transformation logic outside of the presentational Vue component. You can also use this pattern to create different transforms on the same data without mutating that data.

Actions:

An action is a function which takes as it's first parameter an object with a "commit" function. This commit function, in turn, takes a string as it's first parameter - that string determines what command the mutator should run.

Actions are ALWAYS asynchronous. They may resolve more or less instantly, but by design, the commit is passed in a callback, NOT returned. That means that you can wait for Promises to resolve before dispatching the data to the store.

Example:

import * as partnerTypes from "../mutationTypes";

const actions = {
  setCurrentPartner: ({ commit }: Vuex.Store<IState, IRootState>, partnerId: string):void => {
    commit(partnerTypes.SET_CURRENT_PARTNER_ID, partnerId);
  },
  grabPartnerList: async ({ commit }: Vuex.Store<IState, IRootState>):void => {
    try {
      const rawList: IPartner[] = await api.get.partnersList()
      return commit(partnerTypes.SET_PARTNERS_LIST, normalizePartners(partnersList));
    } catch (err: any) => {
      console.error(err); 
      return commit(partnerTypes.SET_ERROR_CONDITION, err); 
    }
  }
};

Actions don't actually change the store data - that's what mutators are for - but they send the command to change data so that it's ready for inclusion in the store.

In the above example, it can be very simple - "setCurrentPartner()" takes an object with the commit method, then runs it with "SET_CURRENT_PARTNER_ID" as the first variable, and the partnerId as the second. Most actions will be that simple.

"grabPartnerList()" on the other hand, shows some of the complexity in the actions. This doesn't take a second parameter, because 'grabPartnerList' is actually kicking off the API call when it's executed.

In the case, the api.get.partnersList() returns a promise object, that has a .then method. Only after the data has been resolved does the data get committed to the store.

In this case, though, it's not the raw data, but a normalized version - as you can see by the function call to normalizeParameters (not pictured here) which will transform the data after it comes from the API and before it gets to the store

(to recap: if you need to transform data before it gets to the store, transform it in the action, if you need to transform data after it comes from the store, transform it in the getter).

On testing Actions: As mentioned, actions don't really return anything, they're designed to run the "commit" callback. Still, this is code that needs to be unit tested. If you need to unit test actions in isolation, consider using a utility like sinon or jest.fn() to create a spy.

describe('someAction', () => {
  it('commits type and value' => {
    const fakeCommit = jest.fn(); 
    someAction({commit: fakeCommit}, "July 16, 1969"})
    expect(fakeCommit.mock.calls[0]).toEqual(['SET_DATE_OF_MOON_LANDING', "July 16, 1969"]); 
  }) 
})

Mutations

Mutations is an object with different methods that take the Observable state as the first parameter. It is here, and ONLY here that any part of the state should be changed.

Because we have put our transformation logic in the actions and getters, most mutations will be fairly simple reassignment affairs.

Example:

const mutations = {
  [types.SET_PARTNERS_LIST](state: IState, partnersList) {
    state.partnersList = partnersList;
  },
  [types.SET_CURRENT_PARTNER_ID](state: IState, partnerId) {
    state.currentPartnerId = partnerId;
  }
};

What is happening is that commit uses the first parameter set to it to determine which of these mutator methods to execute with the values in it's other parameters.

In Review:

You should use this flow, based on the Flux pattern, to organise the lifecycle of changes to the state.

  • STEP 1: EVENT The View (Vue) has an event happen (a button push, a listener trigger, a page load)
  • STEP 2: DISPATCH AN ACTION The View then dispatches an action along with any data payload (such as the component data state).
  • STEP 3: PROCESS THE DATA The action then processes the data from the event, and if necessary, grabs more data from the API. All this data is put into a payload.
  • STEP 4: COMMIT THE PAYLOAD(s) The callback to the commit in the Action (which can happen more than once, and can be asynchronous) commits those changes to the mutator.
  • STEP 5: MUTATE THE STATE Based on the type put into the mutator, the state then gets mutated.
  • STEP 6: OBSERVABLES CHANGE THE VALUE OF GETTERS Because the value of the state has changed, the value of the getters is now recalculated based on these new state values.
  • STEP 7: GETTERS GET COMPUTED INTO THE VIEW The getters, imported into the component, then change the view with the updated information. This in turn MAY trigger another event, but it is more likely that user action will trigger the next event.

Using Store Values in Components

It is possible to read from the store directly and to dispatch a mutation directly to the Vuex store. However it is not advised. Instead, you want to use the getters and actions to interact with the store.

!!! important - need fact checking.

As it so happens, the creators of Vuex have created two handy components for you to use - mapActions and mapGetters. In the Vue 1.0/2.0 syntax, this was the way to do so.

import {mapGetters, mapActions} from 'vuex'

Vue.component({
  name: `PartnerList`,
  computed: {
    ...mapGetters([
      `partnersList`,
      `getPartnerById`,
      `currentPartnerId`,
      `currentPartner`
    ]),
    // ... any other computed properties...
  }
  methods: {
    ...mapActions([
      `setCurrentPartner`,
      `getPartnersList`
    ]),
    handleChangePartner (event) => {
      let newId = event.target.value;
      if(newId !== this.currentPartnerId){
        this.setCurrentPartner(newId); // this.currentPartner automatically updates.
      }
    }
  }
})

However, with the new function API, we need to use the computed wrapper to compute the values. What is a idiomatically consistant programmer to do here?

There will probably be a more elegant solution - perhaps one even already exists (though a brisk googling has not found it), but it's a pretty simple matter to create a utility to remap getters to the new syntax. Here's the thing, though, how do you map Getters/Actions, to components in the new Function API? There may be a more elegant solution from the Vuex team (and this styleguide) will be updated at that time. But in the meantim, try this.

// in ../some_util_dir/remapGetters.ts
import {mapGetters} from 'vuex'

type GetterEntryTuple = [string, (state: any) => any]; 
export type RemappedGetters = {[key: string]: ComputedWrapper<any>}

const remapGetters = (getterNames: string[]): RemappedGetters => 
  Object.entries(...mapGetters(getterNames))
    .reduce((compObj: Dictionary, [getterName, getter]: GetterEntryTuple) => {
      compObj[getterName] = computed(getter)
      return compObj; 
    }, {})

export default remapGetters; 

/**
// Javascript-only version of remapGetters
const remapGetters = (getterNames) => 
  Object.entries(...mapGetters(getterNames))
    .reduce((compObj, [getterName, getter]) => {
      compObj[getterName] = computed(getter)
      return compObj; 
    }, {})
*/

// in component
import {mapActions} from 'vuex';
import remapGetters from '../some_util_dir/remapGetters'; 

export default {
  name: "PartnerList",
  setup((_props: any) => {
    // other stuff. 
    return {
      ...remapGetters([
        `partnersList`,
        `getPartnerById`,
        `currentPartnerId`,
        `currentPartner`
      ]),
      ...mapActions([ 
        `setCurrentPartner`,
        `getPartnersList`
      ])
    }
  })
})

Note the syntax. remapGetters and mapActions take an array, then iterates over that array, and creates computed values that can be referenced by the key. So for example, we can access the partners list with the rest of the items setup returns.

The action mapper is similar - setCurrentPartner() is mapped to this.setCurrentPartner - but wait! Didn't setCurrentPartner() take a commit object as the first parameter? That's true, but ...mapActions automatically curries the commit object and returns a new function which only takes the remaining parameters.

So when this.setCurrentPartner(newId) executes, not only does the currentPartnerId get changed in the store, but the value of this.currentPartner(the getter) changes instantly as well.

Please leave comments below.

Again - I'm working on this in my spare time and it is not done yet. Eventually I want to move the examples to CodeSandbox so that you can play around with them.Thanks for reading.

@yyx990803

This comment has been minimized.

Copy link

commented Jul 14, 2019

Haven't finished reading the whole but just want to bring up one thing - when using TS you don't need to manually declare the type of value wrappers. With Vue's type definitions most of them can be automatically inferred.

@brianboyko

This comment has been minimized.

Copy link
Owner Author

commented Jul 14, 2019

Good to know. I'm working on refining it - you're not the first to say my TS is too verbose. :)

Maybe after work tomorrow I'll get some stuff up on codesandbox and try that.

@pearofducks

This comment has been minimized.

Copy link

commented Jul 17, 2019

Would it make sense to separate TS out from some of these examples?

Many of these examples aren't about TypeScript, but you've added the extra burden for the reader* of parsing TS.

Take the first Basic example, in 'bad' we have:

 {{ fullName.split(' ').map(function (word) { return word[0].toUpperCase() +
    word.slice(1) }).join(' ') }}

which then you'd like the user to see in 'good' is:

const wordMangler: ComputedWrapper<string> = computed(() => fullName.value.split(' ').map((word: string) => {
          return word[0].toUpperCase() + word.slice(1)
        }).join(' '))

When really nothing about this example has anything to do with how that chain is written, it's more about how the code is structured. I'd prefer to see that function written exactly the same in both examples, just moved - so the reader can easily compare the two and see they're identical just in different places.

* I'm assuming the target-audience includes more basic users, or those who might not be familiar with TS.

@fessacchiotto

This comment has been minimized.

Copy link

commented Jul 17, 2019

The section about mapGetters and mapActions has been quite useful! Thanks.

@fessacchiotto

This comment has been minimized.

Copy link

commented Jul 17, 2019

Wrt vuex/router have a look at: https://github.com/u3u/vue-hooks

@brianboyko

This comment has been minimized.

Copy link
Owner Author

commented Jul 17, 2019

@fessachiotto - going to take a look at that repo when I get a chance, thanks! I may even add it into the guide as a recommended tool.

@ffxsam

This comment has been minimized.

Copy link

commented Jul 19, 2019

Just a small pointer, related to TS:

type Dictionary = {[key:string]: any};

This isn't necessary, as TypeScript has a built-in type Record, so you could just do this:

const normalizeData = (event: any): Record<string, any> =>

Also, since you cover style conventions in your post, I figured I'd mention that prefixing TS interfaces with a capital "I" is not recommended. According to this TS eslint rule, which is part of the "recommended" preset:

"never" (default) disallows all interfaces being prefixed with "I"

And while Microsoft makes it clear that this rule is not prescriptive for the general public (only for contributors to TS), it's clear from the official handbook that interfaces are not prefixed with "I".

interface Partner {
  name: string; // also, note the semicolons instead of commas (the TS eslint plugin will auto-fix commas)
  id: string;
}

Just a note from your friendly neighborhood style stickler. 😉 Great work, Brian!

@brianboyko

This comment has been minimized.

Copy link
Owner Author

commented Jul 19, 2019

I honestly thought it was the other way around - that interfaces had to be prefixed by I. Will make the change when I get a chance.

@vladislav-ladicky

This comment has been minimized.

Copy link

commented Jul 19, 2019

Most people are still confused with the v3.0 syntax, so I don't think it's a good idea to complicate things with TS. If TS is not required to use the new API, please explain it without TS. Otherwise you confuse them even more, and your examples become unnecessary more complicated.

@CuongStf

This comment has been minimized.

Copy link

commented Jul 23, 2019

Can you tell me what's mean piece code setup(({todo}: ITodo) => ({todo}))?
Screen Shot 2019-07-23 at 2 55 22 PM

@rebolyte

This comment has been minimized.

Copy link

commented Jul 23, 2019

@CuongStf Written out more verbosely, setup(({todo}: ITodo) => ({todo})) is equivalent to:

setup((props: ITodo) => {
  const { todo } = props; // destructure `todo` property off of props object
  return { 
    todo: todo
  };
});
@CuongStf

This comment has been minimized.

Copy link

commented Jul 23, 2019

Many thanks!!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.