리엑트 postcss에서 sass 문법 설정 방법

react에서 sass 설정을 살펴 보던 중 postcss가 기본설정이 되어 있어
굳이 sass loader를 사용하기 보다 postcss에서 sass문법을 지원하는
플러그인 설치를 하는 방향이 더 좋을거 같습니다.

postcss에서 sass문법을 지원하는 플러그인은 postcss에서 검색을 하면 찾아 볼 수 있습니다.
우선 자주 쓰는 변수 선언, 중첩 css 작성할 수 있게 해주는 precss, postcss-nested을 설치해 보도록 하겠습니다.

config 파일 생성

1
yarn eject

precss, postcss-nested 설치

1
yarn add -D precss postcss-nested

webpack 설정

config/webpack.config.dev
config/webpack.config.prod
파일에서 아래 부분을 찾은다음 추가해 주면 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{
test: /\.css$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
},
},
{
loader: require.resolve('postcss-loader'),
options: {
// Necessary for external CSS imports to work
// https://github.com/facebookincubator/create-react-app/issues/2677
ident: 'postcss',
plugins: () => [
require('precss'), // 추가
require('postcss-nested'), //추가
require('postcss-flexbugs-fixes'),
autoprefixer({
browsers: [
'>1%',
'last 4 versions',
'Firefox ESR',
'not ie < 9', // React doesn't support IE8 anyway
],
flexbox: 'no-2009',
}),
],
},
},
],
},

App.js , App.css 수정 후 확인

아래와 같이 파일을 수정한 후 잘 동작하는지 확인하면 됩니다.

src/App.css

/src/App.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
<div className={`test`}>test</div>
</div>
);
}
}

export default App;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
$blue: #056ef0;

.App {
text-align: center;
.test {
color: $blue;
}
}

.App-logo {
animation: App-logo-spin infinite 20s linear;
height: 80px;
}

.App-header {
background-color: #222;
height: 150px;
padding: 20px;
color: white;
}

.App-title {
font-size: 1.5em;
}

.App-intro {
font-size: large;
}

@keyframes App-logo-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}

terms-stateful-stateless

Service statelessness

When people talk about stateless applications, they in fact mean no state in the web tier. But state can be stored in several layers:

Stateless Protocol

Restful: Stateless

HTTP: Stateless

TCP: Stateful
UDP: connectionless session , Stateless connection

IP(Internet Protocol): stateless

FTP: Stateful

resource

리엑트 컴포넌트 상태(state, props)에 대한 설명

state와 props의 차이점

state와 props(properties의 축약형)는 둘다 자바스크립트 객체입니다.
둘다 렌더링 결과에 영향을 주는 정보들을 가지고 있지만 한가지 중요한 점이 다릅니다.
props는 함수의 파라미터와 같이 컴포넌트를 통과합니다.
반면에 state는 함수 안에 선언된 변수와 같이 컴포넌트 안에서만 상태를 관리합니다.

setState()

setState()는 컴포넌트의 state 객체를 업데이트 하는 함수 입니다.
state가 변경되면 render()가 호출되어 컴포넌트에 상태가 반영됩니다.

setState()는 비동기로 호출 되지만 이벤트 핸들러 안에서 동기적으로 실행됩니다.
예를 들어, 클릭 이벤트 핸들러 함수 안에서 부모와 자식이 setState를 호출하면 자식은 두번 렌더링 하지 않습니다.
리엑트는 브라우저 이벤트가 끝날 때 state 업데이트를 반영합니다.
그 결과로 눈에 띄는 성능 향상이 됩니다.

리엑트는 다시 렌더링 되기 전에 모든 컴포넌트가 컴포넌트 이벤트 핸들러 안에서 setState()를 호출할 때까지 대기합니다.
이것은 불필요한 렌더링을 피함으로서 성능 향상이 됩니다.

setState가 잘못된 값을 주는 이유

리엑트에서는 this.props와 this.state는 현재 화면에 표시된 렌더링된 값을 표현합니다.

예상대로 작동하지 않는 코드 예

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
incrementCount() {
// 아래 코드는 의도한 대로 동작하지 않습니다.
this.setState({count: this.state.count + 1});
}

handleSomething() {
// `this.state.count`는 0부터 시작합니다.
this.incrementCount();
this.incrementCount();
this.incrementCount();
// 리엑트가 컴포넌트를 다시 렌더링할때 this.state.count는 1이고 3이 아닙니다.

// incrementCount()함수는 this.state.count의 값을 사용하지만
// 리엑트가 다시 렌더링 되기 전까지 this.state.count값을 업데이트 하지 않습니다.
// 그래서 incrementCount()는 this.state.count를 매번 0으로 읽고 1로 할당합니다.

}

