Όταν βγήκε ο Angular. Έχουν προτείνει ένα πολύ ωραίο σεμινάριο για τη δημιουργία μιας εφαρμογής που βοηθά μια εταιρεία στελέχωσης να διαχειρίζεται το σταθερό των ηρώων της.

Η εφαρμογή Tour of Heroes καλύπτει τις βασικές αρχές του Angular. Θα δημιουργήσετε μια βασική εφαρμογή που θα έχει πολλά από τα χαρακτηριστικά που θα περιμένατε να βρείτε σε μια πλήρη εφαρμογή που βασίζεται σε δεδομένα: απόκτηση και εμφάνιση λίστας ηρώων, επεξεργασία των λεπτομερειών ενός επιλεγμένου ήρωα και πλοήγηση μεταξύ διαφορετικών προβολών ηρωικά δεδομένα.

Ακολούθησα το ίδιο σεμινάριο όταν άρχισα να δουλεύω με την Angular. Δεδομένου ότι το React και το Redux είναι επίσης ενδιαφέροντα, προσπάθησα να δημιουργήσω την ίδια εφαρμογή χρησιμοποιώντας το React και το Redux. Αυτή ήταν μια ευκαιρία να μάθουμε και να εξασκήσουμε αυτούς τους όμορφους τεχνικούς.
Όλο το σεμινάριο βρίσκεται σε αυτό το αποθετήριο github.

Η εφαρμογή

Ακολουθούν εικόνες για το πώς πρέπει να μοιάζει η εφαρμογή.
Μια γραμμή πλοήγησης που οδηγεί σε δύο προβολές: Πίνακας ελέγχου και Λίστα ηρώων.
Πίνακας ελέγχου που εμφανίζει τους καλύτερους ήρωες :

Ήρωες που εμφανίζουν τη λίστα όλων των Ηρώων με είσοδο για να προσθέσετε έναν Ήρωα στην τρέχουσα λίστα:

Τι χρειάζεστε για να ξεκινήσετε αυτό το σεμινάριο

  • Υποθέτω ότι έχετε βασική κατανόηση Javascript και Html. Κατά τα άλλα, ρίξτε μια ματιά σε αυτό
  • Ενδιαφερόμενος εγκέφαλος
  • Node.js στον υπολογιστή σας
  • Δεν χρειάζεται να έχετε γνώσεις για το Angular

Ξεκινήστε με το React

Το React είναι μια βιβλιοθήκη JavaScript για τη δημιουργία διεπαφών χρήστη. (Το Angular είναι ένα πλαίσιο) Το React μπορεί να περιγραφεί ως εξής:

  • Δηλωτικό : Καθιστά ανώδυνη τη δημιουργία διαδραστικών διεπαφής χρήστη. Σχεδιάστε απλές προβολές για κάθε κατάσταση στην εφαρμογή σας και το React θα ενημερώσει και θα αποδώσει αποτελεσματικά τα σωστά στοιχεία όταν αλλάξουν τα δεδομένα σας. Οι δηλωτικές προβολές κάνουν τον κώδικά σας πιο προβλέψιμο και ευκολότερο στον εντοπισμό σφαλμάτων.
  • Βασισμένο σε Στοιχεία: Δημιουργήστε ενσωματωμένα στοιχεία που διαχειρίζονται τη δική τους κατάσταση και, στη συνέχεια, συνθέστε τα για να δημιουργήσετε σύνθετες διεπαφές χρήστη. Δεδομένου ότι η λογική στοιχείων είναι γραμμένη σε JavaScript αντί για πρότυπα, μπορείτε εύκολα να περάσετε εμπλουτισμένα δεδομένα μέσω της εφαρμογής σας και να διατηρήσετε την κατάσταση εκτός του DOM.
  • Learn Once, Write Anywhere : Δεν κάνουμε υποθέσεις για την υπόλοιπη στοίβα τεχνολογίας, ώστε να μπορείτε να αναπτύξετε νέες δυνατότητες στο React χωρίς να ξαναγράψετε τον υπάρχοντα κώδικα. Το React μπορεί επίσης να αποδώσει στον διακομιστή χρησιμοποιώντας το Node και να ενεργοποιήσει εφαρμογές για κινητά χρησιμοποιώντας το React Native.

