일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- React18
- codingtest
- Babel
- react-helmet
- 고급안내서
- React 고급안내서
- RTK Query
- notFound()
- React 고급 안내서
- react hook
- Nextjs
- Nextjs React 18
- Javascript
- Programmers
- React 18
- React API 참고서
- next13 head
- background setInterval
- hook
- React 공식문서
- context
- getUTCDate
- background tab
- React 18 Nextjs
- Next13
- Render Props
- CSS
- react
- background setttimeout
- background: url
- Today
- Total
akjfal
Material UI 코드 따라가기 - Button 본문
우선 사용하는 방법을 보자
<Button variant="text">Text</Button>
컴포넌트안에 variant="text" 같이 변수를 인자로 넘겨준다.
인자로 들어가는 것들은 Button 에서 살펴볼 수 있으니 확인해보도록 하자.
Button 컴포넌트를 까보도록 하자
const Button = React.forwardRef(function Button(inProps, ref) {
const props = useThemeProps({ props: inProps, name: "MuiButton" });
const {
className: classNameContext,
color: colorContext,
disabled: disabledContext,
disableElevation: disableElevationContext,
disableFocusRipple: disableFocusRippleContext,
disableRipple: disableRippleContext,
fullWidth: fullWidthContext,
size: sizeContext,
variant: variantContext,
} = React.useContext(ButtonGroupContext);
const {
children,
className,
color: colorProp,
component = "button",
disabled: disabledProp,
disableElevation: disableElevationProp,
disableFocusRipple: disableFocusRippleProp,
disableRipple: disableRippleProp,
endIcon: endIconProp,
focusVisibleClassName,
fullWidth: fullWidthProp,
size: sizeProp,
startIcon: startIconProp,
type,
variant: variantProp,
...other
} = props;
const color = colorProp || colorContext || "primary";
// TODO v6: Use nullish coalescing (??) instead of OR operator for these boolean props so that these boolean props for Button with ButtonGroup context take priority. See conversation from https://github.com/mui-org/material-ui/pull/28645#discussion_r738380902.
const disabled = disabledProp || disabledContext || false;
const disableElevation =
disableElevationProp || disableElevationContext || false;
const disableFocusRipple =
disableFocusRippleProp || disableFocusRippleContext || false;
const fullWidth = fullWidthProp || fullWidthContext || false;
const size = sizeProp || sizeContext || "medium";
const variant = variantProp || variantContext || "text";
const disableRipple = disableRippleProp || disableRippleContext || false;
const ownerState = {
...props,
color,
component,
disabled,
disableElevation,
disableFocusRipple,
fullWidth,
size,
type,
variant,
};
const classes = useUtilityClasses(ownerState);
const startIcon = startIconProp && (
<ButtonStartIcon className={classes.startIcon} ownerState={ownerState}>
{startIconProp}
</ButtonStartIcon>
);
const endIcon = endIconProp && (
<ButtonEndIcon className={classes.endIcon} ownerState={ownerState}>
{endIconProp}
</ButtonEndIcon>
);
return (
<ButtonRoot
ownerState={ownerState}
className={clsx(className, classNameContext)}
component={component}
disabled={disabled}
disableRipple={disableRipple}
focusRipple={!disableFocusRipple}
focusVisibleClassName={clsx(classes.focusVisible, focusVisibleClassName)}
ref={ref}
type={type}
{...other}
classes={classes}
>
{startIcon}
{children}
{endIcon}
</ButtonRoot>
);
});
코드를 확인해보면
children은 컴포넌트에 children으로 사용되고 앞 뒤로 startIcon과 endIcon이 내부에서 생서되어서 사용되고 있다.
나머지 인자의 경우 각자의 변환이 이뤄진 후 ButtonRoot로 다시 넘어가게된다.
그렇다면 ButtonRoot로 넘어가보자
const ButtonRoot = styled(ButtonBase, {
shouldForwardProp: (prop) =>
rootShouldForwardProp(prop) || prop === "classes",
name: "MuiButton",
slot: "Root",
overridesResolver: (props, styles) => {
const { ownerState } = props;
return [
styles.root,
styles[ownerState.variant],
styles[`${ownerState.variant}${capitalize(ownerState.color)}`],
styles[`size${capitalize(ownerState.size)}`],
styles[`${ownerState.variant}Size${capitalize(ownerState.size)}`],
ownerState.color === "inherit" && styles.colorInherit,
ownerState.disableElevation && styles.disableElevation,
ownerState.fullWidth && styles.fullWidth,
];
},
})(
({ theme, ownerState }) => ({
...theme.typography.button,
// ...
}),
({ ownerState }) =>
ownerState.disableElevation && {
// ...
},
);
기본 css 설정 관련 코드들이 많아 생략 시켜두었다.
여기서 주의 깊게 보아야 할 것은 styled 함수를 통해서 컴포넌트를 생성한다는 것이다. 보면 styled를 통해 생성한 함수의 return값에 다시 인자를 넣어주어 생성한다. styled함수 안으로 들어가야 좀 더 정확한 파악이 가능하다.
export default function createStyled(input = {}) {
const {
defaultTheme = systemDefaultTheme,
rootShouldForwardProp = shouldForwardProp,
slotShouldForwardProp = shouldForwardProp,
} = input;
return (tag, inputOptions = {}) => {
const {
name: componentName,
slot: componentSlot,
skipVariantsResolver: inputSkipVariantsResolver,
skipSx: inputSkipSx,
overridesResolver,
...options
} = inputOptions;
// ...
const Component = defaultStyledResolver(
transformedStyleArg,
...expressionsWithDefaultTheme,
);
if (process.env.NODE_ENV !== "production") {
let displayName;
if (componentName) {
displayName = `${componentName}${componentSlot || ""}`;
}
if (displayName === undefined) {
displayName = `Styled(${getDisplayName(tag)})`;
}
Component.displayName = displayName;
}
return Component;
};
return muiStyledResolver;
};
}
다양한 설정들이 있지만 스킵하고 넘어가보면 defaultStyledResolver를 통해서 Component를 생성하는데, defaultStyledResolver는 styledEngineStyled를 통해 생성된다. 그럼 다시 styledEngineStyled를 들어가보려고 한다.
import emStyled from '@emotion/styled';
export default function styled(tag, options) {
const stylesFactory = emStyled(tag, options);
// ...
return stylesFactory;
}
export { ThemeContext, keyframes, css } from '@emotion/react';
export { default as StyledEngineProvider } from './StyledEngineProvider';
export { default as GlobalStyles } from './GlobalStyles';
코드를 이해하는데 필요없어보이는 부분을 생략해서 보면 emStyled를 통해 생성된 것을 리턴해주는 것을 알 수 있는데 여기서부턴 emotion.js를 통해 생성된다. emotion.js에 관련한 설명은 CSS-in-JS로 이 글 을 참고해보길 바란다.
emStyled를 따라 들어가보면
import styled from './base'
import { tags } from './tags'
// bind it to avoid mutating the original function
const newStyled = styled.bind()
tags.forEach(tagName => {
// $FlowFixMe: we can ignore this because its exposed type is defined by the CreateStyled type
newStyled[tagName] = newStyled(tagName)
})
export default newStyled
해당 코드가 나오는데 다시 styled가 bind되어 있으니 base로 다시 들어간다.
// @flow
import * as React from 'react'
import {
getDefaultShouldForwardProp,
composeShouldForwardProps,
type StyledOptions,
type CreateStyled,
type PrivateStyledComponent,
type StyledElementType
} from './utils'
import { withEmotionCache, ThemeContext } from '@emotion/react'
import { getRegisteredStyles, insertStyles } from '@emotion/utils'
import { serializeStyles } from '@emotion/serialize'
let isBrowser = typeof document !== 'undefined'
let createStyled: CreateStyled = (tag: any, options?: StyledOptions) => {
// ...
return function <Props>(): PrivateStyledComponent<Props> {
//...
const Styled: PrivateStyledComponent<Props> = withEmotionCache(
(props, cache, ref) => {
// ...
}
)
return Styled
}
}
export default createStyled
createStyled는 또 다시 withEmotionCache를 통해 생성되니 다시 들어가보자
// @flow
import { type EmotionCache } from '@emotion/utils'
import * as React from 'react'
import { useContext, forwardRef } from 'react'
import createCache from '@emotion/cache'
import { isBrowser } from './utils'
let EmotionCacheContext: React.Context<EmotionCache | null> =
React.createContext(
typeof HTMLElement !== 'undefined'
? /* #__PURE__ */ createCache({ key: 'css' })
: null
)
// ...
let withEmotionCache = function withEmotionCache<Props, Ref: React.Ref<*>>(
func: (props: Props, cache: EmotionCache, ref: Ref) => React.Node
): React.AbstractComponent<Props> {
return forwardRef((props: Props, ref: Ref) => {
let cache = ((useContext(EmotionCacheContext): any): EmotionCache)
return func(props, cache, ref)
})
}
// ...
export { withEmotionCache }
그디어 마지막이 보인다. 컴포넌트를 인자로 내려내려 받아서 func에서 보이는 return 인 React.Node가 리턴되는것이다.
... 문제가 생겼다. React.Node를 확인해보려고 하는데 React에서 해당 함수를 찾을 수 없었다. 이는 더 찾아봐야 할 듯하다. 해당 코드를 따라가면서 useContext가 정확히 리턴해주는 값등을 모르니 코드 이해가 겉햝기 식으로 밖에 되지 않아 추가적인 이해가 필요로 하지만, 당장 컴포넌트 분석이 목표이므로 나중으로 미루도록하겠다.