기록

0505: mern 기본 세팅하기 본문

John Ahn 노드 리액트

0505: mern 기본 세팅하기

mnmhbbb 2021. 5. 5. 20:21

https://www.youtube.com/playlist?list=PL9a7QRYt5fqkZC9jc7jntD1WuAogjo_9T 강의 보고 공부한 내용

1. 기본 폴더에 백엔드부터 세팅.

1.1 express

npm init
npm i express

/server 폴더를 만들고 index.js 파일을 생성해서 다음 코드를 복붙.

const express = require('express')
const app = express()
const port = 5000

app.get('/', (req, res) => {
  res.send('Hello World!')
})

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})

package.json을 다음과 같이 수정하고, npm run start 해서 잘 연결됐는지 확인

  "scripts": {
    "start": "node server/index.js"
  },

 

1.2 몽고디비 연결

몽고디비 로그인하면 한 계정당 한 개의 db만 무료로 제공하는 것 같다.
추가로 필요하면 계정을 더 만들어야겠다.

로그인 - cluster 생성 - 무료인 싱가폴 선택하여 생성

위 CONNECT를 클릭하여 ip 연결, 유저 생성
- choose a connections method

- Connect your application - 링크 복사

이제 몽구스를 설치해서 얘를 이용해서 몽고디비를 편하게 사용하려고 함.
npm i mongoose
자바스크립트 객체(object)와 몽고디비(document)를 1대1 매칭해주는 역할을 한다고 함.
즉, DB에서 조회할 때 문서를 조회할 때 자바스크립트 객체로 바꿔줌

index.js에 다음 코드를 입력. 비밀번호 부분에 내가 아까 입력한 비밀번호를 입력.
그런 다음 npm run start 해보면 터미널에 MongoDB Connected... 가 뜰거임.

const mongoose = require("mongoose");
mongoose
  .connect(
    "mongodb+srv://아이디:비밀번호@boilerplate.3rk7w.mongodb.net/myFirstDatabase?retryWrites=true&w=majority",
    {
      useNewUrlParser: true,
      useUnifiedTopology: true,
    }
  )
  .then(() => console.log("MongoDB Connected..."))
  .catch((err) => console.log(err));

 

1.3 비밀번호 관리

아까 위에서 작성한 몽고디비 비번을 가려야겠지.
그래서 비밀정보들을 한 파일에 몰아둔 후 .gitignore 파일에 dev.js를 넣어줄 것.

단! 개발할 때 환경에 따라 다르게 설정해야 한다.
1)로컬(local) 환경: 폴더안에 전용파일을 만들어서 거기에 비밀정보를 입력
2)배포한 후(deploy): 예를 들어, heroku 같은 클라우드서비스를 이용해서 배포한 후 개발할 때는 해당 사이트에다가 몽고디비 정보를 입력해서 관리해야 함

환경변수 process.env.NODE_ENV
development
production

/config/dev.js, prod.js, key.js 3개의 파일을 생성해서,
환경에 따라 다른 파일을 가져오도록.

//config/key.js
if (process.env.NODE_ENV === "production") {
  module.exports = require("./prod");
} else {
  module.exports = require("./dev");
}

//config/dev.js
module.exports = {
  mongoURI:
    "mongodb+srv://mnmhbbb:nodereact64@boilerplate.3rk7w.mongodb.net/myFirstDatabase?retryWrites=true&w=majority",
};

//config/prod.js
module.exports = {
  mongoURI: process.env.MONGO_URI,
};

(MONGO_URI 부분은 헤로쿠 사이트에 입력한거랑 동일하게 입력)

그럼 이제 index.js를 다시 아래와 같이 수정한다.

const express = require('express');
const app = express();
const port = process.env.PORT || 5000;
const config = require('./config/key');

const mongoose = require('mongoose');
mongoose
  .connect(config.mongoURI, {
    useNewUrlParser: true,
    useCreateIndex: true,
    useUnifiedTopology: true,
  })
  .then(() => console.log('MongoDB Connected...'))
  .catch((err) => console.log(err));

