阅读视图

发现新文章,点击刷新页面。

为什么你的 PR 总是多出一堆奇怪的 commit?90% 的人都踩过这个 Git 坑

为什么你的 PR 总是多出一堆奇怪的 commit?90% 的人都踩过这个 Git 坑

在日常开发中,功能分支开发周期较长时,主干分支往往已经有了新的提交。这时需要将主干的最新代码同步到自己的功能分支,常见的方式有两种:mergerebase。本文通过实际场景对比两种方式的区别与使用方法。


背景:为什么需要同步主干?

假设你从主干 main 切出了功能分支 feature/my-work,开发过程中主干有了新的提交,此时你的分支历史如下:

main:    A ── B ── C(新提交)

feature: A ── B ── D(你的提交)

如果不同步主干,提 PR 时可能产生冲突,或者遗漏主干的重要修改。


git pull 是什么?

git pullgit fetch + 合并操作的快捷命令,默认使用 merge 方式:

git pull origin main
# 等价于
git fetch origin
git merge origin/main

也可以指定使用 rebase 方式:

git pull --rebase origin main
# 等价于
git fetch origin
git rebase origin/main

两者的区别与下文 merge / rebase 章节一致。如果希望 git pull 始终使用 rebase,可以设置全局配置:

git config --global pull.rebase true

💡 建议:对于个人功能分支,推荐全局开启 pull.rebase true,保持提交历史线性整洁。


方式一:merge

原理

将主干的最新提交合并进你的分支,并生成一个新的 MergeCommit 记录这次合并。

main:    A ── B ── C
                    \
feature: A ── B ── D ── M(MergeCommit)

操作步骤

第一步:拉取远程最新代码

git fetch origin

第二步:切换到自己的功能分支

git checkout feature/my-work

第三步:合并主干

git merge origin/main

处理冲突(如有)

# 手动解决冲突后
git add .
git merge --continue

# 放弃本次合并
git merge --abort

第四步:推送到远程

git push origin feature/my-work

提 PR 后的 commit 记录

D  你的提交
M  Merge branch 'origin/main' into feature/my-work

方式二:rebase(推荐)

原理

将你的提交「移植」到主干最新提交的后面,历史记录保持线性,不会产生多余的 MergeCommit

main:    A ── B ── C
                    \
feature: A ── B ── C ── D'(你的提交被接在 C 后面)

操作步骤

第一步:拉取远程最新代码

git fetch origin

第二步:切换到自己的功能分支

git checkout feature/my-work

第三步:变基到主干

git rebase origin/main

处理冲突(如有)

rebase 会逐个回放你的每一个 commit,每个 commit 都可能产生冲突,需要逐一处理:

# 手动解决冲突后
git add .
git rebase --continue

# 放弃本次 rebase
git rebase --abort

第四步:force push 到远程

由于 rebase 改写了本地提交历史,必须使用 force push 同步到远程:

git push origin feature/my-work --force-with-lease

⚠️ 为什么用 --force-with-lease 而不是 --force --force 会无条件覆盖远程分支,存在覆盖他人提交的风险。 --force-with-lease 更安全——如果远程分支有你本地没有的新提交,会拒绝推送并提示,避免意外覆盖他人代码。

提 PR 后的 commit 记录

D'  你的提交(仅此一条,干净清晰)

两种方式对比

merge rebase
历史记录 非线性,含 MergeCommit 线性,简洁清晰
PR commit 数量 自己的 commit + MergeCommit 只有自己的 commit
推送方式 正常 git push git push --force-with-lease
冲突处理次数 只处理一次 每个 commit 都可能处理一次
适合场景 多人协作的公共分支 个人功能分支同步主干

常见问题

Q:rebase 时提示 hint: use --reapply-cherry-picks to include skipped commits

这是正常现象。rebase 检测到你分支上的某个 commit 内容已经存在于目标分支(例如之前被 cherry-pick 过),会自动跳过该 commit,避免重复提交。

可以关闭这条提示:

git config --global advice.skippedCherryPicks false

Q:rebase 之后提 PR,为什么还是有重复的 commit?

rebase 只改写了本地历史,如果没有执行 force push,远程分支仍然是旧的历史。PR 提的是远程分支,所以重复的 commit 还是会被带进去。

务必在 rebase 完成后执行:

git push origin feature/my-work --force-with-lease

Q:git pull 默认是 merge,能改成 rebase 吗?

可以,设置全局配置:

# git pull 默认使用 rebase
git config --global pull.rebase true

设置后,git pull 等价于 git pull --rebase,不再产生多余的 MergeCommit。


总结

  • 个人功能分支同步主干,推荐使用 rebase,PR 历史干净,只包含自己真正新增的 commit。
  • 公共分支之间的合并,推荐使用 merge,保留完整历史,不需要 force push,更安全。
  • 无论哪种方式,同步前都应先执行 git fetch origin 获取远程最新状态。

Vue 3 Composition API 最佳实践:从项目实战中汲取的经验

Vue 3 Composition API 最佳实践:从项目实战中汲取的经验

前言

随着 Vue 3 的发布,Composition API(组合式 API)已成为现代 Vue 开发的核心特性。它提供了更好的代码组织、可复用性和类型推断支持。然而,在实际项目中,许多开发者仍面临如何高效使用 Composition API 的挑战。本文基于我在市场监督 Vue 项目中的实战经验,分享一些 Composition API 的最佳实践,帮助你避免常见坑点,提升代码质量。

