一道有关响应式的前端算法题,虽然最终没用用到 getter setter 和 proxy 就是了

43 阅读1分钟
let isInBatch = false
const effectQueue = []
function executeEffect() {
    effectQueue.forEach(cb => {
        cb()
    })
}


/**
 * 一个 createSignal方法,其接受一个初始值作为参数,
 * 并返回-个数组,数组的第一个元素是一个函数,用于获取当前的值,
 * 第二个元素是一个函数,用于更新当前的值
 * @param {*} initialValue
 * @example
 *      const [count,setCount]= createSignal(1);
 *      console.log('count:', count()) // 打印 1
 *      setCount(2)
 *      console.log('count:', count()) //打印 2
 */
function createSignal(initialValue) {
    const target = { val: initialValue }
    const getValue = () => {
        return target.val
    }
    const setValue = (newValue) => {
        target.val = newValue
        if (!isInBatch) {
            executeEffect()
        }
    }

    return [getValue, setValue]
}

/**
 * 接受一个函数作为参数,并并在函数执行时触发一次,
 * 且每次 createSignal 返回的更新函数被调用时也会重新执行一次
 * @param {*} callbackFn
 * @example
 *      const [count, setCount] = createSignal(1)
 *      createEffect(()=>{console.log('count:', count()) // 打印 1
 *      setCount(2) // 触发打印 2
 */
function createEffect(callbackFn) {
    // 这里完全可以提供一个 option,用于选择是否立即执行
    callbackFn()
    effectQueue.push(callbackFn)
}


/**
 * 实现一个 batch 方法,在 batch 中执行多个 setter 操作,
 * createEffect 的函数参数只被调用一次
 *
 * @param {*} callbackFn
 * @example
 *      const [count, setCount] = createSignal(0)
 *      const [str, setStr] = createSignal('hello')
 *      createEffect(() => {
 *          console.log('count:', count(), 'str:', str())
 *      })
 *      batch(() => {
 *          setCount(1)
 *          setStr('hi')
 *      })
 *  // 输出
 *  //      count: 0, str: hello
 *  //      count: 1, str: hi
 */
function batch(callbackFn) {
    isInBatch = true
    callbackFn()
    isInBatch = false
    executeEffect()
}

const [count, setCount] = createSignal(0)
const [str, setStr] = createSignal('hello')
createEffect(() => {
    console.log('count:', count(), 'str:', str())
})
setCount(3)
setCount(4)
batch(() => {
    setCount(1)
    setStr('hi')
})