수정된 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
incrementCount() {
this.setState((prevState) => {
// 업데이트를 할 때 this.state대신 prevState 값을 읽어야 합니다.

return {count: prevState.count + 1}
});
}

handleSomething() {
// `this.state.count`는 0부터 시작합니다.
this.incrementCount();
this.incrementCount();
this.incrementCount();

// 현재 `this.state.count` 값은 0이고 리엑트가 컴포넌트를 다시 렌더링하면 3이 됩니다.

}

resource

리엑트 컴포넌트에서 ajax 호출 방법

Ajax 호출은 componentDidMount() 에서 해야한다.

리엑트 문서에서 Ajax호출은 componentDidMout()에서 해라고 권고한다.
Ajax호출 후 setState를 통해 상태를 변경 하면 render()가 호출 되어 컴포넌트에 반영된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: []
};
}

componentDidMount() {
fetch("https://api.example.com/items")
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result.items
});
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}

render() {
const { error, isLoaded, items } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<ul>
{items.map(item => (
<li key={item.name}>
{item.name} {item.price}
</li>
))}
</ul>
);
}
}
}

resource

리엑트 네이티브 개발환경 설정하기

리엑트 네이티브

리엑트 네이티브는 자바스크립트로 iOS/Android 네이트브앱을 개발할 수 있게 도와주는 도구입니다.

Watchman, Flow 설치

1
2
brew install watchman
brew install flow

리엑트 네이티브 CLI 설치

1
npm install -g react-native-cli

리액트 네이티브 앱 만들기

1
2
3
react-native init HelloWorld
cd HelloWorld
npm install

실행

1
react-native run-ios

수정

App.js 를 열고 파일을 수정할 수 있습니다.

그 후 에뮬레이터에서 cmd + R 을 누르면 수정된 부분이 반영됩니다.

크롬 디버거

iOS simulator 에서 크롬 디버거를 통해 콘솔로그 등 작업을 할 수 있습니다.

  1. iOS simulator에서 Command + D를 누름
  2. Dedub JS Remotely 선택

Expo 설치

리엑트 네이티브를 사용하기 위해서는 Expo라는 개발도구가 필요합니다.
Expo다운로드 링크: https://xde-updates.exponentjs.com/download/mac
Expo를 다운받아 실행 후 로그인까지 하면 위에서 설치한 AwesomeProject를 보실 수 있습니다.

에뮬레이터 설치

리엑트 네이티브로 개발시 웹에서처럼 live-reload기능이 제공됩니다.
데스크탑에서 개발하기 위해서 iOS simulator / Genymotion 설치합니다.
핸드폰에서도 Expo Client(iOS/Android)앱을 설치하면 개발하면서 바로 확인이 가능합니다.

에뮬레이터 실행

AwesomeProject를 클릭 후 제일 오른쪽에 있는 Device를 클릭합니다.
Open on iOS Simulator / Open on Android를 클릭하면 위에서 설치한 Simulator에서
iOS/Android용 AwesomeProject 앱이 실행됩니다.

App.js 수정

/AwesomeProject/App.js 파일을 열고 React Native Test 코드를 추가해 줍니다.
그러면 자동으로 에뮬레이터에 반영되는 모습을 볼 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';

export default class App extends React.Component {
render() {
return (
<View style={styles.container}>
<Text>Open up App.js to start working on your app!</Text>
<Text>Changes you make will automatically reload.</Text>
<Text>Shake your phone to open the developer menu.</Text>
<Text>React Native Test</Text>
</View>
);
}

}

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});

네이티브 코드로 프로젝트 빌드

의존성 설치

1
2
3
brew install node
brew install watchman
npm install -g react-native-cli

React Native Application 생성

1
react-native init AwesomeProject

React Native Application 실행

1
2
cd AwesomeProject
react-native run-ios

resource

리엑트 리덕스

React Redux 란

  • React에 Redux를 공식적으로 바인딩한 라이브러리 입니다.

  • 리엑트 컴포넌트가 Redux store에서 데이터를 읽고 데이터를 업데이트하기 위해 store에게 actions를 발송할 수 있습니다.

React Redux 설치

1
npm install --save react-redux

<Provider />

<Provider /> 를 통해 Redux store를 app에서 사용할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React from 'react'
import ReactDOM from 'react-dom'

import { Provider } from 'react-redux'
import store from './store'

import App from './App'

const rootElement = document.getElementById('root')
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
rootElement
)

connect()

connect() 는 컴포넌트와 store를 연결해 줍니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { connect } from 'react-redux'
import { increment, decrement, reset } from './actionCreators'

// const Counter = ...

const mapStateToProps = (state /*, ownProps*/) => {
return {
counter: state.counter
}
}

const mapDispatchToProps = { increment, decrement, reset }