app.get('/', (req, res) => {
  res.send('Hello World!');
});

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`);
});

 

1.4 몽고디비 모델 & 스키마

회원가입을 할 때 유저 정보에 대한 데이터를 보관하기 위해 모델을 만들자.

Model은 Schema를 감싸주는 역할을 함.

스키마 데이터베이스에서 자료의 구조, 자료의 표현 방법, 자료 간의 관계를 형식 언어로 정의한 구조.
다이어그램에 나와있는 이러한 코드가 스키마를 의미한다.

데이터의 구조, 표현 방법, 관계...

/models/User.js 에 이렇게 스키마를 만들고, 모델로 감싼 다음, 다른 곳에서도 쓸 수 있게 export 해준다.

 

1.5 body-parser

사용자가 회원가입할 때 정보를 입력하면 
클라이언트 --- > 서버로 데이터를 보낼 때 
Request with Body(the JSON, buffer, string, and URL encoded data)를 하는데.
이때 Body 데이터를 분석(parse)해서 req.body로 출력해주는것=Body-parser dependency
그래서 npm i body-parser 로 설치를 한다.
클에서 보낸 body를 파싱해서 데이터베이스에 넣을 수 있게 도와줌.

 

1.6 postman으로 확인

아직 만들어놓은 클라이언트가 없으니까, postman 실행해서 request를 보내기.
그래서 클-서 데이터 요청을 잘 하고 있는지 확인하자.
postman 홈페이지에서 프로그램 설치

index.js에 임시로 이런 코드를 작성한다.

const { User } = require("./models/User");
const bodyParser = require('body-parser');

app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

app.post('/register', (req, res) => {
  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,
    });
  });
});

지난 시간에 만든 모델User(스키마를 감싸고 있는)를 가져오고, 
위에서 설치한 body-parser를 가져오고, 관련 옵션도 적어줬다.

이제 본격 routes 부분인데, app.post( )
클라이언트에서 받아온 정보들을 데이터베이스에 넣기 위해 req.body

req.body 안에는 JSON 형식으로 구성되어있음.
이렇게 req.body 에 이러한 형태로 들어있을 수 있는 것이 body-parser가 있어서 가능한 것.
(복습: body-parser는 클라이언트에서 오는 정보를 서버에서 분석해서 가져올 수 있게 해줌)

+)요청에 대한 정보는 req에 다 들어있고
응답에 대한 정보는 res에 다 들어있다.

이렇게 body-parser를 이용해서 클라이언트에서 보내준 정보를 req.body로 받는다는 거.

그 다음, user.save( ) 부분을 해석하면,
받아온 정보 req.body를 데이터베이스에 저장하는 부분이다.
(스키마를 감싸고 있는 모델 User의 인스턴스인 user에 req.body 정보들이 저장이 되고,
1)저장할 때 에러가 있으면 클라이언트한테 json 형식으로 실패했다는 결과를 전달하고
2)클라이언트에서 받아온 정보 저장을 성공했으면?
성공했다는 의미의 status 200과 마찬가지로 json 형식으로 성공했다고 전달함!)

npm run start로 서버 연결해놓고
postman에서 연결된 http://localhost:5000/register 주소 입력하고, post 선택하고,
Body - raw - JSON 을 선택에서 안에다가
회원가입하듯이 name, email 이런 걸 입력해서 send 하면 결과가 뜸.

"success": true

그러면 회원가입에 성공한 거임.
내 몽고디비 collections에서도 확인할 수 있음.

스키마를 감싼 모델을 가져와서, 클라이언트에게 받은 정보를 데이터베이스에 저장한다.
스키마 모델로 새로운 함수를 만들었고, 거기에 정보를 저장.. 
그 스키마 데이터 구조를 가져다 쓰는... 고런 느낌

1.7 nodemon 설치

소스가 변경됐을 때 변화를 자동으로 감지해서 서버를 재시작해주는 툴
npm i -D nodemon
scripts에 nodemon 명령어를 추가. 이제 npm run backend 하면 변화를 알아서 감지함.

  "scripts": {
    "start": "node server/index.js",
    "backend": "nodemon server/index.js",

 

1.8 bcrypt로 비밀번호 암호화하기

데이터베이스에 쌩으로 저장된 비밀번호를 암호화하기 위해
npm i bcrypt

그 다음 다이어그램에 있는 링크에 들어가서 순서에 따라 진행한다.
www.npmjs.com/package/bcrypt
salt를 생성해서 비밀번호 암호화

아까 했던 user.save를 하기 전 단계에서 암호화를 해야 함.
그러므로 모델 User.js에 가서
10자리인 salt를 생성하고, 이걸 이용해서 암호화 해쉬화

const bcrypt = require("bcrypt");
const saltRounds = 10;

const useSchema = mongoose.Schema({ 
	... 생략
})

//비밀번호 암호화하기
useSchema.pre("save", function (next) {

  var user = this;

  //비밀번호를 수정할 때만 암호화하기
  if (user.isModified("password")) {
    bcrypt.genSalt(saltRounds, function (err, salt) {
      if (err) return next(err);

      bcrypt.hash(this.password, salt, function (err, hash) {
        if (err) return next(err);
        user.password = hash;
        next();
      });
    });
  } else {
    next();
  }
});

useSchema.pre('save', function ---> save 하기 전에 동작시킬 함수.
그리고 이 함수가 끝나면 next()를 해서 user.save() 로 이동하도록.
useSchema를 감싼 model이 User인데 이걸로 생성한 변수 user로 user.save하니까. 순서가 그렇게 되겠지.

에러가 발생하면 바로 user.save 단계로 이동하도록 next() 를.
그리고 이름변경이나 이메일 변경 같은 상황에선 비밀번호 암호화하면 안됨!
비밀번호 변경할 때만 암호화시켜야하기 때문에 if문 추가.
+) 참고로 화살표함수가 안되는 듯.. es5 방식으로 this 를 할당해서 사용함.
"몽구스에서 위 강좌에서처럼 인스턴스 메서드를 만들어서 this를 사용하는 경우에는 function 키워드를 써줘야 합니다. 그래야 자신이 속한 객체, 여기서는 this가 userSchema 도큐먼트를 가리킵니다. 화살표 함수를 사용하면, this가 자신이 종속된 인스턴스를 가리키기 때문에 해당 함수를 호출한 영역을 가리키게 되므로 문서 객체에 접근하지 못합니다."

 

1.9 Register Route 만들기 

유저 관련 라우트들을 한 파일에 넣기 위해 server/routes/users.js 생성하고, 
아까 index.js에 임시로 적어서 테스트 했던 코드를 포함해서 유저 관련 기능들을 여기에 작성.

그리고 index.js에는 이 코드 한 줄만 남기면 됨

// const { User } = require('./models/User');
app.use('/api/users', require('./routes/users'));

 

코드에 필요한 패키지들을 우선 설치하고, 필요한 파일에 require 해준다.
npm i jsonwebtoken cookie-parser 

// index.js
const cookieParser = require('cookie-parser');

app.use(cookieParser());

// models/User.js
const jwt = require('jsonwebtoken');

 

routes/users.js에 다음 코드 복붙 1
1) auth 2) register 3) login 4) logout

const express = require('express');
const router = express.Router();
const { User } = require('../models/User');

const { auth } = require('../middleware/auth');

router.get('/auth', auth, (req, res) => {
  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,
    role: req.user.role,
    image: req.user.image,
  });
});

router.post('/register', (req, res) => {
  const user = new User(req.body);

  user.save((err, doc) => {
    if (err) return res.json({ success: false, err });
    return res.status(200).json({
      success: true,
    });
  });
});

router.post('/login', (req, res) => {
  User.findOne({ email: req.body.email }, (err, user) => {
    if (!user)
      return res.json({
        loginSuccess: false,
        message: 'Auth failed, email not found',
      });

    user.comparePassword(req.body.password, (err, isMatch) => {
      if (!isMatch) return res.json({ loginSuccess: false, message: 'Wrong password' });

      user.generateToken((err, user) => {
        if (err) return res.status(400).send(err);
        res.cookie('w_authExp', user.tokenExp);
        res.cookie('w_auth', user.token).status(200).json({
          loginSuccess: true,
          userId: user._id,
        });
      });
    });
  });
});

router.get('/logout', auth, (req, res) => {
  User.findOneAndUpdate({ _id: req.user._id }, { token: '', tokenExp: '' }, (err, doc) => {
    if (err) return res.json({ success: false, err });
    return res.status(200).send({
      success: true,
    });
  });
});

module.exports = router;

 

로그인

1) 요청한 이메일이 데이터베이스에 있는지 확인하기 위해 User.findOne()
2) 있다면 비밀번호가 일치하는지 확인
비밀번호가 암호화되어서 저장되어있으니까, 요청 온 비밀번호(plain password)를 암호화해서 비교해야 함.
comparePassword라는 메서드를 만들어서 사용하고, models/User.js에서도 bcrypt.compare라는 자체메서드로 비교.
3) 비밀번호가 일치하면 토큰 생성하기(토큰이 로그인한 유저에 대한 고유 정보 같은 것을 의미함)
- 그래서 jsonwebtoken 설치하고 models/Users.js에서 useSchema.methods.generateToken...
user._id는 데이터베이스에 저장된 고유 아이디를 의미함
4) 토큰을 만들어서 어디에 저장할까? 쿠키나 로컬스토리지 세션 스토리지 등등 여러가지 있음
쿠키에 저장하기로 하자. 그래서 cookie-parser를 설치했음.

models/User.js에도 다음 코드를 복붙 2

const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const saltRounds = 10;
const jwt = require('jsonwebtoken');

const userSchema = mongoose.Schema({
  name: {
    type: String,
    maxlength: 50,
  },
  email: {
    type: String,
    trim: true,
    unique: 1,
  },
  password: {
    type: String,
    minglength: 5,
  },
  role: {
    type: Number,
    default: 0,
  },
  image: String,
  token: {
    type: String,
  },
  tokenExp: {
    type: Number,
  },
});

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();
  }
});

userSchema.methods.comparePassword = function (plainPassword, cb) {
  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', user);
  console.log('userSchema', userSchema);
  var token = jwt.sign(user._id.toHexString(), 'secret');

  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;

  jwt.verify(token, 'secret', function (err, decode) {
    user.findOne({ token: token }, function (err, user) {
      if (err) return cb(err);
      cb(null, user);
    });
  });
};

const User = mongoose.model('User', userSchema);

module.exports = { User };

여기에서 비크립트 암호화해서 비밀번호 비교하고, 토큰 생성함. 
그리고 생성한 토큰은 쿠키에 저장.

로그인 전체 과정

 

추가로, server/middleware/auth.js 생성해서 다음 코드를 또 복붙 3

const { User } = require('../models/User');

let auth = (req, res, next) => {
  let token = req.cookies.w_auth;

  User.findByToken(token, (err, user) => {
    if (err) throw err;
    if (!user)
      return res.json({
        isAuth: false,
        error: true,
      });

    req.token = token;
    req.user = user;
    next();
  });
};

module.exports = { auth };

 

 

1.10 auth

routes/users.js에 있는 auth

router.get('/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,
    role: req.user.role,
    image: req.user.image,
  });
});

// ex) role 1 어드민    role 2 특정 부서 어드민
// role 0 -> 일반유저   role 0이 아니면  관리자

1. 페이지 이동 때마다 로그인되있는지 안되어있는지, 관리자 유저인지... 등을 체크해야 함
2. 글을 쓸 때나 지울 때 권한이 있는지 같은 것도 체크

서버 db에 만든 토큰 <------> 클라이언트 cookie에 만든 토큰
서로 일치하는지 계속 체크해야 함.

쿠키에 token이 인코드 되어있는 상태니까 이걸 디코드해주면 유저아이디를 확인할 수 있음
이걸 이용해서 일치하는지 확인
확인하는거니까 get 메서드

이렇게 확인할 때, /middleware/auth.js 를 생성해서 인증처리를 위한 미들웨어 auth를 넣어줘야 함.

//User.js
userSchema.statics.findByToken = function (token, cb) {
  var user = this;
  // user._id + ''  = token
  // 인코딩된 토큰을 decode 한다.
  jwt.verify(token, 'secret', function (err, decoded) {
    //유저 아이디를 이용해서 유저를 찾은 다음에
    //클라이언트에서 가져온 token과 DB에 보관된 token이 일치하는지 확인
    user.findOne({ token: token }, function (err, user) {
      if (err) return cb(err);
      cb(null, user);
    });
  });
};

jwt.verify로 토큰을 디코드하고, findOne으로 토큰이 서로 일치하는지 확인

 

이렇게 확인을 해서 일치한다면,
해당 하는 유저의 아이디, 이름, 이미지, 어드민여부 등을 클라이언트에게 전달함.
res.status(200).json({ ... 
클라이언트는 이 정보를 바탕으로 보여지는 화면을 관리하겠지.(프론트엔드의 역할)

 

1.11 로그아웃

 

로그인 postman 테스트
index.js 에서 app.use('/api/users', require('./routes/users')); 했기 때문에, url이 달라짐.

로그인 성공

여기서 GET 요청으로 바꾸고 logout 하면

로그아웃 성공

 

1.12 CORS 

cors 관리를 백엔드에서 미리해놓자.

npm i cors 

// index.js
const cors = require('cors')

app.use(cors())

 


2. 프론트 시작

현재 mern-lecture/server 이렇게 만들어진 상태고,
mern-lecture/package.json에서 서버 관련 디펜던시들 관리되고 있음.

여기에서 CRA client 프로젝트 생성하면 client 폴더가 생김
근데 나는 툴킷을 사용할 거니까, --template redux를 추가하고 기본으로 필요한 디펜던시 미리 설치.
npx create-react-app client --template redux
npm i react-router react-router-dom
npm i axios

client의 폴더 구성은 
app/store
components/App, 컴포넌트-컴포넌트&스타일
pages
redux/각 리듀서폴더

 

2.1 레이아웃 구조

근데 이제 강사님은 각 페이지컴포넌트들을 hoc를 사용해서 auth로 감싸고, 인증처리를 하도록 코드를 작성하셨다.

//null   Anyone Can go inside
//true   only logged in user can go inside
//false  logged in user can't go inside

function App() {
  return (
    <Suspense fallback={(<div>Loading...</div>)}>
      <NavBar />
      <div style={{ paddingTop: '69px', minHeight: 'calc(100vh - 80px)' }}>
        <Switch>
          <Route exact path="/" component={Auth(LandingPage, null)} />
          <Route exact path="/login" component={Auth(LoginPage, false)} />
          <Route exact path="/register" component={Auth(RegisterPage, false)} />
        </Switch>
      </div>
      <Footer />
    </Suspense>
  );
}

이런 닉김으로...!

일단 이렇게 구성해놓고, 간단하게 스타일을 꾸민 다음, 리덕스를 도입해서 액션을 추가하자.
npm i styled-components

 

2.2 styled-components

스타일드컴포넌트를 활용해서 레이아웃 틀을 반응형으로 다 만들어놓자.
지난 번에 공부한 내용이랑, 어제 들은 유튜브 강의를 참조해서.

NavBar 만들고,
레이아웃 폴더에 LayoutStyle 만들어서, 각 페이지에서 이 레이아웃을 사용하도록.
네비바 아래 컨텐츠 부분의 배열을 반응형으로 어느정도 균일하게 맞추고,
로그인, 회원가입 페이지를 만들자.

miniland 프로젝트에서 사용했던 로그인/회원가입 antd 폼을 사용하려고 했는데,
formik + yup 을 사용해서 유효성검사까지.
아이디저장 같은 기능도 localStorage에 넣는 방식으로...
일단 테스트 해보고 성공하면 코드를 뜯어봐야겠다.

 

2.3 툴킷

리덕스 툴킷 세팅을 해보자.

user 관련 리듀서함수가 일단 필요하니까.
user 폴더에 userSlice.js & userAPI.js(여기에는 axios createAsyncThunk 코드들 모음)

필요한 액션은 크게 4개
회원가입/로그인/로그아웃/인증체크auth

툴킷이 아직 어색하긴 한데, 다음과 같이 작성했다.

// userAPI.js
export const loginUser = createAsyncThunk('user/loginUser', async (dataToSubmit) => {
  const request = await axios
    .post('api/users/login', dataToSubmit)
    .then((response) => response.data);
  return request;
});
// userSlice.js
import { createSlice } from '@reduxjs/toolkit';
import { loginUser, logoutUser, registerUser } from './userAPI';

const userSlice = createSlice({
  name: 'user',
  initialState: {},
  reducers: {},
  extraReducers: {
    [loginUser.pending]: (state) => {
      state.status = 'loading';
    },
    [loginUser.fulfilled]: (state, { payload }) => {
      state.loginSuccess = payload;
      state.status = 'success';
    },
  },
});

export default userSlice.reducer;

그런 다음, 이미 디비에 있는 계정으로 로그인을 했을 때 아래와 같이! 

 

2.5 Concurrently

루트 디렉토리에 와서 npm i concurrently 하고

"dev": "concurrently \"npm run backend\" \"npm run start --prefix client\""

백엔드에서 연결하는 명령어와 프론트에서 연결하는 명령어를 입력하는데,
npm run start를 그냥 입력하면 백단에 있는 명령어랑 겹치니까 --prefix client를 붙여준다는 것.
\" npm 명령어 \" 
\"로 감싼다...

이제 npm run dev 만 입력해도 동시에 둘다 연결할 수 있게 됨~! 신기방기

 

2.6 http-proxy-middleware

npm i -D http-proxy-middleware 설치하고

/src/setupProxy.js

const createProxyMiddleware = require('http-proxy-middleware');

module.exports = function(app) {
  app.use(
    '/api',
    createProxyMiddleware({
      target: 'http://localhost:5000',
      changeOrigin: true,
    })
  );
};

 

backend index.js에서는 아래와 같이 작성했음.

app.use(cors());
app.use(
  cors({
    origin: 'http://localhost:5000',
    credentials: true,
  }),
);

실험삼아 위 코드를 지웠는데 똑같이 정상적으로 동작함.

npm run dev를 하면 터미널이 다음과 같이 나옴.

로그인이 성공적으로 되는 것을 확인했음.

 

+) NEWNEEK 사이트를 참고해서 반응형으로,,
디바이스 별로 max-width를 설정하고, margin: 0 auto;를 줘서, 가운데 정렬을 맞추는 모습이다.
예를 들어, 데스크탑이라서 1200px 이상일 때,
컨텐츠에 max-width: 480px을 주면, 화면 가운데에 480px 만큼 컨텐츠가 자리하고 양옆은 공백.

메인 페이지에 각 뉴스 아티클?들을 모아놓는 section 태그에 
@media (max-width: 820px) 일 때 max-width: 100%; 화면에 꽉 차게
@media (max-width: 1360px)일 때 max-width: 1040px; 제한크기를 1040까지만 줬음. 그리고 margin: 0 auto;

그러면 나도 이런 식으로 breakpoint를 주고 적절히 조절해야겠는데. 768px로 할까
ipad가 768px임. ipadpro는 1024px
모바일 기준으로 만들고,
@media (min-width: 768px) 즉 768px부터는 ~~~ 

작은 화면에서 큰 화면으로 조절해 나갈때는 min-width 를 큰 화면에서 작은 화면으로 조절해 나갈때는 max-width 를 사용합니다.

LayoutStyle 컴포넌트에서 미리 틀을 짜놓아서,
이 레이아웃을 적용만 하면 자동으로 가운데에 예쁘게 들어가게 하려고 했는데,
{children}에 뭐가 들어올지를 모르니까 짜기가 애매하다.
그때그때 적용해야겠다..
그래도 대충 방법을 알게 되었다.

import React from 'react';
import styled from 'styled-components';

const Container = styled.div`
  background-color: skyblue;

  .children {
    max-width: 600px;
    margin: 0 auto;
  }
