A Simple Example app in Vue3+Typescript
Vue3
Vue3 is the latest iteration of the Vue framework.
The attraction of this framework is:
- Component based development.
- Each component can be encapsulated in a single (.vue) file that encapsulates:
- the presentation (Templated HTML),
- view logic script (JavaScript or Typescript),
- style (SCSS).
- A model can be built outside of the Vue component.
- Single code base for front end client.
- Typescript support.
- An unobtrusive reactivity system.
- Node based development environment using VSCode.
The downside is that the documentation and examples are oriented towards JavaScript and not using the Vue components.
This post shows and example of getting started with typescript. In particular:
- Extra steps to add to the template project.
- Instantiating a hierarchy of
.vue
components. - Passing data and events between components
- Adding a model that is isolated from the framework.
- Importing 3rd party JavaScript plugins.
This post is not definitive, it is a scratchpad for experimenting.
The associated source for this post is vue3-typescript-hello-world-plus-alpha.
Getting started.
Install the vue cli. I’m using npm.
I used GitHub to create my repo so I want to merge with that. Also using my local ‘foo’ presets for typescript support.
$ vue create --merge vue3-typescript-hello-world-plus-alpha
> foo ([Vue 3] babel, typescript, eslint)
Updating the Template
The template has been changed for this example:
src/components/HelloWorld.vue
removed.src/components/InputParam.vue
added - this will be the data input form.src/components/OutputTable.vue
added - this will be the output view.src/model/LoanCalc.ts
added - this is a simple data model and algorithm.
Adding Components to main.ts
The template code imports the component into App.vue
. It seems easier to
import components into main.ts
. The components imported in main.ts
will be in scope for all other components.
e.g. Import in App.vue
:
import InputParam from './components/InputParam.vue';
@Options({
components: {
InputParam,
},
})
Import in main.ts
:
import InputParam from './components/InputParam.vue';
const app = createApp(App);
app.component('InputParam',InputParam);
app.mount('#app')
I’m not sure which method is best.
Adding Code Outside of the Vue3 Tree
I’d like to make sure the core logic of the application is not mixed in with the Vue presentation.
Update App.vue
to instantiate the model.
e.g. Create a model/LoanCalc.ts
to compute loan interest.
Import.
import {LoanCalc} from "./model/LoanCalc"
Instantiate the loan_calc
object in App
.
- Declare the
loan_calc
as a member ofApp
. This is declared with a definite assignment assertion via!
. This is as it is not initialized until vue callsdata()
. - Use
data()
method to instantiate the class and return it via aRecord<string, unknown>
. Doing it here ensures reactivity.
export default class App extends Vue {
loan_calc!: LoanCalc;
data(): Record<string, unknown> {
var loan_calc = new LoanCalc(...);
return {
'loan_calc' : loan_calc,
}
}
}
Parameters for Components
In the component
The components/InputsParam.vue
takes a string to use a heading and a custom object (LoanParam
) with a set of parameters.
The @Options
decorator defines the component options.
- This is optional
- The
props
define attributes that can accept data from the parent component.
import { Options, Vue } from 'vue-class-component';
import {LoanParams} from "../model/LoanCalc"
@Options({
props: {
msg: String,
params: LoanParams
}
})
The parameters are bound to the component class members:
export default class InputParam extends Vue {
msg!: string;
params!: LoanParams;
}
Then the template in the component can in turn use and change the parameters. As they are reactive the changes are propagated up and down the hierarchy.
<template>
<h1></h1>
Interest Rate: <input v-model.number="params.rate_percent" type="number" />
</template>
In the parent
The parameters are passed in App.vue
from the component instantiation.
- The colon (
:
) is short is a shorthand for v-bind and binds the object (pass by reference? value semantics??).
<InputParam :params=loan_calc.params msg="Welcome to Your Vue.js + TypeScript App" />
Computed Results
This is where Vue does it’s magic. Provided the data and results are exposed to Vue the (reactivity system)[https://v3.vuejs.org/guide/reactivity.html#what-is-reactivity] takes care of the update.
In this example:
- The
InputParam.vue
component does data entry. - The
OutputTable.vue
displays the results. - The
App.vue
connects these. - The
src/model/LoanCalc.ts
has a simple model with classes for each action above.
The starting point in the input parameters LoanParams
. These are
instantiated in App.vue
’s data()
method. The use of data()
will
allow Vue to wrap the class with a reactive proxy.
The reactivity proxy will :
- Ensure anything derived from
LoanParams
(e.g.LoanResults
) is also wrapped in a reactive proxy. - Build up a graph of dependencies and distribute events.
- When
data()
is called the reactivity is linked to theInputParams.vue
. - When
results()
is called a the reactivity is linked to theOutputTable.vue
.
<script lang="ts">
import { Vue } from 'vue-class-component';
import {LoanCalc, LoanParams, LoanResults} from "./model/LoanCalc"
export default class App extends Vue {
loan_calc!: LoanCalc;
loan_params!: LoanParams;
data(): Record<string, unknown> {
var params = new LoanParams(500000, 25, 3.0);
var loan_calc = new LoanCalc(params);
return {
'loan_calc' : loan_calc,
'loan_params' : params,
}
}
results(): LoanResults {
return this.loan_calc.calc();
}
}
</script>
The template can simply access the members of the component class and pass the reactive versions to the component.
<template>
<InputParam :params=loan_params msg="Input Parameters" />
<OutputTable :results=results() msg="Calculated Results" />
</template>
Importing JavaScript plugins
Add a 3rd party package.
e.g Add vue3-money.
Update package.json
:
"dependencies": {
"v-money3": "^3.19.1",
...
}
Need to add a typings
directory for local type definitions. See adding-custom-type-definitions-to-a-third-party-library.
Update to tsconfig.json
"typeRoots":
[ "./typings", "./node_modules/@types"],
"include": [
"typings/**/index.d.ts",
...
]
The index.d.ts
file in it’s simplest form imports everything.
Add typings/v-money3/index.d.ts
.
declare module 'v-money3'
Or it might import/export explicit symbols.
{
import money, { Money3Component, Money3Directive } from 'v-money3';
export default money;
export { Money3Component, Money3Directive };
}
Then update main.ts
to enable globally.
import money from 'v-money3'
...
app.use(money)
Formatting Text
Filters are depreciated in vue3. Instead normal functions can be exported.
Methods of the component class can be called from the template.
<template>
...
<tr>
<th>Total Payments</th>
<td>\{\{format(results.total_payment)\}\}
</td></tr>
<tr><th>Monthly Payment</th>
<td>\{\{alias_money_format(results.monthly_payment)\}\}</td>
</tr>
...
</template>
The methods are defined in the default exported class:
import {format as money_format} from 'v-money3';
export default class OutputTable extends Vue {
...
alias_money_format = money_format;
format(v: number) : string {
return "$" + money_format(v);
}
}
Verbose logging
By default console.log()
does not go to the console.
export ELECTRON_ENABLE_LOGGING=1
npm run serve
Not Sure if These Are Needed?
Adding the typescript-eslint
plugin.
This is not used in the example for this article, but has helped when using vscode.
plugins: ['@typescript-eslint'],
// Prerequisite `eslint-plugin-vue`, being extended, sets
// root property `parser` to `'vue-eslint-parser'`, which, for code parsing,
// in turn delegates to the parser, specified in `parserOptions.parser`:
/ /https://github.com/vuejs/eslint-plugin-vue#what-is-the-use-the-latest-vue-eslint-parser-error
parserOptions: {
ecmaVersion: 2020,
parser: require.resolve('@typescript-eslint/parser'),
extraFileExtensions: ['.vue'],
ecmaFeatures: {
jsx: true
}
}
extends: [
'plugin:@typescript-eslint/eslint-recommended',
}
Update Rules
Add some exceptions to the linter. Better to fix the errors, but here for later reference.
overrides: [{
files: ['*.ts', '*.tsx'],
rules: {
// The core 'no-unused-vars' rules (in the eslint:recommeded ruleset)
// does not work with type definitions
'no-unused-vars': 'off',
'no-empty-function': 'off',
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
}
}]
References
Vue3
- https://v3.vuejs.org/guide/typescript-support.html
Vue3 + Typescript:
- https://davidjamesherzog.github.io/2020/12/30/vue-typescript-decorators/
- https://github.com/Yama-Tomo/vue-vuex-typescript-sample
- https://github.com/vuejs/vue-test-utils-typescript-example
- https://www.detroitlabs.com/blog/2018/02/28/adding-custom-type-definitions-to-a-third-party-library/
- https://github.com/vuejs/eslint-plugin-vue#what-is-the-use-the-latest-vue-eslint-parser-error
- https://www.thisdot.co/blog/your-first-vue-3-app-using-typescript
Subscribe via RSS