onmounted vue3_基于项目时间阐述vue3.0新型状态管理和逻辑复用方式
作者:Mingle
转发链接:https://mp.weixin.qq.com/s/iOq-eeyToDXJ6lvwnC12DQ
前言
背景:2019年2月6号,React 发布 「16.8.0」 版本,vue紧随其后,发布了「vue3.0 RFC」
Vue3.0受React16.0 推出的hook抄袭启发(咳咳...),提供了一个全新的逻辑复用方案。使用基于函数的 API,我们可以将相关联的代码抽取到一个 "composition function"(组合函数)中 —— 该函数封装了相关联的逻辑,并将需要暴露给组件的状态以相应式的数据源的方式返回出来。
本文目的
本文会介绍Vue3.0「组合api的用法和注意点」。最后会用一个 Todolist 的项目实战,向大家介绍「Vue3.0的逻辑复用写法以及借用provide和inject的新型状态管理方式」
本文提纲:
- 如何新建一个使用vue3.0的项目
- conposition api
- 逻辑复用(hook)和状态管理(provide+inject)
- 结合项目实战,做一个todo list
正文
如何新建一个使用vue3.0的项目
接下来向大家简单介绍下如何尝鲜 -- 自己创建一个vue3.0的项目。
我这边使用的是最新版本的vue-cli 4.4.0
npm install -g @vue/cli# ORyarn global add @vue/cliok了。就这么简单!
conposition api
#### 目录
- 基本例子
- setup()
- reactive
- ref
- computed
- watchEffect
- watch
- 生命周期
- 依赖注入
基本例子
count is {{ count.count }} plusOne is {{ plusOne }} count++setup
❝
该setup功能是新的组件选项。它是组件内部暴露出所有的属性和方法的统一API。
❞
调用时机
创建组件实例,然后初始化 props ,紧接着就调用setup 函数。从生命周期钩子的视角来看,它会在 beforeCreate 钩子之前被调用
模板中使用
如果 setup 返回一个对象,则对象的属性将会被合并到组件模板的渲染上下文
{{ count }} {{ object.foo }}setup 参数
- 不要在子组件中修改props;如果你尝试修改,将会给你警告甚至报错。
- 不要结构props。结构的props会失去响应性。
2.「上下文对象」第二个参数提供了一个上下文对象,从原来 2.x 中 this 选择性地暴露了一些 property。
const MyComponent = { setup(props, context) { context.attrs context.slots context.emit },}Tip:
由于vue3.x向下兼容vue2.x,所以我在尝试之后发现,一个vue文件中你可以同时写两个版本的东西。
import { reactive, computed, watch, onMounted } from 'vue'export default { name: 'HelloWorld', props: { count: Number, }, data () { return { msg: "我是vue2.x中的this" } }, methods: { test () { console.log(this.msg) } }, mounted () { console.log('vue2.x mounted') }, // eslint-disable-next-line no-unused-vars setup (props, val) { console.log(this, 'this') // undefined onMounted(() => { console.log('vue3.x mounted') }) return { ...props } }}当然这边不推荐你在项目中这么用,但是抱着尝鲜和探究的态度,我们势必要弄清如果这么写要注意哪些?
setup中的先执行。因为setup() 在解析 2.x 选项前被调用;
首先在setup中的this将不再指向vue,而是undefined;所以在setup函数内部自然无法访问到vue实例上的this。
setup内部定义的变量和外表的变量并无冲突;
但是如果你要将其return 暴露给template,那么就会产生冲突。
reactive
❝
接收一个普通对象然后返回该普通对象的响应式代理。等同于 2.x 的 Vue.observable()
❞
const obj = reactive({ count: 0 })ref
❝
接受一个参数值并返回一个响应式且可改变的 ref 对象。ref 对象拥有一个指向内部值的单一属性 value。
❞
const count = ref(0)console.log(count.value) // 0count.value++console.log(count.value) // 1tip:
computed
computed和vue2.x版本保持一致,支持getter和setter
- 传入一个 getter 函数,返回一个默认不可手动修改的 ref 对象。
- 或者传入一个拥有 get 和 set 函数的对象,创建一个可手动修改的计算状态。
watchEffect
❝
传入的一个函数,并且立即执行,响应式追踪其依赖,并在其依赖变更时重新运行该函数。
❞
注册监听
import {watchEffect}from 'vue' // 导入apiconst count = ref(0) // 定义响应数据watchEffect(() => console.log(count.value)) // 注册监听函数// -> 打印出 0setTimeout(() => { count.value++ // -> 打印出 1}, 100)注销监听
- 默认情况下是在**组件卸载**的时候停止监听;- 也可以显示**调用返回值**以停止侦听;
const stop = watchEffect(() => { /* ... */})// 之后stop()清除副作用
> 有时副作用函数会执行一些异步的副作用, 这些响应需要在其失效时清除(即完成之前状态已改变了)。所以侦听副作用传入的函数可以接收一个 onInvalidate 函数作入参, 用来注册清理失效时的回调。
当以下情况发生时,这个失效回调会被触发:
- 副作用即将重新执行时
- 侦听器被停止
副作用刷新时机
> Vue 的响应式系统会缓存副作用函数,并异步地刷新它们,这样可以避免同一个 tick 中多个状态改变导致的不必要的重复调用。在核心的具体实现中, 组件的更新函数也是一个被侦听的副作用。当一个用户定义的副作用函数进入队列时, 会在所有的组件更新后执行:
{{ count }}在这个例子中:
- count 会在初始运行时同步打印出来
- 更改 count 时,将在组件更新后执行副作用。
如果副作用需要同步或在组件更新之前重新运行,我们可以传递一个拥有 flush 属性的对象作为选项(默认为 'post'):
// 同步运行watchEffect( () => { /* ... */ }, { flush: 'sync', })// 组件更新前执行watchEffect( () => { /* ... */ }, { flush: 'pre', })watch
> watch API 完全等效于 2.x this.$watch (以及 watch 中相应的选项)。watch 需要侦听特定的数据源,并在回调函数中执行副作用。默认情况是懒执行的,也就是说仅在侦听的源变更时才执行回调。
- 对比 watchEffect,watch 允许我们:
- 懒执行副作用;
- 更明确哪些状态的改变会触发侦听器重新运行副作用;
- 访问侦听状态变化前后的值。
- 侦听单个数据源
侦听器的数据源可以是一个拥有返回值的 getter 函数,也可以是 ref:
// 侦听一个 getterconst state = reactive({ count: 0 })watch( () => state.count, (count, prevCount) => { /* ... */ })// 直接侦听一个 refconst count = ref(0)watch(count, (count, prevCount) => { /* ... */})- 侦听多个数据源
- 与 watchEffect 共享的行为
watch 和 watchEffect 在停止侦听, 清除副作用 (相应地 onInvalidate 会作为回调的第三个参数传入),副作用刷新时机 和 助听器调试 等方面行为一致.
生命周期钩子函数
❝
可以直接导入 onXXX 一组的函数来注册生命周期钩子,这些生命周期钩子注册函数只能在 setup() 期间同步使用,在卸载组件时,在生命周期钩子内部同步创建的侦听器和计算状态也将自动删除。
❞
- 「与 2.x 版本生命周期相对应的组合式 API」
- beforeCreate -> 使用 setup()
- created -> 使用 setup()
- beforeMount -> onBeforeMount
- mounted -> onMounted
- beforeUpdate -> onBeforeUpdate
- updated -> onUpdated
- beforeDestroy -> onBeforeUnmount
- destroyed -> onUnmounted
- errorCaptured -> onErrorCaptured
- 新增的钩子函数
- onRenderTracked
- onRenderTriggered
两个钩子函数都接收一个DebuggerEvent,与 watchEffect 参数选项中的 onTrack 和 onTrigger 类似:
export default { onRenderTriggered(e) { debugger // 检查哪个依赖性导致组件重新渲染 },}依赖注入
❝
provide 和 inject 提供依赖注入,功能类似 2.x 的 provide/inject。两者都只能在当前活动组件实例的 setup() 中调用。
❞
这是本篇文章的重点。结合项目实战以此来探索一下未来的 Vue 状态管理模式和逻辑复用模式。
「用法」
❝
provide 和 inject 提供依赖注入,功能类似 2.x 的 provide/inject。两者都只能在当前活动组件实例的 setup() 中调用。
❞
import { provide, inject } from 'vue'const ThemeSymbol = Symbol()const Ancestor = { setup() { provide(ThemeSymbol, 'dark') },}const Descendent = { setup() { const theme = inject(ThemeSymbol, 'light' /* optional default value */) return { theme, } },}inject 接受一个可选的的默认值作为第二个参数。如果未提供默认值,并且在 provide 上下文中未找到该属性,则 inject 返回 undefined。
- 「注入的响应性」
可以使用 ref 来保证 provided 和 injected 时间值的响应:
// 提供者:const themeRef = ref('dark')provide(ThemeSymbol, themeRef)// 使用者:const theme = inject(ThemeSymbol, ref('light'))watchEffect(() => { console.log(`theme set to: ${theme.value}`)})如果注入一个响应式对象,则它的状态变化也可以被侦听。
逻辑组合与复用
引出问题:
我们通常会基于一堆相同的数据进行花样呈现,有列表展示、有饼图占比、有折线图趋势、有热力图说明频次等等,这些组件使用的是相同的一些数据和数据处理逻辑。对于数据处理逻辑,目前vue有
- Mixins
- 高阶组件 (Higher-order Components, aka HOCs)
- Renderless Components (基于 scoped slots / 作用于插槽封装逻辑的组件)
但是上面的方案是存在一些弊端:
项目预览
源码:https://github.com/961998264/todolist-vue-3.0
项目介绍
项目src目录
hooks文件夹是专门放hook的
context文件夹以模块划分
先来看下context编写(我这边是用的ts)
import { provide, ref, Ref, inject, computed, } from 'vue' //vue apiimport { getListApi } from 'api/home' // mock的api// 以下为定义的ts类型,你也可以单独建一个专门定义类型的文件。type list = listItem[]interface listItem { title: string, context: string, id: number, status: number,}interface ListContext { list: Ref, getList: () => {}, changeStatus: (id: number, status: number) => void, addList: (item: listItem) => void, delList: (id: number) => void, finished: Ref, unFinish: Ref, setContext: (id: number, context: string) => void, setActiveItem: () => void,}provide名称,推荐用Symbol
const listymbol = Symbol()提供provide的函数
export const useListProvide = () => { // 全部事件 const list = ref([]); // 当前查看的事件id const activeId = ref(null) // 当前查看的事件 const activeItem = computed(() => { if (activeId.value || activeId.value === 0) { const item = list.value.filter((item: listItem) => item.id === activeId.value) return item[0] } else { return null } }) // 获取list const getList = async function () { const res: any = await getListApi() console.log("useListProvide -> res", res) if (res.code === 0) { list.value = res.data } } // 新增list const addList = (item: listItem) => { list.value.push(item) } //修改状态 const changeStatus = (id: number, status: number) => { console.log('status', status) const removeIndex = list.value.findIndex((listItem: listItem) => listItem.id === id) if (removeIndex !== -1) { list.value[removeIndex].status = status } }; // 修改事件信息 const setContext = (id: number, context: string) => { const Index = list.value.findIndex((listItem: listItem) => listItem.id === id) if (Index !== -1) { list.value[Index].context = context } } // 删除事件 const delList = (id: number) => { console.log("delList -> id", id) for (let i = 0; i { return list.value.filter(item => item.status === 0) }) // 已完成事件列表 const finished = computed(() => { return list.value.filter(item => item.status === 1) }) provide(listymbol, { list, unFinish, finished, changeStatus, getList, addList, delList, setContext, activeItem, activeId })}在这个函数中定义 待办事件,并且定义一系列增删改查函数,通过provide暴露出去。
提供inject的函数
export const useListInject = () => { const listContext = inject(listymbol); if (!listContext) { throw new Error(`useListInject must be used after useListProvide`); } return listContext};全局状态肯定不止一个模块,所以在 context/index.ts 下做统一的导出
import { useListProvide, useListInject } from './home/index'console.log("useListInject", useListInject)export { useListInject }export const useProvider = () => { useListProvide()}然后在 App.vue 的根组件里使用 provide,在最上层的组件中注入全局状态。
import { useProvider } from './context/index'export default { name: 'App', setup () { useProvider() return { } }}在组件中获取数据:
import { useListInject } from '../../context/home/index'setup () { const { list, changeStatus, getList, unFinish, finished, addList, a ctiveItem, setContext } = useListInject()}不管是父子组件还是兄弟组件,或者是比关系套更深的组件,我们都可以通过useListInject来获取到相应式的数据。
作者:Mingle
转发链接:https://mp.weixin.qq.com/s/iOq-eeyToDXJ6lvwnC12DQ
总结
以上是生活随笔为你收集整理的onmounted vue3_基于项目时间阐述vue3.0新型状态管理和逻辑复用方式的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: python无师自通配套资源_Pytho
- 下一篇: vue如何使用原生js写动画效果_深入理