Vue3.0 Hooks系列之useState

1,703 阅读3分钟

author @栾树

hooks.png

背景

我有一个朋友在看完 Vue3.0 原始值的响应式方案 这篇文章之后给了我一些建议,大意就是让我推出一个系列文章,思来想去,这几年时间里也未曾有文章沉淀,从本篇文章开始,我将针开阔编程思想,将自己的一些奇思妙想给落地下来。由于文章落地较少缺少写作技巧,目前的想法是慢慢来,通过大量写作来提升,加油冲鸭!

GitHub官方旗航店

本文将基于Vue3.0实现React HooksuseState函数,后续接着实现以下函数

  • useReducer
  • useReactive
  • useCallback
  • useStorage
  • useLocalStorage
  • 暂时还在构思中 系列不会停...

前言

实现React HooksuseState函数之前,我们需要大致了解useState函数是干嘛的,在React出现hooks方案后,函数式组件就有了自己的状态

// React18的版本已经不需要引入React
import React, { useState } from 'react'; 

function App() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <h1>count: {count}</h1>
      
      {/* 闭包形式更新 */}
      <button onClick={() => setCount(count + 1)}>+1</button>
      
      {/* 函数式更新 */}
      <button onClick={() => setCount((val) => val + 2)}>+2</button>
    </div>
  );
}

export default App;

基础版

  1. useState() 默认接受一个参数
  2. useState( (val)=> val+1 ) 更新函数支持函数作为参数,也支持直接传入值进行更新设置,那么我们可以通过反推落地useState函数。
function useState(initialState){
    // 初始化 state
    let state = initialState;
    // update 函数
    const dispatch = (newState)=>{
          // 1.如果 newState 是函数我们则手动调用
          if(typeof newState === 'function'){
              state = newState(newState);
          }else{
              // 2.否则直接赋值
              state = newState;
          }
    }
    // 返回
    return [state, dispatch]
}

export default useState

Vue3.0中使用

<template> 
    <h1>{{ count }}</h1>
    <button @click="setCount(2)">+1</button>
</template> 

<script>   
import useState from '@/hooks/useState.js';


export default {
    setup(){
        const [count, setCount] = useState(0);
        
        return {
            count,
            setCount
        }
    }
}; 
</script>

思考

  • 运行效果中,点击按钮数据未发生改变,这是为什么呢?
  • 如果我们此时多调用几次 useState 怎么保存对应的状态呢?
  • 有了这些问题之后我们该怎么进行设计呢?

抱着这些问题我们继续探索 useState 将在最终版本中实现

最终版

  • 由于业务中 useState 会被多次使用,那么我们需要缓存其对应的状态,这里使用数组缓存,这里我也考虑过对象或者 Map 数组的形式稍微好理解一些
  • 由于不停的缓存,当体积使用越多则存储的状态则越大,这个目前还在思考🤔有待改善,有考虑过Weakmap落引用?这种暂未实现
import {ref} from "vue";

// states 存储 initialState 状态
// stateSetters 存储 dispatch 函数
// stateIndex 默认从0开始
let [states, stateSetters, stateIndex] = [[], [], 0]

/**
 * 创建 createState
 * */
function createState(initialState, stateIndex) {
    // 响应式数据包装
    const state = ref(initialState);
    // 如果states中存在则直接返回 否则返回包装的ref数据
    return states[stateIndex] !== undefined ? states[stateIndex] : state;
}

/**
 * 创建 createSetter
 */
function createSetter(stateIndex) {
    // 返回一个函数  这里其实玩了一手 闭包的概念 《stateIndex》被返回的函数引用了
    return function (newState) {
        if (typeof newState === "function") {
            // 如果为函数则手动调用 并且更新states对应值
            states[stateIndex].value = newState(states[stateIndex].value);
        } else {
            // 直接更新states对应值
            states[stateIndex].value = newState;
        }
    };
}

// 业务中 useState 可以多次  所以就需要使用队列
function useState(initialState) {
    // 创建 state
    states[stateIndex] = createState(initialState, stateIndex);
    // 如果没有则直接进行添加
    if (!stateSetters[stateIndex]) {
        stateSetters.push(createSetter(stateIndex));
    }
    
    const _state = states[stateIndex];
    const _setState = stateSetters[stateIndex];
    
    // 每次调用 useState 则会++1 
    stateIndex++;

    return [_state, _setState];
}

export default useState;