使用Vue3.0,我收获了哪些知识点(二)

thbcm阅读(517)

文章来源于公众号:前端有的玩

最近在工作之余一直学习 Vue3.0 相关知识,虽然 Vue3.0 至今还是 rc 版,但这并不影响我们去学习。今天这篇文章主要讲解了以下内容:

  1. Vue3.0中使用watch
  2. Vue3.0中使用计算属性
  3. Vue3.0中使用vue-router
  4. Vue3.0中使用vuex

Vue3.0中使用watch

watch在Vue3.0中并不是一个新的概念,在使用Vue2.x的时候,我们经常会使用watch来监听Vue实例上面的一个表达式或者一个函数计算结果的变化。

回顾Vue2.0中的watch

在 Vue2.0 中,我们使用watch可以通过下面多种方式去监听Vue实例上面的表达式或者函数计算结果的变化,如下罗列了其中的几种

  1. 最常规使用方式
   export default {
     data() {
       return {
         name: '子君',
         info: {
           gzh: '前端有的玩'
         }
       }
     },
     watch: {
       name(newValue, oldValue) {
         console.log(newValue, oldValue)
       },
       'info.gzh': {
         handler(newValue, oldValue) {
           console.log(newValue, oldValue)
         },
         // 配置immediate会在watch之后立即执行
         immediate: true
       }
     }
   }

我们可以在watch属性里面配置要监听的Vue实例上面的属性,也可以通过.键路径去监听对象中的某一个属性的变化,也可以通过配置immediate在监听后立即触发,配置deep去深度监听对象里面的属性,不论嵌套层级有多深。

  1. 使用$watch监听

除了常规的watch对象写法之外,Vue实例上面提供了$watch方法,可以通过$watch更灵活的去监听某一个属性的变化。比如这样一个场景,我们有一个表单,然后希望在用户修改表单之后可以监听到表单的数据变化。但是有一个特殊的场景,就是表单的回填数据是异步请求过来的,这时候我们希望在后台请求完数据之后再去监听变化,这时候就可以使用$watch。如下代码所示:

   export default {
     methods: {
       loadData() {
         fetch().then(data => {
           this.formData = data
           this.$watch(
             'formData',
             () => {
               // formData数据发生变化后会进入此回调函数
             },
             {
               deep: true
             }
           )
         })
       }
     }
   }

  1. 监听函数表达式

除了监听字符串之外,$watch的第一个参数也可以是一个函数,当函数的返回值发生变化之后,触发回调函数

   this.$watch(() => this.name, () => {
     // 函数的返回值发生变化,进入此回调函数
   })

上文中就是 Vue2.0 中我们使用watch的一些常用写法,对于Vue3.0,因为其对 Vue2.0 做了部分的向下兼容,所以上面的用法在Vue3.0中基本都可以使用,但是Vue3.0一个很大的亮点就是composition API,所以除了 Vue2.0 中的写法之外,也可以使用componsition api中提供的watch

在Vue3.0中使用watch

在Vue3.0中,除了 Vue2.0 的写法之外,有两个api可以对数据变化进行监听,第一种是watch,第二种是watchEffect。对于watch,其与 Vue2.0 中的$watch用法基本是一模一样的,而watchEffect是Vue3.0新提供的api

watch的用法

下面的示例演示了如何使用watch

import { watch, ref, reactive } from 'vue'
export default {
  setup() {
    const name = ref('子君')
    const otherName = reactive({
      firstName: '王',
      lastName: '二狗'
    })
    watch(name, (newValue, oldValue) => {
      // 输出 前端有的玩 子君
      console.log(newValue, oldValue)
    })
    // watch 可以监听一个函数的返回值
    watch(
      () => {
        return otherName.firstName + otherName.lastName
      },
      value => {
        // 当otherName中的 firstName或者lastName发生变化时,都会进入这个函数
        console.log(`我叫${value}`)
      }
    )


    setTimeout(() => {
      name.value = '前端有的玩'
      otherName.firstName = '李'
    }, 3000)
  }
}

watch除了可以监听单个值或者函数返回值之外,也可以同时监听多个数据源,比如下面代码所示:

