vue3源码及API解析与vue2项目如何升级vue3(内附vue2源码解析和API教程)【第一篇】

vue3的新特性

  1. [组合式 API]
  2. Teleport
  3. 片段
  4. 触发组件选项
  5. createRenderer API 来自 @vue/runtime-core 创建自定义渲染器
  6. 单文件组件组合式 API 语法糖
  7. 单文件组件状态驱动的 CSS 变量
  8. 单文件组件现在可以包含全局规则或只针对插槽内容的规则

这一篇的主题是组合式API
一,什么是组合式API?

平时使用vue开发的小伙伴们不难发现,在我们封装组件的时候大部分使用组件的选项(data、computed、methods、watch)组织逻辑在大部分情况下都是有效。然而当我们组件变得更大时,逻辑关注点列表增加,组件中的代码量越来越大,导致维护起来变得很困难。
Vue 选项式 API: 按选项类型分组的代码
一个大型组件的示例,其中逻辑关注点是按颜色分组。
这种碎片化使得理解和维护复杂组件变得困难。选项的分离掩盖了潜在的逻辑问题。此外,在处理单个逻辑关注点时,我们必须不断地“跳转”相关代码的选项块。
如果我们能够将与同一个逻辑关注点相关的代码配置在一起会更好。而这正是组合式 API 使我们能够做到的。

于是就有了组合式API

既然我们知道了为什么,我们就可以知道怎么做。为了开始使用组合式 API,我们首先需要一个可以实际使用它的地方。在 Vue组件中,我们将此位置称为 setup
setup 在组件创建之前执行,一旦props被解析,就被充当成API的入口点
由于执行setup时组件实例未被创建,因此setup选项中没有this。这意味着除了props,我们无法访问组件中生命的任何属性–本地状态、计算属性【computed】、方法【methods】

setup的使用:

// src/components/UserRepositories.vue

export default {
  components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
  props: {
    user: { type: String }
  },
  setup(props) {
    console.log(props) // { user: '' }

    return {} // 这里返回的任何内容都可以用于组件的其余部分
  }
  // 组件的“其余部分”
}

代码实战:
1,从假定的外部 API 获取该用户名的仓库,并在用户更改时刷新它
先来看第一段代码⬇️因为我们的 repositories 变量是非响应式的,所以我们要进行修改

// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'

// 在我们的组件内
setup (props) {
  let repositories = []
  const getUserRepositories = async () => {
    repositories = await fetchUserRepositories(props.user)
  }

  return {
    repositories,
    getUserRepositories // 返回的函数与方法的行为相同
  }
}

带ref的响应式变量
在 Vue 3.0 中,我们可以通过一个新的 ref 函数使任何响应式变量在任何地方起作用,如下所示:

import { ref } from 'vue'

const counter = ref(0)
console.log(counter) // { value: 0 }
console.log(counter.value) // 0
//ref接受参数并返回它包装在具有value的property对象中,然后可以使用property 修改响应式变量的值
counter.value++
console.log(counter.value) // 1
//在对象中对于包装值就不是必要的,但是在javaScript中基本类型是通过值来传递的
//如 let val1 = 1 const val2 = val1 值的传递
//对象则是通过引用来传递

在任何值周围都有一个包装器对象,这样我们就可以在整个应用程序中安全地传递它,而不必担心在某个地方失去它的响应性。
提示

   换句话说,ref 对我们的值创建了一个响应式引用。使用引用的概念将在整个组合式 API 中经常使用。

回到之前的代码,我们来完善它:

// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref } from 'vue'

// in our component
setup (props) {
  const repositories = ref([])
  const getUserRepositories = async () => {
    repositories.value = await fetchUserRepositories(props.user)
  }

  return {
    repositories,
    getUserRepositories
  }
}

完成!现在,每当我们调用 getUserRepositories 时,repositories 都将发生变化,视图将更新以反映更改。我们的组件现在应该如下所示:

// src/components/UserRepositories.vue
import { fetchUserRepositories } from '@/api/repositories'
import { ref } from 'vue'

