TypeError:无法读取属性' length'反应组分中的null

时间:2017-08-13 05:14:49

标签: reactjs typescript chart.js jest

好的,所以我在为ChartJS创建React组件方面做得很好,但是在测试时我得到以下错误:

FAIL  lib\chart\chart.test.tsx
  ● renders without crashing

    TypeError: Cannot read property 'length' of null

      at Object.acquireContext (node_modules/chart.js/src/platforms/platform.dom.js:189:19)
      at Chart.construct (node_modules/chart.js/src/core/core.controller.js:72:27)
      at new Chart (node_modules/chart.js/src/core/core.js:7:8)
      at Chart.Object.<anonymous>.Chart.renderChart (lib/chart/chart.tsx:233:26)
      at Chart.Object.<anonymous>.Chart.componentDidMount (lib/chart/chart.tsx:42:10)
      at node_modules/react-dom/lib/ReactCompositeComponent.js:264:25
      at measureLifeCyclePerf (node_modules/react-dom/lib/ReactCompositeComponent.js:75:12)
      at node_modules/react-dom/lib/ReactCompositeComponent.js:263:11
      at CallbackQueue.notifyAll (node_modules/react-dom/lib/CallbackQueue.js:76:22)
      at ReactReconcileTransaction.close (node_modules/react-dom/lib/ReactReconcileTransaction.js:80:26)
      at ReactReconcileTransaction.closeAll (node_modules/react-dom/lib/Transaction.js:209:25)
      at ReactReconcileTransaction.perform (node_modules/react-dom/lib/Transaction.js:156:16)
      at batchedMountComponentIntoNode (node_modules/react-dom/lib/ReactMount.js:126:15)
      at ReactDefaultBatchingStrategyTransaction.perform (node_modules/react-dom/lib/Transaction.js:143:20)
      at Object.batchedUpdates (node_modules/react-dom/lib/ReactDefaultBatchingStrategy.js:62:26)
      at Object.batchedUpdates (node_modules/react-dom/lib/ReactUpdates.js:97:27)
      at Object._renderNewRootComponent (node_modules/react-dom/lib/ReactMount.js:319:18)
      at Object._renderSubtreeIntoContainer (node_modules/react-dom/lib/ReactMount.js:401:32)
      at Object.render (node_modules/react-dom/lib/ReactMount.js:422:23)
      at Object.<anonymous> (lib/chart/chart.test.tsx:7:12)
          at Promise (<anonymous>)
      at Promise.resolve.then.el (node_modules/p-map/index.js:42:16)
          at <anonymous>
      at process._tickCallback (internal/process/next_tick.js:169:7)

  × renders without crashing (275ms)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        1.314s, estimated 3s
Ran all test suites related to changed files.

然而,我花了很长时间查看代码并且无法找出它拒绝正常工作的原因。在创建新图表实例时,错误从renderChart()函数开始。我的第一个猜测是由于某些原因,它没有注册canvas元素,尽管它被id调用。但是当renderChart的内容被移动到render()函数中时,它仍会给出相同的错误。这是测试的代码:

import * as React from 'react'
import * as ClassNames from 'classnames'
import * as ChartJS from 'chart.js'
const IsEqual = require('lodash.isequal')
const Find = require('lodash.find')
const subChart = require('chart.js')

interface IChartProps {
  /** The user-defined classes */
  readonly className?: string
  readonly width?: number
  readonly height?: number
  readonly reRender?: boolean

  readonly type: ChartJS.ChartType
  readonly data: ChartJS.ChartData
  readonly options: ChartJS.ChartOptions
  readonly getDatasetAtEvent?: Function
  readonly getElementAtEvent?: Function
  readonly getElementsAtEvent?: Function
  readonly onElementsClick?: Function
  readonly datasetKeyProvider?: Function
}

interface IChartState {
  /** Add your states here */
}

export class Chart extends React.Component<IChartProps, IChartState> {
  // tslint:disable-next-line
  private chartInstance: any
  private shadowData: {}
  constructor(props: IChartProps) {
    super(props)
  }

  public componentWillMount() {
    // this.chartInstance = undefined
  }

  public componentDidMount() {
    this.renderChart()
  }

  // public componentWillReceiveProps(nextProps: IChartProps) {}

  public shouldComponentUpdate(nextProps: IChartProps, nextState: IChartState) {
    const props = this.props
    if (nextProps.reRender === true) {
      return true
    }

    if (props.height !== nextProps.height || props.width !== nextProps.width) {
      return true
    }

    if (props.type !== nextProps.type) {
      return true
    }

    if (!IsEqual(props.options, nextProps.options)) {
      return true
    }

    const nextData = this.transformDataProp(nextProps)

    if (!IsEqual(this.shadowData, nextData)) {
      return true
    }

    return false
  }