export default {
  setup() {
    const name = ref('子君')
    const gzh = ref('前端有的玩')
    watch([name, gzh], ([name, gzh], [prevName, prevGzh]) => {
      console.log(prevName, name)
      console.log(prevGzh, gzh)
    })


    setTimeout(() => {
      name.value = '前端有的玩'
      gzh.value = '关注我,一起玩前端'
    }, 3000)
  }
}

watchEffect的用法

watchEffect的用法与watch有所不同,watchEffect会传入一个函数,然后立即执行这个函数,对于函数里面的响应式依赖会进行监听,然后当依赖发生变化时,会重新调用传入的函数,如下代码所示:

import { ref, watchEffect } from 'vue'
export default {
  setup() {
    const id = ref('0')
    watchEffect(() => {
      // 先输出 0 然后两秒后输出 1
      console.log(id.value)
    })


    setTimeout(() => {
      id.value = '1'
    }, 2000)
  }
}

  1. 停止执行

Vue2.0 中的$watch会在调用的时候返回一个函数,执行这个函数可以停止watch,如下代码所示

   const unwatch = this.$watch('name',() => {})
   // 两秒后停止监听
   setTimeout(()=> {
     unwatch()
   }, 2000)

在Vue3.0中,watch与watchEffect同样也会返回一个unwatch函数,用于取消执行,比如下面代码所示

   export default {
     setup() {
       const id = ref('0')
       const unwatch = watchEffect(() => {
         // 仅仅输出0
         console.log(id.value)
       })

   
       setTimeout(() => {
         id.value = '1'
       }, 2000)
       // 1秒后取消watch,所以上面的代码只会输出0
       setTimeout(() => {
         unwatch()
       }, 1000)
     }
   }

  1. 清除副作用

想象一下这样的一个场景,界面上面有两个下拉框,第二个下拉框的数据是根据第一个下拉框的数据联动的,当第一个下拉框数据发生变化后,第二个下拉框的数据会通过发送一个网络请求进行获取。这时候我们可以通过watchEffect来实现这个功能,比如像下面代码这样

      import { ref, watchEffect } from 'vue'


      function loadData(id) {
        return new Promise(resolve => {
          setTimeout(() => {
            resolve(
              new Array(10).fill(0).map(() => {
                return id.value + Math.random()
              })
            )
          }, 2000)
        })
      }


      export default {
        setup() {
          // 下拉框1 选中的数据
          const select1Id = ref(0)
          // 下拉框2的数据
          const select2List = ref([])
          watchEffect(() => {
            // 模拟请求
            loadData(select1Id).then(data => {
              select2List.value = data
              console.log(data)
            })
          })


          // 模拟数据发生变化
          setInterval(() => {
            select1Id.value = 1
          }, 3000)
        }
      }

现在假如我切换了一下第一个下拉框的数据之后,这时候数据请求已经发出,然后我将这个页面切换到另一个页面,因为请求已经发出,所以我希望在页面离开的时候,可以结束这个请求,防止数据返回后出现异常,这时候就可以使用watchEffect为第一个回调函数传入的入参来处理这个情况,如下代码所示

      function loadData(id, cb) {
        return new Promise(resolve => {
          const timer = setTimeout(() => {
            resolve(
              new Array(10).fill(0).map(() => {
                return id.value + Math.random()
              })
            )
          }, 2000)
          cb(() => {
            clearTimeout(timer)
          })
        })
      }


      export default {
        setup() {
          // 下拉框1 选中的数据
          const select1Id = ref(0)
          // 下拉框2的数据
          const select2List = ref([])
          watchEffect(onInvalidate => {
            // 模拟请求
            let cancel = undefined
            // 第一个参数是一个回调函数,用于模拟取消请求,关于取消请求,可以了解axios的CancelToken
            loadData(select1Id, cb => {
              cancel = cb
            }).then(data => {
              select2List.value = data
              console.log(data)
            })
            onInvalidate(() => {
              cancel && cancel()
            })
          })
        }
      }

Vue3.0中使用计算属性

