React에서 index.js에 Provider로 ReactDOM을 포장을 하는데 그 이유가 이제 전체 App에서 Provider가 제공하는 값들을 사용하기 위해서이다. 보통 LayoutProvider, UserProvider, ThemeProvider를 생성하게 된다.

UserProvider 에서는 사용자의 상태를 관리하고 있어서 App.js에서는 UserProvider를 이용해서 사용자의 로그인 상태를 가져온다. 이렇게 가져온 상태를 통해서 App의 화면을 어떤것을 보여줄지에 대해서 설명한다.

index.js

ReactDOM.render(
  <LayoutProvider>
    <Provider store={store}>
      <ThemeProvider theme={Themes.default}>
        <BrowserRouter>
          <HelmetProvider>
            <App />
          </HelmetProvider>
        </BrowserRouter>
      </ThemeProvider>
    </Provider>
  </LayoutProvider>,
  document.getElementById('root'),
);

src/context/LayoutContext.js

import React from 'react';

var LayoutStateContext = React.createContext();
var LayoutDispatchContext = React.createContext();

function layoutReducer(state, action) {
  switch (action.type) {
    case 'TOGGLE_SIDEBAR':
      return { ...state, isSidebarOpened: !state.isSidebarOpened };
    default: {
      throw new Error(`Unhandled action type: ${action.type}`);
    }
  }
}

function LayoutProvider({ children }) {
  var [state, dispatch] = React.useReducer(layoutReducer, {
    isSidebarOpened: true,
  });
  return (
    <LayoutStateContext.Provider value={state}>
      <LayoutDispatchContext.Provider value={dispatch}>
        {children}
      </LayoutDispatchContext.Provider>
    </LayoutStateContext.Provider>
  );
}

function useLayoutState() {
  var context = React.useContext(LayoutStateContext);
  if (context === undefined) {
    throw new Error('useLayoutState must be used within a LayoutProvider');
  }
  return context;
}

function useLayoutDispatch() {
  var context = React.useContext(LayoutDispatchContext);
  if (context === undefined) {
    throw new Error('useLayoutDispatch must be used within a LayoutProvider');
  }
  return context;
}

export { LayoutProvider, useLayoutState, useLayoutDispatch, toggleSidebar };

// ###########################################################
function toggleSidebar(dispatch) {
  dispatch({
    type: 'TOGGLE_SIDEBAR',
  });
}

App.js

  • App.js에서는 url을 어떤값을 입력했을때 어떤 페이지로 이동할지에 대한 내용을 작성한다.
  • 여기서 사용되는게 react-router-dom이다.
  • 참고로 function App()으로 정의 되어 있었는데, epoxrt default function App()으로 변경하면 아래 export를 따로 해줄필요가 없다. (매번 자동적으로 입력했는데 이게 좋은듯)
  • 이렇게 가져온 isAuthenticated의 값은 global 하게 사용이 가능하다
  • PrivateRoute, PublicRoute를 나누어서 로그인한 사용자, 로그인하지 않은 사용자의 Route를 나눈다.

대시보드를 만들고 싶어서 React를 시작했다. 밑바닥부터 만들려고 보니.. 정말 끝도 없어보였다. 안드로이드 개발을 하면서 느꼈던게 있는 컴포넌트, 라이브러리를 잘사용하는게 빠르게 개발을 할 수 있었다. 웹도 유사하게 훌륭한 개발자들이 많은 컴포넌트와 라이브러리를 제공한다. 제공된 라이브러리와 컴포넌트에 대한 이해를 하면서 개발을 해보는게 좋을것 같다! 항상 갖다쓸때는 왜 이렇게 구현했을까 생각도 하는게 좋은듯! 물론 매번 그렇게 하면... 필요한것만 아주 잘..

일단은 지금 두개의 대시보드를 토대로 새로운 대시보드를 구성해볼까 한다.
react-material-admin의 경우에는 full version은 유료버전이니 참고

                  {Object.keys(dong_info.layers).map((key, index) => (
                    <Paper className={classes.paper} key={index}>
                      가격:{dong_info.layers[key].price}
                      거래날짜: {dong_info.layers[key].tran_date}
                      타입: {dong_info.layers[key].sale_type}
                    </Paper>
                  ))}
  • Input, TextField에서 onChange를 통해서 Enter의 이벤트를 받을수 있다고 생각을 했으나
  • 실제로 Enter를 받기 위해서는 onKeyDown 또는 onKeyUp, onKeyPress의 이벤트를 이용해야 한다.
  • 하지만 이때 만약 onKeyDown, onKeyUp을 하면 한글로 입력하고 엔터치면 두번 검색되는 문제가 발생한다.
  • 그러니 안전빵으로 onKeyPress의 이벤트를 사용하자
  • 다시보니 이벤트명이... onKeyPress가 적당해보인다.

