Vue3 脚本革命:<script setup> 让你的代码简洁到飞起!

你是不是还在为 Vue 组件的那些繁琐语法头疼?每次写个组件都要 export default、methods、data 来回折腾,感觉代码总是啰里啰嗦的?

告诉你个好消息,Vue3 的 <script setup> 语法糖简直就是为我们这些追求效率的开发者量身定做的!它能让你用更少的代码做更多的事,而且写起来那叫一个爽快。

今天我就带你彻底搞懂这个功能,从基本用法到高级技巧,保证让你看完就能用上,代码量直接减半!

什么是 <script setup>

简单来说,<script setup> 是 Vue3 引入的一种编译时语法糖,它能让单文件组件的脚本部分变得更加简洁明了。

以前我们写个组件得这样:

vue 复制代码
<script>
export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    increment() {
      this.count++
    }
  }
}
</script>

现在用 <script setup> 就简单多了:

vue 复制代码
<script setup>
import { ref } from 'vue'

const count = ref(0)

const increment = () => {
  count.value++
}
</script>

看出来了吧?代码一下子清爽了很多!不再需要那些模板化的结构,直接写逻辑就行。

为什么要用 <script setup>

你可能要问,我已经习惯原来的写法了,为什么要换呢?这里给你几个无法拒绝的理由:

代码量大幅减少,不用再写那些重复的样板代码。组件间的数据传递和事件处理变得更加直观。更好的 TypeScript 支持,类型推断更加准确。编译时优化,性能更优秀。

最重要的是,写起来真的很快乐!你再也不用在 methods、data、computed 之间来回切换了。

基础用法速成

让我们从最简单的开始,一步步掌握 <script setup> 的核心用法。

定义响应式数据,在 <script setup> 里,我们直接用 ref 和 reactive:

vue 复制代码
<script setup>
import { ref, reactive } from 'vue'

// 基本类型用 ref
const name = ref('张三')
const age = ref(25)

// 对象类型可以用 reactive
const userInfo = reactive({
  job: '前端开发',
  salary: 20000
})

// 修改数据也很简单
const updateInfo = () => {
  name.value = '李四'  // ref 需要通过 .value 访问
  userInfo.salary = 25000 // reactive 直接修改属性
}
</script>

定义方法就更简单了,直接写函数就行:

vue 复制代码
<script setup>
const sayHello = () => {
  console.log('你好,Vue3!')
}

const calculate = (a, b) => {
  return a + b
}
</script>

计算属性也用起来:

vue 复制代码
<script setup>
import { ref, computed } from 'vue'

const price = ref(100)
const quantity = ref(2)

// 计算总价
const total = computed(() => {
  return price.value * quantity.value
})

// 复杂的计算属性
const discountTotal = computed(() => {
  const totalVal = price.value * quantity.value
  return totalVal > 200 ? totalVal * 0.9 : totalVal
})
</script>

组件通信变得超简单

<script setup> 里,组件间的通信也变得特别直观。

定义 props 可以用 defineProps:

vue 复制代码
<script setup>
// 基础用法
defineProps(['title', 'content'])

// 带类型检查的用法
defineProps({
  title: String,
  content: {
    type: String,
    required: true
  },
  count: {
    type: Number,
    default: 0
  }
})

// 用 TypeScript 的话更简单
defineProps<{
  title?: string
  content: string
  count?: number
}>()
</script>

定义 emits 用 defineEmits:

vue 复制代码
<script setup>
// 基础用法
const emit = defineEmits(['update', 'delete'])

// 带验证的用法
const emit = defineEmits({
  update: (id) => {
    if (id) return true
    console.warn('需要提供 id')
    return false
  }
})

// 实际使用
const handleUpdate = () => {
  emit('update', 123)
}

const handleDelete = () => {
  emit('delete', 456)
}
</script>

高级技巧让你更专业

掌握了基础用法,再来看看一些提升效率的高级技巧。

使用组合式函数,这是 Vue3 的精髓之一:

vue 复制代码
<script setup>
import { ref, onMounted } from 'vue'

// 封装一个获取数据的组合式函数
const useFetch = (url) => {
  const data = ref(null)
  const loading = ref(false)
  const error = ref(null)

  const fetchData = async () => {
    loading.value = true
    try {
      const response = await fetch(url)
      data.value = await response.json()
    } catch (err) {
      error.value = err
    } finally {
      loading.value = false
    }
  }

  onMounted(fetchData)

  return {
    data,
    loading,
    error,
    refetch: fetchData
  }
}

// 在组件中使用
const { data: userData, loading, error } = useFetch('/api/user')
</script>

使用 defineExpose 暴露组件方法:

vue 复制代码
<script setup>
import { ref } from 'vue'

const count = ref(0)

const increment = () => {
  count.value++
}

const reset = () => {
  count.value = 0
}

// 暴露给父组件的方法
defineExpose({
  increment,
  reset
})
</script>

使用 useSlots 和 useAttrs:

vue 复制代码
<script setup>
import { useSlots, useAttrs } from 'vue'

const slots = useSlots()
const attrs = useAttrs()

// 可以动态处理插槽和属性
const hasHeaderSlot = !!slots.header
const extraClass = attrs.class || ''
</script>

