Vue中选项式和组合式API的学习

Vue中选项式和组合式API的学习

Vue 的组件可以按两种不同的风格书写 :组合式 API和选项式 API 。

组合式 API (Composition API)

通过组合式 API,可以使用导入的 API 函数来描述组件逻辑。在单文件组件中,组合式 API 通常会与

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

// 响应式状态
const count = ref(0)

// 用来修改状态、触发更新的函数
function increment() {
  count.value++
}

// 生命周期钩子
onMounted(() => {
  console.log(`The initial count is ${count.value}.`)
})
</script>

<template>
  <button @click="increment">Count is: {{ count }}</button>
</template>

选项式 API (Options API)

使用选项式 API,可以用包含多个选项的对象来描述组件的逻辑,例如 datamethodsmounted。选项所定义的属性都会暴露在函数内部的 this 上,它会指向当前的组件实例。

javascript 复制代码
<script>
    export default {
	  name: '',  //`name` 属性标识组件名称
      components: { //使用 `components` 选项注册子组件 
       }

       // data() 返回的属性将会成为响应式的状态
       // 并且暴露在 `this` 上
      data() {
        return {
          // 所有响应式数据在这里定义
          count: 0
        };
      },

      // methods 是一些用来更改状态与触发更新的函数
      // 它们可以在模板中作为事件处理器绑定
      methods: {
        // 所有组件方法在这里定义
      },
       
      // 生命周期钩子会在组件生命周期的各个不同阶段被调用
      created() { /* 实例创建后 */ },
      mounted() { /* DOM挂载后 */ },
      updated() { /* 数据更新后 */ },
      destroyed() { /* 实例销毁前 */ },

      // 组件注册
      components: {
        // 子组件注册
      }
    }
<script>

export default 的作用

export default 是 ES6 模块系统的语法,用于导出模块的默认内容。在 Vue 中,它用于导出一个 Vue 组件配置对象

javascript 复制代码
export default {
  // 组件配置选项
}

当在另一个文件中使用 import 时,导入的就是这个默认导出的对象。

data

  • 作用:定义组件的响应式数据
  • 必须是函数:返回一个包含数据的对象
  • 为什么是函数:确保每个组件实例都有独立的数据副本,避免数据共享问题
javascript 复制代码
data() {
  return {
    newUser: { name: '', email: '' },  // 创建新用户时的表单数据
    users: [],                         // 存储从API获取的用户列表
    editingUser: null                  // 当前正在编辑的用户
  };
}

created

  • 生命周期钩子:在组件实例创建完成后立即调用
  • 执行时机:数据观测已建立,但DOM还未挂载
  • 常见用途:初始化数据、调用API
javascript 复制代码
created() {
  this.fetchUsers(); // 组件创建时自动获取用户列表
}

methods

  • 作用:定义组件的方法
  • 特点:这些方法可以直接在模板中调用
javascript 复制代码
methods: {
  // 异步获取用户列表
  async fetchUsers() {
    try {
      const response = await axios.get('https://api.example.com/users');
      this.users = response.data; // 更新响应式数据
    } catch (error) {
      console.error('获取用户列表失败:', error);
    }
  }
}

两种风格对比

组合式 API 的核心思想是直接在函数作用域内定义响应式状态变量,并将从多个函数中得到的状态组合起来处理复杂问题。这种形式更加自由,也需要对 Vue 的响应式系统有更深的理解才能高效使用。相应的,它的灵活性也使得组织和重用逻辑的模式变得更加强大。

选项式 API 是在组合式 API 的基础上实现的。选项式 API 以"组件实例"的概念为中心 (即上述例子中的 this),基于面向对象概念实现。同时,它将响应性相关的细节抽象出来,并强制按照选项来组织代码。

以下是官方给出的建议:

  • 在学习的过程中,推荐采用更易于理解的风格。大部分的核心概念在这两种风格之间都是通用的。熟悉了一种风格以后,也能够很快地理解另一种风格。
  • 在生产项目中:
    • 当不需要使用构建工具,或者打算主要在低复杂度的场景中使用 Vue,例如渐进增强的应用场景,推荐采用选项式 API。
    • 当你打算用 Vue 构建完整的单页应用,推荐采用组合式 API + 单文件组件。

在其他 Vue 组件中导入和使用 export default 定义的组件 有几种方式

基本导入和使用

