What Causes This Warning?
This warning occurs when you try to modify a value defined in <script setup> from the Options API (like methods, computed, or watchers defined in a separate <script> block).
The Problem
<script setup>
const count = ref(0)
</script>
<script>
export default {
methods: {
// ❌ Trying to mutate script setup binding
increment() {
this.count++ // Warning!
}
}
}
</script>
The Fix
Keep All Logic in Script Setup
<script setup>
import { ref } from 'vue'
const count = ref(0)
// ✅ Define methods in script setup
function increment() {
count.value++
}
</script>
Use Options API Entirely
<script>
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
function increment() {
count.value++
}
return { count, increment }
}
}
</script>
Common Scenarios
Mixing for Legacy Reasons
<!-- ❌ Mixing APIs incorrectly -->
<script setup>
const formData = reactive({
name: '',
email: ''
})
</script>
<script>
export default {
methods: {
submitForm() {
// Can't access formData here properly
this.formData.name = 'test' // Warning!
}
}
}
</script>
<!-- ✅ Keep everything in script setup -->
<script setup>
import { reactive } from 'vue'
const formData = reactive({
name: '',
email: ''
})
async function submitForm() {
await api.submit(formData)
}
</script>
Using with defineOptions
<script setup>
import { ref } from 'vue'
// ✅ Use defineOptions for component options
defineOptions({
name: 'MyComponent',
inheritAttrs: false
})
const count = ref(0)
const increment = () => count.value++
</script>
Lifecycle Hooks
<!-- ❌ Don't mix lifecycle approaches -->
<script setup>
const data = ref(null)
</script>
<script>
export default {
mounted() {
this.data = 'loaded' // Warning!
}
}
</script>
<!-- ✅ Use onMounted in script setup -->
<script setup>
import { ref, onMounted } from 'vue'
const data = ref(null)
onMounted(() => {
data.value = 'loaded'
})
</script>
Computed Properties
<!-- ❌ Computed in Options API accessing script setup -->
<script setup>
const firstName = ref('John')
const lastName = ref('Doe')
</script>
<script>
export default {
computed: {
fullName() {
return this.firstName + ' ' + this.lastName // Issues!
}
}
}
</script>
<!-- ✅ Use computed in script setup -->
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed(() => `${firstName.value} ${lastName.value}`)
</script>
When You Need Both Script Blocks
The only valid reason to have both is for component options not available in <script setup>:
<script>
// For options not available in script setup
export default {
name: 'MyComponent',
inheritAttrs: false,
customOption: 'value'
}
</script>
<script setup>
// All reactive code here
const count = ref(0)
</script>
But prefer defineOptions (Vue 3.3+):
<script setup>
defineOptions({
name: 'MyComponent',
inheritAttrs: false
})
const count = ref(0)
</script>
Quick Checklist
- Don’t mix
<script setup>bindings with Options API methods - Keep all reactive logic in
<script setup> - Use
defineOptionsfor component options (Vue 3.3+) - Use Composition API lifecycle hooks (
onMounted, etc.) - If you need both scripts, only use second script for options