How should I organize method-like functions for Redux’s plain object states?

Say I am dealing with 2D vectors, 3D vectors and matrices (and probably more data types) in my app, and I have to do math operations on them.

If not restricted to plain objects, I could define classes and organize those operations as methods:

class Vec2 {
  ...
  add(other: Vec2) -> Vec2 { ... }
  withZ(z: number) -> Vec3 {
    return new Vec3(this.x, this.y, z);
  }
  // returns (a, b, c), where ax + by + c = 0 is the line going through this and other
  line(other: Vec2) -> Vec3 {
    return this.withZ(1).cross(other.withZ(1));
  }
  ...
}
...
class Matrix {
  ...
  add(other: Matrix) -> Matrix { ... }
  mul(other: Matrix) -> Matrix { ... }
  ...
}

However, with Redux I should store them as serializable objects, so:

  • Vec2 is [number, number]
  • Vec3 is [number, number, number]
  • Matrix is number[][]

With all the methods separated from the data, I have to rewrite them as conventional functions working on those plain data, but how should I organize all those method-like functions?

I have come up with 3 plans by far:

  1. Put all functions at the same level, with good old C-style naming (or some ad-hoc naming):

    type Vec2 = [number, number]; // similar for Vec3 & Matrix
    export function vec2Add ...
    export function vec2WithZ ...
    export function vec2Line(v1: Vec2, v2: Vec2) -> Vec3 {
      // so nostalgic writing code like this!
      return vec3Cross(vec2WithZ(v1, 1), vec2WithZ(v2, 1));
    }
    ...
    export function matrixAdd ...
    export function matrixMul ...
    

    Another downside shows up when importing those functions, I have to explicitly list every function used (rather than a single #include in C).

  2. Wrap plain data in rich objects, do the computation, and unwrap

    type Vec2 = [number, number];  // plain data
    class Vec2 { ... }  // rich object
    // FIXME name clash
    
    // reducer be like:
    const somePoint = new Vec2(state.somePoint);
    const newLines = action.newPoints.map((p) => somePoint.line(new Vec2(p)));
    if (...) {
      return { ...state, lines: newLines.map((l) => l.plainData()), ... };
    }
    ...
    

    This looks pretty nice (and quite like what math.js does), except for the naming: I have to name both the plain type and the class. Unfortunately I am not sure which one I should name as simplly Vec2 (and make the other type name more verbose).

  3. Group functions in namespaces (global objects, classes or even modules):

    // using object
    export const Vec2Op = {
      add: ...
      withZ: ...
      line: ...
    };
    
    // using class
    export class MatrixOp {
      static add ...
      static mul ...
    }
    

    This also makes the code quite verbose.

Is there a better way of organizing those functions?