React 기초 정리
last update: 2021.07.31.SAT
리액트란?
- 페이스북에서 개발, 자바스크립트 라이브러리
- 동적 유저 인터페이스(UI)를 구축할 때 용이
- 현재 가장 사용자 점유율이 높음(2021년 기준)
Virtual DOM
JSX
(JavaScript eXtension, 자바스크립트 확장 문법)를 이용컴포넌트(Component)
를 이용하여 코드를 모듈화Hooks
를 이용하여 컴포넌트를 쉽게 관리- 오픈소스
- 데이터는 부모→자식 단방향으로만 전달(props 이용)
리액트 초기 세팅
(정리중)
리액트 설치 직후 초기 파일 살펴보기
index.html
- 리액트로 만든 앱은 아무리 커도 이 root 태그 안에 들어가기로 약속한다.
- 따라서 아래 코드는 index.html에 기본적으로 포함되어야 하고, 리액트를 시작하면 파일에 기본적으로 들어가 있는 코드이기도 하다.
<div id="root"></div>
index.js
- 실질적인 입구역할을 하는 파일.
- src 폴더 내에 위치
-
ReactDOM.render();
- 해당 아이디(여기서는 root)를 가진 html태그 아래에 리액트를 렌더링 할 것이라는 의미
DOM(Document Object Model)
(문서 객체 모델) - HTML 요소를 노드로 포함하는 트리 구조
-
<React.StrictMode> </React.StrictMode>
- 리액트 앱이 로딩되는 부분
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; ReactDOM.render( <React.StrictMode> <App /> <React.StrictMode> document.getElementById('root') );
App.js
- index.js와 마찬가지로 src 폴더 내에 위치
-
- App.js → index.js → index.html 흐름 정리
- App.js의 내용 작성
- index.js에서 임포트
- 위 코드에서는 App.js 내에서 정의된 function App() 부분을 임포트 하여
- index.js 내의 ReactDOM.render() 내부에 있는
<App />
- ‘root’ 태그를 가진 html에 전달되어
- index.html의 <div id='root'>에서 코드가 실행됨
기타 파일 구조
(정리중)
핫 리로딩
출처 : https://reactnative.dev/blog/2016/03/24/introducing-hot-reloading
React-Native 앱을 개발하는 동안 코드 변경 사항을보고 코드 변경 사항을 보려면 React-Native에 두 가지 옵션이 있습니다.
- 핫 리로드 핫 리로드는 앱을 처음부터 다시 시작하지 않고 새로운 코드 변경에 따른 코드 변경 사항 만 표시하며 변경된 코드에만 적용됩니다.
- 라이브 리로드 때로는 탐색과 같은 코드를 테스트하기 위해 Live Reload가 필요할 수 있으므로 Live reload는 이 경우 유용하므로 코드 변경시 전체 응용 프로그램을 다시로드합니다. 참고 : http://daplus.net/javascript-react-native의-핫-리로딩과-라이브-리로딩의-차이점/
JSX
React.createElement()
- 코드틀
const element = React.createElement( 'div', // Tag name {className: show}, // Class name 'hi!' // Content ); // 위 아래는 같은 코드 const element = <div className="show">hi!</div>
- JSX 문법을 이용하지 않고 element를 객체로 표현할 수 있음
- HTML element를 생성하여 객체로 반환
- 엘리먼트는 한번 생성하고 나면 수정이 불가능한 객체(const element…)
⇒ 값 변경을 위해서는 계속 새 엘리먼트를 만들어서 업데이트 해야 함
⇒ setInterval()을 이용해서 tick을 지속적으로 하여 해결할 수 있음
- 코드 작동 순서 : 버튼 클릭 시 클릭 횟수 증가하여 반환하는 예제
- setInterval(tick, 1000), function tick 호출
- button onClick={click}, function click 호출
- value = value + 1하여 반환
- function tick의 element 내부에서 {value}값 업데이트
- ReactDOM.render(element, document.getElementById(‘root’);에서 element를 렌더링
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import * as serviceWorker from './serviceWorker'; //클릭 횟수 저장 변수 let value = 0 //클릭 횟수 증가 함수 function click() { value=value+1; } //엘리먼트 업데이트 & 렌더링 함수 function tick(){ const element = ( <div> <h1>버튼을 클릭해보세요</h1> <button onClick={click}>Click Me!</button> <h2>총 {value}번 클릭했습니다.</h2> </div> ); ReactDOM.render(element,document.getElementById('root')); } //1초마다 tick()함수 호출 setInterval(tick, 1000); serviceWorker.unregister();
- 코드 작동 순서 : 버튼 클릭 시 클릭 횟수 증가하여 반환하는 예제
- JSX 표현식
- 코드 틀
// variable const name = "JYP"; const element = <h1> Hi, {name}</h1>; // function function article(){ return "Hello World!" } const element = <h2>{article()}</h2>; // function with argument function bookInfo(book) return book.title + ' ' + book.author; const book = { title = 'One Piece' author = 'Oda Aiichiro' } function printInfo(book){ return <p>This is {bookInfo(book)}.</p>; } const element = article(book); ReactDOM.render(element, document.getElementById('root'));
주의사항
- HTML을 JSX로 변환할 때, 변수 내에 HTML코드를 저장하기
- 최상단 element는 반드시 하나
- 여러 자식 태그로 구성된 HTML 코드는 반드시 부모 태그 내에 구성하기
- 부모 태그처럼 <>, </>를 쓸 수도 있음
- 문장의 끝은 세미콜론(
;
)을 사용하기 - 닫는 태그를 필수로 작성해야 함
- class대신 className 사용
-
- 인라인 스타일 정의
- 참고 : w3schools-react_css
- CSS 속성명은 camel case로 작성
- “color: red;” (x) - 문자열 안됨!
- 세미콜론 대신 콤마를 사용할 것
- { { } } (o)
→ 바깥쪽 중괄호는 HTML태그 내에서 자바스크립트를 사용하겠다는 선언에 해당
→ 안쪽 중괄호가 내용을 담은 객체에 해당
import React from 'react'; import './App.css'; function App() { return ( <div className="App"> <div style=>안녕하세요.</div> </div> ); } export default App;
객체(Object)
- 데이터를 모아 묶은 것 { id: 1, title: ‘html’, description: ‘html is …’}
- 파이썬의 딕셔너리와 비슷하다고 이해
- 배열에는 데이터가 순서대로 담기지만, 객체는 데이터가 이름을 갖고 저장됨
컴포넌트
- 일종의 사용자 정의 태그
- 코드의 재사용성 증대, 복잡한 코드를 부품으로 만들 수 있음
- 첫글자는 대문자로 시작
- 모든 태그는 닫는 태그가 있어야 하며, 축약표현도 사용 가능 (<></>,
)
컴포넌트가 사용된 기본 코드
import logo from './logo.svg';
import './App.css';
function HeaderTag(){
return (
<header>
<h1>
<a href="index.html">WEB</a>
</h1>
</header>
)
}
function NavTag(){
return (
<nav>
<ul>
<li><a href="1.html">html</a></li>
<li><a href="2.html">css</a></li>
</ul>
</nav>
)
}
function ArticleTag(){
return (
<article>
<h2>Welcome</h2>
Hello, WEB
</article>
)
}
function App() {
return (
<div className="App">
<HeaderTag />
<NavTag />
<ArticleTag />
</div>
);
}
export default App;
속성(props)을 통해 컴포넌트에서 컴포넌트로 입력값 전달하기(1)
- Props : 컴포넌트로 값을 전달하는 매개변수
- 함수컴포넌트에서:
function 함수이름(props){ 함수내용 }
와 같이 쓴다. 입력인자 자리에 props가 들어감 -
클래스 컴포넌트에서: 입력인자 자리가 없는 대신 코드에서
this.props
또는this.props.name
과 같은 매개변수를 쓴다. (this.props.속성
)function ArticleTag(props){ console.log('props', props); //props가 담고 있는 내용을 콘솔에서 확인해보자 return ( <article> <h2>Welcome</h2> Hello, WEB </article> ) } function App() { return ( <div className="App"> <HeaderTag /> <NavTag /> <ArticleTag title="Welcome" description="Hello, WEB"/> </div> ); }
- {props}, {props.title}, {props.description} 과 같은 컴포넌트가 만들어짐
-
사용자 정의 태그로 사용하고 있음
import logo from './logo.svg'; import './App.css'; function HeaderTag(){ return ( <header> <h1> <a href="index.html">WEB</a> </h1> </header> ) } function NavTag(){ return ( <nav> <ul> <li><a href="1.html">html</a></li> <li><a href="2.html">css</a></li> </ul> </nav> ) } function ArticleTag(props){ console.log('props', props.title); return ( <article> <h2>{props.title}</h2> {props.description} </article> ) } function App() { return ( <div className="App"> <HeaderTag /> <NavTag /> <ArticleTag title="Welcome" description="Hello, WEB"/> <ArticleTag title="Hi" description="Hello, React"/> </div> ); } export default App;
속성(props)을 통해 컴포넌트에서 컴포넌트로 입력값 전달하기(2)
- props는 JSX attribute를 단일 object의 형태로 컴포넌트에 전달
-
props는 구조분해할당destructing해서도 사용
// props에 속성 1개 전달 function App(){ return ( <Welcome name="Elice" /> ); } function Welcome(props){ return <div>Hello my name is {props.name}</div> }
// props에 속성 2개 전달 function App(){ return ( <Welcome name="Elice" age="12" /> ); } function Welcome(props){ return <div>Hello my name is {props.name}, I'm {props.age} years old!</div>; }
// props에 속성 여러개 전달 function App(){ return ( <Welcome name="Elice" age="12" /> ); } function Welcome({name,age}){ // object destructuring assignment return <div>Hello my name is {name}, I'm {age} years old!</div>; }
함수 컴포넌트(Function Component)
- JSX를 return
- Hooks 사용 가능, state 사용 불가능
-
코드 기본 틀
import React from 'react'; // 함수는 클래스가 아니므로 상속 개념은 아니지만 // 여기서 임포트가 필요한 이유는 // return 내부가 JSX 반환하는 것이라고 코드가 이해해야 해서 필요 function Header(){ return ( <div> Hello </div>; // JSX 반환 부분 ) }
-
함수 컴포넌트 정의 방법 1) 컴포넌트 정의(대문자로 시작할 것) 2) 컴포넌트 호출 3) ReactDOM에 렌더링
/* ver 1 */ import React from 'react'; // JSX를 쓰기 위한 세팅 //함수 컴포넌트 작성 function Hello() { return <h1>Hello, React!</h1>; } //컴포넌트 호출 const element = <Hello/>; //렌더링 ReactDOM.render(element, document.getElementById('root')); Copy /* ver 2 - Welcome.js */ import React from 'react'; function Welcome(){ const name = "Sara"; return ( // JSX 반환 부분 <h2>Welcome, {name}</h2> ) }; export default Welcome; // App.js에서 쓸 수 있도록 해주는 부분 // 정확히는 Welcome이라는 함수를 다른 파일에서 쓸 수 있게 해주겠다고 설명하는 거 같음 /* ver2 - Welcome.js를 임포트 할 App.js */ import React from 'react'; import './App.css'; import welcome from './components/Welcome'; // Welcome을 임포트해서 사용하기 function App() { return ( <div className="App"> <Welcome /> /**/Welcome 컴포넌트 사용하기 </div> ); } export default App; // App도 어디선가 다른데에서 사용할 수 있게 해줬다 // index.js를 보면 거기서 쓰고 있다(렌더링하고 있음)
클래스 컴포넌트(Class Component)
공식문서 : https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Classes
- state 관련 기능 사용 가능
-
코드 기본 틀
import React from 'react'; // 여기서는 Header 클래스가 React.Component를 상속하니까 임포트를 해야 함 class Header extends React.Component{ render(){ //render() 메서드를 클래스 메서드로 포함해야 함 return ( //JSX 반환부분 <div> Hello </div> ) } }
- 코드 예제
-
Welcome.js
import React from 'react'; const name = 'Sara'; class Welcome extends React.Component{ render(){ return( <div>Welcome, {name}</div> ) } } export default Welcome;
-
App.js
import Rreact from 'react'; import './App.css'; import Welcome from './components/Welcome'; function App(){ return ( <Welcome /> ) } export default App;
-
문법
몇가지 유용한 내장 메서드
parseInt(문자열, 진법)
: 문자열을 해당 진법에 맞는 숫자로 변환###.length
###.value
###.textContent
리액트에서 함수를 호출하기
- 컴포넌트에서 함수를 바로 호출하지 않는다. 직접 다른 함수를 호출하지 않는다.
function HeaderTag(props){
debugger;
console.log('Header', props);
function onClickHandler(){
props.onChangeMode();
// props의 onChangeMode 호출(불러오기)
// function App()에서 onChangeMode는 onChangeModeHandler 함수를 호출하고 있다
// function App()의 코드 : onChangeMode={onChangeModeHandler}
}
return (
<header>
<h1>
<a href="index.html" onClick={onClickHandler}>{props.title}</a>
</h1>
</header>
)
}
this
- 자기 자신의 상위 객체를 의미
- 화살표 함수(Arrow Function)에서는 this를 사용할 수 없음
화살표 함수(Arrow Function)
const 함수내용을 담을 변수명 = (함수이름생략)(입력인자) => 인자.메서드;
const 변수명 = (입력인자) => 다른함수(입력인자);
//map 응용
const poweredArray = arrayA.map(el => pow(el));
- 익명함수
- 재사용하지 않을 때 사용 - 1회용 ⇒ 하지만 변수에 대입을 하면 일반 함수처럼 재사용 가능
- map, forEach 문 등에서 사용
-
화살표 함수 내용에 리턴문만 한 줄이 존재한다면 return, 중괄호 모두 생략 가능
const element = (array) => array.length; /* 동일한 코드 const element = (array) { return array.length; } */
-
함수의 입력인자가 하나이면 주변 괄호도 생략 가능
const element = array => array.length;
참고 : https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Functions/Arrow_functions
map(내장함수)
function NavTag(props){
var d = props.data;
return(
<nav>
<ul>
{d.map((e)=><li><a href={e.id}>{e.title}</a></li>)}
</ul>
</nav>
)
}
forEach
- 배열의 메서드
- 각 요소를 한번씩 순서대로 호출
- forEach내에 들어가는 함수로는 보통 화살표 함수를 사용
// 예제1
const arrayA = [1,2,3];
/* arrayA의 요소들이 각각 element에 한 번씩 대입됩니다.
그 후 각 요소들이 console.log를 통해 콘솔에 찍힙니다. */
arrayA.foreEach(element => console.log(element));
// 예제2
const pow = a*a;
arrayA.forEach(el=>console.log(pow(el));
//el = elements, e도 되고 el도 되고 element도 되고...
구조분해할당(Destructuring)
(정리중)
세 점 표기법(Three dots Expression)
- 나머지 표기법(Rest Expression)
- 구조 분해 할당 후 남은 값을 가져올 때 사용
- 해당 대상이 없으면 빈 배열 또는 빈 객체가 할당됨
const [a, b, ...rest] = [10, 20, 30, 40, 50]; // three dots expression console.log(rest); // expected output: Array [30,40,50] const obj = { d: '1', e: '2', f: '3', g: '4', }; const {d,e,...restObj} = obj; console.log(restObj); // expected output : Object { f: '3', g: '4' } // const [a, b, c, ...restList] = [10, 20, 30, 40, 50] // 어차피 안 쓸 변수에 메모리 할당을 하면 메모리 낭비니까 아래처럼 쓰자 const [, , , ...restList] = [10, 20, 30, 40, 50] console.log(restList) // output : [40, 50]
- 전개 표기법(Spread Expression)
- 원소 나열할 때 사용
- 객체에서도 사용가능
- 단, 객체에서는 같은 키가 존재한다면 나중에 쓴 내용으로 덮어쓰기 됨
let res = [1, 2, 3]; const arrayA = [...res, ...res] console.log(arrayA); // expected output : Array [1, 2, 3, 1, 2, 3] res = [...res, 4] console.log(res) // expected output : Array [1, 2, 3, 4] let obj = { a: '1', b: '2', c: '3', } const copiedObj = { ...obj, d:'4' } console.log(copiedObj); // expected output : Object { a: '1', b: '2', c: '3', d: '4'} const newObj = {...obj, c:'4'} console.log(newObj); // expected output : Object { a: '1', b: '2', c: '4'} let res = [1, 2, 3]; res = [...res, 4]; // expected output : [1, 2, 3, 4]** let obj = { a: '1', b: '2', c: '3', } const newObj = {...obj, a:'4', test:'test'} console.log(newObj) // expected output : {a: "4", b: "2", c: "3", test: "test"}
Prevent Default Behavior
- 리액트는 SPA(Single Page Application?), 따라서 a태그의 기본 동작인 페이지 이동을 막는 편
- 그 때 사용하는 코드 :
functionparameter.preventDefault();
function onClickHandler(e){
e.preventDefault();
props.onChangeMode();
}
State
- input의 데이터를 state를 이용해서 관리 → Controlled component
- 데이터를 필요할 때마다 element에서 가져오는 경우(리액트로 관리하지 않는 경우) → Uncontrolled component
-
함수 컴포넌트에서 사용하기 - useState
function Car(){ const [favorite,setFavorite] = useState("ice cream"); return( <div> <h1>I like {favorite}</h1> </div> ); }
-
클래스 컴포넌트에서 사용하기 - constructor(props)
class Car extends React.Component{ constructor(props){ super(props); this.state = { favorite: "ice cream"; } } render(){ return( <div> <h1>I like {this.state.favorite}</h1> </div> ); } }
- 예제
⇒ HeaderTag 부분의 I love WEB 을 누르면
⇒ setState가 ‘WELCOME’이 되고
⇒ if 문에 따라 mode === ‘WELCOME’이면
⇒ title, desc가 바뀌게 되고
⇒ <ArticleTag title={title} description={desc}/> 부분에 자동 적용되어
⇒ 화면에 보이는 내용이 바뀐다
function App() {
var [mode, setMode] = useState('WELCOME');
// useState 함수에 전달한 'WELCOME'이라는 인자값은 mode의 디폴트값이 됨
console.log('mode', mode);
var topics = [
{id:1, title:'html', description:'html is . . .'},
{id:2, title:'css', description:'css is . . .'}
]
function onChangeHeaderHandler(){
console.log('Header!!!');
setMode('WELCOME');
// mode에 맞는 기능이 실행되도록 하고 싶다.
}
function onChangeNavHandler(){
console.log('onChangeMode!!!');
setMode('READ');
// mode에 맞는 기능이 실행되도록 하고 싶다.
}
var title;
var desc;
if(mode === 'WELCOME'){
title = 'Welcome';
desc = 'Hello, React';
} else if(mode === 'READ') {
title = 'Read Mode';
desc = 'Hello, Read';
}
return (
<div className="App">
<HeaderTag title="I love WEB" onChangeMode={onChangeHeaderHandler} />
<NavTag data={topics} onChangeMode={onChangeNavHandler} />
<ArticleTag title={title} description={desc} />
<ArticleTag title={title} description={desc}/>
</div>
);
}
export default App;
setState()
- 특정 이벤트(클릭, 마우스오버 등)로 상태가 변하는 비동기적 변경
- 클래스 컴포넌트에서 constructor 부분에서 State가 초기화 됨
-
코드 틀
class Example extends React.Component { constructor(props){ super(props); this.state = {isToggleOn: "On"}; } isToggleOn(){ setTimeout( () => {this.setState( {isToggleOn: "Off"} ) }, 1000) } render() { return ( <div>The switch is {this.state.isToggleOn}</div> ); } } ReactDOM.render(<Example />, document.getElementById('root'));
이벤트
바인딩
- 이벤트 함수 선언시 바인딩은 필수
.bind()
- 단, 화살표 함수로 선언시 함수 선언과 이벤트 바인딩이 자동으로 됨
- 지역변수(this 와 같은)를 참조하기 위해서는 명시적으로 바인딩 해야 함
- 화살표 함수에는 바인딩 불필요, 애초에 화살표 함수에는 this를 사용하지 않음
// 예제1
class EventClass extends React.Component {
constructor(props) {
super(props)
this.state = {
message: 'Hello'
}
// 이벤트 바인딩
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({
message: 'Goodbye!'
})
}
render() {
return (
<div>
<div>{this.state.message}</div>
<button
//{/* 이벤트와 함수를 연결하세요. */}
onClick={this.handleClick}>
Click
</button>
</div>
)
}
}
//예제2
class EventClass extends React.Component {
constructor(props) {
super(props);
}
//handleClick 이벤트 정의, 인자값을 받아 alert()을 출력
handleClick = (data) => {
alert(`전달받은 인자값: ${data}`)
};
render() {
var data = "ABCDEFG"
return (
//data값을 인자값으로 제공하는 이벤트 핸들러를 작성
<button onClick={(e) => this.handleClick(data, e)}>
버튼을 눌러주세요!
</button>
);
}
}
조건부 렌더링
조건 ? expr1 : expr 2
조건이 true인 경우 → expr 1 실행 조건이 false인 경우 → expr 2 실행
기타 예제 코드
- 이 함수를 통해, items state에 데이터를 추가하면 별도의 DOM 조작없이 화면에 추가 된 요소가 렌더링 됩니다.
function GroceryList(props) {
const [items, setItems] = useState(["Milk", "Bread", "Eggs"]);
const [itemName, setItemName] = useState('');
function addItem() {
setItems([...items, itemName]);
}
return (
<div>
<h1>Grocery List</h1>
<ul>
{items.map(item => (
<li key={item}>{item}</li>
))}
</ul>
<input type="text" onChange="{e => setItemName(e.target.value)}" />
<button onClick={addItem}>Add React</button>
</div>
)
};
- React는 사용자의 입력을 기반으로 자신의 상태를 관리하고 업데이트 하는 제어 컴포넌트를 이용해 사용자 입력 시 자바스크립트 객체의 텍스트 값을 설정
- 이를 위해 먼저 상태를 정의해야 함
- 입력이 변경될 때마다 설정이 되어야 하여 HTML코드는 다소 복잡
const [itemInput, setItemInput] = useState("");
<input type="text" placeholder="Enter an item" value={itemInput} onChange={e => setItemInput(e.target.value)}
- React로 업데이트하기 - 새로운 목록 추가하기
- React 앱은 자바스크립트 변수의 전체 상태를 유지
- 변수의 각 항목을 매핑한 후 그에 대한 목록 요소를 반환하여 JSX에 표시
- 버튼을 누르는 기능을 정의
setItems
함수를 사용하여 기존 항목에 새 항목을 추가
⇒ 목록이 변경되었음을 자동으로 등록하고 UI를 자동으로 업데이트!
const [items, setItems] = useState(["Milk", "Bread", "Eggs"]);
<ul>
{items.map(item => (
<li key={item}>{item}</li>
))}
</ul>
<button onClick={addItem}>Add React</button>
function addItem() {
console.log(itemInput);
setItems([...items, itemInput]);
}
- 각종 에러 메시지와 해결방법
- DevTools failed to load source map: Could not load content for chrome-extension://pgjjikdiikihdfpoppgaidccahalehjh/webspeed.js.map: HTTP error: status code 404, net::ERR_UNKNOWN_URL_SCHEME (콘솔창에서 에러, 실행도 안 됨)
- index.js 가 비어있을 때 발생
- Each child in a list should have a unique “key” prop
- 목록을 for문으로 생성할 때(map등을 이용할 때) key라는 prop값을 부여해야 한다
- 키를 세팅하지 않으면 위의 에러가 난다
- key는 다른 것과 중복되지 않는 식별자를 써야 한다
- 다른 엘리먼트들과 다른 UNIQUE한 string값을 부여 (.toString()이용)
- 대부분 데이터의 id값을 key값으로 부여함
- index를 사용하는 경우도 있음
- 그러나 배열 내 아이템의 순서가 바뀔 수 있어서 좋지 않음
- 성능이 저하되거나 컴포넌트의 state와 관련한 문제가 발생할 수 있으므로
- id값을 쓰는 편이 나음
- ‘userState’ is not defined no-undef
import {useState} from 'react';
추가해서 해결
- Module not found: Can’t resolve ‘파일경로’
- 파일이 누락되었거나 파일 경로에 오타가 있을 경우 발생(점, 슬래시 등 체크)
- 컴포넌트의 입력인자에 숫자를 할당하려면?
<Profile name="JYP" age="20" /> (o) <Profile name="JYP" age={20} /> (o) <Profile name="JYP" age=25 /> (x)
- DevTools failed to load source map: Could not load content for chrome-extension://pgjjikdiikihdfpoppgaidccahalehjh/webspeed.js.map: HTTP error: status code 404, net::ERR_UNKNOWN_URL_SCHEME (콘솔창에서 에러, 실행도 안 됨)