导入组件

vue 复制代码
<template>
  <div>
    <!-- 使用导入的组件 -->
    <ArticleList />
  </div>
</template>

// 选项式
<script>
// 导入组件
import ArticleList from '@/components/ArticleList.vue'

export default {
  name: 'ParentComponent',
  components: {
    // 注册导入的组件
    ArticleList
  }
}
</script>

调用子组件的方法

方法一:使用 ref 引用

vue 复制代码
<template>
  <div>
    <button @click="refreshArticles">刷新文章</button>
    <button @click="createNewArticle">创建文章</button>
    
    <!-- 给组件添加 ref 属性 -->
    <ArticleList ref="articleListRef" />
  </div>
</template>

<script>
import ArticleList from '@/components/ArticleList.vue'

export default {
  name: 'ParentComponent',
  components: {
    ArticleList
  },
  methods: {
    refreshArticles() {
      // 调用子组件的 fetchArticles 方法
      this.$refs.articleListRef.fetchArticles()
    },
    
    createNewArticle() {
      // 调用子组件的 handleCreateArticle 方法
      this.$refs.articleListRef.handleCreateArticle()
    }
  }
}
</script>

方法二:通过 props 传递回调函数

父组件:

vue 复制代码
<template>
  <div>
    <ArticleList 
      :onRefresh="handleRefresh"
      :onCreate="handleCreate"
    />
  </div>
</template>

<script>
import ArticleList from '@/components/ArticleList.vue'

export default {
  components: {
    ArticleList
  },
  methods: {
    handleRefresh() {
      console.log('父组件处理刷新逻辑')
    },
    handleCreate(articleData) {
      console.log('父组件处理创建逻辑', articleData)
    }
  }
}
</script>

子组件 (ArticleList.vue):

vue 复制代码
<script>
export default {
  name: 'ArticleList',
  props: {
    onRefresh: Function,
    onCreate: Function
  },
  methods: {
    fetchArticles() {
      // 原有的获取文章逻辑
      fetch('http://localhost:8000/posts')
        .then(r => r.json())
        .then(data => {
          this.articles = data
          // 如果有传递回调函数,则调用
          if (this.onRefresh) {
            this.onRefresh(data)
          }
        })
    },
    
    handleCreateArticle() {
      // 原有的创建逻辑...
      
      // 如果有传递回调函数,则调用
      if (this.onCreate) {
        this.onCreate(this.newArticle)
      }
    }
  }
}
</script>

完整的父子组件通信示例

父组件

vue 复制代码
<template>
  <div class="parent">
    <h1>博客管理</h1>
    
    <div class="controls">
      <button @click="triggerRefresh" :disabled="isLoading">
        {{ isLoading ? '加载中...' : '刷新文章' }}
      </button>
      <button @click="showCreateForm = true">新建文章</button>
    </div>

    <!-- 使用子组件 -->
    <ArticleList 
      ref="articleList"
      :externalCreateData="createFormData"
      @articles-loaded="onArticlesLoaded"
      @article-created="onArticleCreated"
    />

    <!-- 父组件的创建表单 -->
    <div v-if="showCreateForm" class="modal">
      <h3>创建新文章</h3>
      <input v-model="createFormData.title" placeholder="标题">
      <textarea v-model="createFormData.content" placeholder="内容"></textarea>
      <button @click="submitCreateForm">提交</button>
      <button @click="cancelCreate">取消</button>
    </div>
  </div>
</template>

<script>
import ArticleList from '@/components/ArticleList.vue'

export default {
  name: 'BlogManagement',
  components: {
    ArticleList
  },
  data() {
    return {
      isLoading: false,
      showCreateForm: false,
      createFormData: {
        title: '',
        content: ''
      }
    }
  },
  methods: {
    // 触发子组件的刷新方法
    triggerRefresh() {
      this.isLoading = true
      this.$refs.articleList.fetchArticles()
    },

    // 触发子组件的创建方法
    submitCreateForm() {
      this.$refs.articleList.handleCreateArticle(this.createFormData)
      this.showCreateForm = false
    },

    cancelCreate() {
      this.showCreateForm = false
      this.createFormData = { title: '', content: '' }
    },

    // 监听子组件的事件
    onArticlesLoaded(articles) {
      this.isLoading = false
      console.log('文章加载完成:', articles)
    },

    onArticleCreated(newArticle) {
      console.log('新文章创建:', newArticle)
      this.createFormData = { title: '', content: '' }
    }
  }
}
</script>