Υπάρχουν πολλοί τρόποι για να ξεκινήσετε ένα React Project (κλωνοποιήστε ένα αρχικό έργο, χρησιμοποιήστε το create-react-app…)

Προτιμώ να ξεκινήσω από την αρχή με το χτίσιμο του έργου, τουλάχιστον μια φορά.

Webpack

Το webpack είναι ένα πακέτο λειτουργιών για σύγχρονες εφαρμογές JavaScript. Όταν το webpack επεξεργάζεται την αίτησή σας, δημιουργεί αναδρομικά ένα γράφημα εξαρτήσεων που περιλαμβάνει κάθε λειτουργική μονάδα που χρειάζεται η εφαρμογή σας και, στη συνέχεια, συσκευάζει όλες αυτές τις ενότητες σε ένα ή περισσότερα πακέτα. Αυτό σημαίνει ότι το webpack χρησιμοποιείται για τη μεταγλώττιση αρχείων Javascript.

Για να χρησιμοποιήσουμε το webpack, θα πρέπει να ρυθμίσουμε εκ των προτέρων ένα αρχείο webpack.config.js στο οποίο θα καθορίσουμε το σημείο εισόδου, την έξοδο, τους φορτωτές…

Εδώ είναι το webpack.config.js μας:

module.exports = {
  context: __dirname,
entry: [
    './src/index.js',
  ],
output: {
    filename: 'bundle.js',
    path: path.join(__dirname, 'public'),
  },
devtool: 'source-map',
devServer: {
    contentBase: './public',
    historyApiFallback: true,
  },
module: {
    rules: [
      {
        test: /\.js$/,
        exclude: [/node_modules/],
        use: [{
          loader: 'babel-loader',
          options: { presets: ['es2015', 'react'] },
        }],
      },
      {
        test: /\.css$/,
        exclude: /node_modules/,
        use: ['style-loader', {
          loader: 'css-loader',
          options: {
            modules: true,
            importLoaders: 1,
            localIdentName: '[path][name]__[local]--[hash:base64:5]',
          },
        }],
      },
    ],
  },
plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
    }),
  ],
};

Σε λειτουργικές μονάδες, ορίζουμε προγράμματα φόρτωσης που επιτρέπουν στο webpack να κατανοεί άλλους τύπους αρχείων εκτός από τα αρχεία Javascript. (Το ίδιο το Webpack καταλαβαίνει μόνο JS) Χρησιμοποιούμε babel-loader επειδή θα μετατρέψει το ES6 σε παλαιότερη έκδοση του JS κατανοητή από όλα τα προγράμματα περιήγησης.

Ρυθμίστε το έργο

  1. Βεβαιωθείτε ότι έχετε την πρόσφατη έκδοση του Node.js
  2. σε έναν φάκελο, ξεκινήστε την εφαρμογή npm και εγκαταστήστε το webpack:
mkdir tour-of-heroes-app
npm init --yes # keep default params
npm install --save-dev webpack

Θα πρέπει να δείτε ένα πακέτο.json στον φάκελό μας. Εδώ θα καθορίσετε τις εξαρτήσεις σας. Προτείνω να αντιγράψω το package.json μου έχει όλες τις εξαρτήσεις που χρειαζόμαστε για αυτό το έργο: babel για μετασχηματισμό ES6, ένζυμο chai eslint mocha και sinon για δοκιμές, react και redux…

"dependencies": {
    "babel-core": "^6.25.0",
    "babel-loader": "^7.1.1",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-react": "^6.24.1",
    "babel-register": "^6.24.1",
    "chai": "^4.1.0",
    "chai-as-promised": "^7.1.1",
    "chai-enzyme": "^0.8.0",
    "css-loader": "^0.28.4",
    "enzyme": "^2.9.1",
    "eslint": "^3.19.0",
    "eslint-config-airbnb": "^15.0.2",
    "eslint-plugin-import": "^2.7.0",
    "eslint-plugin-jsx-a11y": "^5.1.1",
    "eslint-plugin-react": "^7.1.0",
    "html-webpack-plugin": "^2.29.0",
    "ignore-styles": "^5.0.1",
    "jsdom": "^11.1.0",
    "jsdom-global": "^3.0.2",
    "mocha": "^3.4.2",
    "mock-local-storage": "^1.0.3",
    "nock": "^9.0.14",
    "prop-types": "^15.5.10",
    "react": "^15.6.1",
    "react-dom": "^15.6.1",
    "react-redux": "^5.0.6",
    "react-router": "^4.1.2",
    "react-router-dom": "^4.1.2",
    "react-test-renderer": "^15.6.1",
    "redux": "^3.7.2",
    "redux-devtools-extension": "^2.13.2",
    "redux-logger": "^3.0.6",
    "redux-thunk": "^2.2.0",
    "rimraf": "^2.6.1",
    "sinon": "^2.3.8",
    "sinon-chai": "^2.12.0",
    "style-loader": "^0.18.2",
    "webpack": "^3.3.0",
    "webpack-dev-server": "^2.5.1"
  }