想一想在 Vue2.0 中我们一般会用计算属性做什么操作呢?我想最常见的就是当模板中有一个复杂计算的时候,可以先使用计算属性进行计算,然后再在模板中使用,实际上,Vue3.0中的计算属性的作用和 Vue2.0 的作用基本是一样的。

  1. 在Vue2.0中使用计算属性
     computed: {
       getName() {
         const { firstName, lastName } = this.info
         return firstName + lastName
       }
     },

  1. 在Vue3.0中使用计算属性
   <template>
     <div class="about">
       <h1>{{ name }}</h1>
     </div>
   </template>
   <script>
   import { computed, reactive } from 'vue'


   export default {
     setup() {
       const info = reactive({
         firstName: '王',
         lastName: '二狗'
       })


       const name = computed(() => info.firstName + info.lastName)


       return {
         name
       }
     }
   }
   </script>

和 Vue2.0 一样,Vue3.0的计算属性也可以设置getter和setter,比如上面代码中的计算属性,只设置了getter,即加入cumputed传入的参数是一个函数,那么这个就是getter,假如要设置setter,需要像下面这样去写

   export default {
     setup() {
       const info = reactive({
         firstName: '王',
         lastName: '二狗'
       })

   
       const name = computed({
         get: () => info.firstName + '-' + info.lastName,
         set(val) {
           const names = val.split('-')
           info.firstName = names[0]
           info.lastName = names[1]
         }
       })

   
       return {
         name
       }
     }
   }

Vue3.0中使用vue-router

初始化vue-router

在 Vue2.0 中我们使用vue-router的时候,会通过new VueRouter的方式去实现一个VueRouter实例,就像下面代码这样

import Vue from 'vue'
import VueRouter from 'vue-router'


Vue.use(VueRouter)
const router = new VueRouter({
  mode: 'history',
  routes: []
})
export default router

但到了Vue3.0,我们创建VueRouter的实例发生了一点点变化,就像Vue3.0在main.js中初始化Vue实例需要像下面写法一样

import { createApp } from 'vue'
createApp(App).$mount('#app')

vue-router也修改为了这种函数的声明方式

import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({
  // vue-router有hash和history两种路由模式,可以通过createWebHashHistory和createWebHistory来指定
  history: createWebHashHistory(),
  routes
})


router.beforeEach(() => {

  
})


router.afterEach(() => {

  
})
export default router

然后在main.js中,通过

 createApp(App).use(router)

来引用到Vue中

在setup中使用vue-router

在 Vue2.0 中,我们通过this.$route可以获取到当前的路由,然后通过this.$router来获取到路由实例来进行路由跳转,但是在setup中,我们是无法拿到this的,这也意味着我们不能像 Vue2.0 那样去使用vue-router, 此时就需要像下面这样去使用

import { useRoute, useRouter} from 'vue-router'


export default {
  setup() {
    // 获取当前路由
    const route = useRoute()
    // 获取路由实例
    const router = useRouter()
    // 当当前路由发生变化时,调用回调函数
    watch(() => route, () => {
      // 回调函数
    }, {
      immediate: true,
      deep: true
    })

    
    // 路由跳转
    function getHome() {
      router.push({
        path: '/home'
      })
    }

    
    return {
      getHome()
    }
  }
}

上面代码我们使用watch来监听路由是否发生变化,除了watch之外,我们也可以使用vue-router提供的生命周期函数

import { onBeforeRouteUpdate, useRoute } from 'vue-router'
export default {
  setup() {
    onBeforeRouteUpdate(() => {
      // 当当前路由发生变化时,调用回调函数
    })
  }
}

除了onBeforeRouteUpdate之外,vue-router在路由离开的时候也提供了一个生命周期钩子函数

onBeforeRouteLeave(() => {
   console.log('当当前页面路由离开的时候调用')
})

Vue3.0中使用vuex

其实vuex在Vue3.0中的使用方式和vue-router基本是一致的

初始化vuex

首先新建store/index.js,然后添加如下代码

import { createStore } from 'vuex'


export default createStore({
  state: {},
  mutations: {},
  actions: {}
})

然后在main.js中,通过以下方式使用

 createApp(App).use(store)

在setup中使用vuex

和useRouter一样,vuex也提供了useStore供调用时使用,比如下面这段代码

import router from '@/router'
import store from '@/store'
router.beforeEach(async (to, from, next) => {
  if (
    to.path !== '/login' &&
    store.getters['permission/getRoleMenus'].length === 0
  ) {
    await store.dispatch('permission/loadRoleMenus')
    next()
  } else {
    next()
  }
})

