joe-sky/jsx-sfc

Summary of magical TS type problems

joe-sky opened this issue · 0 comments

I record some magical TS type problems here, and then study and analyze the causes. Some of them should be limitation of TS, you can refer to this issue.

The following are type errors and their current solutions:

1. Variables at root child of React.Fragment

✖️ Type error

const App = sfc({
  Component() {
    return { title: 'hello' };
  },

  render: ({ data }) => (
    <>{data.title}</>  // data.title is any
  )
})

✔️ Type passed

// You can do either of the following:
const App = sfc({
  Component() {
    return { title: 'hello' };
  },

  render: ({ data }) => (
    <React.Fragment>{data.title}</React.Fragment>
  )
})

const App2 = sfc({
  Component() {
    return { title: 'hello' };
  },

  render: ({ data }) => (
    <div>
      <>{data.title}</>
    </div>
  )
})

2. If styles is a function, you must use arrow functions to infer types correctly

✖️ Type error

const App = sfc({
  Component({ styles: { Container } }) {  // Container doesn't exist
    return (
      <Container>test</Container>
    );
  },

  styles() {
    return {
      Container: styled.section`
        color: #fff;
      `
    };
  }
});

✔️ Type passed

const App = sfc({
  Component({ styles: { Container } }) {
    return (
      <Container>test</Container>
    );
  },

  styles: () => ({
    Container: styled.section`
      color: #fff;
    `
  })
});

You can refer to similar problems in Vue 3.

3. In JSX(TSX is ok), static function must use the return statement to infer types correctly

✖️ Type error

const App = sfc({
  Component({ utils, constant: { LAST_NAME } }) {  // utils and LAST_NAME are both any
    ...
  },

  static: () => ({
    constant: {
      LAST_NAME: 'sky'
    },

    utils: {
      connectName: (firstName, lastName) => `${firstName}_${lastName}`
    }
  }),

  ...
});

✔️ Type passed

const App = sfc({
  Component({ utils, constant: { LAST_NAME } }) {
    ...
  },

  static: () => {
    return {  // need to explicitly use the return statement
      constant: {
        LAST_NAME: 'sky'
      },

      utils: {
        connectName: (firstName, lastName) => `${firstName}_${lastName}`
      }
    };
  },

  ...
})

4. In JSX(TSX is ok), when Component is arrow function, if there is any type in the function parameter during type inference, the whole data will be inferred as any

✖️ Type error

const App = sfc({
  Component: () => {
    return {
      onChange: e => console.log(e.target.value)
    };
  },

  render: ({ data }) => (
    <button onClick={data.onChange}>test</button>  // onChange is any
  )
})

✔️ Type passed

const App = sfc({
  Component() {  // need to use the object property function syntax
    return {
      onChange: e => console.log(e.target.value)
    };
  },

  render: ({ data }) => (
    <button onClick={data.onChange}>test</button>
  )
})

5. The data parameter of the render function doesn‘t support direct deconstruction

✖️ Type error

const App = sfc({
  Component() {
    return { title: 'hello' };
  },

  render: ({ data: { title } }) => (
    <div>{title}</div>  // title is any
  )
})

✔️ Type passed

const App = sfc({
  Component() {
    return { title: 'hello' };
  },

  render: ({ data }) => {
    const { title } = data;
    return <div>{title}</div>;
  }
})

This problem should be caused by the type circular reference problem of TS, there is no perfect solution for this problem. At present, my solution is to use extends to remove a layer of circular reference. For details, please refer to here.

6. Define functions in static function

✖️ Type error

const App = sfc({
  Component({ func, func2 }) {
    ...
  },

  static: () => {
    return {
      func: (a: string, b = false) => { // with default parameters
        ...
      },

      func2() {
        ...
      }
    };
  }
})

✔️ Type passed

const App = sfc({
  Component({ func, func2 }) {
    ...
  },

  static: () => {
    function func(a: string, b = false) {
      ...
    }

    return {
      func,

      func2: (a: string, b?: boolean) => { // must use arrow function
        ...
      }
    };
  }
})