欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 前端技术 > vue >内容正文

vue

onmounted vue3_基于项目时间阐述vue3.0新型状态管理和逻辑复用方式

发布时间:2023/12/19 vue 36 豆豆
生活随笔 收集整理的这篇文章主要介绍了 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的项目。

  • 安装vue0-cli
  • 我这边使用的是最新版本的vue-cli 4.4.0

    npm install -g @vue/cli# ORyarn global add @vue/cli
  • 将vue升级到bata版本
  • vue add vue-next

    ok了。就这么简单!

    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指向的是外部的props。如果你没有定义props选项,setup中的第一个参数将为undifined。props和vue2.x并无什么不同,仍然遵循以前的原则;
    • 不要在子组件中修改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    }  }}

    当然这边不推荐你在项目中这么用,但是抱着尝鲜和探究的态度,我们势必要弄清如果这么写要注意哪些?

  • 如果我写了mounted(2.x),在setup函数中又写了onMounted(3.x),谁先执行?
  • setup中的先执行。因为setup() 在解析 2.x 选项前被调用;

  • 我在vue2.x选项中中定义在this上的变量,在setup上可以通过this访问吗?可以重复定义吗?可以return吗?
  • 首先在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) // 1

    tip:

  • ref常用于基本类型,reactive用于引用类型。如果ref传入对象,其实内部会自动变为reactive.
  • 当 ref 作为渲染上下文的属性返回(即在setup() 返回的对象中)并在模板中使用时,它会自动解套,无需在模板内额外书写 .value;
  •   {{ count }}
  • 当 ref 作为 reactive 对象的 property 被访问或修改时,也将自动解套 value 值,其行为类似普通属性。
  • const count = ref(0)const state = reactive({  count,})console.log(state.count) // 0state.count = 1console.log(count.value) // 1
  • 注意当嵌套在 reactive Object 中时,ref 才会解套。从 Array 或者 Map 等原生集合类中访问 ref 时,不会自动解套:
  • const arr = reactive([ref(0)])// 这里需要 .valueconsole.log(arr[0].value)const map = reactive(new Map([['foo', ref(0)]]))// 这里需要 .valueconsole.log(map.get('foo').value)

    computed

    computed和vue2.x版本保持一致,支持getter和setter

    • 传入一个 getter 函数,返回一个默认不可手动修改的 ref 对象。
    const count = ref(1)const plusOne = computed(() => count.value + 1)console.log(plusOne.value) // 2plusOne.value++ // 错误!
    • 或者传入一个拥有 get 和 set 函数的对象,创建一个可手动修改的计算状态。
    const count = ref(1)const plusOne = computed({  get: () => count.value + 1,  set: (val) => {    count.value = val - 1  },})plusOne.value = 1console.log(count.value) // 0

    watchEffect

    传入的一个函数,并且立即执行,响应式追踪其依赖,并在其依赖变更时重新运行该函数。

    注册监听

    import {watchEffect}from 'vue' // 导入apiconst count = ref(0) // 定义响应数据watchEffect(() => console.log(count.value)) // 注册监听函数// -> 打印出 0setTimeout(() => {  count.value++  // -> 打印出 1}, 100)

    注销监听

    - 默认情况下是在**组件卸载**的时候停止监听;- 也可以显示**调用返回值**以停止侦听;

    const stop = watchEffect(() => {  /* ... */})// 之后stop()

    清除副作用

    > 有时副作用函数会执行一些异步的副作用, 这些响应需要在其失效时清除(即完成之前状态已改变了)。所以侦听副作用传入的函数可以接收一个 onInvalidate 函数作入参, 用来注册清理失效时的回调。

    当以下情况发生时,这个失效回调会被触发:

    • 副作用即将重新执行时
    • 侦听器被停止
    const count = ref(0)watchEffect(  (onInvalidate) => {    console.log(count.value, '副作用')   const token =  setTimeout(() => {      console.log(count.value, '副作用')    }, 4000)    onInvalidate(() => {    // id 改变时 或 停止侦听时    // 取消之前的异步操作    token.cancel()  })  })

    副作用刷新时机

    > 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) => {  /* ... */})
    • 侦听多个数据源
    watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {  /* ... */})
    • 与 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 / 作用于插槽封装逻辑的组件)

    但是上面的方案是存在一些弊端:

  • 模版中的数据来源不清晰
  • 命名空间冲突。
  • 需要额外的组件实例嵌套来封装逻辑(性能问题);
  • ##### 基于组合api 的解决方案function useMouse() {  const x = ref(0)  const y = ref(0)  const update = e => {    x.value = e.pageX    y.value = e.pageY  }  onMounted(() => {    window.addEventListener('mousemove', update)  })  onUnmounted(() => {    window.removeEventListener('mousemove', update)  })  return { x, y }}// 在组件中使用该函数const Component = {  setup() {    const { x, y } = useMouse()    // 与其它函数配合使用    const { z } = useOtherLogic()    return { x, y, z }  },  template: `{{ x }} {{ y }} {{ z }}`}

    项目预览

    源码: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来获取到相应式的数据。

  • 「逻辑聚合」 同一份数据的相关逻辑我们可以写在一个usexxxx的函数中,不再像以前,按照选择将逻辑分开。在methods,computed,watch,created,mounted中来回跳转。
  • 「取代vuex」 在比较小的项目中,你可以用这种状态管理的方式取代vuex。(反正我用react基本不用redux,不管项目大小)。
  • 作者:Mingle

    转发链接:https://mp.weixin.qq.com/s/iOq-eeyToDXJ6lvwnC12DQ

    总结

    以上是生活随笔为你收集整理的onmounted vue3_基于项目时间阐述vue3.0新型状态管理和逻辑复用方式的全部内容,希望文章能够帮你解决所遇到的问题。

    如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。