一文搞懂 MCP Servers

thbcm阅读(246)

它通过标准化 AI 系统与数据源的交互方式,帮助模型获取更丰富的上下文信息,从而生成更准确、更相关的响应。

试试使用 Vitest 进行测试,确实可以减少bug

thbcm阅读(236)

Vitest 是一个基于 Vite 的单元测试框架,专为现代前端项目设计。

它结合了 Vite 的高性能和 Jest 的易用性,

提供了开箱即用的 TypeScript、ESM 和 JSX 支持,同时与 Vite 的配置无缝集成。

关于 useState 的一切

thbcm阅读(208)

以下文章来源于公众号:魔术师卡颂 ,作者卡颂

作为 React 开发者,你能答上如下两个问题么:

  1. 对于如下函数组件:
function App() {
  const [num, updateNum] = useState(0);
  window.updateNum = updateNum;
  return num;
}

调用window.updateNum(1)可以将视图中的0更新为1么?

  1. 对于如下函数组件:
function App() {
  const [num, updateNum] = useState(0);

  
  function increment() {
    setTimeout(() => {
      updateNum(num + 1);
    }, 1000);
  }

  
  return <p onClick={increment}>{num}</p>;
}

在1秒内快速点击p5次,视图上显示为几?

1. 可以
2. 显示为1

其实,这两个问题本质上是在问:

  • useState如何保存状态?
  • useState如何更新状态?

本文会结合源码,讲透如上两个问题。

这些,就是你需要了解的关于useState的一切。

hook如何保存数据

FunctionComponentrender本身只是函数调用。

那么在render内部调用的hook是如何获取到对应数据呢?

比如:

  • useState获取state
  • useRef获取ref
  • useMemo获取缓存的数据

答案是:

每个组件有个对应的fiber节点(可以理解为虚拟DOM),用于保存组件相关信息。

每次FunctionComponent render时,全局变量currentlyRenderingFiber都会被赋值为该FunctionComponent对应的fiber节点

所以,hook内部其实是从currentlyRenderingFiber中获取状态信息的。

多个hook如何获取数据

我们知道,一个FunctionComponent中可能存在多个hook,比如:

function App() {
  // hookA
  const [a, updateA] = useState(0);
  // hookB
  const [b, updateB] = useState(0);
  // hookC
  const ref = useRef(0);

  
  return <p></p>;
}

那么多个hook如何获取自己的数据呢?

答案是:

currentlyRenderingFiber.memoizedState中保存一条hook对应数据的单向链表。

对于如上例子,可以理解为:

const hookA = {
  // hook保存的数据
  memoizedState: null,
  // 指向下一个hook
  next: hookB
  // ...省略其他字段
};


hookB.next = hookC;


currentlyRenderingFiber.memoizedState = hookA;

FunctionComponent render时,每执行到一个hook,都会将指向currentlyRenderingFiber.memoizedState链表的指针向后移动一次,指向当前hook对应数据。

这也是为什么React要求hook的调用顺序不能改变(不能在条件语句中使用hook) —— 每次render时都是从一条固定顺序的链表中获取hook对应数据的。

useState执行流程

我们知道,useState返回值数组第二个参数为改变state的方法

在源码中,他被称为dispatchAction

每当调用dispatchAction,都会创建一个代表一次更新的对象update

const update = {
  // 更新的数据
  action: action,
  // 指向下一个更新
  next: null
};

对于如下例子

function App() {
  const [num, updateNum] = useState(0);

  
  function increment() {
    updateNum(num + 1);
  }

  
  return <p onClick={increment}>{num}</p>;
}

调用updateNum(num + 1),会创建:

const update = {
  // 更新的数据
  action: 1,
  // 指向下一个更新
  next: null
  // ...省略其他字段
};

如果是多次调用dispatchAction,例如:

function increment() {
  // 产生update1
  updateNum(num + 1);
  // 产生update2
  updateNum(num + 2);
  // 产生update3
  updateNum(num + 3);
}

那么,update会形成一条环状链表。

update3 --next--> update1
  ^                 |
  |               update2
  |______next_______|

                          

