이 글을 읽기에 앞서
이 게시글은 리액트처럼 동작 하는 코드를 작성해보는 것이지 리액트와 100% 동일한 코드를 작성하려 하는 것이 아닙니다.
JSX
jsx는 javascript + xml의 줄임말이다.
이걸 쓰면 html마냥 쓸수가 있다.
파악해보기
근데 이걸 어떻게 html마냥쓸 수가 있지???라는 생각이 들었다.
이건 과연 내부적으로 어떻게 처리가 되는걸까?
바벨의 트랜스파일링을 확인해보자
링크에서 위 이미지를 직접확인할 수 있다.
jsx라는 함수로 변환되는 것을 볼 수 있다. 좀더 복잡한 형태는 어떨까?
요렇게 변환되는 것을 볼 수 있다.
jsx함수 파악하기
그럼 jsx함수는 어떻게 작동하는지를 보자
우선 jsx나 jsxs나 똑같이 jsxProd를 호출한다. 그리고 위의 import문을 보면 알 수 있듯이 jsxProd는 jsx함수다.
그럼 요 링크로 jsx의 함수부를 보자.
마지막에 ReactElement를 호출한다.
ReactElement함수는 element를 반환하는데 인자로 받은 type, key, ref, props를 넘겨준다.
JSX 결론
결론적으로는 하나의 객체형태로 반환되는 것을 볼 수 있다!
JSX적용해보기
나는 vite번들러를 사용해서 JSX를 변환해줄 것이다.
https://ko.vitejs.dev/guide/features.html#jsx
esbuild옵션에 jsxFactory옵션을 줄 수 있는데, 여기서 입력으로 준 값이 변환해줄 함수가 된다.
실제 리액트는 굉장히 잘 쪼개져있는데, 나는 저렇게까지 쪼갤 생각은 없으므로 React.createElement함수로 바꿔주겠다.
vite.config.js
import { defineConfig } from 'vite';
export default defineConfig({
esbuild: {
jsxFactory: 'React.createElement',
},
});
자 이제 이렇게 한뒤에 잘 변환이 되는지 확인해보자.
폴더구조는 아래와 같다.
App.jsx
const App = () => {
return <div></div>;
};
export default App;
index.js
import App from './src/components/App';
console.log(App);
자 이제 어떻게 console이 찍히는지 확인해보자.
JSX가 vite.config.js에 작성한 React.createElement를 호출하는 코드로 변환되었다!
중첩된 JSX의 경우
좀더 복잡한 JSX는 어떻게 변환되는지 확인해보자.
const App = () => {
return (
<div className="asdf">
<h3>제목</h3>
<span>내용</span>
</div>
);
};
export default App;
JSX태그를 만날때마다 잘 변환해주는 것을 알 수 있다!
또, 잘 보면 규칙이 보인다.
형태가
변환해줄함수(태그이름, props들, 내부JSX, 내부JSX)
인것을 알 수 있다!
요소가 더 많을시에는
변환해줄함수(태그이름, props들, 내부JSX, 내부JSX , 내부JSX , 내부JSX... )
이렇게 들어가게 될것이다.
그럼 이를 기반으로 실제 호출될 React.createElement함수를 작성해보자.
React.createElement
사실 함수이름은 내맘대로 붙인것이다.
다른걸로 해도 무방하다.
변환해줄함수(태그이름, props들, 내부JSX, 내부JSX , 내부JSX , 내부JSX... )
형태로 받아야 하므로 이런 형태로 작성해주자.
src/core/createElement.js
const createElement = (tagName, props, ...children) => {
const element = {};
element.tagName = tagName;
element.props = props;
element.children = [...children];
return element;
};
간소화 시켜서 적으면 이렇게 적을 수 있다.
export const createElement = (tagName, props, ...children) => {
return {
tagName,
props,
children,
};
};
이걸 React객체에 담아서 export하자.
src/core/React.js
import { createElement } from './createElement';
export const React = {
createElement,
};
자 이제 이걸 App.jsx에 import만 해주면된다!
src/components/App.jsx
import { React } from '../core/React';
const App = () => {
return (
<div className="asdf">
<h3>제목</h3>
<span>내용</span>
</div>
);
};
export default App;
index.js에서 App을 호출한뒤에
import App from './components/App';
console.log(App());
이제 결과를 보자!
재귀적으로 React.createElement가 호출되면서 children에 다시 element객체가 들어간 것을 확인할 수 있다!
결과적으로 App컴포넌트에 몇개의 중첩 JSX가 있는지는 상관없이, 중첩객체 하나로 return되게 된다!
컴포넌트에서 컴포넌트를 부르는경우
그런데 예외인 경우가 하나 있다.
컴포넌트에서 컴포넌트를 부르는 경우이다...!
import { React } from '../core/React';
export const Header = () => {
return <header>헤더임</header>;
};
const App = () => {
return (
<div className="asdf">
<Header />
<span>내용</span>
</div>
);
};
export default App;
이런 경우, 결과값을 보자.
tagName에 ()=>{}형태의 함수가 들어있고 이름은 Header인 것을 볼 수 있다.
내부 함수는 JSX인데, 이게 호출되지 않아서 고대로 들어간 것이다.
그럼 tagName이 함수인경우에 한해서 예외처리를 해주면 된다.
tagName이 함수인경우, 그 함수를 불러서 제대로 객체로 변환될 수 있게 해주자.
export const createElement = (tagName, props, ...children) => {
if (typeof tagName === 'function') {
return tagName(props, ...children);
}
return {
tagName,
props,
children,
};
};
잘변환되는 것을 확인할 수 있다!
다음 게시글에서는 이 객체요소를 DOM에 렌더 시켜 볼 것이다.
https://github.com/d0422/make-react