其余的使用方式基本和Vue2.0中的用法是一致的,大家具体可以参考vuex官方文档

Vue3.0中的生命周期钩子函数

在前文中,我们说到Vue3.0是兼容一部分 Vue2.0 的,所以对于 Vue2.0 的组件写法,生命周期钩子函数并未发生变化,但是假如你使用的是componsition api,那么就需要做一部分调整

  1. 取消beforeCreate与created

在使用componsition api的时候,其实setup就代替了beforeCreate与created,因为setup就是组件的实际入口函数。

  1. beforeDestroy与destroyed 改名了

在setup中,beforeDestroy更名为onBeforeUnmount,destroyed更名为onUnmounted

  1. 将生命周期函数名称变为on+XXX,比如mounted变成了onMounted,updated变成了onUpdated

在setup中使用生命周期函数的方式

setup() {
    onMounted(() => {
      console.log('mounted!')
    })
    onUpdated(() => {
      console.log('updated!')
    })
    onUnmounted(() => {
      console.log('unmounted!')
    })
  }

实际用法与 Vue2.0 基本是一致的,只是写法发生了变化,所以学习成本是很低的。

以上就是W3Cschool编程狮关于使用Vue3.0,我收获了哪些知识点(二)的相关介绍了,希望对大家有所帮助。

Vue Router 4 值得期待的新特性

thbcm阅读(426)

文章来源于公众号:前端全栈开发者 ,作者张张

Vue Router 4目前处于测试阶段。让我们看一下这个新版本中的一些很酷的功能。

Vue3支持

Vue 3 引入了 createApp API,它改变了将插件添加到Vue实例的方式。由于这个原因,过去版本的Vue Router将不兼容Vue 3。

Vue Router 4引入了 createRouter API,可以创建一个可与Vue 3一起安装的路由器实例。

src/router/index.js

import { createRouter } from "vue-router";


export default createRouter({
  routes: [...],
});

src/main.js

import { createApp } from "vue";
import router from "./router";


const app = createApp({});
app.use(router);
app.mount("#app");

History选项

在之前的Vue Router版本中,你可以设置 mode 选项设置导航的模式。

hash 模式利用 URL hash 来模拟完整的 UR L,这样当 URL 发生变化时,页面不会被重新加载。history 利用 HTML5 History API 来实现 URL 导航,而不需要重新加载页面。

src/router/index.js

// Vue Router 3
const router = new VueRouter({
  mode: "history",
  routes: [...]
});

在Vue Router 4中,这些模式已被抽象到模块中,可以将其导入并分配给新的 history 选项。这种额外的灵活性使你可以根据需要自定义路由历史记录的实现。

src/router/index.js

// Vue Router 4
import { createRouter, createWebHistory } from "vue-router";


export default createRouter({
  history: createWebHistory(),
  routes: [],
});

动态路由

当路由使用新的 .addRoute 方法运行时,Vue Router 4将允许你添加动态路由。

这可能不是你每天都会使用的功能,但是确实有一些有趣的用例。例如,假设你正在为一个文件系统应用程序创建一个用户界面,并且希望动态添加路径,你可以这样做:

src/components/FileUploader.vue

methods: {
  uploadComplete (id) {
    router.addRoute({
      path: `/uploads/${id}`,
      name: `upload-${id}`,
      component: FileInfo
    });
  }
}

你还可以使用以下相关方法:

  • removeRoute
  • hasRoute
  • getRoutes

导航守卫可以返回值而不是next

