Today I Learned ... react.js
🙋 My Dev Blog
./public/index.html
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css" />
./src/App.js
import React from 'react';
function App() {
return (
<div className="App">
<nav>
<div className='nav-wrapper'>
<div>Do IT! React</div>
</div>
</nav>
<h1>머터리얼 CSS</h1>
</div>
);
}
export default App;
머터리얼 디자인 공식 사이트에서 MATERIALIZE를 눌러
materialize.zip 파일을 내려받는다.
-> materialize.css 파일을 찾아 ./src폴더 안에 붙여넣는다.
import './materialize.css';
SCSS
를 사용해보자!머터리얼 디자인 공식 사이트에서 scss 파일도 제공한다.
./src/app.js
import './sass/meterialize.scss';
> npm i -D node-sass
예> Colors 에 있는 $primary-color의 밝기를 조정하는 lighten-
의 숫자를 바꿔본 결과
InputWithStyle.jsx
파일을 생성해보자....
return (
<div className="input-field">
<input
type={type}
id={`input_${name}`}
ref={this.setRef}
onChange={this.handleChange}
onFocus={onFocus}
value={value}
/>
<label htmlFor={`input_${name}`}>{label}</label>
{errorMsg && <span className="helper-text">{errorMsg}</span>}
</div>
);
> npm install -D sass-loader
webpack.config.js
파일 생성const path = require("path")
module.exports = {
module: {
rules: [
{
test: /\.scss$/,
loaders: ["sass-loader"],
include: path.resolve(__dirname, "../")
}
]
}
};
import '../src/sass/materialize.scss';
return(
<div className="input-field">
<input
id={`input_${name}`}
className={`validate ${errorMsg && 'invalid'}`} // 에러메시지 있으면 - invalid 클래스 추가
ref={this.setRef}
type={type}
onChange={this.handleChange}
value={value}
/>
<label className="active" for={`input_${name}`}> // active 클래스추가
{label}
</label>
{errorMsg && <span className="helper-text" data-error={errorMsg}>{errorMsg}</span>} // date-error 프로퍼티에 오류메시지 전달
</div>
> yarn add react-with-styles aphrodite react-with-styles-interface-aphrodite
export default {
color: {
primary: '#03a9f4',
secondary: '#795548',
white: '#FFFFFF',
gray: "#CCCCCC",
default: '#999999',
},
size: {
xg: 24,
lg: 28,
md: 14,
sm: 12,
xs: 10,
},
lineHeight: {
xg: '60px',
lg: '54px',
md: '36px',
sm: '24px',
xs: '18px',
},
unit: 4,
}
src/component/withStyles.jsx
import ThemedStyleSheet from 'react-with-styles/lib/ThemedStyleSheet';
import aphroditeInterface from 'react-with-styles-interface-aphrodite';
import { css, withStyles, withStylesPropTypes } from 'react-with-styles';
import Theme from './Theme';
ThemedStyleSheet.registerTheme(Theme);
ThemedStyleSheet.registerInterface(aphroditeInterface);
export { css, withStyles, withStylesPropTypes, ThemedStyleSheet };
export default withStyles;
Text.jsx
import React, { PureComponent } from "react";
import PropTypes from "prop-types";
class Text extends PureComponent {
render() {
const { children } = this.props;
return <span>{children}</span>;
}
}
Text.propTypes = {
children: PropTypes.node.isRequired,
};
export default Text;
/src/stories/TextStory.jsx
import React from "react";
import { storiesOf } from "@storybook/react";
import Text from "../component/Test";
storiesOf("Text", module)
.add("기본 설정", () => <Text>안녕하세요</Text>)
.add("프로퍼티 전달", () => <Text children="반갑습니다" />);
react-with-style
적용return function() { return class { render() { return () } } }
src/component/Text.jsx (수정)
import React, { PureComponent } from "react";
import PropTypes from "prop-types";
import { withStyles } from "react-with-styles"; // 👈 import 하고
class Text extends PureComponent {
render() {
const { children } = this.props;
return <span>{children}</span>;
}
}
Text.propTypes = {
children: PropTypes.node.isRequired,
};
export default withStyles()(Text); // 👈 export할때 withStyles()(Text)
import React, { PureComponent } from "react";
import PropTypes from "prop-types";
import withStyles, { css } from "./withStyles"; // ./withStyles.js에서 export해온 css함수를 import
class Text extends PureComponent {
render() {
const { children, styles } = this.props;
return <span {...css(styles.default)}>{children}</span>;
// css 함수는 속성값을 객체로 반환하므로 spread 해서 스타일 적용함.
}
}
Text.propTypes = {
children: PropTypes.node.isRequired,
};
export default withStyles(({ color, size }) => ({
default: {
color: color.default, // 스타일 생성 함수 호출 - color, size를 withStyles()함수에 전달
fontSize: size.md, // (객체를 만들어서 return하는 콜백)
},
}))(Text);
스토리북 도구로 보면, 스타일이 color는 default(#999999), size는 md(14px)로 적용된 것을 볼 수 있다.
또한, span 태그에는 class="default_xxxxx"라는 스타일클래스가 적용되어있다.
src/component/Test.jsx (수정)
import React, { PureComponent } from "react";
import PropTypes from "prop-types";
import withStyles, { css } from "./withStyles";
class Text extends PureComponent {
render() {
const {
children,
styles,
large,
xlarge,
small,
xsmall,
primary,
secondary,
} = this.props;
return (
<span
{...css(
styles.default,
xsmall && styles.xsmall, // 단축평가 - 좌항이 false면 false고,
small && styles.small, // true면 우항의 값을 반환함
large && styles.large,
xlarge && styles.xlarge,
secondary && styles.secondary,
primary && styles.primary
)}
>
{children}
</span>
);
}
}
Text.propTypes = {
children: PropTypes.node.isRequired,
large: PropTypes.bool,
xlarge: PropTypes.bool,
small: PropTypes.bool,
xsmall: PropTypes.bool,
primary: PropTypes.bool,
secondary: PropTypes.bool,
};
export default withStyles(({ color, size }) => ({
default: {
color: color.default,
fontSize: size.md,
},
xlarge: {
fontSize: size.xg,
},
large: {
fontSize: size.lg,
},
small: {
fontSize: size.sm,
},
xsmall: {
fontSize: size.xs,
},
primary: {
color: color.primary,
},
secondary: {
color: color.secondary,
},
}))(Text);
import React from "react";
import { storiesOf } from "@storybook/react";
import Text from "../component/Test";
storiesOf("Text", module)
.add("기본 설정", () => <Text>안녕하세요</Text>)
.add("large", () => <Text large>large</Text>)
.add("xlarge", () => <Text xlarge>large</Text>)
.add("small", () => <Text small>small</Text>)
.add("xsmall", () => <Text xsmall>xsmall</Text>)
.add("primary", () => <Text primary>primary</Text>)
.add("secondary", () => <Text secondary>secondary</Text>)
.add("primary + large", () => (
<Text primary large>
primary+large
</Text>
));
src/component/Button.jsx
import React, { PureComponent } from "react";
import PropTypes from "prop-types";
class Button extends PureComponent {
render() {
const { children, disabled, onPress } = this.props;
return <button onClick={onPress}>{children}</button>;
}
}
Button.propTypes = {
childen: PropTypes.node.isRequired,
onPress: PropTypes.func,
};
Button.defaultProps = {
onPress: () => {},
};
export default Button;
props
로 전달받는다.src/component/Button.jsx (수정)
import React, { PureComponent } from "react";
import PropTypes from "prop-types";
import withStyles, { css } from "./withStyles";
class Button extends PureComponent {
render() {
const {
children,
disabled,
styles,
large,
xlarge,
small,
xsmall,
primary,
secondary,
onPress,
} = this.props;
return (
<button
{...css(
styles.default,
xsmall && styles.xsmall,
small && styles.small,
large && styles.large,
xlarge && styles.xlarge,
secondary && styles.secondary,
primary && styles.primary
)}
onClick={onPress}
>
{children}
</button>
);
}
}
Button.propTypes = {
childen: PropTypes.node.isRequired,
large: PropTypes.bool,
xlarge: PropTypes.bool,
small: PropTypes.bool,
xsmall: PropTypes.bool,
primary: PropTypes.bool,
secondary: PropTypes.bool,
onPress: PropTypes.func,
};
Button.defaultProps = {
onPress: () => {},
xsmall: false,
small: false,
large: false,
xlarge: false,
primary: false,
secondary: false,
};
export default withStyles(({ color, size, unit }) => ({
default: {
border: 1,
borderStyle: "solid",
borderColor: color.default,
borderRadius: 2,
color: color.default,
fontSize: size.md,
padding: unit * 2,
cursor: "pointer",
},
xlarge: {
fontSize: size.xg,
},
large: {
fontSize: size.lg,
},
xsmall: {
fontSize: size.sm,
padding: unit,
},
small: {
fontSize: size.xs,
padding: unit,
},
primary: {
color: color.white,
borderColor: color.primary,
backgroundColor: color.primary,
},
secondary: {
color: color.secondary,
borderColor: color.secondary,
},
}))(Button);
import React from "react";
import { storiesOf } from "@storybook/react";
import Button from "../component/Button";
storiesOf("Button", module)
.add("기본설정", () => <Button>전송</Button>)
.add("large", () => <Button large>전송</Button>)
.add("xlarge", () => <Button xlarge>전송</Button>)
.add("small", () => <Button small>전송</Button>)
.add("xsmall", () => <Button xsmall>전송</Button>)
.add("primary", () => <Button primary>전송</Button>)
.add("secondary", () => <Button secondary>전송</Button>)
.add("primary+large", () => (
<Button primary large>
전송
</Button>
));
export const LARGE_AND_ABOVE = 'largeAndAbove';
const BREAKPOINT_NAMES = {
LARGE: 'large',
MEDIUM: 'medium',
SMALL: 'small',
};
const breakpoints = {
[BREAKPOINT_NAMES.LARGE]: 1128, // 동적 프로퍼티키 할당(객체 프로퍼티값을 키로 사용)
[BREAKPOINT_NAMES.MEDIUM]: 744,
[BREAKPOINT_NAMES.SMALL]: 327,
};
const responsive = {
[LARGE_AND_ABOVE]: `@media (min-width: ${breakpoints[BREAKPOINT_NAMES.LARGE]}px)`,
// large보다 클때는 = @media(min-width: 1128px)
[BREAKPOINT_NAMES.SMALL]: `@media (max-width: ${breakpoints[BREAKPOINT_NAMES.MEDIUM] - 1}px)`,
// small 일때는 = @media(max-width: 743px)
print: '@media print'
};
export default {
// (이전과 동일한 부분)
responsive,
}
📌 브레이크 포인트(Break Point)
src/component/Button.jsx (수정)
...
export default withStyles(({ color, size, unit, ✅ responsive }) => ({
default: {
border: 1,
borderStyle: "solid",
borderColor: color.default,
borderRadius: 2,
color: color.default,
fontSize: size.md,
padding: unit * 2,
cursor: "pointer",
✅ [responsive.small]: {
width: "100%",
},
},
...
@media (max-width: ${breakpoints[BREAKPOINT_NAMES.LARGE]}px)`)
이므로 743px 이하일때는, 해당 컴포넌트의 width가 100%가 된다.