Θα ορίσουμε ορισμένα σενάρια για να κάνουμε την εκτέλεση εύκολη:

"scripts": {
    "clean": "rimraf public && mkdir public",
    "build": "npm run clean && webpack -p",
    "start": "webpack-dev-server --progress --inline --hot --quiet --colors --open",
    "lint": "eslint --fix src/**/*.js",
    "mocha": "mocha --reporter spec --compilers js:babel-register -r ignore-styles -r jsdom-global/register -r mock-local-storage --recursive 'src/**/*.spec.js'",
    "test": "npm run lint & npm run mocha",
    "test-watch": "npm run mocha -- --watch"
  }

Μπορείτε να αντιγράψετε/επικολλήσετε αυτά τα σενάρια στο δικό σας πακέτο.json

στη συνέχεια εγκαταστήστε όλες τις εξαρτήσεις:

npm install
  1. Δημιουργήστε το σημείο εισόδου σας

Όπως αναφέρθηκε στο webpack.config.js, το σημείο εισόδου είναι src/index.js Δημιουργήστε αυτό το αρχείο : (Μπορείτε να χρησιμοποιήσετε το IDE σας!)

mkdir src

Σε αυτό το source forlder src/, δημιουργήστε το index.js:

import React from 'react';
import App from './App/App';
render(<App />, document.getElementById('root'));
if (module.hot) {
  module.hot.accept('./App/App.js', () => {
    const UpdatedAppComponent = require('./App/App.js').default; // eslint-disable-line global-require
    render(<UpdatedAppComponent />, document.getElementById('root'));
  });
}

Αυτό είναι ένα συνηθισμένο σημείο εισόδου με το React. Αλλά επειδή θα χρησιμοποιήσουμε το Redux, ας ξεκινήσουμε από εδώ.

Redux

Το Redux είναι ένα προβλέψιμο κοντέινερ κατάστασης για εφαρμογές JavaScript.

Σας βοηθά να γράφετε εφαρμογές που συμπεριφέρονται με συνέπεια, εκτελούνται σε διαφορετικά περιβάλλοντα (πελάτης, διακομιστής και εγγενές) και είναι εύκολο να δοκιμαστούν. Επιπλέον, παρέχει μια εξαιρετική εμπειρία προγραμματιστή, όπως η ζωντανή επεξεργασία κώδικα σε συνδυασμό με ένα πρόγραμμα εντοπισμού σφαλμάτων που ταξιδεύει στο χρόνο.

Μπορείτε να χρησιμοποιήσετε το Redux μαζί με το React ή με οποιαδήποτε άλλη βιβλιοθήκη προβολών.

Για να εγκαταστήσετε το Redux:

npm install --save redux
npm install --save react-redux
npm install --save-dev redux-devtools

Δεν χρειάζεται να το εγκαταστήσετε, τα έχετε ήδη στις εξαρτήσεις σας. Το αρχείο παραμένει:

import React from 'react';
import { render } from 'react-dom';
import logger from 'redux-logger';
import reduxThunk from 'redux-thunk';
import { Provider } from 'react-redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import { createStore, combineReducers, applyMiddleware } from 'redux';
import App from './App/App';
import heroesReducer from './Store/HeroesReducer';
const routeReducer = combineReducers(
  {
    heroes: heroesReducer,
  });