这条链表保存在哪里呢?

既然这条update链表是由某个useStatedispatchAction产生,那么这条链表显然属于该useState hook

我们继续补充hook的数据结构。

const hook = {
  // hook保存的数据
  memoizedState: null,
  // 指向下一个hook
  next: hookForB
  // 本次更新以baseState为基础计算新的state
  baseState: null,
  // 本次更新开始时已有的update队列
  baseQueue: null,
  // 本次更新需要增加的update队列
  queue: null,
};

其中,queue中保存了本次更新update的链表。

在计算state时,会将queue的环状链表剪开挂载在baseQueue最后面,baseQueue基于baseState计算新的state

在计算state完成后,新的state会成为memoizedState

为什么更新不基于memoizedState而是baseState,是因为state的计算过程需要考虑优先级,可能有些update优先级不够被跳过。所以memoizedState并不一定和baseState相同。

回到我们开篇第一个问题:

function App() {
  const [num, updateNum] = useState(0);
  window.updateNum = updateNum;
  return num;
}

调用window.updateNum(1)可以将视图中的0更新为1么?

我们需要看看这里的updateNum方法的具体实现:

updateNum === dispatchAction.bind(null, currentlyRenderingFiber, queue);

可见,updateNum方法即绑定了currentlyRenderingFiberqueue(即hook.queue)的dispatchAction

上文已经介绍,调用dispatchAction的目的是生成update,并插入到hook.queue链表中。

既然queue作为预置参数已经绑定给dispatchAction,那么调用dispatchAction就步仅局限在FunctionComponent内部了。

update的action

第二个问题

function App() {
  const [num, updateNum] = useState(0);

  
  function increment() {
    setTimeout(() => {
      updateNum(num + 1);
    }, 1000);
  }

  
  return <p onClick={increment}>{num}</p>;
}

在1秒内快速点击p5次,视图上显示为几?

我们知道,调用updateNum会产生update,其中传参会成为update.action

在1秒内点击5次。在点击第五次时,第一次点击创建的update还没进入更新流程,所以hook.baseState还未改变。

那么这5次点击产生的update都是基于同一个baseState计算新的state,并且num变量也还未变化(即5次update.action(即num + 1)为同一个值)。

所以,最终渲染的结果为1。

useState与useReducer

那么,如何5次点击让视图从1逐步变为5呢?

由以上知识我们知道,需要改变baseState或者action

其中baseStateReact 的更新流程决定,我们无法控制。

但是我们可以控制action

action不仅可以传,也可以传函数

// action为值
updateNum(num + 1);
// action为函数
updateNum(num => num + 1);

在基于baseStateupdate链表生成新state的过程中:

let newState = baseState;
let firstUpdate = hook.baseQueue.next;
let update = firstUpdate;


// 遍历baseQueue中的每一个update
do {
  if (typeof update.action === 'function') {
    newState = update.action(newState);
  } else {
    newState = action;
  }
} while (update !== firstUpdate)

可见,当传时,由于我们5次action为同一个值,所以最终计算的newState也为同一个值。

而传函数时,newState基于action函数计算5次,则最终得到累加的结果。

如果这个例子中,我们使用useReducer而不是useState,由于useReduceraction始终为函数,所以不会遇到我们例子中的问题。

事实上,useState本身就是预置了如下reduceruseReducer

function basicStateReducer(state, action) {
  return typeof action === 'function' ? action(state) : action;
}

总结

通过本文,我们了解了useState的完整执行过程。

以上就是W3Cschool编程狮关于关于 useState 的一切的相关介绍了,希望对大家有所帮助。

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

thbcm阅读(279)

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

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

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

Vue3.0中使用watch

watchVue3.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用法基本是一模一样的,而watchEffectVue3.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中,watchwatchEffect同样也会返回一个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的计算属性也可以设置gettersetter,比如上面代码中的计算属性,只设置了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.0main.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

其实vuexVue3.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. 取消beforeCreatecreated

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

  1. beforeDestroydestroyed 改名了

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,我收获了哪些知识点(二)的相关介绍了,希望对大家有所帮助。

联系我们