export default {
  components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
  props: {
    user: { type: String }
  },
  setup (props) {
    const repositories = ref([])
    const getUserRepositories = async () => {
      repositories.value = await fetchUserRepositories(props.user)
    }

    return {
      repositories,
      getUserRepositories
    }
  },
  data () {
    return {
      filters: { ... }, // 3
      searchQuery: '' // 2
    }
  },
  computed: {
    filteredRepositories () { ... }, // 3
    repositoriesMatchingSearchQuery () { ... }, // 2
  },
  watch: {
    user: 'getUserRepositories' // 1
  },
  methods: {
    updateFilters () { ... }, // 3
  },
  mounted () {
    this.getUserRepositories() // 1
  }
}

mounted 钩子调用 getUserRepositores

// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted } from 'vue'

// in our component
setup (props) {
  const repositories = ref([])
  const getUserRepositories = async () => {
    repositories.value = await fetchUserRepositories(props.user)
  }

  onMounted(getUserRepositories) // on `mounted` call `getUserRepositories`

  return {
    repositories,
    getUserRepositories
  }
}

现在我们使用watch来监听user

// 组合式API
import { ref, watch } from 'vue'
// 如同vue2我们可以从vue中导入watch,他们的功能基本相同,现在watch接收三个参数:
//1,一个响应式回应或者我们想要监听的getter函数
//2,一个回调
//3,可选的配置项
const counter = ref(0)
watch(counter, (newValue, oldValue) => {
  console.log('The new counter value is: ' + counter.value)
})

我们来看一下和vue2(选项式API)的对比

// 选项式API
export default {
  data() {
    return {
      counter: 0
    }
  },
  watch: {
    counter(newValue, oldValue) {
      console.log('The new counter value is: ' + this.counter)
    }
  }
}

现在配合我们上面的例子来继续vue3组合式API开发的模式来完成代码

// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch, toRefs } from 'vue'

// 在我们组件中
setup (props) {
  // 使用 `toRefs` 创建对prop的 `user` property 的响应式引用
  const { user } = toRefs(props)

  const repositories = ref([])
  const getUserRepositories = async () => {
    // 更新 `prop.user` 到 `user.value` 访问引用值
    repositories.value = await fetchUserRepositories(user.value)
  }

  onMounted(getUserRepositories)

  // 在用户 prop 的响应式引用上设置一个侦听器
  watch(user, getUserRepositories)

  return {
    repositories,
    getUserRepositories
  }
}

你可能已经注意到在我们的 setup 的顶部使用了 toRefs。这是为了确保我们的侦听器能够对 user prop 所做的更改做出反应。

有了这些变化,我们就把第一个逻辑关注点移到了一个地方。我们现在可以对第二个关注点执行相同的操作——基于 searchQuery 进行过滤,这次是使用计算属性。先来看下代码

//与ref 和 watch类似,也可以使用从vue导入的computed 函数在vue组件外部创建计算属性
//下面是counter的例子
import { ref, computed } from 'vue'

const counter = ref(0)
const twiceTheCounter = computed(() => counter.value * 2)

counter.value++
console.log(counter.value) // 1
console.log(twiceTheCounter.value) // 2
// vue3简直太顶了,作者忍不住要夸一下,牛*

在这里,computed函数返回一个作为computed的第一个参数传递的getter类回调的输出的一个只读的响应式引用。为了访问新创建的计算变量的value,我们需要像使用ref一样使用.value property
接下来就是搜索功能,同样使用vue3的组合式API

// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch, toRefs, computed } from 'vue'

// in our component
setup (props) {
  // 使用 `toRefs` 创建对 props 的 `user` property 的响应式引用
  const { user } = toRefs(props)

  const repositories = ref([])
  const getUserRepositories = async () => {
    // 更新 `props.user ` 到 `user.value` 访问引用值
    repositories.value = await fetchUserRepositories(user.value)
  }

  onMounted(getUserRepositories)

  // 在用户 prop 的响应式引用上设置一个侦听器
  watch(user, getUserRepositories)

  const searchQuery = ref('')
  const repositoriesMatchingSearchQuery = computed(() => {
    return repositories.value.filter(
      repository => repository.name.includes(searchQuery.value)
    )
  })

  return {
    repositories,
    getUserRepositories,
    searchQuery,
    repositoriesMatchingSearchQuery
  }
}

