You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript中if语句为何会改变signal.write的类型定义?

问题:Signal的write函数中if语句导致类型异常

我正在自主练习学习JS Signals,尝试用TypeScript重写参考代码以加深理解。大部分类型定义已适配,但在signal.write函数中遇到类型问题,错误表现为类型推断异常,代码如下:

type Observer = { notify: () => void; link: (unlink: any) => void; }
type Signal = <T>(value:T) => [() => T, (value: T | ((value: T) => T)) => void]

let activeObserver: Observer | null = null

const signal:Signal = <T>(value:T) => {
    let _value: T = value
    const _subscribers: Set<Observer> = new Set()

    function unlink(dep:Observer) {
        _subscribers.delete(dep)
    }

    function read() {
        if (activeObserver && !_subscribers.has(activeObserver)) {
            _subscribers.add(activeObserver)
            activeObserver.link(unlink)
        }
        return _value
    }

    function write(valueOrFn: T | ((value: T) => T)) {
        const newValue:T = typeof valueOrFn === "function" ? valueOrFn(_value) : valueOrFn

        if (newValue === _value) return
        _value = newValue

        for (const subscriber of [..._subscribers]) {
            subscriber.notify()
        }
    }

    return [read, write]
}

const effect = (cb: () => unknown) => {
    let _externalCleanup: unknown // defined explicitly by user
    let _unlinkSubscriptions: Set<(dep:Observer) => void> = new Set() // track active signals (to unlink on re-run)

    const effectInstance = { notify: execute, link }

    function link(unlink: (dep:Observer) => void) {
        _unlinkSubscriptions.add(unlink)
    }

    function execute() {
        dispose()
        activeObserver = effectInstance
        _externalCleanup = cb()
        activeObserver = null
    }

    function dispose() {
        if (typeof _externalCleanup === "function") {
            _externalCleanup()
        }
    }

    execute()

    return dispose
}

let [count, setCount] = signal(0); // state value set to 0

setInterval(
    () => {
        setCount(prev => prev + 1)
    },
    1000
);

effect(() => {
    console.log('Count has changed!', count());
})

问题原因

TypeScript通过typeof valueOrFn === "function"只能判断出变量是函数类型,但无法自动推断出这个函数的签名就是(value: T) => T——从类型系统角度看,它可能是任意函数。

当你执行const newValue:T = ...赋值时,TypeScript会检查赋值的安全性,但因为无法确认函数的参数和返回值类型与T匹配,直接调用valueOrFn(_value)会触发潜在的类型错误。而添加if (newValue === _value) return语句后,TypeScript的类型检查上下文发生变化,导致原本的隐性类型问题被明确暴露出来。

修复方案

方案1:显式类型断言

通过类型断言明确函数的签名,让TypeScript确认调用的安全性:

function write(valueOrFn: T | ((value: T) => T)) {
    const newValue: T = typeof valueOrFn === "function" 
        ? (valueOrFn as (val: T) => T)(_value) 
        : valueOrFn;

    if (newValue === _value) return;
    _value = newValue;

    for (const subscriber of [..._subscribers]) {
        subscriber.notify();
    }
}

方案2:自定义类型守卫

更严谨的方式是创建类型守卫函数,明确判断函数的签名:

function isUpdaterFn<T>(val: T | ((val: T) => T)): val is (val: T) => T {
    return typeof val === "function";
}

function write(valueOrFn: T | ((value: T) => T)) {
    const newValue: T = isUpdaterFn(valueOrFn) 
        ? valueOrFn(_value) 
        : valueOrFn;

    if (newValue === _value) return;
    _value = newValue;

    for (const subscriber of [..._subscribers]) {
        subscriber.notify();
    }
}

补充说明

TypeScript的类型系统无法仅通过typeof关键字识别函数的具体参数和返回值类型,因此需要开发者手动辅助类型推断。显式断言或自定义类型守卫能让类型系统明确函数的签名,从而消除类型错误。

内容的提问来源于stack exchange,提问作者Jumscrafteur

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 18:56:07