반응 - FlatCoin

37956 단어
React - FlatCoin에 오신 것을 환영합니다! 이 앱은 Flatiron School React 프로젝트로 개발되었습니다. 사용자가 가상(가짜) 화폐를 사용하여 암호화폐 거래를 연습할 수 있도록 돕기 위해 이 앱을 개발했습니다.

First thing to mention, this app is an incomplete one. My original plan was to come up with a fully functional trading application but I didn't anticipate multiple tricky variables like calculating true and updated return on total investment and data collected to display charts. But I made sure to meet project requirements, this app is still a work in progress.



백엔드 설정:



Rails를 사용하면 React-Project용 백엔드 API를 매우 쉽게 설정할 수 있습니다.

rails new react-flatcoin-rails-backend --api

scaffold를 통해 모델을 생성했습니다.

rails generate scaffold user first_name last_name email password:digest
rails generate scaffold portfolio name description initial_balance:decimal current_balance:decimal user_id:integer
rails generate scaffold trade coin_name coin_id price:decimal quantity:integer user_id:integer portfolio_id:integer


데이터를 JSON으로 표시하는 방식을 쉽게 제어하기 위해 직렬 변환기를 사용했습니다.

gem 'active_model_serializers'


마지막으로 사용자 인증을 처리하기 위해 세션 컨트롤러를 만들었습니다.

rails generate controller Session new --no-helper --no-assets --no-test-framework --skip-routes --skip


여기GitHub에서 내 백엔드 저장소를 확인하십시오!

Note: You may refer to my previous Rails project for more detailed information on Rails API .



프런트 엔드:



먼저 반응 앱의 기본 구조를 만드는 것이 정말 쉽습니다.

npx create-react-app react-flatcoin


그런 다음 Redux를 추가해야 합니다.

npm install react-redux --save


처음부터 앱을 시작할 때 최종 제품을 시각화하여 시작하는 것이 좋습니다. 최종 제품과 정확히 같을 필요는 없지만 최소한 앱의 다양한 구성 요소에 대해 잘 알고 있어야 합니다. 내 앱에는 사용자가 로그인하고 암호 화폐 포트폴리오를 가지고 놀아야 하므로 사용자 인증을 설정하여 시작하는 것이 좋습니다.
이 부분은 저에게 매우 힘든 시간을 주었지만 3일 간의 투쟁 끝에 설정에 대한 이 유용한 문서Using JWT in Your React+Redux App for Authorization를 찾았습니다. 확인해 보십시오. 그러면 시간을 절약할 수 있습니다.

내 다음 과제는 내 앱의 기능을 만들기 시작하기 위해 React/Redux 내에서 미들웨어 및 비동기 작업을 완전히 이해하는 것이었습니다. 앱이 내 Rails API 백엔드에서 데이터를 가져오고 CoinGecko에서 실시간 암호화폐 데이터를 가져와야 했기 때문입니다. 열심히 조사한 끝에 전체 프로세스를 완전히 이해하는 데 도움이 되는 이 비디오를 찾았습니다.index.js에서 저는 Provider를 사용하여 일을 단순하게 유지하기 위해 글로벌 스토어를 만들고 있습니다. 또한 combineReducerscryptosReducer를 결합하는 데 usersReducer를 사용하고 있습니다.

// React
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

// Imporing CSS
import './index.css';

// Redux - Thunk
import { Provider } from 'react-redux'
import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import logger from 'redux-logger'

// Imporing Reducers
import { combineReducers } from 'redux'
import cryptosReducer from './reducers/cryptosReducer'
import usersReducer from './reducers/usersReducer'

import reportWebVitals from './reportWebVitals';

const rootReducer = combineReducers({
  user: usersReducer,
  crypto: cryptosReducer
})

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

// Creating Store
const store = createStore(rootReducer, composeEnhancers(applyMiddleware(thunk, logger)))

ReactDOM.render(
  <React.StrictMode>  
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);

reportWebVitals();

logger는 통과된 action를 기반으로 이전 상태와 다음 단계를 시각화하는 훌륭한 도구임이 밝혀졌습니다.

npm install react-logger --save


저장소를 만들 때 두 번째 미들웨어로 추가하기만 하면 됩니다.

const store = createStore(rootReducer, composeEnhancers(applyMiddleware(thunk, logger)))


콘솔에서:

App.js 파일은 따라하기 쉽고 fetchEverything를 통해 fetchLoggedInUserfetchCryptos를 처리하는 componentDidMount 함수를 만들었습니다.
handleCryptoListLoading 의 논리를 통해 로드를 처리한 다음 렌더링이 모든 콘텐츠를 반환합니다.
mapStateToProps 함수는 적절한 상태 값을 매핑하고 mapDispatchToProps 해당 값을 전달한 다음 connect를 사용하여 둘 다 연결합니다.

export default connect(mapStateToProps, mapDispatchToProps)(App)


전체 코드:

import React, { Component } from 'react'
import { connect } from 'react-redux'
import { fetchCryptos } from './actions/cryptoActions'
import { fetchLoggedInUser } from './actions/userActions'
import { logOutUser } from './actions/userActions'
import CryptoList from './components/CryptoList'
import TradeCryptoList from './components/TradeCryptoList'
import NavBar from './components/NavBar'
import NotLoggedInNavBar from './components/NotLoggedInNavBar'
import Dashboard from './components/Dashboard'
import LoginForm from './containers/loginForm'
import SignUpForm from './containers/signUpForm'
import TradeCryptoHoldingDescription from './components/TradeCryptoDescription'
import TradeCryptoHistoryList from './components/TradeCryptoHistoryList'

