TypeScript结合React开发D3可视化时,如何避免非空断言符解决空检查报错?
根因分析
你需要到处加非空断言的核心问题,是代码控制流和TS的类型推断逻辑没有对齐:
- 你写的
let x = defaultData.data && scaleBand()...这类短路赋值,TS推导出来的x类型永远是null | undefined | 比例尺实例,哪怕你在同一个逻辑块里完全确定defaultData.data不可能为空,TS也不会跨变量关联判断条件。 - 空值检查逻辑散落在各个变量的赋值环节,没有统一前置拦截,TS没法准确追踪到所有可能的空值执行路径,只能靠你手动加
!来跳过类型检查。
落地方案
统一前置空值守卫,利用TS自动类型收窄
这是冗余代码最少、最不容易出运行时空值问题的方案,完全不需要逐行加非空断言:
- 在图表逻辑的最开头,一次性把所有空值前置条件做拦截,命中空值就直接退出逻辑。后面整个执行块里,TS会自动把所有相关变量收窄为确定的非空类型,所有之前需要加
!的地方都可以直接删掉。 - 改造后的核心代码结构:
useEffect(() => { // 第一重守卫:selection为空就赋值后直接返回,不执行后续图表逻辑 if (!selection) { setSelection(select(ref.current)); return; } // 第二重守卫:data为空就直接返回,不执行后续绘图逻辑 if (!defaultData.data) { return; } // 到这一步,TS已经自动确认defaultData.data是确定的非空数组,所有后续变量都不会有null/undefined类型 const x = scaleBand<string>() .domain(defaultData.data.map((d) => d.name)) .range([0, canvas.chartWidth]) .paddingInner(0.1); // 仅对D3本身可能返回空值的max方法做一次兜底处理,不用全代码加断言 const maxChartValue = max(defaultData.data, (d) => axis ? d.bikeC : d.divC) ?? 0; const y = scaleLinear() .domain([0, maxChartValue]) .range([canvas.chartHeight, 0]); // 指定scaleLinear的输出泛型为字符串,匹配插值函数的入参要求 const color = scaleLinear<string>() .domain([0, maxChartValue]) .range([0.2, 0.8]); const xAxisBot = d3.axisBottom(x); const yAxisRight = d3.axisRight(y); selection .selectAll('rect') .data(defaultData.data) .enter() .append('rect') .attr('fill', (d) => axis ? d3.interpolateGreens(color(d.bikeC)) : d3.interpolateGreens(color(d.divC)) ) .attr('width', x.bandwidth()) .attr('x', (d) => x(d.name)) .attr('height', 0) .attr('y', canvas.chartHeight) .transition() .duration(1000) .delay((_, i) => i * 100) .ease(easeCircleOut) .attr('height', (d) => canvas.chartHeight - y(axis ? d.bikeC : d.divC) - 10) .attr('y', (d) => y(axis ? d.bikeC : d.divC)); }, [/* 补全effect的依赖项即可 */]);
这个方法的核心是不需要你逐行给变量做空值校验,只需要把同一类的空值判断挪到逻辑最开头做一次就行,TS会顺着控制流自动把后面所有用到的变量类型推对。
替换容易引发类型推断问题的短路赋值
不要用let x = 条件 && 函数返回值这种写法当变量的初始化逻辑:
- 这种写法的类型推断是松散的,TS没法把条件成立的状态和后续的变量使用关联起来,后续用x的时候它还是会提示x可能为空
- 要么用前面说的前置守卫给变量直接赋确定的非空类型,要么明确给变量标注
类型 | undefined,在实际使用前做一次判断,不要依赖短路表达式传递类型信息
D3结合TS的适配细节
- D3内置的
max()/min()方法本身的返回值就带undefined类型,因为传入空数组时会返回空,这类场景不需要全代码加断言,只需要在第一次计算最大值的位置加个兜底值(比如?? 0)就够了。 - 用到
interpolateGreens这类颜色插值函数时,对应的比例尺需要指定输出字符串的泛型(比如scaleLinear<string>()),不然TS会认为比例尺返回数值,直接传给插值函数就会报类型错。 - 非空断言
!只适合用在你100%确认不可能为空的场景,不要为了快速过类型检查到处滥用,否则TS的空值检查保护就完全失效了,真遇到运行时空值照样会出报错。
内容的提问来源于stack exchange,提问作者badbeoti
相关产品推荐
相关产品推荐

