묻고 답해요
158만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
해결됨실전 리액트 프로그래밍
useEffect 실전 활용법2 부분 질문
끝에 첨부한 코드로 실행하면 작동은 잘 됩니다.window.addEventListener('click', () => { onClickRef.current();});문제는 강의에 나오는 코드인 위 코드로 작성하면 removeEventListener를 반환 해주더라도계속해서 이벤트가 삭제되지 않고 클릭할 때마다 Listener가 계속 생성되면서Count 가 몇 배로 불어나는 버그가 생깁니다.addEventListener에서 화살표함수를 사용하여 OnClickRef.current(); 를 호출해주는이유가 있나요?물론 removeEventListener없이 화살표 함수로 eventListener 를 실행하여도의존성 배열부분에 빈배열로 남기면 당연히 작동은 잘 합니다.그런데 보통 removeEventListener 를 리턴해주는 것이 정석인 것 같은데화살표함수로 호출한 eventListener는 removeEventListener로 제거가 안되더라구요.별거 아닌 코드인데 원인을 모르니 답답하네요.그리고 제가 작성한 코드에서도 이 부분을 ref객체를 useEffect 내에서 수정하지 않으면 문제가 생기는데 왜 그런지 모르겠네요..강의에서 설명해주신 바로는 useEffect 내에서 ref객체를 수정하는 이유는 이후에 있을 concurrent mode로 실행될 때를 대비해 사용하는 것이고,concurrent mode로 실행하지 않을 시에는 문제가 생기지 않을 것이다라고 하셨는데,Component 함수에서 직접 수정 시엔 원인 모를 문제로 웹이 다운 되어버립니다. 느낌상 어떤 부분이 지나치게 렌더링이 되면서 다운 되는 느낌인데 뭔지 모르겠습니다.왜 이런 현상이 일어나는지 알 수 있을까요?import { useState } from "react"; import MyComponent from "./components/MyComponent"; function App() { const [count, setCount] = useState(0); function onClick() { setCount(count + 1); } return ( <div> <MyComponent onClick={onClick}/> <div>{count}</div> </div> ); } export default App;import { useEffect, useRef } from "react"; export default function MyComponent({ onClick }) { const onClickRef = useRef(); useEffect(() => { onClickRef.current = onClick; }); useEffect(() => { window.addEventListener('click', onClickRef.current); return () => window.removeEventListener('click', onClickRef.current); }); return ( <div>테스트</div> ); }
-
미해결리액트로 나만의 블로그 만들기(MERN Stack)
Redux/saga를 많이 사용하게 되면 불리할까요?
강의와 별개의 내용일수도 있지만, 한가지 여쭙고 싶습니다. 선생님 강의를 듣고 테이블을 좀 많이 사용하는 프로젝트를 하나 해보고 있습니다. 각 테이블마다 saga/reducer를 하나씩 만들고 있는데요, 이럴경우 문제가 될 수 있나요? 공통된 부분은 묶어내고 싶긴 한데, 분류하기가 조금 복잡해서요. 예를 들어, authReducer, customerReducer, productReducer 등등 각 테이블마다 다 만들었거든요. 이런 경우 퍼포먼스 면에서 부족함이 있을까요? 내용이많고 큰 프로젝트보단 중/소 규모의 프로젝트가 리액트에 어울리다라는 이야기를 들은적이 있었지만, 개발하는 사람의 능력에 따라 다르겠지만, 제가 배운 내용을 기반으로 한번 프로젝트를 진행해보고 싶어서 이렇게 시도해보고 있습니다. 강의와 관련있는 질문을 남겨주세요.• 강의와 관련이 없는 질문은 지식공유자가 답변하지 않을 수 있습니다. (사적 상담, 컨설팅, 과제 풀이 등)• 질문을 남기기 전, 비슷한 내용을 질문한 수강생이 있는지 먼저 검색을 해주세요. (중복 질문을 자제해주세요.)• 서비스 운영 관련 질문은 인프런 우측 하단 ‘문의하기’를 이용해주세요. (영상 재생 문제, 사이트 버그, 강의 환불 등) 질문 전달에도 요령이 필요합니다.• 지식공유자가 질문을 좀 더 쉽게 확인할 수 있게 도와주세요.• 강의실 페이지(/lecture) 에서 '질문하기'를 이용해주시면 질문과 연관된 수업 영상 제목이 함께 등록됩니다.• 강의 대시보드에서 질문을 남길 경우, 관련 섹션 및 수업 제목을 기재해주세요. • 수업 특정 구간에 대한 질문은 꼭 영상 타임코드를 남겨주세요! 구체적인 질문일수록 명확한 답을 받을 수 있어요.• 질문 제목은 핵심 키워드를 포함해 간결하게 적어주세요.• 질문 내용은 자세하게 적어주시되, 지식공유자가 답변할 수 있도록 구체적으로 남겨주세요.• 정확한 질문 내용과 함께 코드를 적어주시거나, 캡쳐 이미지를 첨부하면 더욱 좋습니다. 기본적인 예의를 지켜주세요.• 정중한 의견 및 문의 제시, 감사 인사 등의 커뮤니케이션은 더 나은 강의를 위한 기틀이 됩니다. • 질문이 있을 때에는 강의를 만든 지식공유자에 대한 기본적인 예의를 꼭 지켜주세요. • 반말, 욕설, 과격한 표현 등 지식공유자를 불쾌하게 할 수 있는 내용은 스팸 처리 등 제재를 가할 수 있습니다.
-
해결됨[리뉴얼] React로 NodeBird SNS 만들기
제로초님 질문드립니다ㅜㅜ
const express = require('express'); const bcrypt = require('bcrypt'); const { User } = require('../models') // 구조분해 할당 const router = express.Router(); router.post('/', async (req,res,next)=> { try { const exUser = await User.findOne({ where : { email: req.body.email, } }); // 비동기 fidOne 찾는 함수 if (exUser) { return res.status(403).send('이미 사용중인 아이디입니다.'); } const hashedPassword = await bcrypt.hash(req.body.password, 12) await User.create({ email: req.body.email, password: hashedPassword, nickname: req.body.nickname, }) res.status(200).send('success'); } catch (error) { console.error(error); next(error); } }) module.exports = router; 이미 있는 이메일로 가입 했을경우, status 403과 send로 에러 메세지를 보냈습니다. 이미 있는 이메일로 가입 했을 경우에 SIGN_UP_REQUEST 만 실행되고 SIGN_UP_FAILUTE 실행되지 않아 alert창이 뜨질 않습니다 ㅠㅠ 뭐가 문제 일까요..
-
미해결실전 리액트 프로그래밍
setState 비동기 처리관련질문입니다.
제가 setState가 비동기처리이면서 배치이다 라는것을 보면서 궁금한 점이생겼는데요.. onAdd함수를 보면 currentId를 1증가시키고, todo변수에 값을 할당한뒤 setTodoList에 값을 넣어서 렌더링하는 순서인데요. 만약에 동기처리처럼 onAdd버튼을 클릭시 currentId에 1을 증가한후, 그값을 todo에 넣은뒤 그 값을 setTodoList에 반영 하고 싶다고면 어떻게 해야할까요?
-
해결됨[리뉴얼] React로 NodeBird SNS 만들기
AppLayout.js 에서
AppLayout.prototype = { children: Protypes.node.isRequired } 꼭 사용해야 next 에서 children 를 사용할수 있는건가요? react에서는 prototype을 사용하지 않고 children를 사용한것으로 기억해서요
-
미해결[리뉴얼] React로 NodeBird SNS 만들기
redux-saga 의 흐름.
질문1. 리덕스사가에 액션이 전달되는 과정 dispatch({ type: LOG_IN_REQUEST, data: { email, password }, }); 위와 같이 로그인 버튼을 눌렀을때 인자로 전달된 액션을 dispatch 하게 되는데. function* watchLogIn() { yield takeLatest(LOG_IN_REQUEST, logIn); } 이후 사가에서 LOG_IN REQUEST 에관한 액션이 왔을떄 login 함수가 실행된다는 흐름은 이해를 했습니다. 하지만 액션객체의 type 속성인 LOG_IN_REQUEST 가 어떻게 위에 takeLatest 의 첫번째 인자로 인식이되어 login 함수가 실행되는지 직관적으로 이해가 가지않습니다. 이미 사가 라이브러이에서 정해진 규칙같은 건가요 ? 액션을 디스패치 할때 {type: `액션타입`} 형태여야만 takeLatest('액션타입') 이 인식이되는지 궁금합니다. 질문2. yield function* logIn(action) { try { console.log('saga logIn'); // const result = yield call(logInAPI); yield delay(1000); => 멈춤? yield put({ => 멈춤? type: LOG_IN_SUCCESS, data: action.data, }); } catch (err) { console.error(err); yield put({ type: LOG_IN_FAILURE, error: err.response.data, }); } } yield 는 중단점 역활을 한다고 이해했습니다. 그럼 위에 takeLatest 의 두번째인자로 위 login 함수가 실행될때 첫번째 yield delay(1000) 이 실행되고 멈추는게아니라 계속에서 아래 yelid put 쪽을 실행되는부분이 이해가 잘 가지않습니다.! 영상 몇번 돌려보고 찾아도 봤는데 스스로 해결하지 못해 질문 드립니다 ㅜㅠ..
-
해결됨[리뉴얼] React로 NodeBird SNS 만들기
axios.defaults.baseURL 지정 후, 다른 서버에 요청을 보내려면?
/front/sagas/index.js 에서 axios.defaults.baseURL = 'http://localhost:3065'; axios.defaults.withCredentials = true; 이렇게 공통으로 지정을 해주는데, 만약에 예외적으로 다른 URL을 사용하고 싶으면 어떻게 하나요? function loadPostsAPI(data) { return axios.get('http://naver.com/test', data); } 이런식으로 개별적으로 URL 넣어주면 될까요? 아니면 애초에 공통URL을 지정해주지 말고 각자 넣어줘야 하는걸까요? 공통URL과 더불어 withCredentials 값도 각자 넣으려면 어떻게 해야하는지 궁금합니다!
-
미해결따라하며 배우는 노드, 리액트 시리즈 - 기본 강의
Route.get() requires a callback function but got a [object Object]
인자를 객체로 넘겼다고 에러가 뜨는데, auth를 구현하는 app.get에서 자꾸 문제가 있다고 합니다. 오타는 도저히 못찾겠는데,, 문제가 무엇일까요 const express = require('express') const app = express() const port = 5000 const bodyParser = require('body-parser'); const { User } = require("./models/User"); const config = require('./config/key'); const cookieParser = require('cookie-parser'); const auth = require('./middleware/auth'); // application / x-www-form-urlencoded app.use(bodyParser.urlencoded({extended : true})); // application / json app.use(bodyParser.json()); app.use(cookieParser()); const mongoose = require('mongoose'); //mongoDB 연결 mongoose.connect(config.mongoURI,{ useNewUrlParser: true, useUnifiedTopolongy: true, useCreateIndex: true, useFindAndModify: false }).then(() => console.log('MongoDB connected !')) .catch(err => console.log('MongoDB Error !')); //Hello world 출력 app.get('/', (req, res) => { res.send('Hello World!') }); //register 라우트 app.post('/api/users/register', (req, res) => { // 회원가입 할 때 필요한 정보들을 client으로부터 받아오면 // 그것들을 database에 넣어준다. const user = new User(req.body) user.save((err, userInfo) => { if(err) return res.json({ success : false, err }) return res.status(200).json({ success: true }); }); }); // login 라우트 app.post('/api/users/login', (req, res) => { // console.log('ping') //요청된 이메일을 데이터베이스에서 있는지 찾는다. User.findOne({ email: req.body.email }, (err, user) => { // console.log('user', user) if (!user) { return res.json({ loginSuccess: false, message: "제공된 이메일에 해당하는 유저가 없습니다." }) } //요청된 이메일이 데이터 베이스에 있다면 비밀번호가 맞는 비밀번호 인지 확인. user.comparePassword(req.body.password, (err, isMatch) => { // console.log('err',err) // console.log('isMatch',isMatch) if (!isMatch) return res.json({ loginSuccess: false, message: "비밀번호가 틀렸습니다." }) //비밀번호 까지 맞다면 토큰을 생성하기. user.generateToken((err, user) => { if (err) return res.status(400).send(err); // 토큰을 저장한다. 어디에 ? 쿠키 , 로컳스토리지 res.cookie("x_auth", user.token) .status(200) .json({ loginSuccess: true, userId: user._id }) }) }) }) }) // auth 라우트 (미들웨어로 사용될 것입니다.) app.get('/api/users/auth', auth, (req, res) => { //여기 까지 미들웨어를 통과해 왔다는 얘기는 Authentication 이 True 라는 말. res.status(200).json({ _id: req.user._id, isAdmin: req.user.role === 0 ? false : true, isAuth: true, email: req.user.email, name: req.user.name, lastname: req.user.lastname, role: req.user.role, image: req.user.image }) }) app.get('/api/users/logout', auth, (req, res) => { // console.log('req.user', req.user) User.findOneAndUpdate({ _id: req.user._id }, { token: "" } , (err, user) => { if (err) return res.json({ success: false, err }); return res.status(200).send({ success: true }) }) }) // 포트를 통해 index.js 를 실행합니다. app.listen(port, () => { console.log(`Example app listening at http://localhost:${port}`) }); 위는 index.js const { User } = require('../models/User'); let auth = (req, res, next) => { //인증 처리를 하는곳 //클라이언트 쿠키에서 토큰을 가져온다. let token = req.cookies.x_auth; // 토큰을 복호화 한후 유저를 찾는다. User.findByToken(token, (err, user) => { if (err) throw err; if (!user) return res.json({ isAuth: false, error: true }) // console.log('userh', user) req.token = token; req.user = user; next(); }) } module.exports = { auth }; auth.js // 모듈 (User에서는 데이터 베이스를 위한 몽구스, 유저의 비밀번호 암호화를 위한 비크립트, 유저의 개개인 토큰을 얻기 위한 제이슨웹토큰을 가져온다.) const mongoose = require('mongoose'); const bcrypt = require('bcrypt'); const jwt = require('jsonwebtoken'); // 비밀번호 암호화를 위한 salt 값 설정. salt 값은 암호화된 비밀번호를 해킹하기 어렵게 만들 수 있음. const saltRounds = 10; // 유저의 정보들을 객체화, 이른 바 유저 스키마를 생성한다. ( 몽구스의 메소드 ) // 각 유저들의 이름, 이메일, 비밀번호 등 개인정보를 담기 위한 객체임. const userSchema = mongoose.Schema({ name : { type: String, maxlength: 50 }, email : { type: String, trim: true, unique: 1 }, password : { type: String, minlength: 5 }, lastname : { type: String, maxlength: 50, }, role : { type: Number, default: 0 }, token: { type: String, }, tokenExp: { type: Number }, image : String }) // 유저 스키마 이전에 실행될 것임. userSchema.pre('save', function (next) { var user = this; if (user.isModified('password')) { //비밀번호를 암호화 시킨다. bcrypt.genSalt(saltRounds, function (err, salt) { if (err) return next(err) bcrypt.hash(user.password, salt, function (err, hash) { if (err) return next(err) user.password = hash next() }) }) } else { next() } }) // comparePassword 라는 이름의 유저 스키마 메소드를 생성합니다. ( 당연히 비밀번호 비교를 위한 메소드이겠지요? ) userSchema.methods.comparePassword = function (plainPassword, cb) { //plainPassword 1234567 암호회된 비밀번호 $2b$10$l492vQ0M4s9YUBfwYkkaZOgWHExahjWC bcrypt.compare(plainPassword, this.password, function (err, isMatch) { if (err) return cb(err); cb(null, isMatch); }) } userSchema.methods.generateToken = function (cb) { var user = this; // console.log('user._id', user._id) // jsonwebtoken을 이용해서 token을 생성하기 var token = jwt.sign(user._id.toHexString(), 'secretToken') // user._id + 'secretToken' = token // -> // 'secretToken' -> user._id user.token = token user.save(function (err, user) { if (err) return cb(err) cb(null, user) }) } userSchema.statics.findByToken = function(token, cb) { var user = this; // 토큰을 decode 한다. jwt.verify(token, 'secretToken', function(err, decoded) { // 유저 아이디를 통해서 유저를 찾은 다음에 // 클라이언트에서 가져온 token과 DB에서 가져온 토큰이 일치하는지 확인합니다. user.findOne({ "_id" : decoded, "token" : token }, function(err, user) { if(err) return cb(err); cb(null, user); }) }) } // 유저스키마 => User 라는 이름으로 모델화. const User = mongoose.model('User', userSchema); // 방금 모델화한 User 밖에서도 사용가능하도록 exports. module.exports = { User }; user.js 입니다. 문제가 무엇일까요, ㅠ
-
해결됨[리뉴얼] React로 NodeBird SNS 만들기
models 최신 문법으로 변경 후
index 페이지에 /Posts 요청에서 sequelizeeagerloadingerror Image is not associated to Comment 에러가 나고 로그인도 안되요 models 폴더 말고 다른 파일도 수정해야하는 곳이 있나요?
-
해결됨[리뉴얼] React로 NodeBird SNS 만들기
Router 를 사용해 SSR 페이지 이동할 때 질문입니다!
/user/[id].js 로 Link 태그를 통해 접근했을 때 userInfo 가 null 인 에러를 해결하려고 이것저것 해보던중 /user/[id].js 안에 {/* <title>{userInfo.nickname}님의 글</title> <meta name="description" content={`${userInfo.nickname}님의 게시글`} /> <meta property="og:title" content={`${userInfo.nickname}님의 게시글`} /> <meta property="og:description" content={`${userInfo.nickname}님의 게시글`} /> <meta property="og:image" content="https://nodebird.com/favicon.ico" /> <meta property="og:url" content={`https://nodebird.com/user/${id}`} /> */} Head 안에 위 부분을 주석처리하니까 정상적으로 렌더링 되었어요! 혹시 원인이 뭔지 알 수 있을까요? 스크립트가 실행되기 전 Head 안에 userInfo 를 찾다가 에러를 나는 것인지.. 다른 이유에서인지.. 그리고 제로초님 깃헙 클론받아서 실행할 때 next 디펜던시 버전을 9.5.3으로 변경해서 실행해보니 저와 동일한 에러가 났었어요 혹시 확인 가능하실까요?
-
미해결웹 게임을 만들며 배우는 React
dispatch가 자식컴포넌트로 넘어가질 않습니다.
(사진)
-
미해결따라하며 배우는 노드, 리액트 시리즈 - 기본 강의
로그아웃시 에러
리액트 강의 마지막 인증처리 빼고 로그아웃 강의까지 들으면서 따라 했는데 로그아웃에서 에러가 나옵니다 로그아웃시에 콘솔창에는 { isAuth: false, error: true } 뜨는데요 이건 auth 미들웨어에서 사용자를 찾지 못했을 때 나타나는 에러인데 왜 로그인이 됐고 토큰도 생성이 됐는데 사용자를 찾지 못하는 지 모르겠어요ㅠㅠ 깃허브에 올려주신 코드 비교도 해봤는데 안 되네요...
-
미해결따라하며 배우는 노드, 리액트 시리즈 - 기본 강의
antd 관련
john 님 안녕하세요? 좋은 강의 재밌게 잘 들었습니다. 강의를 참고해서 간단한 웹 페이지를 만드려고 하는 중입니다. antd 를 가져다가 메뉴 바를 만드는 부분이 따로 강의에서 다루고 있지 않으셔서 깃허브에 있는 boiler-plate 를 받아서 확인해보고 있는데요. 아무래도 제가 이해하고 해보는게 중요할 거 같아서 antd 홈페이지의 메뉴 바 예제를 확인 중입니다. https://ant.design/components/menu/ 위 링크에서 예제를 보고 있는데 다 훅이 아닌 클래스형으로 작성했더라구요. 혹시 제가 찾지 못한 hook 스타일 document가 있는지 궁금합니다. 그게 아니라면 class 형 스타일 예제 코드를 john 님이 고쳐서 적용한 건지요? 아직 리액트가 익숙하지가 않아서 여쭤봅니다!
-
미해결생활코딩 - React
update구현:state변경 8분이후
안녕하세요 강사님. Update구현 : state 변경중간까지 따라해서 작동이 다 되었는데 7분이후에 concat 이 나오는 부분에서 ( 제 생각에는 이부분은 이전 강의에서 이미 변경이 되었다고 생각되는데) 동영상에 나오는대로 코드를 따라 했는데, 이후에는 에러가 발생하면서 , 기존 디폴트로 주었던 id :2 ( title:css 와 desc : 내용) 갑을 갖고 getContent 부분으로 이동한후에 무한루프에 빠져버리는 것 같습니다. 이 부분 동영상을 계속 보긴 했는데 어느부분에서 잘못된건이지 찾을 수가 없네요. 혹시 update 구현부분에 해당 하는 UpdateContent.js 를 갖고 계시면 혹시 보내주실 수 있는지요? 아무래도 비교해봐야 할것 같습니다. 좋은 강의 감사합니다
-
미해결따라하며 배우는 노드, 리액트 시리즈 - 기본 강의
로그아웃 버튼을 로그인 상태일 때만 보이게 해주고 싶은데 store 안에 있는 값들을 어떻게 접근해야 할지 모르겠습니다..ㅠㅠ
LandingPage에 있는 로그아웃 버튼을 로그인 했을때만 볼 수 있게 해주고 싶어서 Store 안에 loginSuccess 값이나 userId 값을 체크해서 값이 존재 할때만 버튼을 렌더해주려고 하는데 이 값들을 어떻게 접근해야 할지 모르겠습니다ㅠㅠ
-
미해결따라하며 배우는 노드, 리액트 시리즈 - 유튜브 사이트 만들기
댓글에 writer 정보가 넘어오지 않습니다
안녕하세요. 강의 재미있게 잘 듣고 있습니다! 다름이 아니라 며칠째 해결해보려고 올려두신 github 소스코드와 비교하며 해결해보려고 했으나 좀처럼 해결이 되지 않아 질문 드립니다. 댓글 기능 (3) SingleComment의 약 15분 경에 해당하는 댓글 리스트를 console에 찍어내는 것까지는 성공했습니다. 이를 화면에 출력하고자 하는데 댓글 내용은 잘 나오지만 댓글 작성자의 이름과 프로필 사진이 나타나지 않는 문제가 발생하였습니다. 출력된 에러 메세지는 다음과 같습니다. console.log로 props.comment.writer.name을 출력하면 잘 나오기는 하던데 어떤 부분이 문제인지 알 수 있을까요?? https://github.com/coding-Benny/react-youtube-clone
-
해결됨따라하며 배우는 노드, 리액트 시리즈 - 기본 강의
마지막 강의 auth 부분 질문 있습니다
export default function (SpecificComponent,option,adminRoute = null) { function AuthenticationCheck(props) { const dispatch = useDispatch(); useEffect(() => { dispatch(auth()).then(response => { if(!response.payload.isAuth) { if(option) { props.history.push('/login') } } else { if(adminRoute && !response.payload.isAdmin){ props.history.push('/') } else { props.history.push('/') } } }) }, []) return( <SpecificComponent/> ) } return AuthenticationCheck } 요즘 웹에 빠져서 프론트랑 백엔드 강의를 보는데 재밌는 강의 올려주셔서 감사합니다 !! 궁금한 건 예를들어서 로그인이 필요한 페이지에 로그인 없이 접근한다해도 결국 마지막엔 <SpecificComponent/> 이걸 리턴시켜줘서 렌더링이 될거라고 생각을 했는데요 그런데 useEffect라는 함수를 찾아보면 렌더링이 될 때 특정 작업을 수행해주는 함수라고 했고 느리게 잘 보면 로그인이 필요한 페이지에 로그인 없이 접근할 때 한 0.2초 정도는 잠깐 페이지가 뜨지만 바로 사이트 접근이 안되더라구요 그래서 useEffect 함수를 사용해서 return을 통해 랜더링을 하고 인증 확인 절차가 가능한 부분이구나 했는데 위에 있는 코드에서 useEffect 함수를 안쓰고 해도 로그인이 필요한 페이지에 로그인 없이 접근이 안되는 건 같아서요 그럼 결국 if문에 있는 props.history.push 코드가 실행이 되면 밑에 return <SpecificComponent/> 까지 코드가 진행이 안되고 바로 넘어가는거라고 이해해야하나요??? 제가 아직 자바스크립트에 대한 이해가 부족해서 이상한 질문일수도 있지만 궁금해서 질문해봅니다 감사합니다!!
-
해결됨웹 게임을 만들며 배우는 React에 TypeScript 적용하기
ref 관련 질문드립니다
안녕하세요 강의 수강 중 궁금한 점이 생겨 질문드립니다 1. function useRef<T>(initialValue : T | null) : RefObject<T>;2. function useRef<T>(initialValue : T ) : MutableRefObject<T>; 3. function useRef<T = undefined>() : MutableRefObject<T | undefined>;setTimeout, useRef 타이핑 강의 끝부분에서<T>를 <number | null> 을 줬습니다.그러면 2번 꼴이 만들어지는 거를 확인할 수 있었는데요,그럼, <number> 만 잇엇을때에도 똑같이 Mutable~ 로 인식할수잇다고 생각햇는데, 제가 어디를 놓친건지 알려주시면 감사하겟습니다
-
해결됨웹 게임을 만들며 배우는 React
TicTacToe.jsx reducer 부분 질문입니다
case CLICK_CELL: const tableData = [...state.tableData]; tableData[action.row] = [...tableData[action.row]]; // immer라는 라이브러리로 가독성 해결 tableData[action.row][action.cell] = state.turn; // console.log(tableData[action.row]); return { ...state, tableData, recentCell: [action.row, action.cell], } 위 코드에서 tableData[action.row], [...tableData[action.row]] 두 값이 동일한데 [...tableData[action.row]] <-- 이 배열을 대입해주는 이유가 있을까요? 콘솔로 찍어봐도 동일한값으로 나오길래 주석처리하고 진행해보니 정상작동은 하는데 최적화 부분에서 memo를 적용했을 때 렌더링이 정상적으로 되지 않더라구요 어떤 이유 때문에 위와 같은 현상이 생기는지 알 수 있을까요?
-
선생님 제발 도와주세요
삭제된 글입니다