export default connect(
mapStateToProps,
mapDispatchToProps
)(Counter)
1
2
3
4
export const toggleTodo = id => ({
type: 'TOGGLE_TODO',
id
})
1
2
3
4
5
export const VisibilityFilters = {
SHOW_ALL: 'SHOW_ALL',
SHOW_COMPLETED: 'SHOW_COMPLETED',
SHOW_ACTIVE: 'SHOWD_ACTIVE'
}

References

크로스 도메인에서 쿠키 사용방법과 Set-Cookie 정리

크로스 도메인 Ajax요청 시 쿠키 사용방법

브라우저는 크로스 도메인 Ajax(XMLHttpRequest)요청시 응답에서 Set-Cookie항목은 무시합니다.
그래서 http://www.service.com에서 http://api.service.com 요청시 쿠기 생성이 되지 않습니다.
쿠키를 사용 하려면 withCredentials=true 옵션을 설정하면 되고 Javascript로 쿠키에 접근은 되지 않습니다.
1개의 도메인만 사용할 경우 withCredentials 옵션은 아무런 영향이 없습니다

1
2
3
4
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://example.com/', true);
xhr.withCredentials = true;
xhr.send(null);

쿠키를 생성 하려면 HTTP Response Header에서 Set-Cookie 설정을 해주면 됩니다.
가장 기본적인 Set-Cookie 설정은 아래와 같으며 각 요소를 ; 으로 구분합니다.

1
Set-Cookie: sessionid=38afes7a8; HttpOnly; Path=/
  • Domain = domain-value
    생략시 현재 도메인으로 지정되며 해당 도멘인 이외의 요청에서는 쿠키가 서버로 전송되지 않습니다.

  • Path = path-value
    URL 경로를 지정하며 하위 디렉토리까지 쿠키를 서보로 전송합니다.

  • HttpOnly
    위의 요소를 적용하면 Javascript(Document.cookie)로 쿠키에 접근할 수 없습니다.

  • Secure
    SSL 요청시에만 쿠키가 서버로 전송 됩니다.

Resource

리엑트 빌드시 개발환경에 따라 API 주소 설정 (Custom Environment)

웹프로젝트 개발시 개발,QA,상용 서버로 개발환경을 나누게 됩니다.
그럴 경우 각 환경에 따라 API 주소가 https://dev.service.com, https://qa.service.com 등으로 다르게 되는데
리엑트에서 빌드시 환경에 따른 API주소를 설정하는 방법을 알아보려고 합니다.

빌드시 각 환경은 아래와 같은 명령으로 구분하겠습니다.

1
$ yarn build <dev | qa | prd>

우선 create-react-app으로 프로젝트 생성을 합니다.

1
$ create-react-app <project-directory>

BuildConfig.js 생성

기존 설정파일 수정을 최소한으로 하는 방향으로 구성했습니다.
리엑트에서 커스텀 환경변수를 사용하기 위해서는 REACTAPP 로 시작하는 환경변수를 사용해야 합니다.
리엑트에서 기본으로 PUBLIC_URL 변수가 있지만 REACT_APP_SERVICE_URL 로 따로 지정해서 사용하겠습니다.

1
process.env.REACT_APP_SERVICE_URL = serviceUrl

아래 파일을 생성 후 코드를 작성합니다.
/tasks/BuildConfig.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
class BuildConfig {

constructor () {
let processEnv, serviceUrl

const processArgv = process.argv
this.currentEnv = processArgv[2] // dev | qa | prd Argument 담는 변수

switch (this.currentEnv) {
case 'dev':
processEnv = 'development'
serviceUrl = 'https://dev.service.com'
break
case 'qa':
processEnv = 'production'
serviceUrl = 'https://qa.service.com'
break
default:
processEnv = 'production'
serviceUrl = 'https://www.service.com'
}

process.env.REACT_APP_SERVICE_URL = serviceUrl
process.env.BABEL_ENV = processEnv
process.env.NODE_ENV = processEnv
}

getConfigData () {

const data = {
config: this.getConfig()
}

return data

}

getConfig () {
let config

switch (this.currentEnv) {
case 'dev':
config = require('../configs/webpack.config.dev')
break
case 'qa':
config = require('../configs/webpack.config.prod')
break
default:
config = require('../configs/webpack.config.prod')
}

return config
}
}

const buildConfig = new BuildConfig()
const data = buildConfig.getConfigData()

module.exports = data

빌드 코드 수정

/tasks/build.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const { config, timestamp } = require('./buildConfig')

// Do this as the first thing so that any code reading it knows the right env.
//process.env.BABEL_ENV = 'production'; // 주석처리
//process.env.NODE_ENV = 'production'; // 주석처리

// Makes the script crash on unhandled rejections instead of silently
// ignoring them. In the future, promise rejections that are not handled will
// terminate the Node.js process with a non-zero exit code.
process.on('unhandledRejection', err => {
throw err;
});