import './App.css';

import Loading from './svg/Loading'

import {
  BrowserRouter as Router,
  Switch,
  Route
} from 'react-router-dom'

class App extends Component {

  fetchEverything = () => {
    this.props.fetchLoggedInUser()
    this.props.fetchCryptos()
  }

  componentDidMount() {
    this.fetchEverything()
  }

  handleCryptoListLoading = () => { console.log true or false
    if(this.props.loading) {
      return <Loading />
    } else {
console.log(this.props.current_user.first_name)
      return <CryptoList cryptoData={this.props.cryptoData} />
    }
  }

  handleTradeCryptoListLoading = () => { console.log true or false
    if(this.props.user_loading) {
      return <Loading />
    } else {
      return <TradeCryptoList cryptoData={this.props.cryptoData} current_user={this.props.current_user} />
    }
  }

  handleTradeCryptoHistoryListLoading = () => { console.log true or false
    if(this.props.user_loading) {
      return <Loading />
    } else {
      return <TradeCryptoHistoryList cryptoData={this.props.cryptoData} current_user={this.props.current_user} />
    }
  }

  logOut = () => {
    localStorage.removeItem("token")
    this.props.logOutUser()
    alert("Succesfully log out!")
  }

  render() {
    return (
      <>
        <div className="App">
          <Router>
            <div className="app__navBar">
              {this.props.login? <NavBar logOut = {this.logOut}/> : <NotLoggedInNavBar/> }
            </div>
            <Switch>

              <Route exact path="/">   
                <div className="app__body">
                  <div className="app__container">
                    <Dashboard current_user={this.props.current_user}/>
                    {this.handleCryptoListLoading()}
                  </div>
                </div>
              </Route>

              <Route exact path="/login">
                <LoginForm />
              </Route>

              <Route exact path="/signup">
                <SignUpForm />
              </Route>

              <Route exact path="/logout">
                <h1>Loguot</h1>
              </Route>


              <Route path="/coins/:coin_id" component={(routeInfo) => {
                const paramsCoinId = routeInfo.match.params.coin_id
                const foundCoin = this.props.current_user.coins.find(p=> p.coin_id === paramsCoinId)
                const foundCrypto = this.props.cryptoData.find(p=> p.symbol === paramsCoinId)
                const foundPortfolio = this.props.current_user.portfolios.find(p=> p.name === "Initial Portfolio")
                return <TradeCryptoHoldingDescription coins={foundCoin} current_user={this.props.current_user} crypto={foundCrypto} portfolio={foundPortfolio}/>
              </Route>

              <Route exact path="/trades">               {this.handleTradeCryptoHistoryListLoading()}
              </Route> 

              <Route exact path="/portfolio">
                {this.handleTradeCryptoListLoading()}
              </Route>            

              <Route path="/" render={() => <div><h1>Oops! That page doesn't exist.</h1></div>} />

            </Switch>
          </Router>
        </div>
      </>
    )
  }
}

const mapStateToProps = state => {
  return {
    cryptoData: state.crypto.cryptos,
    loading: state.crypto.loading,
    login: state.user.login,
    current_user: state.user.user,
    user_loading: state.user.loading
  }
}

const mapDispatchToProps = dispatch => {
  return {
    fetchCryptos: () => dispatch(fetchCryptos()),
    fetchLoggedInUser: () => dispatch(fetchLoggedInUser()),
    logOutUser: () => dispatch(logOutUser())
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(App)


작업: CoinGecko에서 데이터를 가져올 때 ADD_CRYPTOS 작업을 사용합니다.

export const fetchCryptos = () => {
    return (dispatch) => {
        dispatch({ type: 'LOADING_CRYPTOS'})
        fetch('https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&ids=bitcoin%2C%20ethereum%2C%20binancecoin%2C%20tether%2C%20polkadot%2C%20cardano%2C%20ripple%2C%20litecoin%2C%20chainlink%2C%20bitcoin-cash%2C%20stellar%2C%20usd-coin%2C%20uniswap%2C%20dogecoin%2C%20wrapped-bitcoin%2C%20okb%2C%20aave%2C%20cosmos%2C%20nem%2C%20solana%20%20&order=market_cap_desc&per_page=100&page=1&sparkline=false&price_change_percentage=1h')
        .then(response => {
            return response.json()
        })
        .then(responseJSON => {
            dispatch({ type: 'ADD_CRYPTOS', cryptos: responseJSON})
        })
    }
}


흡진기:

const cryptosReducer = (state = {
    cryptos: [],
    loading:false
}, action) => {
    switch(action.type) {
        case 'LOADING_CRYPTOS':
            return {
                ...state,
                cryptos: [...state.cryptos],
                loading: true
            }
        case 'ADD_CRYPTOS':
            return {
                ...state,
                cryptos: action.cryptos,
                loading: false
            }
        default:
            return state
    }
}

export default cryptosReducer


Rails API 백엔드에서 사용자 데이터를 가져오는 데 매우 유사한 접근 방식을 사용했습니다. 전체 코드는 여기GitHub에서 프런트 엔드 리포지토리를 확인할 수 있습니다.
내 응용 프로그램의 빠른 비디오를 확인하십시오.

I went through a lot of challenges when building up this App, but it gave me a clear understanding of React. Now I feel ready to keep on refining this project or start another from scratch and avoid those mistakes I made with this one.

좋은 웹페이지 즐겨찾기