目录

  • Composition API 基础回顾
  • 最佳实践 1:逻辑复用与 composables
  • 最佳实践 2:响应式数据管理
  • 最佳实践 3:生命周期与副作用处理
  • 最佳实践 4:类型安全与 TypeScript 集成
  • 性能优化技巧
  • 总结

Composition API 基础回顾

Composition API 允许我们将组件的逻辑按功能分组,而不是按选项分组。核心函数包括 refreactivecomputedwatch 等。与 Options API 相比,它更灵活,适合大型应用。

// 基础示例
import { ref, computed } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const doubleCount = computed(() => count.value * 2)
    
    return { count, doubleCount }
  }
}

最佳实践 1:逻辑复用与 composables

在项目中,我们经常需要复用逻辑,如数据获取、表单验证。Composition API 通过 composables(组合函数)实现这一点。

实践建议:

  • 将可复用逻辑提取到独立的 composables 文件中。
  • 使用 use 前缀命名,如 useFetchData
  • 避免在 composables 中直接操作 DOM,确保纯逻辑。
// composables/useFetchData.js
import { ref, onMounted } from 'vue'
import axios from 'axios'

export function useFetchData(url) {
  const data = ref(null)
  const loading = ref(false)
  const error = ref(null)

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

  onMounted(fetchData)

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

在组件中使用:

// components/DataList.vue
import { useFetchData } from '@/composables/useFetchData'

export default {
  setup() {
    const { data, loading, error } = useFetchData('/api/data')
    return { data, loading, error }
  }
}

经验分享: 在市场监督项目中,我们将 API 调用逻辑抽象为 composables,大大减少了重复代码。记得处理错误边界,避免 composables 耦合过多。

最佳实践 2:响应式数据管理

响应式是 Vue 的核心。Composition API 提供了 refreactive,选择取决于数据结构。

实践建议:

  • 对于基本类型使用 ref,对象使用 reactive
  • 避免深层嵌套响应式对象,使用 shallowRefshallowReactive 优化性能。
  • 使用 toRefs 将 reactive 对象解构为 ref,避免丢失响应性。
import { reactive, toRefs } from 'vue'

export default {
  setup() {
    const state = reactive({
      user: { name: 'John', age: 30 },
      settings: { theme: 'dark' }
    })

    // 正确解构
    return { ...toRefs(state) }
  }
}

坑点提醒: 直接解构 reactive 对象会丢失响应性。项目中曾因忘记 toRefs 导致数据不更新,调试了半天。

最佳实践 3:生命周期与副作用处理

Composition API 使用 onMountedonUnmounted 等钩子管理生命周期。

实践建议:

  • 将副作用逻辑(如定时器、事件监听)封装在 composables 中。
  • 使用 onBeforeUnmount 清理资源,防止内存泄漏。
  • 对于异步操作,使用 watchEffectwatch 监听依赖。
import { ref, onMounted, onUnmounted } from 'vue'

export function useInterval(callback, delay) {
  const intervalId = ref(null)

  onMounted(() => {
    intervalId.value = setInterval(callback, delay)
  })

  onUnmounted(() => {
    if (intervalId.value) {
      clearInterval(intervalId.value)
    }
  })

  return { intervalId }
}

经验分享: 在实时监控页面,我们用这个 composable 管理数据轮询。记得在组件销毁时清理,避免后台运行。

最佳实践 4:类型安全与 TypeScript 集成

TypeScript 与 Composition API 完美配合,提供更好的开发体验。

实践建议:

  • 为 composables 定义接口。
  • 使用 Ref<T>ComputedRef<T> 类型。
  • 利用 Vue 3 的类型推断,减少显式类型声明。
import { ref, computed, type Ref } from 'vue'

interface User {
  id: number
  name: string
}

export function useUser(): { user: Ref<User | null>, isLoggedIn: ComputedRef<boolean> } {
  const user = ref<User | null>(null)
  const isLoggedIn = computed(() => !!user.value)

  return { user, isLoggedIn }
}

经验分享: 项目中引入 TypeScript 后,IDE 提示更准确,减少了运行时错误。建议从核心 composables 开始逐步迁移。

性能优化技巧

  • 使用 shallowRefshallowReactive:对于大型对象,避免深层响应式。
  • 合理使用 computed:缓存计算结果,避免重复计算。
  • 拆分大型组件:将逻辑拆分为多个 composables,减少单个 setup 函数的复杂度。
  • 使用 nextTick:在 DOM 更新后执行逻辑。
import { nextTick } from 'vue'

// 示例:等待 DOM 更新后聚焦输入框
await nextTick()
inputRef.value.focus()

总结

Composition API 让 Vue 开发更现代化,但需要良好的架构思维。通过 composables 复用逻辑、正确管理响应式和生命周期,我们可以写出更可维护的代码。在市场监督项目中,这些实践帮助我们从 2.x 顺利迁移到 3.x,提升了开发效率。

希望这篇文章对你有帮助!如果你有其他 Vue 3 经验,欢迎在评论区分享。代码示例可在 GitHub 上找到完整项目。

❌