wafflestudio / seminar-2022 Goto Github PK
View Code? Open in Web Editor NEW와플스튜디오 20.5기 Rookies Repository
와플스튜디오 20.5기 Rookies Repository
특별한 몇몇 경우가 아닌 이상 getElementById
나 기타 방식으로 DOM 객체를 직접 다뤄야 할 일은 별로 없습니다. 99%의 경우 useState
로 커버되고, 0.99%는 useRef
로 DOM을 얻어올 수 있습니다.
a
요소 (또는 Link
컴포넌트)어떤 요소를 클릭하면 페이지가 이동하는 경우, a
나 Link
로 구현해주시는 게 접근성 면에서 좋습니다. 브라우저가 링크로 인식한다는 점에서 스크린 리더나 브라우저 확장기능과 잘 결합할 수 있고, 커서를 올렸을 때 이동할 링크가 나타난다는 특징도 있습니다.
input 요소의 값이 리액트에서 사용되는 경우 onChange
와 value
props를 둘 다 사용하여 관리해야 합니다. 예기치 않게 input의 값과 react의 값이 달라지는 경우를 막을 수 있습니다.
useEffect
안에서함수 컴포넌트 안에는 훅 함수(useState
, useEffect
등)를 제외하고는 순수한 동작만 있어야 합니다. 즉, setTimeout
으로 타이머를 설정하거나 상태를 변경하는 등 순수하지 않은 코드는 렌더링 중에 실행되어서는 안 됩니다. 그러한 동작은 모두 useEffect
내에서 실행하세요.
function Example() {
// don't:
setTimeout(() => {
alert("100ms 후의 미래");
}, 100);
// do:
useEffect(() => {
setTimeout(() => {
alert("100ms 후의 미래");
}, 100);
}, []);
/* ... */
return null;
}
const
로 이름을 붙여 사용하는 것이 좋습니다.// 상황 자체는 좋은 예시가 아닙니다. 상수를 선언하여 사용하는 부분에 주목해주세요.
const CANCEL = 0, MODIFY = 1;
setButtonBehavior(CANCEL);
let
이젠 괜히 쓰시는 분 없으리라 생각합니다. 값이 변하지 않는 변수는 언제나 const
로 선언해야 합니다.
컴포넌트는 반드시 PascalCase
로 써야 하고, 그 외의 javascript에서 쓰는 이름은 99% camelCase
로 씁니다. 파일명은 컴포넌트의 경우 export하는 컴포넌트의 이름과 똑같이 쓰고 그 외에는 적당히 camelCase
로 지어주시면 됩니다. 아 컨텍스트도 대문자로 시작하게 써주세요.
보통 height
속성을 직접 지정하면 화면 높이가 약간 안 맞아서 스크롤이 생기곤 합니다. flex
나 grid
로 해결하는 게 개인적으로 가장 깔끔하다고 생각하고, 크기를 꼭 직접 지정하고 싶다면 box-sizing: border-box
를 사용해보시면 도움이 될 수 있습니다. 아직 레이아웃 잡는 게 힘드시다면 세미나2.5 참고해서 flex랑 grid 써보세요.
key
propkey
prop의 기능은 리스트 안의 컴포넌트들을 구분하는 기능 그거 하나뿐입니다. 리스트 밖에서는 사용하면 안 된다는 건 아니지만 동작 면에서도 가독성 면에서도 별다른 기능이 없고 코드 리뷰에 한 줄 추가되는 기능 뿐입니다.
id
propHTML에서 id
는 주로 document.getElementById
할 때 많이 쓰게 되는데 리액트에서는 거의 쓸 일이 없습니다. 괜히 id
충돌날 건덕지만 만드는 일이니까 스타일 지정하고 싶다면 className
쓰는 게 좋을 것 같습니다. 저번엔 key
랑 id
에 같은 값을 내려주는 코드도 많이 봤는데, 둘은 기능이 전혀 다르니 잘 구분해서 사용하시기 바랍니다.
useEffect
, useMemo
등등은 두번째 인자로 dependency array를 받습니다. 이걸 그냥 비워놓으신 분들 꽤 있던데 함수 안에서 사용하는 이름은 import로 가져온 거 빼고 전부 넣어준다고 생각하시면 됩니다. 특정 값이 변할 때만 무언가 동작하도록 일부러 빼는 경우도 있을텐데, 그러면 99% 오류 납니다. 그런 건 useEffect
안에서 if 문으로 처리하시면 되구요 아무튼 핵심은 dependency array 잘 넣으시라는 겁니다.
React Context의 주요한 기능은 props drilling을 막는 것이지만, state 등을 분리하는 김에 관련된 기능들도 여러 관심사에 따라 분리하고 추상화할 수 있습니다. 예를 들면 메뉴 목록을 state로 만들어 setState와 함께 내려주는 방법도 있지만:
function MenuDataProvider({children}) {
const [menus, setMenus] = useState([]);
const [maxId, setMaxId] = useState(4);
return <MenuDataContext.Provider value={{menus, setMenus, maxId, setMaxId}}>
{children}
</MenuDataContext.Provider>;
}
한편으로는 관련된 기능들을 함수로 만들어 내려주는 방법도 있습니다. 이 경우에는 maxId
와 같이 사용 방식을 제한된 변수들을 다른 컴포넌트들에게서 숨겨 추상화할 수 있습니다.
function MenuDataProvider({children}) {
const [menus, setMenus] = useState([]);
const [maxId, setMaxId] = useState(4);
const addMenu = useCallback(() => { /* ... */ });
return <MenuDataContext.Provider value={{menus, addMenu, /* ... */}}>
{children}
</MenuDataContext.Provider>;
}
과제하다가 픽토그램이 필요하신데 이름을 모르시겠다구요?
아래 앱에서 검색해보시면 됩니다!!!
https://developer.apple.com/sf-symbols/
개발할 때 이미지 사용하려면
UIImage(systemName: "심볼이름")
이렇게 이미지 생성해서 사용하시면 됩니다
import './New.css';
import {useCounterContext} from "./CounterContext";
import Header from "./Header";
import {useNavigate} from "react-router-dom";
import React, {useState} from "react";
function New() {
const {addMenu, menus, loginStatus} = useCounterContext();
const [enteredNum, setEnterdNum] = useState("");
const changeEnteredNum = (e) => {
const value: string = e.target.value;
const removedCharacterValue = value.replace(/[^0-9]/g, '');
const removedCommaValue: number = Number(removedCharacterValue.replaceAll(",", ""));
setEnterdNum(removedCommaValue.toLocaleString());
};
const navigate = useNavigate();
const Goout = () => {
navigate("/stores/1");
};
const addbutton = () => {
const inputName = document.getElementById('name').value;
const inputType = document.getElementById('typeselect').value;
const inputImage = document.getElementById('image').value;
const inputDes = document.getElementById('description').value;
if (inputName === "" || enteredNum === ""|| inputType=== "") {
alert("빼먹은 부분이 없느지 확인해주세요");
} else {
let isexist = menus.some((menu)=>menu.name===inputName);
if (isexist === false) {
if (inputName.length <= 20) {
if (Number(enteredNum.replaceAll(",", ""))>=100 && Number(enteredNum.replaceAll(",", ""))<=1000000) {
addMenu(inputName, enteredNum, inputImage, inputType, inputDes);
Goout();
}
else {
alert("가격이 범위에 맞지 않습니다. (100<price<1000000)");
}
}
else {
alert("이름이 너무 깁니다.");
}
}
else {
alert("동일한 이름의 제품이 이미 존재합니다.");
}
}
};
if (loginStatus === true)
{
return 1;
}
else
{
alert("GoOut");
navigate(-1);
return 0;
}
{/*return (
<>
<Header/>
<section className="Newsection">
<div className="title">새 메뉴 추가</div>
<div className="nameadd-str">이름</div>
<input type="text" placeholder="맛있는와플" id="name" className="nameadd-box"/>
<div className="typeadd-str">종류</div>
<select id="typeselect" className="typeadd-box">
<option value='' selected>상품의 종류를 선택하세요</option>
<option value='coffee'>커피</option>
<option value='waffle'>와플</option>
<option value='beverage'>음료</option>
</select>
<div className="priceadd-str">가격</div>
<input type="text" value={enteredNum} onChange={changeEnteredNum} placeholder="5,000"
className="priceadd-box"/>
<span className="unit">원</span>
<div className="imageadd-str">상품 이미지</div>
<input type="text" placeholder="이미지 주소를 입력해주세요" id="image" className="imageadd-box"/>
<div className="desadd-str">설명</div>
<textarea placeholder="상품에 대한 자세한 설명을 입력해주세요" id="description" className="desadd-box"></textarea>
</section>
<button className="add" onClick={addbutton}>
추가
</button>
<button className="close" onClick={Goout}>
취소
</button>
</>
);*/}
}
export default New;
menus/new에 loginstatus가 false면 접근하지 못하게 하려고 합니다.
일단 new 다 만들어두었고 그 기능만 추가하면 됩니다.
하지만 테스트하는 과정에서 alert는 두번 뜨고 navigate(-1)은 작동을 안하는 것을 확인했습니다.
어떻게 고쳐야하나요?
제공해주신 해당 Android Codelab 링크 https://developer.android.com/courses/kotlin-android-fundamentals/overview 로 접속하니 "Caution: This course is no longer maintained. Please use Android Basics in Kotlin or Android Basics with Compose instead." 라는 문구가 나타납니다. 혹시 명시된 대로 Android Basics in Kotlin 에 연결된 https://developer.android.com/courses/android-basics-kotlin/course 링크의 course로 과제 진행해도 문제 없을까요?
감사합니다 :)
안녕하세요. 세미나2 과제 관련해서 구글 검색을 해도 방법을 찾지 못해 질문드립니다.
URL을 변경하면, 기존의 메뉴와 로그인 context가 초기화되는 문제입니다. 예를 들면 아래와 같습니다.
menus/4
를 입력하면 로그인 상태가 해제됩니다.stores/1
를 입력하면 기본 메뉴만 남아있습니다.헤더를 비롯한 각 컴포넌트 내의 Link
를 이용하여 이동할 때는 이러한 문제가 발생하지 않습니다.
현재 제 App 컴포넌트는 대략 아래와 같습니다. (과제 수행과 관련하여 문제되는 부분이 있다면 수정하겠습니다)
... // data.json을 data로 import한 상태
function App() {
const [menuItems, setMenuItems] = useState(data);
const [loginObj, setLoginObj] = useState({
isLogin: false,
id: "",
password: "",
});
return (
<div>
<SessionContext.Provider value={{ loginObj, setLoginObj }}> // SessionContext.js 파일을 import한 상태
<MenuDataContext.Provider value={{ menuItems, setMenuItems }}> // MenuDataContext.js 파일을 import한 상태
<BrowserRouter>
<Routes>
... // Route 들
</Routes>
</BrowserRouter>
</MenuDataContext.Provider>
</SessionContext.Provider>
</div>
});
문제가 발생한 이유를 파악하기 위해 이 App 컴포넌트의 return 문 위에 console.log를 입력해보았는데, URL을 바꿔 입력할 때마다 콘솔에 찍혔습니다. 이를 통해 App의 useState
를 이용한 부분 역시 URL이 바뀌어 입력될 때마다 새로 실행되고, 이 때문에 menuItems
와 loginObj
역시 초기화되는 것이라고 생각하였습니다. 그러나 이후에 어떻게 이 문제를 해결해야할지에 대한 방법을 찾지 못하고 있습니다. 혹시 이와 관련하여 도움 주신다면 정말 감사하겠습니다!
세미나와 관련된 질문은 현재 위치인 rookies repository의 issue로 남겨주세요.
모든 세미나가 레포지토리를 공유하기 때문에 labeling을 잘 해주셔야 합니다.
Label
은 issue를 생성한 뒤, 생성한 issue 창에서 위와 같이 설정할 수 있습니다. 아래 조건을 꼭 지켜주세요!
위 조건을 맞추어 Label 중 2개 이상을 필수로 달아주셔야 하며, Label이 달려 있지 않을 시 답변을 하지 않을 수 있습니다.
milestone
도 비슷한 방법으로 설정할 수 있습니다. Label이 어떤 분야의 이슈인지에 대한 내용이라면, Milestone은 언제 생긴 이슈인지에 대한 내용입니다. 같은 방법으로 달아 주시기 바랍니다.
이와 같이 Label과 Milestone을 부탁드리는 이유는 루키 여러분의 검색 편의 및 세미나장분들의 로드를 줄이기 위함입니다!
Label과 Milestone 말고는 달아주시지 않으셔도 괜찮습니다.
리액트 과제 관련하여 아무리 구글링 해도 해답을 찾지 못해 질문 드립니다ㅠㅠ
모달 내에서 가격 입력 시 자동으로 쉼표가 생성되게 하는 스펙을 구현하라고 하셨는데, 이를 구현하고 보니 8번째 자리에서 짤리는 현상을 발견했습니다.
123,456
→ 1,234,567
→ 8
입력 → 1,234
999,999
→ 9,999,999
→ 9
입력 → 9,999
이런 식으로 앞에 네 자리만 남기고 뒤에 네 자리만 남습니다.
더 큰 수를 복사 붙여넣기 하여 input 안에 집어넣어도 한자리 더 입력하면 뒤에 네 자리가 사라집니다.
123456789
붙여넣기 → 123,456,789
로 표시됨 → 0
입력 → 123,456
그런데 또 열 자리 수를 붙여넣기 하고 한자리 더 입력하면 일곱 자리가 사라집니다.
1234567890
붙여넣기 → 1,234,567,890
으로 표시됨 → 0
입력 → 1,234
아래는 가격을 입력하는 input 태그입니다. onChange 이벤트 발생 시 value 문자열에 쉼표를 다 뗀 후 parseInt 하여 price 에 저장합니다.
<input type="text" className="modal-input-bar" placeholder="5,000"
value={ insertCommas(input.price) }
onChange={ e => {
setInput({...input, price: parseInt(e.target.value.replace(",", ""))});
}}
/>
아래는 insertCommas 함수입니다.
function insertCommas(n) {
if(!n){ return ""; }
let s = n.toString();
for (let i = s.length - 3; i > 0; i -= 3)
s = s.slice(0, i) + ',' + s.slice(i);
return s;
}
Invalid Input일 경우 alert를 띄우는 함수는 추가
버튼을 눌렀을 때만 실행되므로 이 오류와는 무관한 것으로 보입니다.
100만 이하의 가격을 입력하고 추가하는 스펙은 별다른 오류 없이 작동합니다.
왜 이런 현상이 발생하는지 도움 주시면 감사하겠습니다 ㅠㅠ
안녕하세요. 와플스튜디오 20.5기 루키 유창민이라고 합니다. 세미나 참여 기준과 관련하여 질문할 사항이 두 가지 있어 질문 드립니다.
감사합니다!
기본적으로 UIKit을 사용하여 프로젝트를 만들면 자동으로 Main.storyboard가 시작 화면이 됩니다.
우리는 코드로 뷰를 구성할 예정이기 때문에 프로젝트 기본 세팅을 부숴줘야 하죠..
그래서 기본적인 프로젝트의 정보를 담고 있는 info.plist에서 default 시작점을 없애고 SceneDelegate에서 UIWindow를 생성함과 동시에 그 window의 root로 우리가 필요로 하는 VC(ViewController)를 넣어주면 되는 간단한 이치랍니다 ㅎㅎ
당연히 모를 수 밖에 없는 부분이니 참고하셔서 과제 진행 부탁드려요!!
안녕하세요, 2차시 세션에서 설명이 제대로 이루어지지 않은 부분이 있었는데요!
저희가 package javax.servlet.http
안에 있는 HttpServletRequest를 사용해서 setAttribute 해주기 때문에, 어트리뷰트를 가져올 때도 이 객체로부터 정보를 얻어와야 합니다.
request.getAttribute() 메소드를 사용할 때, 스프링에 내장된 NativeWebRequest 타입의 getAttribute 메소드는 시그니처로 name뿐만 아니라 scope
를 받습니다. 스코프는 세션 레벨에서의 다양한 정보 저장을 위한 기능인데요,
아래처럼 NativeWebRequest
타입의 요청을 ServletWebRequest
라는 객체로 감싸주면 (어댑터 클래스입니다),
그 객체 내부에 있는 request 프로퍼티를 통해 인터셉터에서 가로챘던 HttpServletRequest 에 접근할 수 있습니다.
override fun resolveArgument(
parameter: MethodParameter,
mavContainer: ModelAndViewContainer?,
webRequest: NativeWebRequest,
binderFactory: WebDataBinderFactory?
): Any? {
parameter.hasMethodAnnotation(UserContext::class.java)
val password = (webRequest as ServletWebRequest).request.getAttribute("pwd")
return password
}
Docker로 띄운 MySQL을 Datagrip으로 연결하다가 발생한 문제를 해결했는데 왜 됐는지.. 모르겠어서 질문을 올립니다.
우선은 사용하는 운영체제는 윈도우입니다.
Docker로 MySQL을 실행시킨뒤 Docker의 Mysql cli를 사용해서 mysql -u root -p 와 비밀번호를 입력해서 들어가면 잘 들어가지는 상황이었는데 Datagrip을 통해 올바른 user와 password를 쳐도 비밀번호가 틀렸다는 access denied 오류가 계속 발생했습니다.
이것저것 해보다가 127.0.0.1 3306번 포트의 PID를 taskkill 했는데 문제가 해결이 되어서.. 이게 왜 된건지 혹시 아시는 분 도움주실 수 있을까요..?
Docker나 포트같은 개념에 대해 잘알지못해서,, 혹시 답변을 위해 더 필요한 정보가 있다면 추가해보도록 하겠습니다!
상세뷰를 만들려고 합니다!
view.js는 아래와 같이 만들었습니다.
(아직 기능은 미완성, 특정 menu를 선택했을 때 뜨는지만 보려고 해논겁니다.)
import React from 'react';
import { useState } from "react";
import './view.css';
const view = (props) => {
const { open, deselect, menus, update, remove} = props;
const selectedMenu = menus.map((m) => (m.isselected==="selected" ? m:null));
return (
<div className={open ? 'openView' : 'closeView'}>
{open ? (
<section onClick={(e)=>e.stopPropagation()}>
<main>
{selectedMenu.name}
</main>
<footer>
<button className="close" >
수정
</button>
<button className="close" >
삭제
</button>
</footer>
</section>
) : null}
</div>
);
};
export default view;
view.css는 아래와 같습니다.
.openView{
position: absolute;
right: 20px;
top: 130px;
background-color: white;
width: 30%;
height: 90%;
border-style: solid;
border-color: black;
border-radius: 10px;
}
이를 App.js에서 불러왔습니다.
import view from './view.js';
<view open={globalSelect} deselect={deselect} menus={menus} update={updateMenu} remove={removeMenu}></view>
그런데
상세뷰가 안뜨네요;; 뭐 css적용해놔서 테두리라도 뜨길 바랬는데 안되서 이것저것 해봤는데도 그대롭니다...
@Jhvictor4 님의 예시 답안 링크
class StudentTable(
private val students: LinkedList<String> // (1)
) {
private var cursor = 0 // (2)
private val cache = mutableListOf<Cached>() // (3)
private data class Cached(
val originalIndex: Int,
val studentName: String,
)
...
private fun moveDown(count: Int) {
val resultLoc = cursor + count // (2)
if (resultLoc >= students.size) { // (1)
println("Error 100")
return
}
cursor = resultLoc
}
...
private fun delete() {
val removed = this.students.removeAt(cursor) // (1)
this.cache.add(Cached(cursor, removed)) // (3)
// now current Index has moved to it's next one
// reallocate current index if no next exists
if (cursor == students.size) { // (1)
cursor -= 1
}
}
...
}
(1) : LinkedList 타입의 students 변수 (생성자)
students.size
를, delete에서는 this.studetns.removeAt(cursor)
를 사용하여, this의 유무가 차이가 납니다. 일반적으로 생성자에 정의되었기 때문에 this를 붙임으로써 StudentTable 클래스 자체를 지칭한다고 하지만 굳이 this를 붙이지 않아도 상관 없는지, 붙이면 어떤 것이 좋은지 궁금합니다.(2) : Int 타입의 cursor 변수 (클래스 내부 변수)
(3) : mutableListOf< Cached > 타입의 cache 변수 (클래스 내부 변수)
this.cache.add(Cached(cursor, removed))
에서는 왜 this가 붙어서 굳이 StudentClass를 지칭하는지 궁금합니다. (cursor는 붙지 않았다는 것과 비교하여)이번과제 관련해서 코드 질문이 있습니다!
이번 과제 할때 todolist 연습용으로 올려주신 코드를 많이 참고했는데요 왜그런지 모르는 오류가 자꾸 생겨서요... 특정 메뉴를 클릭하면 선택 상태가 되게하고 만약 그 상태에서 다른 특정메뉴를 클릭하면 원래 선택상태의 메뉴는 선택이 풀리고 그 다른 특정메뉴가 선택상태로 바뀌는 것을 구현하려고 합니다.
이처럼 isselected를 따로 만들고
onClick에 menus.map해서 하나하나 none 만들고나서 그 클릭한것만 selected해줄려고 했는데
이상하게 가장 마지막 selected로 바꾸는것만 되고 앞에서 모든걸 none으로 만들어 주는 부분이 안되네요
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.