看完上面的代码,你会发现,组合式API不就是把选项式API组合起来放到setup中使其具有聚合功能的作用吗?那说明你已经好好地看了本篇文章的上半部分。但实战却刚刚开始,接下来就让我们一起看实战中组合式API是如何使用的:

//封装组合式函数--根据响应式的参数动态获取接口数据并返回
// src/composables/useUserRepositories.js
import { retApi } from '@/api/repositories'
import { ref, onMounted, watch} from 'vue'
export default function userRepositories(user){
	const repositories = ref([])
	const getUserRepositor = async() =>{
		repositories.value = await retApi(user.value)
	}
	onMounted(getUserRepositor)
	watch(user,getUserRepositor)
	return {
		repositories,
		getUserRepositor
	}
}

将搜索功能也同样封装(聪明的小伙伴已经开始跟着作者敲代码了 )

// src/composables/useRepositoryNameSearch.js
//搜索功能显然要写在computed函数里面
import { ref, computed } from 'vue'
export default function userSearch(repositories){
	const searchQuery = ref('')
	const repositoriesMatchingSearchQuery = computed(()=>{
		return repositories.value.filter(repository=>{
			return repository.name.includes(searchQuery.value)
		})
	})
	return{
		searchQuery,
		repositoriesMatchingSearchQuery
	}
}

功能函数封装好了,接下来我们来完成组件部分:

// src/components/UserRepositories.vue
import useUserRepositories from '@/composables/useUserRepositories'
import useRepositoryNameSearch from '@/composables/useRepositoryNameSearch'
import { roRefs } from 'vue'
export default {
	components:{
		RepositoriesFilters,
		RepositoriesSortBy, 
		RepositoriesList
	},
	props:{
		user:{ type: String }
	},
	setup(props){
		const { user } = toRefs(props)
	    const { repositories, getUserRepositories } = useUserRepositories(user)
	    const {
	      searchQuery,
	      repositoriesMatchingSearchQuery
	    } = useRepositoryNameSearch(repositories)
	    return {
	      // 因为我们并不关心未经过滤的仓库
	      // 我们可以在 `repositories` 名称下暴露过滤后的结果
	      repositories: repositoriesMatchingSearchQuery,
	      getUserRepositories,
	      searchQuery,
	    }
	},
	data () {
	   return {
	      filters: { ... }, // 3
	    }
	},
	computed: {
	    filteredRepositories () { ... }, // 3
	},
	methods: {
	    updateFilters () { ... }, // 3
	}
}

继续更新,补全代码---------

// src/components/UserRepositories.vue
import { toRefs } from 'vue'
import useUserRepositories from '@/composables/useUserRepositories'
import useRepositoryNameSearch from '@/composables/useRepositoryNameSearch'
import useRepositoryFilters from '@/composables/useRepositoryFilters'

export default {
  components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
  props: {
    user: { type: String }
  },
  setup(props) {
    const { user } = toRefs(props)

    const { repositories, getUserRepositories } = useUserRepositories(user)

    const {
      searchQuery,
      repositoriesMatchingSearchQuery
    } = useRepositoryNameSearch(repositories)

    const {
      filters,
      updateFilters,
      filteredRepositories
    } = useRepositoryFilters(repositoriesMatchingSearchQuery)

    return {
      // 因为我们并不关心未经过滤的仓库
      // 我们可以在 `repositories` 名称下暴露过滤后的结果
      repositories: filteredRepositories,
      getUserRepositories,
      searchQuery,
      filters,
      updateFilters
    }
  }
}

随着前端生态的不断发展,前端岗位对于开发人员的挑战也越来越大。释放焦虑,才有动力,有压力,才有成长!

本文章参考vue3文档

第一篇 end

你可能感兴趣的:(vue3,技术分享,vue,大前端,javascript,es6,typescript)