子组件 (ArticleList.vue)

vue 复制代码
<template>
  <div class="article-list">
    <div v-if="articles.length === 0" class="empty">暂无文章</div>
    
    <ArticleCard 
      v-for="article in articles" 
      :key="article.id" 
      :article="article" 
    />
  </div>
</template>

<script>
import ArticleCard from './ArticleCard.vue'

export default {
  name: 'ArticleList',
  components: {
    ArticleCard
  },
  props: {
    // 从父组件接收创建数据
    externalCreateData: {
      type: Object,
      default: () => ({ title: '', content: '' })
    }
  },
  data() {
    return {
      articles: [],
      newArticle: {
        title: '',
        content: '',
        author: '左越'
      }
    }
  },
  watch: {
    // 监听外部创建数据的变化
    externalCreateData: {
      handler(newData) {
        if (newData.title || newData.content) {
          this.newArticle = { ...newData, author: '左越' }
          this.handleCreateArticle()
        }
      },
      deep: true
    }
  },
  created() {
    this.fetchArticles()
  },
  methods: {
    async fetchArticles() {
      try {
        const response = await fetch('http://localhost:8000/posts')
        const data = await response.json()
        this.articles = data
        
        // 触发事件通知父组件
        this.$emit('articles-loaded', data)
      } catch (err) {
        console.log('error', err)
        this.$emit('articles-loaded', [])
      }
    },
    
    async handleCreateArticle(externalData = null) {
      const articleData = externalData || this.newArticle
      
      if (!articleData.title.trim() || !articleData.content.trim()) {
        alert('请填写所有字段')
        return
      }

      try {
        const response = await fetch('http://localhost:8000/posts', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json'
          },
          body: JSON.stringify(articleData)
        })
        
        const newArticle = await response.json()
        this.articles.unshift(newArticle)
        
        // 触发事件通知父组件
        this.$emit('article-created', newArticle)
        
        // 重置表单
        this.resetForm()
      } catch (err) {
        console.error('error: ', err)
      }
    },
    
    resetForm() {
      this.newArticle = {
        title: '',
        content: '',
        author: '左越'
      }
    }
  }
}
</script>

关键点总结:

  1. 导入组件 :使用 import ComponentName from 'path/to/component'
  2. 注册组件 :在 components 选项中注册
  3. 使用 ref :通过 this.$refs.refName.methodName() 调用子组件方法
  4. 事件通信 :子组件使用 this.$emit('event-name', data),父组件使用 @event-name="handler"
  5. Props 传递:父组件通过 props 向子组件传递数据和方法

选择哪种方式取决于具体需求:

  • 简单的调用使用 ref
  • 复杂的通信使用 propsevents
  • 状态管理考虑使用 Vuex(对于大型应用)

这种结构让 Vue 能够自动处理数据与视图的同步,大大简化了前端开发的复杂度。


愿你我都能在各自的领域里不断成长,勇敢追求梦想,同时也保持对世界的好奇与善意!

相关推荐
3秒一个大2 小时前
Vue 任务清单开发:数据驱动 vs 传统 DOM 操作
前端·javascript·vue.js
阿蒙Amon2 小时前
JavaScript学习笔记:2.基础语法与数据类型
javascript·笔记·学习
an86950012 小时前
vue自定义组件this.$emit(“refresh“);
前端·javascript·vue.js
Avicli2 小时前
Gemini3 生成的基于手势控制3D粒子圣诞树
前端·javascript·3d
o__A_A2 小时前
渲染可配置报告模板+自适应宽度(vue3)
前端·vue.js
鹏北海2 小时前
Vue 组件解耦实践:用回调函数模式替代枚举类型传递
前端·vue.js
San302 小时前
拒绝做 DOM 的“搬运工”:从 Vanilla JS 到 Vue 3 响应式思维的进化
javascript·vue.js·响应式编程
Beginner x_u2 小时前
从组件点击事件到业务统一入口:一次前端操作链的完整解耦实践
前端·javascript·vue·业务封装
L、2182 小时前
Flutter 与 OpenHarmony 深度融合实践:打造跨生态高性能应用(进阶篇)
javascript·flutter·华为·智能手机·harmonyos