导航守卫是Vue Router的钩子,允许用户在导航事件之前或之后运行自定义逻辑,如 beforeEach、`beforeRouterEnter等。

它们通常用于检查用户是否有权限访问某个页面,验证动态路由参数,或者销毁监听器。

自定义逻辑运行后,next 回调用于确认路由、声明错误或重定向。

在第4版中,你可以从钩子中返回一个值或Promise来代替。这将允许像下面这样方便的速记。

// Vue Router 3
router.beforeEach((to, from, next) => {
  if (!isAuthenticated) {
    next(false);
  }
  else {
    next();
  }
})


// Vue Router 4
router.beforeEach(() => isAuthenticated);

总结

这些只是版本4中新增的一些新功能。您可以在Vue Router Next仓库中了解更多信息。

感谢Eduardo和团队为Vue Router 4所做的巨大努力!

以上就是W3Cschool编程狮关于Vue Router 4 值得期待的新特性的相关介绍了,希望对大家有所帮助。

想在生产搞事情?那试试这些 Redis 命令

thbcm阅读(369)

文章来源于公众号:Java极客技术 作者:鸭血粉丝

哎,最近阿粉又双叒叕犯事了。

事情是这样的,前一段时间阿粉公司生产交易偶发报错,一番排查下来最终原因是因为 Redis 命令执行超时。

可是令人不解的是,生产交易仅仅使用 Redis set 这个简单命令,这个命令讲道理是不可能会执行这么慢。

那到底是什么导致这个问题那?

为了找出这个问题,我们查看分析了一下 Redis 最近的慢日志,最终发现耗时比较多命令为 keys XX*

看到这个命令操作的键的前缀,阿粉才发现这是自己负责的应用。可是阿粉排查一下,虽然自己的代码并没有主动去使用 keys命令,但是底层使用框架却在间接使用,于是就有了今天这个问题。

问题原因

阿粉负责的应用是一个管理后台应用,权限管理使用 Shiro 框架,由于存在多个节点,需要使用分布式 Session,于是这里使用 Redis 存储 Session 信息。

由于 Shiro 并没有直接提供 Redis 存储 Session 组件,阿粉不得不使用 Github 一个开源组件 shiro-redis。

由于 Shiro 框架需要定期验证 Session 是否有效,于是 Shiro 底层将会调用 SessionDAO#getActiveSessions 获取所有的 Session 信息。

而 shiro-redis 正好继承 SessionDAO 这个接口,底层使用用keys 命令查找 Redis 所有存储的 Session key。

public Set<byte[]> keys(byte[] pattern){
    checkAndInit();
    Set<byte[]> keys = null;
    Jedis jedis = jedisPool.getResource();
    try{
        keys = jedis.keys(pattern);
    }finally{
        jedis.close();
    }
    return keys;
}

找到问题原因,解决办法就比较简单了,github 上查找到解决方案,升级一下 shiro-redis 到最新版本。

在这个版本,shiro-redis 采用 scan命令代替 keys,从而修复这个问题。

public Set<byte[]> keys(byte[] pattern) {
    Set<byte[]> keys = null;
    Jedis jedis = jedisPool.getResource();


    try{
        keys = new HashSet<byte[]>();
        ScanParams params = new ScanParams();
        params.count(count);
        params.match(pattern);
        byte[] cursor = ScanParams.SCAN_POINTER_START_BINARY;
        ScanResult<byte[]> scanResult;
        do{
            scanResult = jedis.scan(cursor,params);
            keys.addAll(scanResult.getResult());
            cursor = scanResult.getCursorAsBytes();
        }while(scanResult.getStringCursor().compareTo(ScanParams.SCAN_POINTER_START) > 0);
    }finally{
        jedis.close();
    }
    return keys;


}

虽然问题成功解决了,但是阿粉心里还是有点不解。

为什么keys 指令会导致其他命令执行变慢?

为什么Keys 指令查询会这么慢?

为什么Scan 指令就没有问题?

Redis 执行命令的原理

首先我们来看第一个问题,为什么keys 指令会导致其他命令执行变慢?

回答这个问题,我们首先看下 Redis 客户端执行一条命令的情况:

站在客户端的视角,执行一条命令分为三步:

  1. 发送命令
  2. 执行命令
  3. 返回结果

但是这仅仅客户端自己以为的过程,但是实际上同一时刻,可能存在很多客户端发送命令给 Redis ,而 Redis 我们都知道它采用的是单线程模型。

为了处理同一时刻所有的客户端的请求命令,Redis 内部采用了队列的方式,排队执行。

于是客户端执行一条命令实际需要四步:

  1. 发送命令
  2. 命令排队
  3. 执行命令
  4. 返回结果

由于 Redis 单线程执行命令,只能顺序从队列取出任务开始执行。

只要 3 这个过程执行命令速度过慢,队列其他任务不得不进行等待,这对外部客户端看来,Redis 好像就被阻塞一样,一直得不到响应。

所以使用 Redis 过程切勿执行需要长时间运行的指令,这样可能导致 Redis 阻塞,影响执行其他指令。

KEYS 原理

接下来开始回答第二个问题,为什么Keys 指令查询会这么慢?

回答这个问题之前,请大家回想一下 Redis 底层存储结构。

不太清楚朋友的也没关系,大家可以回看一下之前的文章「阿里面试官:HashMap 熟悉吧?好的,那就来聊聊 Redis 字典吧!」。

这里阿粉复制之前文章内容,Redis 底层使用字典这种结构,这个结构与 Java HashMap 底层比较类似。

keys命令需要返回所有的符合给定模式 pattern 的 Redis 中键,为了实现这个目的,Redis 不得不遍历字典中 ht[0]哈希表底层数组,这个时间复杂度为 「O(N)」(N 为 Redis 中 key 所有的数量)。

如果 Redis 中 key 的数量很少,那么这个执行速度还是也会很快。等到 Redis key 的数量慢慢更加,上升到百万、千万、甚至上亿级别,那这个执行速度就会很慢很慢。

下面是阿粉本地做的一次实验,使用 lua 脚本往 Redis 中增加 10W 个 key,然后使用 keys 查询所有键,这个查询大概会阻塞十几秒的时间。

eval "for i=1,100000  do redis.call('set',i,i+1) end" 0

这里阿粉使用 Docker 部署 Redis,性能可能会稍差。

SCAN 原理

最后我们来看下第三个问题,为什么scan 指令就没有问题?

这是因为 scan命令采用一种黑科技-「基于游标的迭代器」。

每次调用 scan 命令,Redis 都会向用户返回一个新的游标以及一定数量的 key。下次再想继续获取剩余的 key,需要将这个游标传入 scan 命令, 以此来延续之前的迭代过程。

简单来讲,scan 命令使用分页查询 redis 。

下面是一个 scan 命令的迭代过程示例:

scan 命令使用游标这种方式,巧妙将一次全量查询拆分成多次,降低查询复杂度。

虽然 scan 命令时间复杂度与 keys一样,都是 「O(N)」,但是由于 scan 命令只需要返回少量的 key,所以执行速度会很快。

最后,虽然scan 命令解决 keys不足,但是同时也引入其他一些缺陷:

  • 同一个元素可能会被返回多次,这就需要我们应用程序增加处理重复元素功能。
  • 如果一个元素在迭代过程增加到 redis,或者说在迭代过程被删除,那个这个元素会被返回,也可能不会。

以上这些缺陷,在我们开发中需要考虑这种情况。

除了 scan以外,redis 还有其他几个用于增量迭代命令:

  • sscan:用于迭代当前数据库中的数据库键,用于解决 smembers可能产生阻塞问题
  • hscan命令用于迭代哈希键中的键值对,用于解决 hgetall 可能产生阻塞问题。
  • zscan:命令用于迭代有序集合中的元素(包括元素成员和元素分值),用于产生 zrange 可能产生阻塞问题。

总结

Redis 使用单线程执行操作命令,所有客户端发送过来命令,Redis 都会现放入队列,然后从队列中顺序取出执行相应的命令。

如果任一任务执行过慢,就会影响队列中其他任务的,这样在外部客户端看来,迟迟拿不到 Redis 的响应,看起来就很阻塞了一样。

所以不要在生产执行 keys、smembers、hgetall、zrange这类可能造成阻塞的指令,如果真需要执行,可以使用相应的scan 命令渐进式遍历,可以有效防止阻塞问题。

以上就是W3Cschool编程狮关于想在生产搞事情?那试试这些 Redis 命令的相关介绍了,希望对大家有所帮助。

C#/.NET/.NET Core技术前沿周刊 | 第 27 期(2025年2.17-2.23)

thbcm阅读(391)

C#/.NET/.NET Core技术前沿周刊,你的每周技术指南针!记录、追踪C#/.NET/.NET Core领域、生态的每周最新、最实用、最有价值的技术文章、社区动态、优质项目和学习资源等。让你时刻站在技术前沿,助力技术成长与视野拓宽。

联系我们