// Ensure environment variables are read.
require('../configs/env');

const path = require('path');
const chalk = require('chalk');
const fs = require('fs-extra');
const webpack = require('webpack');
//const config = require('../configs/webpack.config.prod'); // 주석처리
const paths = require('../configs/paths');
const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
const printHostingInstructions = require('react-dev-utils/printHostingInstructions');
const FileSizeReporter = require('react-dev-utils/FileSizeReporter');
const printBuildError = require('react-dev-utils/printBuildError');

...

여기까지 작성하면 빌드 설정관련 작업은 끝납니다.
이제 위에서 할당한 환경변수 사용방법을 알아 보겠습니다.

REACT_APP_SERVICE_URL 변수 사용방법

axios의 baseURL에 할당하면 다른 컴포넌트에서 편하게 사용할 수 있습니다.

/src/axios.js

1
2
3
4
5
import axios from 'axios'

axios.defaults.baseURL = process.env.REACT_APP_SERVICE_URL

export default axios

빌드

위 작업을 다 마치고 빌드를 하게되면 개발환경에 맞는 API주소로 통신을 하게 됩니다.

1
$ yarn build <dev | qa | prd>

위 코드를 조금 더 수정하면 빌드시 원하는 작업을 쉽게 추가할 수 있습니다.

필요하신 분들은 활용하시면 좋을거 같습니다.

Resource

Vue 컴포넌트 활용방법

Global Component, Local Component 사용방법

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div id="app">
<global-component></global-component>
<local-component></local-component>
</div>

<script>
Vue.component('globalComponent', {
template: '<div>globalComponent</div>'
})

new Vue({
el: '#app',
components: {
'localComponent': {
template: '<div>localComponent</div>'
}
}
})
</script>

자식 컨포넌트 Input Value 변경시 부모 컨포넌트의 Data 변경하는 방법

/src/components/Main.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<div>
<Toolbar :q.sync="q"></Toolbar>
</div>
</template>
£
<script>
import Toolbar from '@/components/Toolbar'

export default {
components: {
Toolbar
},
name: 'Main',
data () {
return {
q: ''
}
}
}
</script>

/src/compnents/Toolbar.vue

1
2
3
4
5
6
7
<template>
<div>
<input type="text"
:value="q"
@input="$emit('update:q', $event.target.value)"/>
</div>
</template>

자식 컴포넌트 Input 이벤트 발생 시 부모 컴포넌트 methods 실행 방법

/src/components/Main.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div>
<Toolbar @search="search"></Toolbar>
</div>
</template>

<script>
import Toolbar from '@/components/Toolbar'

export default {
components: {
Toolbar
},
name: 'Main',
methods: {
search () {
console.log('search method')
}
}

/src/compnents/Toolbar.vue

1
2
3
4
5
6
<template>
<div>
<input type="text"
@keyup.enter="$emit('search')"/>
</div>
</template>

Resource

Vue에서 Filters 사용방법

개인적으로 React에서 아쉬운 부분이 Angular,Vue에서의 Template기능입니다.
Template에서 Filters를 사용하면 Render과정에서 원본 데이터의 변화없이 출력할 수 있어 간단하게 화면을 조작할 수 있습니다.

Global Filters

Global Filters는 전체 components에서 사용가능합니다.
Global Filters 등록은 반드시 메인 Vue Instance 위에 작성을 해야합니다.

/src/main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import Vue from 'vue'
import App from './App'
import router from './router'

Vue.config.productionTip = false

Vue.filter('addText', function (value) {
return `${value} Add Text Global Filters`;
});

/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>',
})

/src/components/Hello.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<div class="hello">
<h1>{{ msg | addText}}</h1>
</div>
</template>

<script>
export default {
name: 'hello',
data () {
return {
msg: 'Welcome to Your Vue.js App'
}
}
}
</script>

<style scoped>
...
</style>

Local Filters

Local Filters는 해당 component에서만 사용할 수 있습니다.
사용방법은 해당 component에 filters를 등록 후 사용하면 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template>
<div class="hello">
<h1>{{ msg | addLocalText}}</h1>
</div>
</template>

<script>
export default {
name: 'hello',
data () {
return {
msg: 'Welcome to Your Vue.js App'
}
},
filters: {
addLocalText() {
return `${value} Add Text Local Filters`;
}
}
}
</script>

<style scoped>
...
</style>

여러 Filters를 Pipe( | )를 통해 같이 사용 가능

1
2
3
4
5
<template>
<div class="hello">
<h1>{{ msg | addLocalText | addText}}</h1>
</div>
</template>

Filters에 다중 파라미터 작성 가능

1
2
3
Vue.filter('addText', function (value, second, third) {
return `${value} Add Text Global Filters ${second} ${third}`;
});

Resource