Im a capable react developer, but have inherited a vue.js project from another developer and have maintained it for several years now, unfortunately I haven't gone through much personal effort to learn vue as i should.
I have a strange error being thrown from using lodash, I believe it doesnt like my _.debounce
call
Component:
<script>
import _ from 'lodash'
import CostCodeField from '@/components/workdays/CostCodeField'
// ...
</script>
<template lang='html'>
<!-- relevant code snippet -->
<!-- ... -->
<b-table :data="workday.charges" :striped="true" :mobile-cards="false" :row->
<b-table-column label="Cost Code" width="260">
<b-field expanded="expanded">
<cost-code-field
:value="props.row.cost_code.number" :disabled="timecard.is_submitted || locked || isLoading(props)"
:job="props.row.job"
@input="set($event, props.index, 'cost_code')"
@change="_.debounce(submit(props.row, props.index), 100)"
></cost-code-field>
<p >
<a
@click="triggerCostCode(props.row, props.index)" :disabled="props.row.job.jd_job_number_id === undefined || timecard.is_submitted || isLoading(props)"
>
<b-icon icon="magnify"></b-icon>
</a>
</p>
</b-field>
</b-table-column>
<!-- ... -->
</b-table>
</template>
console.error
[Vue warn]: Property or method "_" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.
found in
---> <ChargesTable> at src/components/workdays/ChargesTable.vue
<BTabItem>
<BTabs>
<WorkdayListItem> at src/components/workdays/WorkdayListItem.vue
<Timecard> at src/components/timecards/Timecard.vue
<App> at src/App.vue
<Root> vue.esm.js:628
VueJS 3
change ChargesTable.vue:127
VueJS 4
setCode CostCodeField.vue:108
VueJS 12
mutations timecards.js:322
mutations timecards.js:319
wrappedMutationHandler vuex.esm.js:844
commitIterator vuex.esm.js:466
commit vuex.esm.js:465
_withCommit vuex.esm.js:624
commit vuex.esm.js:464
boundCommit vuex.esm.js:409
submit CostCodeLookup.vue:134
submit CostCodeLookup.vue:16
VueJS 33
[Vue warn]: Error in v-on handler: "TypeError: _vm._ is undefined"
found in
---> <CostCodeField> at src/components/workdays/CostCodeField.vue
<BField>
<BTableColumn>
<BTable>
<ChargesTable> at src/components/workdays/ChargesTable.vue
<BTabItem>
<BTabs>
<WorkdayListItem> at src/components/workdays/WorkdayListItem.vue
<Timecard> at src/components/timecards/Timecard.vue
<App> at src/App.vue
<Root> vue.esm.js:628
TypeError: _vm._ is undefined
change ChargesTable.vue:127
VueJS 4
setCode CostCodeField.vue:108
VueJS 12
mutations timecards.js:322
mutations timecards.js:319
wrappedMutationHandler vuex.esm.js:844
commitIterator vuex.esm.js:466
commit vuex.esm.js:465
_withCommit vuex.esm.js:624
commit vuex.esm.js:464
boundCommit vuex.esm.js:409
submit CostCodeLookup.vue:134
submit CostCodeLookup.vue:16
VueJS 33
CodePudding user response:
Any var referenced in template is prefixed with this
under the hood. Which means you can't reference global vars in the template, unless they're expressly declared in its context.
Simply put, the template context is an object. If you don't define a particular key and don't assign it a particular value, it's going to be undefined
.
Probably the most concise way of exposing globals to the template is using computed
:
// ...
computed: {
_: () => _
}
// ...
In Composition API, the simplest way to expose globals is to add them in the object returned by setup
:
export default {
// ...
setup() {
//...
return {
_,
//...
}
}
}
CodePudding user response:
Accessing _
in template
Assuming you're using the Options API, importing _
does not automatically make it available to the template (as @tao pointed out in his answer). The template can only access fields exposed via the component options (e.g., data
, props
, methods
, computed
, etc.) in addition to a few allow-listed globals (Vue 2 allowed globals, Vue 3 allowed globals).
Using _.debounce
_.debounce
's first argument is a function reference:
_.debounce(submit(props.row, props.index), 100) // ❌ calls `submit()`, and passes the result to _.debounce()
To create a debounced submit
, pass the submit
-reference as an argument to _.debounce
like this:
_.debounce(submit, 100)
You technically could invoke the debounced function immediately:
_.debounce(submit, 100)(props.row, props.index)
...but don't do that in the template (see reason below).
Using a debounced event handler for v-on
When the value of the v-on
directive (@
for shorthand) is an expression (as in your case), the template compiler automatically wraps the expression in a function, so this:
@change="_.debounce(submit, 100)"
...essentially becomes:
@change="($event) => _.debounce(submit, 100)"
...which would have no effect, since debounce
doesn't invoke the wrapped function itself.
You might be tempted to call the function immmediately in:
@change="_.debounce(submit, 100)(props.row, props.index)"
...but that creates a new debounced function on every event, which defeats the debouncing.
Solution
Create a debounced function in the <script>
part of the SFC that could then be used as the v-on
value:
Options API:
<script>
import { debounce } from 'lodash'
export default {
created() {
this.debouncedSubmit = debounce(this.submit, 100)
},
methods: {
submit(row, index) {/*...*/}
}
}
</script>
Composition API in setup()
option:
<script>
import { debounce } from 'lodash'
export default {
setup() {
const submit = (row, index) => {/*...*/}
return {
debouncedSubmit: debounce(submit, 100),
}
}
}
</script>
Composition API in <script setup>
:
<script setup>
import { debounce } from 'lodash'
const submit = (row, index) => {/*...*/}
const debouncedSubmit = debounce(submit, 100)
</script>
Template:
<cost-code-field @change="debouncedSubmit(props.row, props.index)" />