`;

const LayoutStyle = ({ children }) => {
  return (
    <Container>
      <div className="children">{children}</div>
    </Container>
  );
};

export default LayoutStyle;

children이 들어갈 div에 className을 줘서 아예 지정하는 방법이 생각났다.
이렇게 하면 LayoutStyle을 사용할 때 가운데 정렬이 예쁘게 된다.

그리고 최소 width는 아이폰se 320px니까 그걸 유념해서 만들 것.

background: #ebebeb;
border: 1px solid #161616;

 

2.7 auth 적용

툴킷에도 auth 관련 함수를 만들고,
/hoc/auth.js 에 코드를 복붙하고,
App.js를 다음과 같이 감싸서 각 페이지 접근 권한을 나눈다.
그래서 해당 페이지로 갈 때마다 체크하는 액션이 디스패치된다.
null: 아무나 접근 가능
true: 로그인한 유저만 접근 가능
false: 로그인한 유저는 접근 불가

      <Switch>
        <Route exact path="/" component={Auth(MainPage, null)} />
        <Route exact path="/login" component={Auth(LoginPage, false)} />
        <Route exact path="/register" component={Auth(RegisterPage, false)} />
      </Switch>

그리고 리듀서 함수에서 state 몇 가지를 수정했다.
리덕스데브툴을 보고 조정했다.

일단 여기까지 하고 커밋.

햄버거버튼을 수정하고, 드디어 본 강의로 들어가야겠다.

2.8

 

2.9 auth에 따른 로그인/로그아웃 화면 구현하기

NavBar 컴포넌트에서 삼항연산자로 userData.isAuth의 boolean 상태에 따라 로그인회원가입/로그아웃이 보이게 하려는데 제대로 구현되지 않고 있다. 
로그인이 되었는데도, 로그인회원가입이 보인다.

App에서 hoc 방식으로 hoc/auth.js의 Auth 함수를 어떻게 사용하고 있냐면,

        <Route exact path="/" component={Auth(MainPage, null)} />
        <Route exact path="/login" component={Auth(LoginPage, false)} />
        <Route exact path="/register" component={Auth(RegisterPage, false)} />

Auth는 (컴포넌트와 null/false) 를 받는데,
그 컴포넌트를 ComposedClass로 받아서, 
export const Auth = (ComposedClass, reload, adminRoute = null) => {
...
return <ComposedClass {...props} user={user} />;
이렇게 props를 전달하는 컴포넌트로 반환하고 있다. user도 전달하고 있음. 그 user는 리덕스 state
그래서 LoginPage 같은 컴포넌트에서 props를 받았던 것.

아.. 오타때문이었다.
그래도 저 props를 제대로 알게 되었다.

이제 회원가입,로그인 폼 부분 스타일만 맞추면 진짜 끝...!
근데 css 예쁘게 꾸미기가 왜이렇게 생각보다 힘들까.
antd를 사용하고 있어서 그런가?

회원가입에 동의하기도 넣어봐야할텐데.
모두 동의
필수/선택

 

+) 로그인 페이지 antd가 적용되지 않아서 그냥 form, input으로 바꾸던 과정에서 발생한 에러
input is a void element tag and must neither have `children` nor use `dangerouslySetInnerHTML`.

styled component 를 사용해 input 을 만든 후에 자식 tag를 넣어줘서 발생한 에러.
input은 자식 tag(children)을 가질 수 없기 때문에 감싸주는 것은 div로 바꿔줘야 함 

아이디 저장을 input type="checkbox"로 하려고 input으로 아이디저장이라는 단어를 감쌌는데, 그렇게 하면 안됨
label을 사용하고 input id="rememberMe" label for="rememberMe" 이렇게 사용해야 함.
근데 리액트 jsx에서는 htmlFor으로 해야 함!

회원가입 form - fieldset - div - label&input
이런 구조로 되어있음.
뉴닉 사이트를 참고!

 

'John Ahn 노드 리액트' 카테고리의 다른 글

무비앱 만들기  (0) 2021.03.13
Comments