const store = createStore(routeReducer, composeWithDevTools(applyMiddleware(logger, reduxThunk)));
render(<Provider store={store}><App /></Provider>, document.getElementById('root'));
if (module.hot) {
  module.hot.accept('./App/App.js', () => {
    const UpdatedAppComponent = require('./App/App.js').default; // eslint-disable-line global-require
    render(<UpdatedAppComponent />, document.getElementById('root'));
  });
}

Ας δούμε τι θα γίνει :

  • Το Provider είναι ένα ειδικό συστατικό React Redux που με μαγικό τρόπο καθιστά το κατάστημα διαθέσιμο σε όλα τα στοιχεία κοντέινερ της εφαρμογής χωρίς να το διαβιβάσει ρητά. Χρειάζεται να το χρησιμοποιήσετε μόνο μία φορά όταν αποδώσετε το ριζικό στοιχείο.
  • Το κατάστημα μπορεί να θεωρηθεί ως μια κοινόχρηστη τοποθεσία όπου ολόκληρη η κατάσταση της εφαρμογής μεταξύ όλων των στοιχείων. Για να γίνει αυτό, τα δοχεία εξαρτημάτων θα πρέπει να εγγραφούν (να συνδεθούν) στο κατάστημα Redux. Μια επιλογή θα ήταν να το περάσετε ως στήριγμα σε κάθε εξάρτημα δοχείου.
  • Οι μειωτήρες είναι καθαρές συναρτήσεις που παίρνουν την προηγούμενη κατάσταση και μια ενέργεια και επιστρέφουν την επόμενη κατάσταση (μια νέα κατάσταση).

Τώρα που δημιουργήσαμε το σημείο εισόδου μας, ας δημιουργήσουμε τα στοιχεία της εφαρμογής και ορίζουμε μειωτήρες και δημιουργούς ενεργειών.

2. Στοιχεία εφαρμογής: Αυτή η εφαρμογή χρειάζεται: 9 στοιχεία. Αυτό το σχήμα εξηγεί τη σύνδεση μεταξύ τους.

Θα ορίσουμε μια στρατηγική για το χειρισμό στοιχείων : Θα δημιουργήσουμε έναν φάκελο για κάθε στοιχείο στον οποίο θα βάλουμε το στυλ (css) και τις δοκιμές μας (*.spec.js).

  • Ας ξεκινήσουμε με το root Component που είναι: App Θέλουμε η εφαρμογή μας να περιέχει μια γραμμή πλοήγησης. Μπορούμε να ορίσουμε τις διαδρομές μας στο ίδιο στοιχείο. Δεν χρειαζόμαστε ολόκληρη κλάση για εξαγωγή, καθώς το στοιχείο αποδίδει μόνο ένα στοιχείο. Μπορούμε να εξάγουμε μια απλή συνάρτηση:
export default function App() {
  return (
    <BrowserRouter>
      <div className={AppClass.container}>
        <NavigationBar />
        <Route exact path="/" component={Dashboard} />
        <Route exact path="/heroes" component={AllHeroes} />
      </div>
    </BrowserRouter>
  );
}
  • NavigationBar: Το NavigationBar έχει δύο στοιχεία που θα πρέπει να ανακατευθύνουν στα στοιχεία του πίνακα ελέγχου και του AllHeroes. Θα χρησιμοποιήσουμε: NavLink από το 'react-router-dom'.
export default function NavigationBar() {
  return (
    <div className={NavidationBarClass.navigationBar}>
      <div className={NavidationBarClass.navigationBarInnerContainer}>
        <h1>Tour Of Heroes</h1>
        <nav className={NavidationBarClass.nav}>
          <NavLink exact to="/" activeClassName={NavidationBarClass.active}>Dashboard</NavLink>
          <NavLink exact to="/heroes" activeClassName={NavidationBarClass.active}>Heroes</NavLink>
        </nav>
      </div>
    </div>
  );
}

Όταν κάνετε κλικ σε ένα NavLink, μπορείτε να αλλάξετε το στυλ αυτού του στοιχείου χρησιμοποιώντας το "activeClassName".

  • Ταμπλό: Αυτό το στοιχείο θα πρέπει να εμφανίζει τρεις πρώτους ήρωες στη λίστα (οι καλύτεροι ήρωες).

Η λίστα ηρώων θα πρέπει να είναι προσβάσιμη σε όλα τα στοιχεία και εάν ένα στοιχείο την αλλάξει, οι άλλοι θα πρέπει να γνωρίζουν τις αλλαγές.

