Winston Guess
Winston Guess

Reputation: 117

Why is my parameter assumed to be of one type when it is actually another?

Why do I have to hack the type definition from material: Material | Material[]; to material: Material; to fix the error detailed below? TypeScript is seemingly assuming that the material parameter is of type Material[] even when I explicitly set it to Material. Am I missing something?

Typescript/ThreeJS Error:

this.obj3D.traverse((child) => {
    if (child instanceof THREE.Mesh) {
        // Error in line below:
        // Property 'shading' does not exist on type 'Material | Material
        child.material.shading = THREE.SmoothShading;
        child.material.side = THREE.DoubleSide;
        child.scale.set(this.scale, this.scale, this.scale);
        child.castShadow = this.castShadow;
        child.receiveShadow = true;
        child.material.needsUpdate = true;
    }
});

ThreeJS Type Definition:

export class Mesh extends Object3D {
    constructor(geometry?: Geometry, material?: Material | Material[]);
    constructor(geometry?: BufferGeometry, material?: Material | Material[]);

    geometry: Geometry | BufferGeometry;
    material: Material | Material[]; // Had to delete *| Material[]* to fix
    drawMode: TrianglesDrawModes;

    setDrawMode(drawMode: TrianglesDrawModes): void;
    updateMorphTargets(): void;
    getMorphTargetIndexByName(name: string): number;
    raycast(raycaster: Raycaster, intersects: any): void;
}

Upvotes: 1

Views: 338

Answers (1)

jcalz
jcalz

Reputation: 330216

As far as I can tell from your screenshots, the material property of a Mesh object could either be a Material object or an array of Material objects. TypeScript is helpfully warning you that you are treating child.material as a single Material object without checking if it actually is one.

Unless you are absolutely sure that every instance of Mesh your code will ever touch has a single Material object and not an array as its material property, it's a bad idea for you to alter the type definition in the library. If the library declaration file is correct and some Mesh objects have arrays of Material objects, your code will behave incorrectly at runtime as soon as you try to set the shading property of an array.

Instead, the right thing to do would be to check whether child.material is an array, like:

  const doStuffToMaterial = function(m: Material):void {
     m.shading = THREE.SmoothShading; 
     m.side = THREE.DoubleSide;
  }
  // check for an array
  if (Array.isArray(child.material)) {
    child.material.forEach(doStuffToMaterial);
  } else {
    doStuffToMaterial(child.material);
  }

which handles the array case by performing the same action on each element.

Or, if you're sure that child.material is not an array, you can do something like:

  // Assert that it's not an array
  const m = child.material as Material; 
  m.shading = THREE.SmoothShading; 
  m.side = THREE.DoubleSide;

where you tell TypeScript you know that child.material is not an array, by using a type assertion. This could still do bad things at runtime if your assertion turns out to be wrong, but presumably you know better.

I hope one of those solutions works for you. Good luck!

Upvotes: 1

Related Questions