实战案例:打造一个任务管理器

光说不练假把式,我们来写个完整的任务管理组件:

vue 复制代码
<template>
  <div class="task-manager">
    <div class="add-task">
      <input 
        v-model="newTask" 
        @keyup.enter="addTask"
        placeholder="输入新任务..."
        class="task-input"
      >
      <button @click="addTask" class="add-btn">添加</button>
    </div>
    
    <div class="task-list">
      <div 
        v-for="task in filteredTasks" 
        :key="task.id"
        :class="['task-item', { completed: task.completed }]"
      >
        <input 
          type="checkbox" 
          v-model="task.completed"
          class="task-checkbox"
        >
        <span class="task-text">{{ task.text }}</span>
        <button @click="removeTask(task.id)" class="remove-btn">删除</button>
      </div>
    </div>
    
    <div class="task-stats">
      <span>总计: {{ totalTasks }} 个任务</span>
      <span>已完成: {{ completedTasks }} 个</span>
      <button @click="filter = 'all'">全部</button>
      <button @click="filter = 'active'">未完成</button>
      <button @click="filter = 'completed'">已完成</button>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, onMounted } from 'vue'

// 响应式数据
const newTask = ref('')
const tasks = ref([])
const filter = ref('all')

// 添加新任务
const addTask = () => {
  if (newTask.value.trim()) {
    tasks.value.push({
      id: Date.now(),
      text: newTask.value.trim(),
      completed: false
    })
    newTask.value = ''
    saveToLocalStorage()
  }
}

// 删除任务
const removeTask = (id) => {
  tasks.value = tasks.value.filter(task => task.id !== id)
  saveToLocalStorage()
}

// 计算属性
const totalTasks = computed(() => tasks.value.length)
const completedTasks = computed(() => 
  tasks.value.filter(task => task.completed).length
)

const filteredTasks = computed(() => {
  switch (filter.value) {
    case 'active':
      return tasks.value.filter(task => !task.completed)
    case 'completed':
      return tasks.value.filter(task => task.completed)
    default:
      return tasks.value
  }
})

// 本地存储
const saveToLocalStorage = () => {
  localStorage.setItem('vue-tasks', JSON.stringify(tasks.value))
}

const loadFromLocalStorage = () => {
  const saved = localStorage.getItem('vue-tasks')
  if (saved) {
    tasks.value = JSON.parse(saved)
  }
}

// 生命周期
onMounted(() => {
  loadFromLocalStorage()
})
</script>

<style scoped>
.task-manager {
  max-width: 500px;
  margin: 0 auto;
  padding: 20px;
}

.task-input {
  padding: 8px;
  border: 1px solid #ddd;
  border-radius: 4px;
  margin-right: 8px;
}

.add-btn {
  padding: 8px 16px;
  background: #4CAF50;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.task-item {
  display: flex;
  align-items: center;
  padding: 8px;
  border-bottom: 1px solid #eee;
}

.task-item.completed .task-text {
  text-decoration: line-through;
  color: #888;
}

.task-checkbox {
  margin-right: 8px;
}

.task-text {
  flex: 1;
}

.remove-btn {
  padding: 4px 8px;
  background: #ff4757;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.task-stats {
  margin-top: 16px;
  display: flex;
  gap: 12px;
  align-items: center;
}
</style>

这个例子展示了 <script setup> 在实际项目中的强大能力,代码结构清晰,逻辑组织得当。

常见问题解答

Q: 从 Options API 迁移到 <script setup> 难吗? A: 其实不难!大部分概念都是相通的,只是写法更简洁了。建议先从简单的组件开始尝试。

Q: <script setup> 对 TypeScript 支持怎么样? A: 支持非常好!类型推断更加准确,写起来特别舒服。

Q: 还能和普通的 <script> 混用吗? A: 可以的,但通常不建议。除非你有特殊的模块导出需求。

Q: 现有的 Vue2 项目能直接用吗? A: 需要升级到 Vue3,但升级过程比想象中简单,官方提供了详细的迁移指南。

最佳实践推荐

根据我的经验,这些实践能让你的代码质量更高:

按逻辑组织代码,而不是按功能类型。把相关的数据、方法、计算属性放在一起。合理使用组合式函数抽离可复用逻辑。使用 TypeScript 获得更好的开发体验。保持组件的单一职责,不要写太复杂的组件。

相关推荐
U***e631 小时前
JavaScript在Node.js中的Webpack
javascript·webpack·node.js
IT_陈寒1 小时前
Python 3.12新特性解析:10个让你代码效率提升30%的实用技巧
前端·人工智能·后端
故厶1 小时前
webpack实战
前端·javascript·webpack
_果果然1 小时前
你真的懂递归吗?没那么复杂,但也没那么简单
前端·javascript
菜泡泡@2 小时前
仓库地图vue-grid-layout
前端·javascript·vue.js
u***u6854 小时前
React环境
前端·react.js·前端框架
X***E4634 小时前
前端数据分析应用
前端·数据挖掘·数据分析
4***14905 小时前
React社区
前端·react.js·前端框架
LFly_ice5 小时前
学习React-24-路由传参
前端·学习·react.js