Λέμε ότι η λίστα ηρώων βρίσκεται στο κατάστημα και τα στοιχεία που χρησιμοποιούν αυτήν τη λίστα πρέπει να εγγραφούν στο κατάστημα.

Ένα μέρος από αυτό, ένα συστατικό έχει πρόσβαση στο Κατάστημα μέσω των «σκηνικών» του. Είναι αυτό που λαμβάνει από τον εξωτερικό κόσμο.

Δεδομένου ότι ο πίνακας ελέγχου είναι το πρώτο στοιχείο που χρησιμοποιεί τη λίστα, θα πρέπει να αποσταλεί στα άλλα στοιχεία. Ας ονομάσουμε τη συνάρτηση αποστολής «updateHeroesAction». Στην πραγματικότητα είναι μια ενέργεια που λέει: Αυτή είναι η λίστα των ηρώων.

Οι ενέργειες είναι ωφέλιμα φορτία πληροφοριών που στέλνουν δεδομένα από την εφαρμογή σας στο κατάστημά σας. Είναι η μόνη πηγή πληροφοριών για το κατάστημα. Τα στέλνετε στο κατάστημα χρησιμοποιώντας το store.dispatch().

Όταν αποστέλλεται μια ενέργεια, το Redux καλεί το συσχετισμένο Reducer για να αναλάβει δράση.

//Action : 
export function updateHeroesAction(heroes) {
  return {
    type: 'UPDATE_HEROES',
    payload: heroes,
  };
}
//Reducer :
export default function heroesReducer(prevState = heroes, action) {
  if (action.type === 'UPDATE_HEROES') { return action.payload.reverse(); }
  return prevState;
}

Όλοι οι μειωτήρες θα πρέπει να επιστρέψουν στην προηγούμενη κατάσταση εάν η ενέργεια που αποστέλλεται δεν σχετίζεται με τον τρέχοντα μειωτήρα.

  • BestHeroes: Αυτό το στοιχείο εμφανίζει τα τρία πρώτα στοιχεία των ηρώων.
function displayHero(hero) {
  return <Hero hero={hero} />;
}
function BestHeroes({ heroes }) {
  const bestHeroes = [];
  bestHeroes.push(heroes[0]);
  bestHeroes.push(heroes[1]);
  bestHeroes.push(heroes[2]);
  return (
    <div>
      <h3 className={BestHeroesClass.h3}>Best Of Heroes</h3>
      <div className={BestHeroesClass.container}>
        {bestHeroes.map(displayHero)}
      </div>
    </div>
  );
}
  • BestHeroesContainer: Αυτό το κοντέινερ εγγράφει το BestHeroes στο κατάστημα χρησιμοποιώντας:
export default connect(mapStateToProps)(BestHeroes);
function mapStateToProps(currentState) {
  return {
    heroes: currentState.heroes,
  };
}
  • Ήρωας: Αυτή είναι η μονάδα κάθε Στοιχείου λίστας. Εμφανίζει μόνο ένα όνομα ήρωα.
export default function Hero({ hero }) {
  return (
    <h4 className={HeroClass.module} style={{ marginRight: `${1}em` }}>{hero.name}</h4>
  );
}
Hero.propTypes = {
  hero: Proptypes.shape({
    name: Proptypes.string.isRequired,
    id: Proptypes.number.isRequired,
  }).isRequired,
};

Το PropTypes ορίζει τα στοιχεία ενός Αντικειμένου και τους τύπους τους και το συνδέει με το Τρέχον Στοιχείο.

Συγχαρητήρια !! Μόλις ολοκληρώσατε την πρώτη σας προβολή χρησιμοποιώντας React και Redux.

Θα κάνουμε το ίδιο για την άλλη προβολή:

  • AllHeroes : Αυτό το στοιχείο εμφανίζει όλους τους ήρωες (όνομα και αναγνωριστικό) και εγγράφει το στοιχείο Heroes στο Store.
function AllHeroes() {
  return (
    <div>
      <Input />
      <HeroesContainer />
    </div>
  );
}
  • Ήρωες Αυτό το στοιχείο έχει πρόσβαση στη λίστα ηρώων στο κατάστημα. Αυτό έγινε μέσω του HeroesContainer:
