본문 바로가기

React

이슈 11 React.createElement: type is invalid

반응형

2020/01/17 - [엥휴/Electron-Webpack + React] - 단 한 줄! index.js로 내 코드 깔끔하게 import하기에서 소개한대로, export default를 통해 각 파일에서 클래스를 내보낸 뒤, index.js를 통해 import 하다보니, 두 개 이상의 클래스를 export 해야하는 상황이 찾아왔다.

 

단 한 줄! index.js로 내 코드 깔끔하게 import하기

수정 사항 이 코드는 export default class에 대해 작동하지만, export { foo, bar }와 같은 형식에선 작동하지 않습니다. 이에 대한 수정 사항이은 아래 게시글에서 확인하실 수 있습니다. 이슈 11 React.createE..

roomedia.tistory.com

이때 export default { foo, bar }처럼 클래스를 JSON으로 만들어 처리했다. index.js는 아래와 같이 변형했다.

const glob = require('glob')
const path = require('path')

const folder = './src/components/'
const select = '!(index)'
const format = '.js'
const local = folder + select + format

module.exports = {}
glob.sync(local).forEach(file => {
  const name = path.basename(file, format)
  const m = require('./' + name).default
  if (typeof(m) === "function") {
    module.exports[m.name] = m
    return
  }
  if (typeof(m) === "object") {
    module.exports = {
      ...module.exports,
      ...m,
    }
    return
  }
})

아래와 같은 에러가 발생한다.

Warning: 
React.createElement: type is invalid -- expected a 
string (for built-in components) or a 
class/function (for composite components) but got: 
undefined. You likely forgot to export your 
component from the file it's defined in, or you
might have mixed up default and named imports.

Uncaught Error: Element type is invalid: expected a string
(for built-in components) or a class/function (for
composite components) but got: undefined. You 
likely forgot to export your component from the 
file it's defined in, or you might have mixed up
default and named imports.

좀더 테스트 해보니 같은 클래스를 외부 폴더에서 import 했을 때는 불러와지나, 같은 폴더 내 다른 파일에서 불러올 때는 값이 undefined가 된다.

1차 시도

분명 export default { foo, bar }에 문제가 있다. 처음에는 default가 문제인 줄 알았다. 그래서 default를 없애고 index.js도 다음과 같이 바꾸었다.

const glob = require('glob')
const path = require('path')

const folder = './src/components/'
const select = '!(index)'
const format = '.js'
const local = folder + select + format

module.exports = {}
glob.sync(local).forEach(file => {
  const fileName = path.basename(file, format)
  const m = require('./' + fileName)
  if (m.default !== undefined) {
    const { name } = m.default
    module.exports[name] = m.default
  } else {
    module.exports = {
      ...module.exports,
      ...m,
    }
  }
})

음... 여전하다..!

2차 시도

require을 통해 가져오거나, index.js를 거치지 않고 문제가 되는 파일만 따로 import 하면 정상 사용이 가능하지만, 점점 관리가 불편해질 것 같다는 생각에 방법을 더 찾아봤다. require와 import의 차이에 대해 검색하던 중 아래와 같은 글을 발견했다.

 

[javascript] require vs import (CommonJs와 ES6)

CommonJs, AMD, ES6 Module require 와 import 에 대해서 비교해 보기 위해서는 우선 CommonJs와 AMD(Asynchronous Module Definition), ES6 내장모듈과 같은 자바스크립트의 모듈 시스템에…

blueshw.github.io

중요한 부분은 결론

바벨과 같은 트랜스파일링 모듈을 사용한다면 주저없이 ES6 를 사용합니다. 혼용하는것도 가능하지만 가급적이면 통일되게 사용하는 것이 좋다고 생각합니다. 실제로 제가 프로젝트 진행하면서 mocha 테스트 중 ES6 의 import/export 와 CommonJs 의 module.exports 를 혼용하여 사용시 문제가 발생했었습니다(자세히 언급하지 않음).

이 의견을 보고나니 ES6의 export 자체 문제가 아니라는 생각이 들었고, webpack 문제가 아닐까 짐작했다. 빙고. 금방 비슷한 문제를 겪은 사용자를 찾을 수 있었다.

 

export default returns undefined · Issue #7767 · webpack/webpack

Bug report What is the current behavior? index.js import two from './two'; console.log('index'); console.log(two); function foo () { return 'index foo' } export default foo;...

github.com

친절한 답변. default 개념을 익히는 데 도움이 되었다. 일단 아래 두 코드는 같은 표현이며, foo 값이 무엇이던 그걸 default로 설정한다는 의미이다.

export default function foo () {
  return 'index foo'
}

function foo () {
  return 'index foo'
}

export { foo as default };

아래 두 코드도 같은 표현이지만 의미가 살짝 다르다.

export default foo;

const _default = foo;
export { _default as default };

위 코드는 모듈이 const _default = foo;를 수행하기 전에는 TDZ error가 발생하거나, default 값이 undefined로 설정될 수 있다. 이걸 기반으로 궁리를 해봤는데.. 역시 모르겠다.

해결

답은 의외로 간단했다. 동기적으로 실행되는 반면, default가 아닌 모듈들은 구조 분해 할당을 통해 비동기적으로 실행된다. forEach도 비동기, 구조 분해 할당도 비동기이니 개발자가 실행 시점을 예측하거나 제어하기 매우 힘들다. 고로 코드를 동기적으로 바꿔주었다.

const glob = require('glob')
const path = require('path')

const folder = `./${__dirname}/`
const select = '!(index)'
const format = '.js'
const local = folder + select + format

module.exports = {}
for (const file of glob.sync(local)) {
  const fileName = path.basename(file, format)
  const m = require('./' + fileName)
  for (const ele of Object.values(m)) {
    module.exports[ele.name] = ele
  }
}

이중 for 문이 밉지만... 뭐 상관하지 않기로 했다. 겸사겸사 folder도 general 하게 바꿨다.

반응형