참고

  • material-ui/styles/
  • style library interoperability (아무거나 적용해도 상관 없음!)
    • Plain CSS
    • Global CSS
    • styled Componetns
    • CSS Modules
    • Emotion
    • ReactJSS
  • 하지만 material-ui에서는 material-ui/styles의 패키지를 따로 제공한다는.. 어떤걸 사용해도 상관없음
  • 그래도 styled components랑 비슷하니 이걸 사용하는게 좋을지도...

styled componets vs material-ui/styles

  • styled components는 element별로 변수를 선언
  • material-ui/styles은 변수하나에 여러개의 element의 변수를 선언

styled components

import React from 'react';
import { styled } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';

const MyButton = styled(Button)({
  background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
  border: 0,
  borderRadius: 3,
  boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
  color: 'white',
  height: 48,
  padding: '0 30px',
});

export default function StyledComponents() {
  return <MyButton>Styled Components</MyButton>;
}

material-ui/styles

import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';

const useStyles = makeStyles({
  root: {
    background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
    border: 0,
    borderRadius: 3,
    boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
    color: 'white',
    height: 48,
    padding: '0 30px',
  },
});

export default function Hook() {
  const classes = useStyles();
  return <Button className={classes.root}>Hook</Button>;
}
  • 기존에 /src/components에서 각각 component를 생성했다면
  • component 생성된 녀석들을 가져와서 사용을 해보자
yarn add @material-ui/core
  • material-ui/core를 설치
  • components에 추가하면 아래와 같은 에러가 난다.
index.js:1 Warning: Failed prop type: The prop `children` is marked as required in `ForwardRef(Button)`, but its value is `undefined`.
    in ForwardRef(Button) (created by WithStyles(ForwardRef(Button)))
    in WithStyles(ForwardRef(Button)) (at Search.js:18)
    in div (created by styled.div)
    in styled.div (at Search.js:16)
    in Search (at AptComplexPage.js:12)
    in div (at AptComplexPage.js:9)
    in AptComplexPage (created by Context.Consumer)
    in Route (at App.js:19)
    in App (at src/index.js:41)
    in HelmetProvider (at src/index.js:40)
    in Router (created by BrowserRouter)
    in BrowserRouter (at src/index.js:39)
    in Provider (at src/index.js:38)
  • 어이없게도 <Button> </Button> 사이에 값을 넣지 않았다... 값을 넣지 않으면 children undefined 에러가 생긴다

스타일 적용하기

import { makeStyles } from '@material-ui/core/styles';

const useStyles = makeStyles(theme => ({
  root: {
    display: 'flex',
    flexWrap: 'wrap',
  },
  textField: {
    marginLeft: theme.spacing(1),
    marginRight: theme.spacing(1),
    width: 200,
  },
}));


(...)
   const classes = useStyles();

  return (
    <div className={classes.root}>
      <div>
        <TextField
          id="standard-full-width"
          label="Label"
          style={{ margin: 8 }}
          placeholder="Placeholder"
          helperText="Full width!"
          fullWidth
          margin="normal"
          InputLabelProps={{
            shrink: true,
          }}
        />
  • useStyles 각각의 tag별로 정의하고 root의 경우 <div>에 지정
  • 나머지 textfield, button등에 스타일을 주면 적용이 된다.
  • 백엔드 서버를 통해 리액트 앱을 제공할 수 있도록 빌드
yarn build
  • 서버에서 정적 파일을 제공하기 위해서는 koa-static을 설치
yarn add koa-static
  • /src/index.js에 아래와 같이 추가
import serve from 'koa-static';
import path from 'path';
import send from 'koa-send';

const buildDirectory = path.resolve(__dirname, '../../client/build');
app.use(serve(buildDirectory));
app.use(async ctx => {
  // Not Found이고, 주소가 /api로 시작하지 않는 경우
  if (ctx.status === 404 && ctx.path.indexOf('/api') !== 0) {
    // index.html 내용을 반환
    await send(ctx, 'index.html', { root: buildDirectory });
  }
});

+ Recent posts