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 能够自动处理数据与视图的同步,大大简化了前端开发的复杂度。


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

相关推荐
吃吃喝喝小朋友1 分钟前
JavaScript事件
开发语言·前端·javascript
WYiQIU17 分钟前
普及一下字节前端岗需要达到的强度......
前端·javascript·vue.js·面试·职场和发展
Leweslyh32 分钟前
【实战】如何在家定位国际空间站 (ISS)? —— 坐标转换的魔法 (例题 5.9)
开发语言·javascript·ecmascript
帆张芳显34 分钟前
智表zcell产品V3.5 版发布,新增行列选中操作等功能
前端·javascript·excel·插件·canva可画
苦藤新鸡1 小时前
27.合并有序链表,串葫芦
前端·javascript·链表
_OP_CHEN1 小时前
【前端开发之HTML】(四)HTML 标签进阶:表格、表单、布局全掌握,从新手到实战高手!
前端·javascript·css·html·html5·网页开发·html标签
谢尔登1 小时前
Vue3底层原理——keep-alive
javascript·vue.js·ecmascript
Deca~2 小时前
VueVirtualLazyTree-支持懒加载的虚拟树
前端·javascript·vue.js
2501_944526422 小时前
Flutter for OpenHarmony 万能游戏库App实战 - 主题切换实现
android·开发语言·javascript·python·flutter·游戏·django
爱上妖精的尾巴2 小时前
7-11 WPS JS宏 对象的属性值为函数的写法与用法
前端·javascript·wps·js宏·jsa