- File -
PascalCase
.tsx - Class -
PascalCase
- Enum -
PascalCase
enum DoobooScreen { Ios, Android, HelloWorld, TestDevice }
- Constants -
UPPER_SNAKE_CASE
- Object, classes, variables and functions -
camelCase
- Asset file name -
lower_snake_case
.png
파일 이름에 도메인을 접두사로 추가합니다.
// Do
CompanyAdd.tsx
CompanyEdit.tsx
// Don't
AddCompany.tsx
EditCompany.tsx
구성 요소의 경우 아래와 같이 이름을 지정합니다.
// Do
function CompanyAdd() {}
function CompanyEdit() {}
// Do
const onAddCompany = () => {}
const onDeleteCompany = () => {}
// Don't
const onCompanyAdd = () => {}
const onCompanyDelete = () => {}
일반적으로 많은 개발자들이 모든 부울 접두사를 is
로 지정하며 종종 혼란을 야기합니다. 아래 코드를 예로 들어보겠습니다.
const isFriend = () => user.friends.length > 0;
위의 코드는 가독성이 좋지 않습니다. 올바른 접두사를 사용하지 않았기 때문입니다. 위 경우 친구가 있는지 확인하는 변수이기 때문에 hasFriend
로 바꾸는 것이 좋습니다.
is
대신 쓸 수 있는 몇 가지 다른 접두사가 있습니다. 다음은 권장할 수 있는 다른 옵션입니다.
- has
- should
또한 부울 접두사 자체가 명확해 보이면 접두사를 피하는 것이 좋습니다. 예를 들어 접두사가 없는 loading
, disabled
, checked
가 더 좋다고 생각합니다.
함수가 Promise
를 반환하거나 비동기 함수인 경우 접미사 Async
를 추가하는 것을 권장합니다.
// Do
function readAsync() {
return new Promise();
}
async function readAsync() {
await fetch();
return;
}
class User {
// Don't
void addUser() {
}
// Do
void add() {
}
void addMessage() {
}
}
자산은 애플리케이션에서 사용되는 모든 종류의 리소스입니다. 그들은 소스 코드와 같은 수준에 머물러서는 안됩니다.
YourApp/
├─ assets
│ └─ icons
│ └─ images
│ └─ localizations
├─ src/
구성 파일은 소스 코드와 분리되어야 합니다.
YourApp/
├─ src/
├─ .buckconfig
├─ .flowconfig
├─ .gitattributes
├─ .gitignore
├─ .watchmanconfig
├─ app.json
├─ babel.config.js
├─ index.js
├─ jest.config.js
├─ package.json
├─ README.md
├─ tsconfig.json
└─ eslint.config.js
jest, babel, eslint, typescript, flow, git에 대한 구성 파일은 모두
src
디렉토리에서 분리됩니다.
YourApp/
├─ src/
│ └─ components/
│ └─ pages/
│ └─ HelloWorld.tsx
│ └─ uis/
│ └─ providers/
├─ test/
│ └─ components/
│ └─ pages
│ └─ HelloWorld.test.tsx
테스트 파일은 모두
test
디렉토리 아래에 있어야 합니다.
YourApp/
├─ src/
│ └─ utils/
│ └─ localize.ts
│ └─ localize.spec.ts <=== Unit test may stays along with original file
YourApp/
├─ __mocks__/
│ └─ reanimated-masonry-list.ts
3-1-1. dooboolab eslint 패키지를 따름
React의 경우 @dooboo/eslint-config-react
, React Native의 경우 @dooboo/eslint-config-react-native
의 import 규칙을 따릅니다.
자산 경로를 관리하지 않으면 아래와 같은 문제에 자주 직면하게 됩니다.
Module not found: Error: Can't resolve '../icons/btn_add'
Module not found: Error: Can't resolve '../../icons/btn_back'
Module not found: Error: Can't resolve '../../../icons/camera'
대신 아래와 같이 에셋을 하나의 파일로 정리하여 내보내기를 권장합니다.
import icAddW from '../../assets/icons/btn_add.png';
import icBack from '../../assets/icons/btn_back.png';
import icCamera from '../../assets/icons/camera.png';
import icCircleX from '../../assets/icons/x_circle.png';
export const IC_ADD_W = icAddW;
export const IC_BACK = icBack;
export const IC_CAMERA = icCamera;
export const IC_CIRCLE_X = icCircleX;
// Don't
function Page({}: Props) {}
// Do
function Page({}: Props): ReactElement {}
// Don't
const Page = () => {};
class Page extends ReactComponent {}
// Do
function Page({}: Props): ReactElement {}
// Do
const onPress = () => {
doSomething();
};
// Don't
const onPress = () => doSomething('work');
const onPress = doSomething;
type Styles = {
container?: StyleProp<ViewStyle>;
text?: StyleProp<TextStyle>;
disabledButton?: StyleProp<ViewStyle>;
disabledText?: StyleProp<TextStyle>;
hovered?: StyleProp<ViewStyle>;
};
function Button({
styles,
style,
...
});
Button L62-L63에서 예제를 볼 수 있습니다. 이를 통해 개발자는 구성 요소에 어떤 종류의 스타일이 중첩되어 있는지 확인할 수 있습니다. 또한 어떤 스타일이
style
prop과 중첩되어 있는지 몰라도 재사용 가능한 컴포넌트의 스타일을 쉽게 처리할 수 있습니다. 사용자는margin
또는padding
을 변경하려고 합니다.
// Don't
if (checkStatus(user) === 'busy') {
message = 'User is busy!';
} else if (checkStatus(user) !== 'busy') {
message = 'User is not busy!';
if (availableForCall(user)) {
call("010-xxxx-xxxx");
} else {
call("119");
}
}
// Do
if (checkStatus(user) !== 'busy') {
message = 'User is not busy!';
availableForCall(user) ? call('010-xxxx-xxxx') : call('119');
return;
}
message = 'User is busy';
가드 절로 중첩된 조건문 교체를 사용하면 문장이 더 간결하고 읽기 쉬워집니다.
// Don't
availableForCall(user) ? call('010-xxxx-xxxx') : call('119');
// Do
call(availableForCall(user) ? '010-xxxx-xxxx' : '119');
가능하면 중복 함수 호출을 제거하십시오.
테이블 기반 방법은 if-else
대신 결정 테이블을 사용하여 더 나은 소프트웨어를 구축하도록 도와줍니다.
// Don't
public string GetMonthName(int month, string language) {
if (month == 1 && language == "EN") {
return "January";
} else if (month == 1 && language == "DA") {
return "Januar";
} else if (month == 1 && language == "KO") {
return "1월"
} else if (month == 2 && language == "EN") {
return "Feburary";
} else if (month == 2 && language == "DA") {
return "Feburar";
} else if (month == 2 && language == "KO") {
return "2월";
} else {
return "Unknown";
}
}
// Do
const monthDictionary = {
"EN": {
1: "January",
2: "Faburary",
},
"DA": {
1: "Januar",
2: "Feburar",
},
"KO": {
1: "1월",
2: "2월",
},
}
public string GetMonthName(string language, int month) => monthDictionary[language][month];
Refer to article.
함수 추출은 종종 코드를 더 읽기 쉽게 만드는 데 사용됩니다.
// Don't
const checkString = (str: string) => {
let subString = string.slice();
let counter = 0;
while (subString !== '') {
counter++;
subString = subString.slice(1);
}
setString(str);
}
// Do
function getStringLengthWithoutLengthProperty(string) {
let subString = string.slice();
let counter = 0;
while (subString !== '') {
counter++;
subString = subString.slice(1);
}
return counter;
}
const checkString = (str: string) => {
const strLength = getStringLengthWithoutLengthProperty(str);
setString(`${str}: ${counter}`);
}
// Do
const setYoutubeVideo = async (url: string): Promise<void> => {
if (urlType === 'youtube') {
const videoURL = await getYouTubeDetails(url);
if (videoURL) {
setYoutubeURL(videoURL);
}
}
};
setYoutubeVideo(url);
위
if (str.length % 2)
코드는 해당 코드를 처음 접하는 개발자가 로직을 해부하기 전까지 코드의 의도를 알 수 없습니다. 이럴 때는 함수를 추출하여 더 의미 있게 만들어야 합니다. 또한 youtube 동영상은 vscode에서 함수를 보다 쉽게 추출하는 방법을 알려줍니다.
// Do
function Page({}: Props): ReactElement {
return (
<Container>
<Text>Page</Text>
</Container>
);
};
Also, see templates in dooboo-cli.
// don't
{list.length && <div>{list.map((item) => (...))}</div>}
// do
{list.length ? <div>{list.map((item) => (...))}</div> : null}
Find out more in the link for the decision.
props 내에서 동일한 조건이 여러 번 사용된 경우 아래와 같이 조건문으로 추출합니다.
// Don't
function Parent() {
const [isHighlighted, setIsHighlighted] = useState(false);
return (
<div>
<Child
style={...{isHighlighted && {color: '#fff'}}}
headerColor={isHighlighted ? 'red' : 'blue'}
/>
</div>
);
}
// Do
function Parent() {
const [isHighlighted, setIsHighlighted] = useState(false);
if(isHighlighted) {
return (
<div>
<Child
style={{color: '#fff'}}
headerColor='red'
/>
</div>
);
}
return (
<div>
<Child headerColor='blue' />
</div>
);
}
여기서
isHighlighted
조건은Child
컴포넌트 프롭스에서 두 번 이상 사용됩니다. 이를 추출하여 유지보수 시 개발자들이 부작용 대신 대상 코드에 집중할 수 있게 도와줍니다. 또한&&
연산자를 사용하는 것은 **3-5-2**와 달리{isHighlighted && {color: '#fff'}
와 같이 프롭스에서 유용하게 사용할 수 있습니다.
가능한 한 단순하지만 유연하게 Props를 정의합니다. 최소한의 요구 사항을 정당화하고 더 많은 사양이 필요한 경우 사용자에게 권한을 넘겨줌으로써 이를 달성할 수 있습니다.
예를 들어 Button
아래에는 loading
및 disabled
유형이 있고 title
및 onPress
기능이 있습니다.
type Props = {
type?: 'loading' | 'disabled' | undefined,
onPress?: () => {}
title: string
}
function Button({
type,
onPress,
title,
}: Props): ReactElement {
if (type === 'loading') {
return ...
}
if (type === 'disabled') {
return ...
}
return (
<TouchableOpacity onPress={onPress}>
<Text>{title}</Text>
</TouchableOpacity>
);
};
위의 코드가 제품에서 사용하기 위한 최소한의 요구 사항이라면, 점점 더 많은 props를 노출시키는 대신 그대로 유지하고 렌더 콜백을 제공함으로 그 가능성을 노출합니다.
type Props = {
type?: 'loading' | 'disabled' | undefined,
onPress?: () => {}
title: string | (type: 'loading' | 'disabled' | undefined) => ReactElement;
}
function Button({
type,
onPress,
title,
onPress
}: Props): ReactElement {
if (type === 'loading') {
return ...
}
if (type === 'disabled') {
return ...
}
return (
<TouchableOpacity onPress={onPress}>
{render?.(type) || <Text>{title}</Text>}
</TouchableOpacity>
);
};
테이블 기반 방법론은
if-else
대신 의사결정 테이블을 사용하여 더 나은 소프트웨어를 구축하도록 도와줍니다.
// Do
<Button textStyle={textStyle} />
// Don't
<Button textColor={textColor} />
스타일 프롭스에 단일 속성을 노출하면 복잡할 뿐만 아니라 구성 요소들을 유지 관리하기 어려워집니다. 예 flutter_calendar_carousel.
렌더링 시 삼항 연산자를 여러 번 사용하면 가독성이 떨어집니다.
function Button({
type,
onPress,
title,
onPress
}: Props): ReactElement {
// Don't
return type === 'loading' ? ... : type === 'disabled' ? ... : return ...
// Do
// 1. If statement
if (type === 'loading') return ...;
if (type === 'disabled') return ...;
return ...;
// 2. Switch statement
switch (type) {
case 'loading':
return ...
case 'disabled':
return ...
default:
return ...
}
// 3. Good with single condition
return loading ? ... : return ...
}
아래와 같이 spread operator로 나머지 프롭스들을 extract 해주는 경우에는 rest
용어를 사용해주세요.
otherProps, restProps
등등 다양하게 사용될 수 있는 용어를 제한하여 커뮤니케이션을 통일하기 위함입니다.
function Button({
type,
onPress,
title,
onPress
...rest,
}: Props): ReactElement {
불필요하게 아래와 같이 render
함수를 사용하지 말아주세요.
function Sample() {
const renderHello = () => {
return <Text>HELLO</Text>;
}
return <>
{renderHello()}
</>
}
특정 기능이 없는 렌더 함수는 element로 대체해주세요.
function Sample() {
const renderHello = <Text>HELLO</Text>;
return <>
{Hello}
</>
}
// Don't
<View style={{
backgroundColor: 'red',
}}/>
// Do
<View style={{backgroundColor: 'red'}}/>
재사용 가능한 구성 요소에서 비즈니스 논리를 추상화하지 마십시오. 비즈니스 관리자에게 전달하십시오.
// Don't
function Button(): ReactElement {
return (
// Don't
<TouchableOpacity onPress={() => {
console.log('Hello I am doing something!!!');
}}>
{render?.(type) || <Text>{title}</Text>}
</TouchableOpacity>
);
};
// Do
function Button(): ReactElement {
return (
// Don't
<TouchableOpacity onPress={onPress}>
{render?.(type) || <Text>{title}</Text>}
</TouchableOpacity>
);
};
UI 구성 요소에 중첩된 자식 구성 요소가 여러 개 있는 경우 provider등을 사용하여 리팩터링을 시도하십시오. 이 경우 recoil을 선호합니다.
// Don't => But only when needed
interface Props {}
// Do
type Props = {}
// Don't
const name = useState<string>('');
// Do
const name = useState('');