  // public componentWillUpdate(nextProps: IChartProps, nextState: IChartState) {}

  public componentDidUpdate(prevProps: IChartProps, prevState: IChartState) {
    if (this.props.reRender) {
      this.chartInstance.destroy()
      this.renderChart()
      return
    }
    this.updateChart()
  }

  public transformDataProp(props: IChartProps) {
    const data = props.data
    if (typeof data === 'function') {
      const node = document.getElementById('bar-chart') as HTMLCanvasElement
      return data(node)
    } else {
      return data
    }
  }

  public memoizeDataProps(props?: IChartProps) {
    if (!this.props.data) {
      return
    }
    const data = this.transformDataProp(this.props)

    this.shadowData = {
      ...data,
      datasets:
        data.datasets &&
        data.datasets.map((set: string[]) => {
          return { ...set }
        })
    }
    return data
  }

  public updateChart() {
    const options = this.props.options

    const data = this.memoizeDataProps(this.props)

    if (!this.chartInstance) {
      return
    }

    if (options) {
      this.chartInstance.options = subChart.helpers.configMerge(
        this.chartInstance.options,
        options
      )
    }

    let currentDatasets =
      (this.chartInstance.config.data &&
        this.chartInstance.config.data.datasets) ||
      []
    const nextDatasets = data.datasets || []

    const currentDatasetKeys = currentDatasets.map(
      this.props.datasetKeyProvider
    )
    const nextDatasetKeys = nextDatasets.map(this.props.datasetKeyProvider)
    const newDatasets = nextDatasets.filter(
      (d: object) =>
        currentDatasetKeys.indexOf(this.props.datasetKeyProvider(d)) === -1
    )

    for (let idx = currentDatasets.length - 1; idx >= 0; idx -= 1) {
      const currentDatasetKey = this.props.datasetKeyProvider(
        currentDatasets[idx]
      )
      if (nextDatasetKeys.indexOf(currentDatasetKey) === -1) {
        // deleted series
        currentDatasets.splice(idx, 1)
      } else {
        const retainedDataset = Find(
          nextDatasets,
          (d: object) => this.props.datasetKeyProvider(d) === currentDatasetKey
        )
        if (retainedDataset) {
          // update it in place if it is a retained dataset
          currentDatasets[idx].data.splice(retainedDataset.data.length)
          retainedDataset.data.forEach((point: number, pid: number) => {
            currentDatasets[idx].data[pid] = retainedDataset.data[pid]
          })
          // const { data, ...otherProps } = retainedDataset
          currentDatasets[idx] = {
            data: currentDatasets[idx].data,
            ...currentDatasets[idx],
            ...retainedDataset.otherProps
          }
        }
      }
    }
    // finally add any new series
    newDatasets.forEach((d: object) => currentDatasets.push(d))
    const { datasets, ...rest } = data

    this.chartInstance.config.data = {
      ...this.chartInstance.config.data,
      ...rest
    }

    this.chartInstance.update()
  }

  public componentWillUnmount() {
    this.chartInstance.destroy()
  }

  public onClickEvent = (event: React.MouseEvent<HTMLCanvasElement>) => {
    // this.props.getDatasetAtEvent &&
    this.props.getDatasetAtEvent(
      this.chartInstance.getDatasetAtEvent(event),
      event
    )

    // this.props.getElementAtEvent &&
    this.props.getElementAtEvent(
      this.chartInstance.getElementAtEvent(event),
      event
    )

    // this.props.getElementsAtEvent &&
    this.props.getElementsAtEvent(
      this.chartInstance.getElementsAtEvent(event),
      event
    )

    // this.props.onElementsClick &&
    this.props.onElementsClick(
      this.chartInstance.getElementsAtEvent(event),
      event
    )
  }

  public render() {
    const className = ClassNames('chart', this.props.className)

    // bar.update()
    return (
      <div className={className}>
        <canvas
          id="chart-instance"
          width={this.props.width ? this.props.width : '400'}
          height={this.props.height ? this.props.height : '400'}
          onClick={this.onClickEvent}
        />
      </div>
    )
  }

  public renderChart() {
    const { options, type, data } = this.props
    const node = document.getElementById('chart-instance') as HTMLCanvasElement
    // const data = this.memoizeDataProps()

    this.chartInstance = new ChartJS(node, {
      type,
      data,
      options
    })
  }
}

有人可以帮我弄清楚为什么这不能正常工作吗?

1 个答案:

答案 0 :(得分:0)

可能因为这个原因:

currentDatasets[idx].data.splice(retainedDataset.data.length)

您还应该检查retainedDataset.data

if (retainedDataset && retainedDataset.data) { ... }