Containers/HeroesContainer.js
function mapStateToProps(currentState) {
  return {
    heroes: currentState.heroes,
  };
}
export default connect(mapStateToProps)(Heroes);
//Heroes/Heroes.js
function displayHero(hero) {
  return <HeroDetails hero={hero} />;
}
export default function Heroes({ heroes }) {
  return (
    <div className={HeroesClass.container}>
      {heroes.map(displayHero)}
    </div>
  );
}
  • Λεπτομέρειες Hero:

Εμφανίζει κάθε στοιχείο της λίστας:

export default function HeroDetails({ hero }) {
  return (
    <div className={HeroClass.heroesli}>
      <span className={HeroClass.badge}>{hero.id}</span>
      <span>{hero.name}</span>
    </div>
  );
}
  • Εισαγωγή : Η είσοδος εγγράφεται επίσης στο κατάστημα, επειδή ενημερώνει τη λίστα ηρώων. Μπορείτε να προσθέσετε έναν ήρωα στην τρέχουσα λίστα.
function mapStateToProps(currentState) {
  return {
    heroes: currentState.heroes,
  };
}
class Input extends React.Component {
  constructor(props) {
    super(props);
this.actionToTaKe = this.actionToTaKe.bind(this);
  }
actionToTaKe(event) {
    if (event.key === 'Enter') {
      this.props.dispatch(addHero({ name: event.target.value }, this.props.heroes));
      event.target.value = '';
    }
  }
render() {
    return (
      <div className={InputClass.container} style={{ marginRight: `${3}em` }}> Hero name
        <input className={InputClass.input} type="text" onKeyPress={this.actionToTaKe} />
      </div>
    );
  }
}
export default connect(mapStateToProps)(Input);

Η εισαγωγή αποστέλλει μια ενέργεια για να ειδοποιήσει το κατάστημα σχετικά με τις αλλαγές. Σε αυτήν την περίπτωση, η ενέργεια είναι μια συνάρτηση «addHero» που παίρνει έναν ήρωα και τη λίστα των ηρώων σε παραμέτρους.

Αυτή η ενέργεια είναι:

export function addHero(hero, heroes) {
  const lastId = heroes.length;
  const newHeroes = Object.assign({}, {
    heroes: [
      ...heroes,
      {
        id: lastId + 1,
        name: hero.name,
      },
    ] });
  return (dispatch) => {
    dispatch(updateHeroesAction(newHeroes.heroes));
  };
}

Μπορείτε να παρατηρήσετε ότι στο τέλος, η ενέργεια επιστρέφει μια αποστολή του 'updateHeroesAction' που ειδοποιεί όλα τα στοιχεία χρησιμοποιώντας τη λίστα ηρώων.

3. Ελέγξτε τα στοιχεία σας Όπως αναφέρθηκε προηγουμένως, οι δοκιμές θα γραφτούν στο ‹component_name›.spec.js

Ακολουθεί ένα παράδειγμα δοκιμής:

  • App.spec.js : Στην πραγματικότητα απλώς βεβαιωνόμαστε ότι η εφαρμογή περιέχει στο NavigationBar. Επειδή, είμαστε βέβαιοι ότι όλα τα άλλα στοιχεία είναι όρχεις στο React (Διαδρομή…)
chai.use(chaiEnzyme());
describe('App component', () => {
  it('should have one Navigation bar', () => {
    const wrapper = shallow(<App />);
    expect(wrapper.find(NavigationBar)).to.have.length(1);
  });
});

Αποτελέσματα

Τα τελικά αποτελέσματα μέχρι τώρα είναι τα επόμενα οπτικά.

Ταμπλό:

Λίστα Ηρώων:

Επόμενο Βήμα ΣΥΝΕΙΣΦΟΡΑ

Βασίζομαι σε εσάς για να με βοηθήσετε να ολοκληρώσω το σεμινάριο.

  • Εμφάνιση των στοιχείων του Ήρωα

  • διαγράψτε έναν ήρωα από τη λίστα
  • ΔΟΚΙΜΑΣΤΕ τα υπόλοιπα!

Σας ευχαριστούμε που παρακολουθήσατε αυτό το σεμινάριο.

Με ❤