글을 시작하기에 앞서 저는 열렬한 React 유저입니다. React와 TypeScript를 사용해서 여러 토이프로젝트를 진행하고 있습니다. 익히 알고 있듯이 React는 쉬운 사용과 견고한 코드 구조, 커뮤니티, 생산성 외에도 엄청난 이점이 있습니다. 그러나 React에도 치명적인 단점이 있습니다. 이 포스트에서는 React의 단점과 그 대안인 Lit을 소개합니다.
바야흐로 프론트엔드의 전성기다. jQuery를 시작으로 React, Vue, Angular, Svelte 등 프론트엔드 프레임워크와 라이브러리들이 우후죽순 생겨났고 여전히 그 강자는 React가 차지하고 있다. 2013년 React가 만들어질 당시만 해도 프론트엔드 개발은 그저 서버에서 받아온 정보를 뿌려주는데 그쳤다. 물론 성능도 떨어졌다. 하나하나 DOM을 업데이트해서 정보를 변경해주는건 자원낭비였고 코드 구조화도 제대로 되어있지 않았다. 그런 혼란스러운 상황에 React가 등장해서 컴포넌트 중심 개발을 주도했고, 그 결과 React는 현대 웹 프로젝트에 필수가 되었다.
React에도 단점은 존재한다. 짧게 소개하고 넘어가겠다.
React는 SPA다. 한 페이지를 로딩하는데 있어 모든 JS파일을 한번에 다운로드 한다. 사용자는 다운로드 시간동안 흰색 바탕화면을 보고있어야 하고 네트워크 상태에 따라 기간이 길어지는 문제가 있다.
빠르게 컨텐츠를 제공해야하는 웹사이트에 SPA는 적절하지 않다. React는 클라이언트 사이드 렌더링 방식을 채택한다. 일단 JS 번들링 파일을 서버로부터 가져온 후 화면을 보여주어야 하기 때문에 그 공백기간(랜더링시간)이 필연적으로 걸릴 수 밖에 없다. 랜더링 시간이 길다면 사용자는 금방 떠난다.
로딩시간이 3초 이상 걸리면 사용자는 이탈한다. 굳이 통계를 확인하지 않아도 본인의 평소 모습을 돌이켜보면 알 수 있을 듯 하다. 이 글을 읽는 여러분도 마찬가지로 로딩시간이 오래걸리면 다른 사이트를 찾아다니지 않던가.
아래 그림은 크롬 환경에서 Native DOM과 React의 메모리 점유율 정도를 나타낸 그래프다. Row가 증가할 수록 React의 메모리 사용량은 급격하게 늘어나는 모습을 볼 수 있다.
React는 화면에 그려지는 성능을 높이기 위해 메모리에 Tree구조로 DOM을 저장한다. 이때 그려지는 화면이 복잡한 만큼 메모리에 올라간 Virtual DOM의 크기는 커질 수 밖에 없다. 즉, 일반적으로 DOM을 조작하는 방식보다 메모리를 많이 점유하게 된다.
물론 모든 상용 라이브러리에 해당하는 말이지만, React도 언젠간 사라진다. 프론트엔드 패러다임이 변하고 그 상황에 맞는 기술이 등장하면 이전 기술은 힘을 잃게된다. 따라서 개발자은 이러한 종속성을 늘 경계해야 한다.
종속성에 대한 간단한 예시를 들자면, 유니티 사태를 예로들 수 있겠다. 전 세계에서 많이 사용하는 게임엔진 중 하나인 유니티는 최근 과금 구조 개편을 강행했다. 그 과금구조는 "설치할 때마다 개발자는 비용을 지불하라" 였다. 만일 악성유저가 지우고 깔고를 반복하면 개발자는 큰 타격을 입게 된다. 이런 정책에 많은 인디게임 회사와 개발자들이 반발했고 유니티는 정책 변경을 고려하겠다고 답했다.
하나의 기술 provider가 사라지거나 대체된다면 그 기술을 사용하는 개발자들에게는 치명적이다. 따라서 개발자는 근본적인 기술을 찾아 배워두거나 여러 프레임워크를 다룰 수 있는 능력을 개발해서 개발 의존성을 줄여나가야 한다.
React도 마찬가지다. 오픈소스와 커뮤니티, 다수의 회사에 기대고 있으나 그 인기는 언제 식을지 모른다. 지금도 Vue, Svelte등이 React를 위협하고 있다.
소개한 React의 단점을 느낀 개발자들은 Web Components 진영으로 넘어갔다. 여기에 더해 Web Components를 기반으로 하는 Lit이 등장했다. 이 포스트에서 최종적으로 소개할 Lit을 알기 위해서는 먼저 이 표준안을 이해해야 한다.
웹 컴포넌트 표준이 정리되기 전 JS 컴포넌트에는 많은 문제점이 있었다. 우선 성능이 느렸다. 웹 로드가 완료된 이후 적용되었고 당시 모바일 기기의 성능은 그다지 좋지 못했다. 두번째로 적용 및 개발이 어려웠다. JS 컴포넌트를 만들고 사용하려면 JS, 스타일 파일을 포함하고 특정한 구조를 채택해 개발해야 했다. 즉 협업이 어려웠다.
이런 문제를 해결하기 위해 웹 컴포넌트 표준이 제안되었다. 당시 존재했던 HTML Components와 비슷한 방식으로 엘리먼트를 만드는 기술이다.
물론 웹 컴포넌트만을 가지고 실제 개발을 하기에는 무리가 있다. 그럼에도 웹 표준이라는 점과 더불어 웹이 사라지지 않는 한 사라지지 않는다는 점이 지금도 사용되는 이유다.
Web Components의 주요 표준안중 하나인 Custom Element의 문법을 알아보자.
우선 JS를 잘 알아야 쓸 수 있다. 초기에 문법을 익히고 패러다임을 잘 적용하기 위한 학습곡선은 조금 높은 편이다. 표준 API의 특성상 상당히 불친절하고 프로토타입의 느낌이 강한데, 이는 어쩔 수 없는 문제인가 보다.
또한 매번 customElements.define
을 적어주어야 하니 여간 귀찮은게 아니다. 이런 이유로 뒤에서 설명할 Lit은 데코레이터 문법을 채택한다.
여기서 constructor, connectedCallback만 클래스에 포함된 함수고 나머지는 모두 직접 구현해야 한다. 즉, 어떤 특정한 기능을 만들기 위해서는 직접 개발해야 한다. 그만큼 자유도가 높지만 처음 배우는 사람에게는 어려울 수 밖에 없다.
class TestCode extends HTMLElement {
constructor() {
super();
}
render(){
const template = this.template();
this.innerHTML = template;
}
template() {
return `<div> ... </div>`
}
connectedCallback() {
this.render();
}
}
customElements.define('test-code', TestCode);
<test-code></test-code>
커스텀 엘리먼트 자체로는 간단한 HTML 엘리먼트를 만들때 사용한다. HTML 기본 엘리먼트인 <input></input>
을 변형하거나 캘린더 컴포넌트 등 프로젝트에 원자 단위로 이식할 경우에 사용하게 된다.
다만 프로젝트가 커질경우 관리가 힘들기 때문에 구조 설계가 필요해진다. 필자가 개발한 nugget.studio 영상편집 프로그램이 커스텀 엘리먼트로 구성되어 있는데 이때 설계하지 않고 대충 개발했더니 컴포넌트 의존성 구분이 안되고 있다. React의 경우 소스가 꼬이지 않도록 최소한의 설계 문법을 구성해놓는데, Custom Element는 자유도가 높아 어떻게든 구현은 가능하니 생기는 문제다.
그럼에도 JS문법을 잘 알고있고 설계 원칙을 적용할 수 있을 정도의 고급 개발자들은 상용 프로젝트에도 잘 이식할 수 있을거라 생각한다.
이와 관련해서 잘 설명된 영상이 있으니 첨부한다.
깃헙 개발자들이 React 안쓰는 이유 : Web Component
커스텀 엘리먼트를 React처럼 사용할 수 있도록 한게 Lit이다. TypeScript를 기본으로 지원하며 Redux도 적용할 수 있다. 즉, JS에서 사용 가능한 라이브러리는 대부분 사용 가능하다.
Lit은 직관적이고 간단한 문법을 제공한다. 아래는 공식 홈페이지에서 가져온 코드다. 특히 데코레이터 구문을 활용해서 불필요한 문법을 간소화한게 인상적이다. 앞서 언급한 커스텀 엘리먼트 보다 스타일링, props가 추가되었고 백틱을 이용한 랜더링도 편리하게 추가되었다.
import {html, css, LitElement} from 'lit';
import {customElement, property} from 'lit/decorators.js';
@customElement('simple-greeting')
export class SimpleGreeting extends LitElement {
static styles = css`p { color: blue }`;
@property()
name = 'Somebody';
render() {
return html`<p>Hello, ${this.name}!</p>`;
}
}
<simple-greeting name="World"></simple-greeting>
만일 여기서 데코레이터 구문을 사용하지 않았다면 컴포넌트를 생성할때마다 customElements.define('app-root', AppRoot);
이런식의 구문을 추가해주어야 한다. Lit의 문법이 앞서 언급한 Custom Element 보다 훨신 간결하다.
랜더링 부분은 모두 백틱 html`
을 사용한다. React는 백틱 없이 ()에 감싸서 훨씬 깔끔해 보이지만 Lit은 그렇지 않다. 그래서 VSCode에서 Lit 코드를 실행하면 초록색(일반 텍스트) 색으로 그려진다. 게다가 자동완성같은 기초적인 편의기능도 제공하지 않는다. 이 문제를 해결하기 위해 lit-html 익스텐션을 설치해서 Lit 코드를 인식하도록 해야한다.
랜더링은 아래와 같이 render()
함수를 이용한다. 이때 Shadow DOM이 기본으로 적용된다.
render() {
return `...
}
만일 Shadow DOM을 해제하고 싶다면 아래와 같이 createRenderRoot()
함수를 랜더링 밑에 적어주면 된다.
render() {
return html`<p>This Is Web Components.</p>`;
}
createRenderRoot() {
return this;
}
React에서의 상태값은 @state() 데코레이터를 통해 지정 가능하다.
@state()
_counter = 0;
render() {
return html`<p>${this._counter}</p>`;
}
상태를 변경하기 위해서는 this._(지정한 state 이름)
을 변경하면 된다. 클릭했을때 _counter
상태를 변경하기 위해서는 아래와 같이 작성한다.
_increment() {
this._counter += 1
}
render() {
return html`<p @click="${this._increment}">${this._counter}</p>`;
}
@property는 React에 props에 해당한다.
@property({type: String})
name = '';
아래와 같이 입력받을 수 있다.
<test-component name="Test"></test-component>
property를 출력할때는 ${this.name}
으로 출력해주면 된다.
스타일은 CSS와 동일하게 작성해주면 된다. 그러나 필자의 경우 Object Style CSS in JS를 선호하는데 Emotion은 lit에서 지원하지 않는다. 만일 Shadow DOM을 해제한다면 가능하나 굳이 Shadow DOM의 장점을 해치고 싶지는 않다. 기본적으로 지원하는 스타일 방법은 아래와 같다.
static styles = css`
p {
color: green;
}
`;
만약 필자처럼 Object Style로 CSS를 작성하고 싶다면 아래와 같은 방법도 있다. 그러나 이때는 스타일 자동완성을 지원하지 않는다.
<p style=${styleMap(
{
color: "#000000"
}
)}>This Is Web Components.</p>
사실 Lit의 기본적인 문법은 이게 대부분이다. 이 외의 이벤트 핸들링, 데코레이터 사용법 등 세부적인 문법은 아래 공식문서에 있다.
https://lit.dev/docs/components/overview/
Lit은 React와 비슷하지만 성능면에서 훨씬 좋은 성과를 보여주고 있다.
이는 자연스레 초기 로딩 속도를 향상시킬 수 있는 방식으로 앞서 React 단점에서 언급한 로딩지연 문제를 해결할 수 있다.
커스텀 엘리먼트 기술을 사용한 만큼 네이티브 API와 비슷한 속도로 랜더링 성능이 향상된다. 물론 생 바닐라가 더 빠르겠지만 개발 생산성 측면에서 Lit을 사용하는게 훨씬 효율적이라 생각한다.
Lit은 웹 표준을 기반으로 한다. 언제든 다른 프레임워크로 이식할 수 있고 웹 표준인 만큼 동시에 다른 프레임워크로 개발할 수도 있다. 이는 향후 특정 프레임워크에 종속되지 않으며 결과적으로 리팩토링에 들어가는 비용을 획기적으로 절감할 수 있다.
실제로 lit의 npm 설치수는 꾸준히 늘어나고 있다. 그만큼 많은 사람들이 관심이 있다는 이야기고 이는 커뮤니티 형성으로 늘어날 여지가 충분하다. 다만 한가지 우려되는 사실중 하나는 lit의 개발사가 구글이라는 점이다. 구글이 플러터처럼 꾸준히 관리했으면 좋겠다.
lit이 그렇게 좋고, 이식성이 훌륭하다면 왜 많은 사람들은 lit을 사용하지 않는걸까.
몇 년 전 lit에 대한 reddit 글을 가져왔다. lit은 죽었고 커리어 향상에 도움이 안된다는 내용의 댓글이다. 사실 어찌보면 맞는말이다. stack overflow developer survey results 2023에 따르면 현재 대부분의 프론트엔드 개발자들은 React를 쓴다. 즉, 안쓰면 취업이 안된다.
수요가 없다면 주니어 개발자들이 굳이 배울 필요가 있을까. 보통 국비학원이나 부트캠프의 경우 취업을 목적으로 강의를 제공하는데 대부분이 React를 가르친다. 그만큼 수요가 높고 안정적이니까. 그렇게 React를 배운 주니어 개발자가 시니어 개발자가 되어 지속적으로 React를 개선시켜가는 생태계가 만들어 진다.
반면 Lit은 주니어 개발자들이 배우기에 적합한 라이브러리는 아니다. 특정한 목적(경량화)을 염두해두고 만들어졌고, React의 대체품이라는 느낌이 강하게 든다. 초기에 구현해야하는 양도 많아서 의식의 흐름대로 개발하면 스파게티 코드가 되는 놀라운 체험(!)도 할 수 있다.
그래서 Lit은 장기적인 개발 문화를 만들어가기에 적합한 라이브러리는 아니다.
그러나 앞서 설명했듯 Lit은 죽지 않았다. 적어도 아직까지는. Lit은 Custom Element를 잘 사용하기 위한 wrapper에 불과하고 이는 마치 JS와 TypeScript 관계와도 같다. 아마 최근에 있었던 TypeScript 퇴출 운동 처럼 Lit에서 Web Components로 넘어갈지도 모르지만 말이다.
Lit을 공부하기 위해서는 바닐라 JS에 대한 빠삭한 이해가 있어야 한다. 거기에 더해 컴포넌트 주도 개발 방법론과 원자화 전략 및 DRY원칙에 대해서도 숙지하고 있어야 하며, 의존성 분리와 기타 설계 방법들에 대해서도 이해가 필요하다. 무턱대고 개발하다가는 여기저기 의존성이 꼬여있는 코드를 발견하게 될거다.
React를 예로들자면 "setState함수를 props로 넘기지 마라" 같은 원칙이다. 즉, 이런 원칙들을 제대로 적용하고 코드를 유지보수 해 나가야 견고한 프로덕트를 만들 수 있다.
이는 커뮤니티 부족과 연결된다. React의 경우 SPA구현을 위한 라우터, React 전용 redux, React 디자인 컴포넌트 등 React 전용 라이브러리들이 대부분 구현되어 있다. 이러한 생태계는 필요한 모든 걸 개발할 필요 없이 오픈소스를 가져다 쓰기만 하면 되는 편의성을 제공하며, 라이브러리에 락 인(lock-in)효과를 부여해 많은 개발자로 하여금 떠나지 않고 지속적으로 참여하게끔 유도한다.
반면 Lit의 경우 오픈소스 진영이 부족하다. SPA 조차도 구현되어 있지 않고 개별적으로 구현해야하는 점이 아쉽다. 그러나 그만큼 자유도가 높고 기술에 대한 심층적인 이해가 있다면 빠르게 수정할 수 있으므로 숙련자에게는 좋은 도구가 될 가능성이 있다. 마찬가지로 React 또한 높은 자유도로 JS 숙련자들을 끌어모았다는 점에서 향후 발전 상황을 주목해볼만 하다.
필자는 스타일링 도구로 Emotion을 사용한다. Lit은 Shadow Dom 방식을 채택하고 있어 Emotion이 적용될 수 없다. 물론 Shadow Dom을 끄고 사용할 수 있으나 그렇게 되면 Lit을 쓰는 장점이 사라진다. 따라서 기본적으로 제공되는 css 라이브러리를 사용할 수 밖에 없다. 어찌보면 확장성 있어 보이면서도 페쇄적인건 기분탓인건가.
Webpack에서 데코레이터를 사용하는데 문제가 있었다. 트러블슈팅을 위해 검색해본 결과, 단 한 군데에서도 Lit관련 문제를 찾아볼 수 없었다. 자료가 심각하게 부족한 상황이고 이는 비단 국내뿐 아니라 해외에서도 동일한 문제다. 해당 문제에 대해서는 현재 해결한 상태고 tsconfig에 target을 ES2015로 바꾸면 된다는 깃허브 이슈탭을 보고 해결했다. 그래도 구글에서 개발한 라이브러리고 꾸준하게 지원이나 업데이트를 하기 때문에 잘 찾아보면 나오기는 한다.
정리하자면, 문제가 생겨 검색해도 잘 안나오고, 적용도 어려운 Lit을 굳이 현 시점에 사용할 이유는 없어보인다. 그럼에도 Lit은 매력적인 라이브러리다. 그 이유는 전망에 있다.
성능을 목적으로 한 프론트엔드 제품의 경우 자주 사용될 것으로 보인다. 공식 웹사이트에서도 simple, fast를 강조하듯이 React와 비교해 압도적인 성능 차이를 보여준다. 보다 네이티브에 가까운 성능을 내야하는 제품의 경우 가벼운 프론트엔드를 위해 채택할 수도 있다. 예를들면 웹 이미지 편집도구라던가 웹 기반 영상편집등이 있다.
문법은 React와 비슷하고 기존에 개발하던 관행대로 Lit을 사용해 개발할 수 있으니 결국 커뮤니티에 달려있다고 믿는다.
React의 인기는 계속될걸로 생각한다. Lit이 아무리 빠르고 문법이 표준에 가깝다고 해도 React의 생태계를 따라올 수 없다. Lit에는 디자인 컴포넌트도 없고 SPA를 개발하기 위한 라우터도 없다. 더해서 보안 문제 측면에서도 React가 더 많이 유지보수되고 있다. 만일 Lit으로 개발한 오픈소스가 많아진다면 도입을 고려해보는 기업이 늘어날지도 모른다.
간혹가다 커스텀 엘리먼트와 React를 비교하는 글을 찾아보곤 한다. 사실 말도 안되는 소린데, 이 두 기술의 목적이 다르기 때문이다. 올바른 질문은 Lit과 React를 비교하는거라 생각한다. 두 라이브러리의 차이는 성능에 있다고 본다. React를 사용하다보면 무겁다는 생각이 들고 실제로도 조금만 뭘 추가해도 번들링 시간은 오래걸리고 파일 크기도 계속 올라간다. 반면 Lit은 첫 인상부터 가볍다는 느낌을 주며 초기 랜더링 속도라던가 파일 크기도 React에 비해 가볍다.
.
이해를 돕기 위해 필자가 개발한 Lit 보일러플레이트도 첨부해둔다.
저도 Lit 에 공감하고 잘될거라고 생각했는데 생태계가 커지는 것 같아 기분이 좋네요
web component가 미래라고 생각했는데 저는 SSR과 타입스크립트, 쉐도우 돔이 Lit을 안쓰게 만들었네요
lit-ssr이 최근 진행중인 듯 하지만 아직 시간이 더 필요해 보여요.
web-component를 사용하는게 아이러니하게도 러닝커브가 더 높을 수 있겠다라고 생각했고 ,생태계가 커지는데 낮은 러닝커브가 중요하다고 생각했습니다.
저는 qwik의 철학에 공감하고 qwik에 집중하고 있답니다.
최근 도입되는 rsc로 클라와 서버 번들의 영역을 구분하기에 이전 모델처럼 기본적으로 클라 번들을 모두 호출하지 않아 번들사이즈가 획기적으로 감소했다고 알고 있습니다.. 이는 곧 로딩시간의 감소로도 이어지지 않을까요?
게다가 suspense나 이제 도입되려는 server action을 잘 활용한다면 lit 처럼 네이티브에 가깝지는 않지만 유저입장에서 그리 버겁지 않은 ux를 선사해줄 수 있다고 생각합니다!
물론 릿이 리액트보다 성능적으로는 더 낫다는 점은 동의합니다! 아마 무엇을 선택하는지는 레거시 및 앞으로의 상황이나 개발 대상에 따라 달라질 것 같습니다!
react를 쓴다고 무조건 초기 로딩 시간이 오래 걸리는건 아니잖아요?
개발하는 사람도 이정도는 염두해두고 개발을 하고, react를 쓴다는것 부터가 초기 로딩 시간에 민감한 사이트는 아니라는 의미죠.
"react는 초기 로딩 시간이 기니까 버리고 lit을 써야한다"라는 것부터가 "편협한 사고 방식에서 나오는 맹목적인 기술 의존"이 아닐까요?
기업에서 근무하는 개발자는 시장 점유율을 따라가면 됩니다.
트렌드에 민감하게 반응하지 마시고 유심히 지켜보시다가 많은 기업에서 원할 때 대체하면 됩니다.
어떤 기술이 좋다, 좋지 않다는 점유율을 보고 판단하시면 됩니다.
좋은 글 감사합니다. 확실히 러닝커브가 심하다면 대중화 되긴 어렵겠죠, 근데 그만큼 사용하는 인력에 대한 희소성은 커질 것이고(물론 수요도 있어야 하지만), 많은 개발자 분들이 언제가 Lit에 대한 관심이 생기길..!!
SSR만큼은 아니지만 번들 최적화를 통해 초기로딩속도를 많이 개선할 수 있습니다.
그리고 리액트 단점으로 검색엔진최적화가 어렵다는 부분도 있는데, Lit은 검색엔진최적화가 가능한가요?
단순히 수요가 없어서 점유율이 낮다기보다 왜 수요가 없는지 알려주시면 좋을 것 같네요.
Lit이라는 프레임워크는 처음 봤네요. 글 너무 잘읽었습니다. 😄
저도 언젠간 React를 사용하지 않아도 되는 날이 올꺼라 생각하긴 합니다만, Lit이 되었건, Svelte, Vue.js가 되었건 React와 React 기반 프레임워크를 쓰는 이유는 제가 생각하기로는 사용자 경험이지 않을까 싶습니다. 사용자 경험이 좋으므로 생산성이 좋아 많은 개발자들이 사용하는 것이지요. 서비스 개발은 취미가 아니고 금전적인 것이 관여되는 것이기 때문에 생산성이 가장 중요함으로 저는 아직까지 많은 개발자들이 React를 꾸준히 사용하는 것 같습니다.
아무리 자극적인 제목이라 해도 발작들 보소... 물론 제목이 좀 자극적이긴 하지만,
발작 일으킨 분들은 리액트 외에 다른 프론트엔드가 어떤 것이 있고, 조사해 봤는지 물어보고 싶어지는군요.
제가 저런 분들을 위해 '프론트엔드에 리액트가 전부인 개발자들에게' 로 어그로 시도했었죠.
뭐 아무렴 어때요. 저같은 경우 개인적으로 리액트는 서버 컴포넌트가 아니면 쳐다도 안볼 기술인 입장이라.
한국 밖을 나가면 물론 프론트엔드에 리액트 점유율이 높지만, 다른 기술도 있어 자바스크립트의 스펙트럼 중요성을 좀 알아봤으면 좋겠군요. 외국물 드신 개발자들은 이런 자극에 끄떡도 안 합니다.
가장 중요한 건 웹 컴포넌트 표준 호환성이죠. 현재 점유율이 있는 주요 프론트엔드 기술 중엔 리액트가 유일하게 웹 컴포넌트 표준을 지원하지 않습니다.
이게 뭐가 문제냐 하는 개발자도 있더군요... 에휴...
제가 10여년 전 HTML5에 열광하고 웹 컴포넌트 표준을 파고 발표까지 했는데... 지금 이 모습을 보니 참... 한국의 웹 기술 패턴은 10년 전이나 지금이나 달라진 게 하나도 없네요. 네. IE만 돌아가면 장땡이던 그 시절 말이죠.
이미 알고 있는 기술인 만큼 흥미롭게 읽었습니다. 앞으로도 이런 기술의 칼럼 자주 써주세요.
1-1. 로딩시간이 길다 - Next.js
1-3. React.js는 "오픈 소스"에요.
리액트가 비주류가 될 일은 없음.